Home | History | Annotate | Download | only in bluetooth
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import socket
      7 import struct
      8 
      9 import btsocket
     10 
     11 SDP_HDR_FORMAT        = '>BHH'
     12 SDP_HDR_SIZE          = struct.calcsize(SDP_HDR_FORMAT)
     13 SDP_TID_CNT           = 1 << 16
     14 SDP_MAX_UUIDS_CNT     = 12
     15 SDP_BODY_CNT_FORMAT   = '>HH'
     16 SDP_BODY_CNT_SIZE     = struct.calcsize(SDP_BODY_CNT_FORMAT)
     17 BLUETOOTH_BASE_UUID   = 0x0000000000001000800000805F9B34FB
     18 
     19 # Constants are taken from lib/sdp.h in BlueZ source.
     20 SDP_RESPONSE_TIMEOUT    = 20
     21 SDP_REQ_BUFFER_SIZE     = 2048
     22 SDP_RSP_BUFFER_SIZE     = 65535
     23 SDP_PDU_CHUNK_SIZE      = 1024
     24 
     25 SDP_PSM                 = 0x0001
     26 
     27 SDP_UUID                = 0x0001
     28 
     29 SDP_DATA_NIL            = 0x00
     30 SDP_UINT8               = 0x08
     31 SDP_UINT16              = 0x09
     32 SDP_UINT32              = 0x0A
     33 SDP_UINT64              = 0x0B
     34 SDP_UINT128             = 0x0C
     35 SDP_INT8                = 0x10
     36 SDP_INT16               = 0x11
     37 SDP_INT32               = 0x12
     38 SDP_INT64               = 0x13
     39 SDP_INT128              = 0x14
     40 SDP_UUID_UNSPEC         = 0x18
     41 SDP_UUID16              = 0x19
     42 SDP_UUID32              = 0x1A
     43 SDP_UUID128             = 0x1C
     44 SDP_TEXT_STR_UNSPEC     = 0x20
     45 SDP_TEXT_STR8           = 0x25
     46 SDP_TEXT_STR16          = 0x26
     47 SDP_TEXT_STR32          = 0x27
     48 SDP_BOOL                = 0x28
     49 SDP_SEQ_UNSPEC          = 0x30
     50 SDP_SEQ8                = 0x35
     51 SDP_SEQ16               = 0x36
     52 SDP_SEQ32               = 0x37
     53 SDP_ALT_UNSPEC          = 0x38
     54 SDP_ALT8                = 0x3D
     55 SDP_ALT16               = 0x3E
     56 SDP_ALT32               = 0x3F
     57 SDP_URL_STR_UNSPEC      = 0x40
     58 SDP_URL_STR8            = 0x45
     59 SDP_URL_STR16           = 0x46
     60 SDP_URL_STR32           = 0x47
     61 
     62 SDP_ERROR_RSP           = 0x01
     63 SDP_SVC_SEARCH_REQ      = 0x02
     64 SDP_SVC_SEARCH_RSP      = 0x03
     65 SDP_SVC_ATTR_REQ        = 0x04
     66 SDP_SVC_ATTR_RSP        = 0x05
     67 SDP_SVC_SEARCH_ATTR_REQ = 0x06
     68 SDP_SVC_SEARCH_ATTR_RSP = 0x07
     69 
     70 
     71 class BluetoothSDPSocketError(Exception):
     72     """Error raised for SDP-related issues with BluetoothSDPSocket."""
     73     pass
     74 
     75 
     76 class BluetoothSDPSocket(btsocket.socket):
     77     """Bluetooth SDP Socket.
     78 
     79     BluetoothSDPSocket wraps the btsocket.socket() class to implement
     80     the necessary send and receive methods for the SDP protocol.
     81 
     82     """
     83 
     84     def __init__(self):
     85         super(BluetoothSDPSocket, self).__init__(family=btsocket.AF_BLUETOOTH,
     86                                                  type=socket.SOCK_SEQPACKET,
     87                                                  proto=btsocket.BTPROTO_L2CAP)
     88         self.tid = 0
     89 
     90 
     91     def gen_tid(self):
     92         """Generate new Transaction ID
     93 
     94         @return Transaction ID
     95 
     96         """
     97         self.tid = (self.tid + 1) % SDP_TID_CNT
     98         return self.tid
     99 
    100 
    101     def connect(self, address):
    102         """Connect to device with the given address
    103 
    104         @param address: Bluetooth address.
    105 
    106         """
    107         try:
    108             super(BluetoothSDPSocket, self).connect((address, SDP_PSM))
    109         except btsocket.error as e:
    110             logging.error('Error connecting to %s: %s', address, e)
    111             raise BluetoothSDPSocketError('Error connecting to host: %s' % e)
    112         except btsocket.timeout as e:
    113             logging.error('Timeout connecting to %s: %s', address, e)
    114             raise BluetoothSDPSocketError('Timeout connecting to host: %s' % e)
    115 
    116     def send_request(self, code, tid, data, forced_pdu_size=None):
    117         """Send a request to the socket.
    118 
    119         @param code: Request code.
    120         @param tid: Transaction ID.
    121         @param data: Parameters as bytearray or str.
    122         @param forced_pdu_size: Use certain PDU size parameter instead of
    123                calculating actual length of sequence.
    124 
    125         @raise BluetoothSDPSocketError: if 'send' to the socket didn't succeed.
    126 
    127         """
    128         size = len(data)
    129         if forced_pdu_size != None:
    130             size = forced_pdu_size
    131         msg = struct.pack(SDP_HDR_FORMAT, code, tid, size) + data
    132 
    133         length = self.send(msg)
    134         if length != len(msg):
    135             raise BluetoothSDPSocketError('Short write on socket')
    136 
    137 
    138     def recv_response(self):
    139         """Receive a single response from the socket.
    140 
    141         The response data is not parsed.
    142 
    143         Use settimeout() to set whether this method will block if there is no
    144         reply, return immediately or wait for a specific length of time before
    145         timing out and raising TimeoutError.
    146 
    147         @return tuple of (code, tid, data)
    148         @raise BluetoothSDPSocketError: if the received packet is too small or
    149                if size of the packet differs from size written in header
    150 
    151         """
    152         # Read the response from the socket.
    153         response = self.recv(SDP_RSP_BUFFER_SIZE)
    154 
    155         if len(response) < SDP_HDR_SIZE:
    156             raise BluetoothSDPSocketError('Short read on socket')
    157 
    158         code, tid, length = struct.unpack_from(SDP_HDR_FORMAT, response)
    159         data = response[SDP_HDR_SIZE:]
    160 
    161         if length != len(data):
    162             raise BluetoothSDPSocketError('Short read on socket')
    163 
    164         return code, tid, data
    165 
    166 
    167     def send_request_and_wait(self, req_code, req_data, forced_pdu_size=None):
    168         """Send a request to the socket and wait for the response.
    169 
    170         The response data is not parsed.
    171 
    172         @param req_code: Request code.
    173         @param req_data: Parameters as bytearray or str.
    174         @param forced_pdu_size: Use certain PDU size parameter instead of
    175                calculating actual length of sequence.
    176 
    177         Use settimeout() to set whether this method will block if there is no
    178         reply, return immediately or wait for a specific length of time before
    179         timing out and raising TimeoutError.
    180 
    181         @return tuple of (rsp_code, data)
    182         @raise BluetoothSDPSocketError: if Transaction ID of the response
    183                doesn't match to Transaction ID sent in request
    184 
    185         """
    186         req_tid = self.gen_tid()
    187         self.send_request(req_code, req_tid, req_data, forced_pdu_size)
    188         rsp_code, rsp_tid, rsp_data = self.recv_response()
    189 
    190         if req_tid != rsp_tid:
    191             raise BluetoothSDPSocketError("Transaction IDs for request and "
    192                                           "response don't match")
    193 
    194         return rsp_code, rsp_data
    195 
    196 
    197     def _pack_list(self, data_element_list):
    198         """Preappend a list with required header.
    199 
    200         Size of the header is chosen to be minimal possible.
    201 
    202         @param data_element_list: List to be packed.
    203 
    204         @return packed list as a str
    205         @raise BluetoothSDPSocketError: if size of the list is larger than or
    206                equal to 2^32 bytes, which is not supported in SDP transactions
    207 
    208         """
    209         size = len(data_element_list)
    210         if size < (1 << 8):
    211             header = struct.pack('>BB', SDP_SEQ8, size)
    212         elif size < (1 << 16):
    213             header = struct.pack('>BH', SDP_SEQ16, size)
    214         elif size < (1 << 32):
    215             header = struct.pack('>BI', SDP_SEQ32, size)
    216         else:
    217             raise BluetoothSDPSocketError('List is too long')
    218         return header + data_element_list
    219 
    220 
    221     def _pack_uuids(self, uuids, preferred_size):
    222         """Pack a list of UUIDs to a binary sequence
    223 
    224         @param uuids: List of UUIDs (as integers).
    225         @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
    226 
    227         @return packed list as a str
    228         @raise BluetoothSDPSocketError: the given preferred size is not
    229                supported by SDP
    230 
    231         """
    232         if preferred_size not in (16, 32, 128):
    233             raise BluetoothSDPSocketError('Unsupported UUID size: %d; '
    234                                           'Supported values are: 16, 32, 128'
    235                                           % preferred_size)
    236 
    237         res = ''
    238         for uuid in uuids:
    239             # Fall back to 128 bits if the UUID doesn't fit into preferred_size.
    240             if uuid >= (1 << preferred_size) or preferred_size == 128:
    241                 uuid128 = uuid
    242                 if uuid < (1 << 32):
    243                     uuid128 = (uuid128 << 96) + BLUETOOTH_BASE_UUID
    244                 packed_uuid = struct.pack('>BQQ', SDP_UUID128, uuid128 >> 64,
    245                                           uuid128 & ((1 << 64) - 1))
    246             elif preferred_size == 16:
    247                 packed_uuid = struct.pack('>BH', SDP_UUID16, uuid)
    248             elif preferred_size == 32:
    249                 packed_uuid = struct.pack('>BI', SDP_UUID32, uuid)
    250 
    251             res += packed_uuid
    252 
    253         res = self._pack_list(res)
    254 
    255         return res
    256 
    257 
    258     def _unpack_uuids(self, response):
    259         """Unpack SDP response
    260 
    261         @param response: body of raw SDP response.
    262 
    263         @return tuple of (uuids, cont_state)
    264 
    265         """
    266         total_cnt, cur_cnt = struct.unpack_from(SDP_BODY_CNT_FORMAT, response)
    267         scanned = SDP_BODY_CNT_SIZE
    268         uuids = []
    269         for i in range(cur_cnt):
    270             uuid, = struct.unpack_from('>I', response, scanned)
    271             uuids.append(uuid)
    272             scanned += 4
    273 
    274         cont_state = response[scanned:]
    275         return uuids, cont_state
    276 
    277 
    278     def _unpack_error_code(self, response):
    279         """Unpack Error Code from SDP error response
    280 
    281         @param response: Body of raw SDP response.
    282 
    283         @return Error Code as int
    284 
    285         """
    286         error_code, = struct.unpack_from('>H', response)
    287         return error_code
    288 
    289 
    290     def service_search_request(self, uuids, max_rec_cnt, preferred_size=32,
    291                                forced_pdu_size=None, invalid_request=False):
    292         """Send a Service Search Request
    293 
    294         @param uuids: List of UUIDs (as integers) to look for.
    295         @param max_rec_cnt: Maximum count of returned service records.
    296         @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
    297         @param forced_pdu_size: Use certain PDU size parameter instead of
    298                calculating actual length of sequence.
    299         @param invalid_request: Whether to send request with intentionally
    300                invalid syntax for testing purposes (bool flag).
    301 
    302         @return list of found services' service record handles or Error Code
    303         @raise BluetoothSDPSocketError: arguments do not match the SDP
    304                restrictions or if the response has an incorrect code
    305 
    306         """
    307         if max_rec_cnt < 1 or max_rec_cnt > 65535:
    308             raise BluetoothSDPSocketError('MaximumServiceRecordCount must be '
    309                                           'between 1 and 65535, inclusive')
    310 
    311         if len(uuids) > SDP_MAX_UUIDS_CNT:
    312             raise BluetoothSDPSocketError('Too many UUIDs')
    313 
    314         pattern = self._pack_uuids(uuids, preferred_size) + struct.pack(
    315                   '>H', max_rec_cnt)
    316         cont_state = '\0'
    317         handles = []
    318 
    319         while True:
    320             request = pattern + cont_state
    321 
    322             # Request without any continuation state is an example of invalid
    323             # request syntax.
    324             if invalid_request:
    325                 request = pattern
    326 
    327             code, response = self.send_request_and_wait(
    328                     SDP_SVC_SEARCH_REQ, request, forced_pdu_size)
    329 
    330             if code == SDP_ERROR_RSP:
    331                 return self._unpack_error_code(response)
    332 
    333             if code != SDP_SVC_SEARCH_RSP:
    334                 raise BluetoothSDPSocketError('Incorrect response code')
    335 
    336             cur_list, cont_state = self._unpack_uuids(response)
    337             handles.extend(cur_list)
    338             if cont_state == '\0':
    339                 break
    340 
    341         return handles
    342 
    343 
    344     def _pack_attr_ids(self, attr_ids):
    345         """Pack a list of Attribute IDs to a binary sequence
    346 
    347         @param attr_ids: List of Attribute IDs.
    348 
    349         @return packed list as a str
    350         @raise BluetoothSDPSocketError: if list of UUIDs after packing is larger
    351                than or equal to 2^32 bytes
    352 
    353         """
    354         attr_ids.sort()
    355         res = ''
    356         for attr_id in attr_ids:
    357             # Each element could be either a single Attribute ID or
    358             # a range of IDs.
    359             if isinstance(attr_id, list):
    360                 packed_attr_id = struct.pack('>BHH', SDP_UINT32,
    361                                              attr_id[0], attr_id[1])
    362             else:
    363                 packed_attr_id = struct.pack('>BH', SDP_UINT16, attr_id)
    364 
    365             res += packed_attr_id
    366 
    367         res = self._pack_list(res)
    368 
    369         return res
    370 
    371 
    372     def _unpack_int(self, data, cnt):
    373         """Unpack an unsigned integer of cnt bytes
    374 
    375         @param data: raw data to be parsed
    376         @param cnt: size of integer
    377 
    378         @return unsigned integer
    379 
    380         """
    381         res = 0
    382         for i in range(cnt):
    383             res = (res << 8) | ord(data[i])
    384         return res
    385 
    386 
    387     def _unpack_sdp_data_element(self, data):
    388         """Unpack a data element from a raw response
    389 
    390         @param data: raw data to be parsed
    391 
    392         @return tuple (result, scanned bytes)
    393 
    394         """
    395         header, = struct.unpack_from('>B', data)
    396         data_type = header >> 3
    397         data_size = header & 7
    398         scanned = 1
    399         data = data[1:]
    400         if data_type == 0:
    401             if data_size != 0:
    402                 raise BluetoothSDPSocketError('Invalid size descriptor')
    403             return None, scanned
    404         elif data_type <= 3 or data_type == 5:
    405             if (data_size > 4 or
    406                 data_type == 3 and (data_size == 0 or data_size == 3) or
    407                 data_type == 5 and data_size != 0):
    408                 raise BluetoothSDPSocketError('Invalid size descriptor')
    409 
    410             int_size = 1 << data_size
    411             res = self._unpack_int(data, int_size)
    412 
    413             # Consider negative integers.
    414             if data_type == 2 and (ord(data[0]) & 128) != 0:
    415                 res = res - (1 << (int_size * 8))
    416 
    417             # Consider booleans.
    418             if data_type == 5:
    419                 res = res != 0
    420 
    421             scanned += int_size
    422             return res, scanned
    423         elif data_type == 4 or data_type == 8:
    424             if data_size < 5 or data_size > 7:
    425                 raise BluetoothSDPSocketError('Invalid size descriptor')
    426 
    427             int_size = 1 << (data_size - 5)
    428             str_size = self._unpack_int(data, int_size)
    429 
    430             res = data[int_size : int_size + str_size]
    431             scanned += int_size + str_size
    432             return res, scanned
    433         elif data_type == 6 or data_type == 7:
    434             if data_size < 5 or data_size > 7:
    435                 raise BluetoothSDPSocketError('Invalid size descriptor')
    436 
    437             int_size = 1 << (data_size - 5)
    438             total_size = self._unpack_int(data, int_size)
    439 
    440             data = data[int_size:]
    441             scanned += int_size + total_size
    442 
    443             res = []
    444             cur_size = 0
    445             while cur_size < total_size:
    446                 elem, elem_size = self._unpack_sdp_data_element(data)
    447                 res.append(elem)
    448                 data = data[elem_size:]
    449                 cur_size += elem_size
    450 
    451             return res, scanned
    452         else:
    453             raise BluetoothSDPSocketError('Invalid size descriptor')
    454 
    455 
    456     def service_attribute_request(self, handle, max_attr_byte_count, attr_ids,
    457                                   forced_pdu_size=None, invalid_request=None):
    458         """Send a Service Attribute Request
    459 
    460         @param handle: service record from which attribute values are to be
    461                retrieved.
    462         @param max_attr_byte_count: maximum number of bytes of attribute data to
    463                be returned in the response to this request.
    464         @param attr_ids: a list, where each element is either an attribute ID
    465                or a range of attribute IDs.
    466         @param forced_pdu_size: Use certain PDU size parameter instead of
    467                calculating actual length of sequence.
    468         @param invalid_request: Whether to send request with intentionally
    469                invalid syntax for testing purposes (string with raw request).
    470 
    471         @return list of found attributes IDs and their values or Error Code
    472         @raise BluetoothSDPSocketError: arguments do not match the SDP
    473                restrictions or if the response has an incorrect code
    474 
    475         """
    476         if max_attr_byte_count < 7 or max_attr_byte_count > 65535:
    477             raise BluetoothSDPSocketError('MaximumAttributeByteCount must be '
    478                                           'between 7 and 65535, inclusive')
    479 
    480         pattern = (struct.pack('>I', handle) +
    481                    struct.pack('>H', max_attr_byte_count) +
    482                    self._pack_attr_ids(attr_ids))
    483         cont_state = '\0'
    484         complete_response = ''
    485 
    486         while True:
    487             request = (invalid_request if invalid_request
    488                        else pattern + cont_state)
    489 
    490             code, response = self.send_request_and_wait(
    491                     SDP_SVC_ATTR_REQ, request, forced_pdu_size)
    492 
    493             if code == SDP_ERROR_RSP:
    494                 return self._unpack_error_code(response)
    495 
    496             if code != SDP_SVC_ATTR_RSP:
    497                 raise BluetoothSDPSocketError('Incorrect response code')
    498 
    499             response_byte_count, = struct.unpack_from('>H', response)
    500             if response_byte_count > max_attr_byte_count:
    501                 raise BluetoothSDPSocketError('AttributeListByteCount exceeds'
    502                                               'MaximumAttributeByteCount')
    503 
    504             response = response[2:]
    505             complete_response += response[:response_byte_count]
    506             cont_state = response[response_byte_count:]
    507 
    508             if cont_state == '\0':
    509                 break
    510 
    511         id_values_list = self._unpack_sdp_data_element(complete_response)[0]
    512         if len(id_values_list) % 2 == 1:
    513             raise BluetoothSDPSocketError('Length of returned list is odd')
    514 
    515         return id_values_list
    516 
    517 
    518     def service_search_attribute_request(self, uuids, max_attr_byte_count,
    519                                          attr_ids, preferred_size=32,
    520                                          forced_pdu_size=None,
    521                                          invalid_request=None):
    522         """Send a Service Search Attribute Request
    523 
    524         @param uuids: list of UUIDs (as integers) to look for.
    525         @param max_attr_byte_count: maximum number of bytes of attribute data to
    526                be returned in the response to this request.
    527         @param attr_ids: a list, where each element is either an attribute ID
    528                or a range of attribute IDs.
    529         @param preferred_size: Preffered size of UUIDs in bits (16, 32, or 128).
    530         @param forced_pdu_size: Use certain PDU size parameter instead of
    531                calculating actual length of sequence.
    532         @param invalid_request: Whether to send request with intentionally
    533                invalid syntax for testing purposes (string to be prepended
    534                to correct request).
    535 
    536         @return list of found attributes IDs and their values or Error Code
    537         @raise BluetoothSDPSocketError: arguments do not match the SDP
    538                restrictions or if the response has an incorrect code
    539 
    540         """
    541         if len(uuids) > SDP_MAX_UUIDS_CNT:
    542             raise BluetoothSDPSocketError('Too many UUIDs')
    543 
    544         if max_attr_byte_count < 7 or max_attr_byte_count > 65535:
    545             raise BluetoothSDPSocketError('MaximumAttributeByteCount must be '
    546                                           'between 7 and 65535, inclusive')
    547 
    548         pattern = (self._pack_uuids(uuids, preferred_size) +
    549                    struct.pack('>H', max_attr_byte_count) +
    550                    self._pack_attr_ids(attr_ids))
    551         cont_state = '\0'
    552         complete_response = ''
    553 
    554         while True:
    555             request = pattern + cont_state
    556             if invalid_request:
    557                 request = invalid_request + request
    558 
    559             code, response = self.send_request_and_wait(
    560                     SDP_SVC_SEARCH_ATTR_REQ, request, forced_pdu_size)
    561 
    562             if code == SDP_ERROR_RSP:
    563                 return self._unpack_error_code(response)
    564 
    565             if code != SDP_SVC_SEARCH_ATTR_RSP:
    566                 raise BluetoothSDPSocketError('Incorrect response code')
    567 
    568             response_byte_count, = struct.unpack_from('>H', response)
    569             if response_byte_count > max_attr_byte_count:
    570                 raise BluetoothSDPSocketError('AttributeListByteCount exceeds'
    571                                               'MaximumAttributeByteCount')
    572 
    573             response = response[2:]
    574             complete_response += response[:response_byte_count]
    575             cont_state = response[response_byte_count:]
    576 
    577             if cont_state == '\0':
    578                 break
    579 
    580         id_values_list = self._unpack_sdp_data_element(complete_response)[0]
    581 
    582         return id_values_list
    583