Home | History | Annotate | Download | only in bluetooth_SDP_ServiceSearchAttributeRequest
      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 uuid
      7 import xml.etree.ElementTree as ET
      8 
      9 from autotest_lib.client.common_lib import error
     10 from autotest_lib.server.cros.bluetooth import bluetooth_test
     11 
     12 class 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