• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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