• 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 uuid
7import xml.etree.ElementTree as ET
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.cros.bluetooth import bluetooth_test
11
12class bluetooth_SDP_ServiceSearchAttributeRequest(bluetooth_test.BluetoothTest):
13    """
14    Verify the correct behaviour of the device when searching for services and
15    attributes.
16    """
17    version = 1
18
19    MIN_ATTR_BYTE_CNT                = 7
20    MAX_ATTR_BYTE_CNT                = 300
21
22    BLUETOOTH_BASE_UUID              = 0x0000000000001000800000805F9B34FB
23
24    NON_EXISTING_SERVICE_CLASS_ID    = 0x9875
25    SDP_SERVER_CLASS_ID              = 0x1000
26    PUBLIC_BROWSE_GROUP_CLASS_ID     = 0x1002
27    GAP_CLASS_ID                     = 0x1800
28    PNP_INFORMATION_CLASS_ID         = 0x1200
29    PUBLIC_BROWSE_ROOT               = 0x1002
30    AVRCP_TG_CLASS_ID                = 0x110C
31
32    NON_EXISTING_ATTRIBUTE_ID        = 0xABCD
33    SERVICE_CLASS_ID_ATTRIBUTE_ID    = 0x0001
34    SERVICE_DATABASE_STATE_ATTR_ID   = 0x0201
35    PROTOCOL_DESCRIPTOR_LIST_ATTR_ID = 0x0004
36    ICON_URL_ATTR_ID                 = 0x000C
37    VERSION_NUMBER_LIST_ATTR_ID      = 0x0200
38    PROFILE_DESCRIPTOR_LIST_ATTR_ID  = 0x0009
39    BROWSE_GROUP_LIST_ATTR_ID        = 0x0005
40    DOCUMENTATION_URL_ATTR_ID        = 0x000A
41    CLIENT_EXECUTABLE_URL_ATTR_ID    = 0x000B
42    ADDITIONAL_PROTOCOLLIST_ATTR_ID  = 0x000D
43
44    L2CAP_UUID                       = 0x0100
45    ATT_UUID                         = 0x0007
46
47    ATT_PSM                          = 0x001F
48
49    BLUEZ_URL                        = 'http://www.bluez.org/'
50
51    FAKE_SERVICE_PATH                = '/autotest/fake_service'
52    FAKE_SERVICE_CLASS_ID            = 0xCDEF
53    FAKE_ATTRIBUTE_VALUE             = 42
54    LANGUAGE_BASE_ATTRIBUTE_ID       = 0x0006
55    FAKE_GENERAL_ATTRIBUTE_IDS       = [
56                                        0x0002, # TP/SERVER/SSA/BV-07-C
57                                        0x0007, # TP/SERVER/SSA/BV-09-C
58                                        0x0003, # TP/SERVER/SSA/BV-10-C
59                                        0x0008, # TP/SERVER/SSA/BV-14-C
60                                        # TP/SERVER/SSA/BV-13-C:
61                                        LANGUAGE_BASE_ATTRIBUTE_ID
62                                       ]
63    FAKE_LANGUAGE_ATTRIBUTE_OFFSETS  = [
64                                        0x0000, # TP/SERVER/SSA/BV-16-C
65                                        0x0001, # TP/SERVER/SSA/BV-17-C
66                                        0x0002  # TP/SERVER/SSA/BV-18-C
67                                       ]
68
69    ERROR_CODE_INVALID_SYNTAX        = 0x0003
70    ERROR_CODE_INVALID_PDU_SIZE      = 0x0004
71
72
73    def fail_test(self, testname, value):
74        """Raise an error for a particular SDP test.
75
76        @param testname: a string representation of the test name.
77        @param value: the value that did not pass muster.
78
79        """
80        raise error.TestFail('SDP test %s failed: got %s.' % (testname, value))
81
82
83    def build_service_record(self):
84        """Build SDP record manually for the fake service.
85
86        @return resulting record as string
87
88        """
89        value = ET.Element('uint16', {'value': str(self.FAKE_ATTRIBUTE_VALUE)})
90
91        sdp_record = ET.Element('record')
92
93        service_id_attr = ET.Element(
94            'attribute', {'id': str(self.SERVICE_CLASS_ID_ATTRIBUTE_ID)})
95        service_id_attr.append(
96            ET.Element('uuid', {'value': '0x%X' % self.FAKE_SERVICE_CLASS_ID}))
97        sdp_record.append(service_id_attr)
98
99        for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
100            attr = ET.Element('attribute', {'id': str(attr_id)})
101            attr.append(value)
102            sdp_record.append(attr)
103
104        for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
105            attr_id = self.FAKE_ATTRIBUTE_VALUE + offset
106            attr = ET.Element('attribute', {'id': str(attr_id)})
107            attr.append(value)
108            sdp_record.append(attr)
109
110        sdp_record_str = ('<?xml version="1.0" encoding="UTF-8"?>' +
111                          ET.tostring(sdp_record))
112        return sdp_record_str
113
114
115    def test_non_existing(self, class_id, attr_id):
116        """Check that a single attribute of a single service does not exist
117
118        @param class_id: Class ID of service to check.
119        @param attr_id: ID of attribute to check.
120
121        @raises error.TestFail if service or attribute does exists.
122
123        """
124        for size in 16, 32, 128:
125            result = self.tester.service_search_attribute_request(
126                         [class_id],
127                         self.MAX_ATTR_BYTE_CNT,
128                         [attr_id],
129                         size)
130            if result != []:
131                raise error.TestFail('Attribute %s of class %s exists when it '
132                                     'should not!' % (class_id, attr_id))
133
134
135    def get_attribute(self, class_id, attr_id, size):
136        """Get a single attribute of a single service using Service Search
137        Attribute Request.
138
139        @param class_id: Class ID of service to check.
140        @param attr_id: ID of attribute to check.
141        @param size: Preferred size of UUID.
142
143        @return attribute value if attribute exists
144
145        @raises error.TestFail if attribute does not exist
146
147        """
148        res = self.tester.service_search_attribute_request(
149                  [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id], size)
150
151        if (isinstance(res, list) and len(res) == 1 and
152            isinstance(res[0], list) and len(res[0]) == 2 and
153            res[0][0] == attr_id):
154            return res[0][1]
155
156        raise error.TestFail('Attribute %s of class %s does not exist! (size '
157                             '%s)' % (class_id, attr_id, size))
158
159
160    def test_attribute(self, class_id, attr_id):
161        """Test a single attribute of a single service using 16-bit, 32-bit and
162        128-bit size of UUID.
163
164        @param class_id: Class ID of service to check.
165        @param attr_id: ID of attribute to check.
166
167        @return attribute value if attribute exists and values from three tests
168        are equal
169
170        @raises error.TestFail if attribute doesn't exist or values not equal
171
172        """
173        result_16 = self.get_attribute(class_id, attr_id, 16)
174        for size in 32, 128:
175            result_cur = self.get_attribute(class_id, attr_id, size)
176            if result_16 != result_cur:
177                raise error.TestFail('Attribute test failed %s: expected %s, '
178                                     'got %s' % (size, result_16, result_cur))
179
180        return result_16
181
182
183    def test_non_existing_service(self):
184        """Implementation of test TP/SERVER/SSA/BV-01-C from SDP Specification.
185
186        @raises error.TestFail if test fails
187
188        """
189        self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID,
190                               self.SERVICE_CLASS_ID_ATTRIBUTE_ID)
191
192
193    def test_non_existing_attribute(self):
194        """Implementation of test TP/SERVER/SSA/BV-02-C from SDP Specification.
195
196        @raises error.TestFail if test fails
197
198        """
199        self.test_non_existing(self.PUBLIC_BROWSE_GROUP_CLASS_ID,
200                               self.NON_EXISTING_ATTRIBUTE_ID)
201
202
203    def test_non_existing_service_attribute(self):
204        """Implementation of test TP/SERVER/SSA/BV-03-C from SDP Specification.
205
206        @raises error.TestFail if test fails
207
208        """
209        self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID,
210                               self.NON_EXISTING_ATTRIBUTE_ID)
211
212
213    def test_existing_service_attribute(self):
214        """Implementation of test TP/SERVER/SSA/BV-04-C from SDP Specification.
215
216        @raises error.TestFail if test fails
217
218        """
219        value = self.test_attribute(self.SDP_SERVER_CLASS_ID,
220                                    self.SERVICE_CLASS_ID_ATTRIBUTE_ID)
221        if not value == [self.SDP_SERVER_CLASS_ID]:
222            self.fail_test('TP/SERVER/SSA/BV-04-C', value)
223
224
225    def test_service_database_state_attribute(self):
226        """Implementation of test TP/SERVER/SSA/BV-08-C from SDP Specification.
227
228        @raises error.TestFail if test fails
229
230        """
231        value = self.test_attribute(self.SDP_SERVER_CLASS_ID,
232                                    self.SERVICE_DATABASE_STATE_ATTR_ID)
233        if not isinstance(value, int):
234            self.fail_test('TP/SERVER/SSA/BV-08-C', value)
235
236
237    def test_protocol_descriptor_list_attribute(self):
238        """Implementation of test TP/SERVER/SSA/BV-11-C from SDP Specification.
239
240        @raises error.TestFail if test fails
241
242        """
243        value = self.test_attribute(self.GAP_CLASS_ID,
244                                    self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID)
245
246        # The first-layer protocol is L2CAP, using the PSM for ATT protocol.
247        if value[0] != [self.L2CAP_UUID, self.ATT_PSM]:
248            self.fail_test('TP/SERVER/SSA/BV-11-C', value)
249
250        # The second-layer protocol is ATT. The additional parameters are
251        # ignored, since they may reasonably vary between implementations.
252        if value[1][0] != self.ATT_UUID:
253            self.fail_test('TP/SERVER/SSA/BV-11-C', value)
254
255
256
257    def test_browse_group_attribute(self):
258        """Implementation of test TP/SERVER/SSA/BV-12-C from SDP Specification.
259
260        @raises error.TestFail if test fails
261
262        """
263        value = self.test_attribute(self.GAP_CLASS_ID,
264                                    self.BROWSE_GROUP_LIST_ATTR_ID)
265        if not value == [self.PUBLIC_BROWSE_ROOT]:
266            self.fail_test('TP/SERVER/SSA/BV-12-C', value)
267
268
269    def test_icon_url_attribute(self):
270        """Implementation of test TP/SERVER/SSA/BV-15-C from SDP Specification.
271
272        @raises error.TestFail if test fails
273
274        """
275        value = self.test_attribute(self.GAP_CLASS_ID,
276                                    self.ICON_URL_ATTR_ID)
277        if not value == self.BLUEZ_URL:
278            self.fail_test('TP/SERVER/SSA/BV-15-C', value)
279
280
281    def test_version_list_attribute(self):
282        """Implementation of test TP/SERVER/SSA/BV-19-C from SDP Specification.
283
284        @raises error.TestFail if test fails
285
286        """
287        value = self.test_attribute(self.SDP_SERVER_CLASS_ID,
288                                    self.VERSION_NUMBER_LIST_ATTR_ID)
289        if not isinstance(value, list) and value != []:
290            self.fail_test('TP/SERVER/SSA/BV-19-C', value)
291
292
293    def test_profile_descriptor_list_attribute(self):
294        """Implementation of test TP/SERVER/SSA/BV-20-C from SDP Specification.
295
296        @raises error.TestFail if test fails
297
298        """
299        value = self.test_attribute(self.PNP_INFORMATION_CLASS_ID,
300                                    self.PROFILE_DESCRIPTOR_LIST_ATTR_ID)
301        if not (isinstance(value, list) and len(value) == 1 and
302                isinstance(value[0], list) and len(value[0]) == 2 and
303                value[0][0] == self.PNP_INFORMATION_CLASS_ID):
304            self.fail_test('TP/SERVER/SSA/BV-20-C', value)
305
306
307    def test_documentation_url_attribute(self):
308        """Implementation of test TP/SERVER/SSA/BV-21-C from SDP Specification.
309
310        @raises error.TestFail if test fails
311
312        """
313        value = self.test_attribute(self.GAP_CLASS_ID,
314                                    self.DOCUMENTATION_URL_ATTR_ID)
315        if not value == self.BLUEZ_URL:
316            self.fail_test('TP/SERVER/SSA/BV-21-C', value)
317
318
319    def test_client_executable_url_attribute(self):
320        """Implementation of test TP/SERVER/SSA/BV-22-C from SDP Specification.
321
322        @raises error.TestFail if test fails
323
324        """
325        value = self.test_attribute(self.GAP_CLASS_ID,
326                                    self.CLIENT_EXECUTABLE_URL_ATTR_ID)
327        if not value == self.BLUEZ_URL:
328            self.fail_test('TP/SERVER/SSA/BV-22-C', value)
329
330
331    def test_additional_protocol_descriptor_list_attribute(self):
332        """Implementation of test TP/SERVER/SSA/BV-23-C from SDP Specification.
333
334        @raises error.TestFail if test fails
335
336        """
337
338        """AVRCP is not supported by Chromebook and no need to run this test
339        value = self.test_attribute(self.AVRCP_TG_CLASS_ID,
340                                    self.ADDITIONAL_PROTOCOLLIST_ATTR_ID)
341        if not isinstance(value, list) and value != []:
342            self.fail_test('TP/SERVER/SSA/BV-23-C', value)
343        """
344
345    def test_fake_attributes(self):
346        """Test values of attributes of the fake service record.
347
348        @raises error.TestFail if test fails
349
350        """
351        for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
352            value = self.test_attribute(self.FAKE_SERVICE_CLASS_ID, attr_id)
353            if value != self.FAKE_ATTRIBUTE_VALUE:
354                self.fail_test('fake service attributes', value)
355
356        for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
357            lang_base = self.test_attribute(self.FAKE_SERVICE_CLASS_ID,
358                                            self.LANGUAGE_BASE_ATTRIBUTE_ID)
359            attr_id = lang_base + offset
360            value = self.test_attribute(self.FAKE_SERVICE_CLASS_ID, attr_id)
361            if value != self.FAKE_ATTRIBUTE_VALUE:
362                self.fail_test('fake service attributes', value)
363
364
365    def test_continuation_state(self):
366        """Implementation of test TP/SERVER/SSA/BV-06-C from SDP Specification.
367
368        @raises error.TestFail if test fails
369
370        """
371        for size in 16, 32, 128:
372            # This request should generate a long response, which will be
373            # split into 98 chunks.
374            value = self.tester.service_search_attribute_request(
375                        [self.PUBLIC_BROWSE_GROUP_CLASS_ID],
376                        self.MIN_ATTR_BYTE_CNT,
377                        [[0, 0xFFFF]], size)
378            if not isinstance(value, list) or value == []:
379                self.fail_test('TP/SERVER/SSA/BV-06-C', value)
380
381
382    def test_invalid_request_syntax(self):
383        """Implementation of test TP/SERVER/SSA/BI-01-C from SDP Specification.
384
385        @raises error.TestFail if test fails
386
387        """
388        for size in 16, 32, 128:
389            value = self.tester.service_search_attribute_request(
390                        [self.SDP_SERVER_CLASS_ID],
391                        self.MAX_ATTR_BYTE_CNT,
392                        [self.SERVICE_CLASS_ID_ATTRIBUTE_ID],
393                        size,
394                        invalid_request='9875')
395            if value != self.ERROR_CODE_INVALID_SYNTAX:
396                self.fail_test('TP/SERVER/SSA/BI-01-C', value)
397
398
399    def test_invalid_pdu_size(self):
400        """Implementation of test TP/SERVER/SSA/BI-02-C from SDP Specification.
401
402        @raises error.TestFail if test fails
403
404        """
405        for size in 16, 32, 128:
406            value = self.tester.service_search_attribute_request(
407                        [self.SDP_SERVER_CLASS_ID],
408                        self.MAX_ATTR_BYTE_CNT,
409                        [self.SERVICE_CLASS_ID_ATTRIBUTE_ID],
410                        size,
411                        forced_pdu_size=100)
412            if value != self.ERROR_CODE_INVALID_PDU_SIZE:
413                self.fail_test('TP/SERVER/SSA/BI-02-C', value)
414
415
416    def correct_request(self):
417        """Run tests for Service Search Attribute request.
418
419        @raises error.TestFail if any test fails
420
421        """
422        # connect to the DUT via L2CAP using SDP socket
423        self.tester.connect(self.adapter['Address'])
424
425        self.test_non_existing_service()
426        self.test_non_existing_attribute()
427        self.test_non_existing_service_attribute()
428        #self.test_existing_service_attribute()
429        self.test_service_database_state_attribute()
430        self.test_protocol_descriptor_list_attribute()
431        self.test_browse_group_attribute()
432        self.test_icon_url_attribute()
433        self.test_version_list_attribute()
434        self.test_profile_descriptor_list_attribute()
435        self.test_documentation_url_attribute()
436        self.test_client_executable_url_attribute()
437        self.test_additional_protocol_descriptor_list_attribute()
438        self.test_fake_attributes()
439        self.test_continuation_state()
440        self.test_invalid_request_syntax()
441        self.test_invalid_pdu_size()
442        logging.info('correct_request finished successfully!')
443
444
445    def run_once(self):
446        # Reset the adapter to the powered on, discoverable state.
447        if not self.device.reset_on():
448            raise error.TestFail('DUT adapter could not be powered on')
449        if not self.device.set_discoverable(True):
450            raise error.TestFail('DUT could not be set as discoverable')
451
452        self.adapter = self.device.get_adapter_properties()
453
454        # Create a fake service record in order to test attributes,
455        # that are not present in any of existing services.
456        uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) +
457                   self.BLUETOOTH_BASE_UUID)
458        uuid_str = str(uuid.UUID(int=uuid128))
459        sdp_record = self.build_service_record()
460        self.device.register_profile(self.FAKE_SERVICE_PATH,
461                                     uuid_str,
462                                     {"ServiceRecord": sdp_record})
463
464        # Setup the tester as a generic computer.
465        if not self.tester.setup('computer'):
466            raise error.TestFail('Tester could not be initialized')
467
468        # Since radio is involved, this test is not 100% reliable; instead we
469        # repeat a few times until it succeeds.
470        passing = False
471        for failed_attempts in range(0, 4):
472            try:
473                self.correct_request()
474                passing = True
475            except error.TestFail as e:
476                logging.warning('Ignoring error: %s', e)
477            if passing:
478                break
479        else:
480            self.correct_request()
481
482        # Record how many attempts this took, hopefully we'll one day figure out
483        # a way to reduce this to zero and then the loop above can go away.
484        self.write_perf_keyval({'failed_attempts': failed_attempts})
485