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 5import logging 6import socket 7import struct 8 9import btsocket 10 11SDP_HDR_FORMAT = '>BHH' 12SDP_HDR_SIZE = struct.calcsize(SDP_HDR_FORMAT) 13SDP_TID_CNT = 1 << 16 14SDP_MAX_UUIDS_CNT = 12 15SDP_BODY_CNT_FORMAT = '>HH' 16SDP_BODY_CNT_SIZE = struct.calcsize(SDP_BODY_CNT_FORMAT) 17BLUETOOTH_BASE_UUID = 0x0000000000001000800000805F9B34FB 18 19# Constants are taken from lib/sdp.h in BlueZ source. 20SDP_RESPONSE_TIMEOUT = 20 21SDP_REQ_BUFFER_SIZE = 2048 22SDP_RSP_BUFFER_SIZE = 65535 23SDP_PDU_CHUNK_SIZE = 1024 24 25SDP_PSM = 0x0001 26 27SDP_UUID = 0x0001 28 29SDP_DATA_NIL = 0x00 30SDP_UINT8 = 0x08 31SDP_UINT16 = 0x09 32SDP_UINT32 = 0x0A 33SDP_UINT64 = 0x0B 34SDP_UINT128 = 0x0C 35SDP_INT8 = 0x10 36SDP_INT16 = 0x11 37SDP_INT32 = 0x12 38SDP_INT64 = 0x13 39SDP_INT128 = 0x14 40SDP_UUID_UNSPEC = 0x18 41SDP_UUID16 = 0x19 42SDP_UUID32 = 0x1A 43SDP_UUID128 = 0x1C 44SDP_TEXT_STR_UNSPEC = 0x20 45SDP_TEXT_STR8 = 0x25 46SDP_TEXT_STR16 = 0x26 47SDP_TEXT_STR32 = 0x27 48SDP_BOOL = 0x28 49SDP_SEQ_UNSPEC = 0x30 50SDP_SEQ8 = 0x35 51SDP_SEQ16 = 0x36 52SDP_SEQ32 = 0x37 53SDP_ALT_UNSPEC = 0x38 54SDP_ALT8 = 0x3D 55SDP_ALT16 = 0x3E 56SDP_ALT32 = 0x3F 57SDP_URL_STR_UNSPEC = 0x40 58SDP_URL_STR8 = 0x45 59SDP_URL_STR16 = 0x46 60SDP_URL_STR32 = 0x47 61 62SDP_ERROR_RSP = 0x01 63SDP_SVC_SEARCH_REQ = 0x02 64SDP_SVC_SEARCH_RSP = 0x03 65SDP_SVC_ATTR_REQ = 0x04 66SDP_SVC_ATTR_RSP = 0x05 67SDP_SVC_SEARCH_ATTR_REQ = 0x06 68SDP_SVC_SEARCH_ATTR_RSP = 0x07 69 70 71class BluetoothSDPSocketError(Exception): 72 """Error raised for SDP-related issues with BluetoothSDPSocket.""" 73 pass 74 75 76class 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