Home | History | Annotate | Download | only in bluetooth_SDP_ServiceAttributeRequest
      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 uuid
      6 import xml.etree.ElementTree as ET
      7 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.server.cros.bluetooth import bluetooth_test
     10 
     11 class bluetooth_SDP_ServiceAttributeRequest(bluetooth_test.BluetoothTest):
     12     """
     13     Verify the correct behaviour of the device when searching for attributes of
     14     services.
     15     """
     16     version = 1
     17 
     18     MAX_REC_CNT                      = 3
     19     MAX_ATTR_BYTE_CNT                = 300
     20 
     21     SDP_SERVER_CLASS_ID              = 0x1000
     22     SERVICE_RECORD_HANDLE_ATTR_ID    = 0x0000
     23 
     24     GAP_CLASS_ID                     = 0x1800
     25     BROWSE_GROUP_LIST_ATTR_ID        = 0x0005
     26     PUBLIC_BROWSE_ROOT               = 0x1002
     27 
     28     BLUEZ_URL                        = 'http://www.bluez.org/'
     29     DOCUMENTATION_URL_ATTR_ID        = 0x000A
     30     CLIENT_EXECUTABLE_URL_ATTR_ID    = 0x000B
     31     ICON_URL_ATTR_ID                 = 0x000C
     32 
     33     PROTOCOL_DESCRIPTOR_LIST_ATTR_ID = 0x0004
     34     L2CAP_UUID                       = 0x0100
     35     ATT_UUID                         = 0x0007
     36 
     37     ATT_PSM                          = 0x001F
     38 
     39     PNP_INFORMATION_CLASS_ID         = 0x1200
     40     MIN_ATTR_BYTE_CNT                = 7
     41 
     42     VERSION_NUMBER_LIST_ATTR_ID      = 0x0200
     43     SERVICE_DATABASE_STATE_ATTR_ID   = 0x0201
     44 
     45     AVRCP_TG_CLASS_ID                = 0x110C
     46     PROFILE_DESCRIPTOR_LIST_ATTR_ID  = 0x0009
     47     ADDITIONAL_PROTOCOLLIST_ATTR_ID  = 0x000D
     48 
     49     FAKE_SERVICE_PATH                = '/autotest/fake_service'
     50     FAKE_SERVICE_CLASS_ID            = 0xCDEF
     51     FAKE_ATTRIBUTE_VALUE             = 42
     52     LANGUAGE_BASE_ATTRIBUTE_ID       = 0x0006
     53     FAKE_GENERAL_ATTRIBUTE_IDS       = [
     54                                         0x0003, # TP/SERVER/SA/BV-04-C
     55                                         0x0002, # TP/SERVER/SA/BV-06-C
     56                                         0x0007, # TP/SERVER/SA/BV-07-C
     57                                         0x0008, # TP/SERVER/SA/BV-10-C
     58                                         # TP/SERVER/SA/BV-09-C:
     59                                         LANGUAGE_BASE_ATTRIBUTE_ID
     60                                        ]
     61     FAKE_LANGUAGE_ATTRIBUTE_OFFSETS  = [
     62                                         0x0000, # TP/SERVER/SA/BV-12-C
     63                                         0x0001, # TP/SERVER/SA/BV-13-C
     64                                         0x0002  # TP/SERVER/SA/BV-14-C
     65                                        ]
     66     NON_EXISTING_ATTRIBUTE_ID        = 0xFEDC
     67     BLUETOOTH_BASE_UUID              = 0x0000000000001000800000805F9B34FB
     68     SERVICE_CLASS_ID_ATTR_ID         = 0x0001
     69 
     70     ERROR_CODE_INVALID_RECORD_HANDLE = 0x0002
     71     ERROR_CODE_INVALID_SYNTAX        = 0x0003
     72     ERROR_CODE_INVALID_PDU_SIZE      = 0x0004
     73     INVALID_RECORD_HANDLE            = 0xFEEE
     74     INVALID_SYNTAX_REQUEST           = '123'
     75     INVALID_PDU_SIZE                 = 11
     76 
     77     @staticmethod
     78     def assert_equal(actual, expected):
     79         """Verify that |actual| is equal to |expected|.
     80 
     81         @param actual: The value we got.
     82         @param expected: The value we expected.
     83         @raise error.TestFail: If the values are unequal.
     84         """
     85         if actual != expected:
     86             raise error.TestFail(
     87                 'Expected |%s|, got |%s|' % (expected, actual))
     88 
     89 
     90     @staticmethod
     91     def assert_nonempty_list(value):
     92         """Verify that |value| is a list, and that the list is non-empty.
     93 
     94         @param value: The value to check.
     95         @raise error.TestFail: If the value is not a list, or is empty.
     96         """
     97         if not isinstance(value, list):
     98             raise error.TestFail('Value is not a list. Got |%s|.' % value)
     99 
    100         if value == []:
    101             raise error.TestFail('List is empty')
    102 
    103 
    104     def get_single_handle(self, class_id):
    105         """Send a Service Search Request to get a handle for specific class ID.
    106 
    107         @param class_id: The class that we want a handle for.
    108         @return The record handle, as an int.
    109         @raise error.TestFail: If we failed to retrieve a handle.
    110         """
    111         res = self.tester.service_search_request([class_id], self.MAX_REC_CNT)
    112         if not (isinstance(res, list) and len(res) > 0):
    113             raise error.TestFail(
    114                     'Failed to retrieve handle for 0x%x' % class_id)
    115         return res[0]
    116 
    117 
    118     # TODO(quiche): Place this after get_attribute(), so all the tests are
    119     # grouped together.
    120     def test_record_handle_attribute(self):
    121         """Implementation of test TP/SERVER/SA/BV-01-C from SDP Specification.
    122 
    123         @raise error.TestFail: If the DUT failed the test.
    124         """
    125         # Send Service Search Request to find out record handle for
    126         # SDP Server service.
    127         record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID)
    128 
    129         # Send Service Attribute Request for Service Record Handle Attribute.
    130         res = self.tester.service_attribute_request(
    131                   record_handle,
    132                   self.MAX_ATTR_BYTE_CNT,
    133                   [self.SERVICE_RECORD_HANDLE_ATTR_ID])
    134 
    135         # Ensure that returned attribute is correct.
    136         self.assert_equal(res,
    137                           [self.SERVICE_RECORD_HANDLE_ATTR_ID, record_handle])
    138 
    139 
    140     def get_attribute(self, class_id, attr_id):
    141         """Get a single attribute of a single service
    142 
    143         @param class_id: Class ID of service to check.
    144         @param attr_id: ID of attribute to check.
    145         @return attribute value if attribute exists, None otherwise
    146 
    147         """
    148         record_handle = self.get_single_handle(class_id)
    149         res = self.tester.service_attribute_request(
    150                   record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id])
    151         if isinstance(res, list) and len(res) == 2 and res[0] == attr_id:
    152             return res[1]
    153         return None
    154 
    155 
    156     # TODO(quiche): Move this up, to be grouped with the other |assert|
    157     # methods.
    158     def assert_attribute_equals(self, class_id, attr_id, expected_value):
    159         """Verify that |attr_id| of service with |class_id| has |expected_value|
    160 
    161         @param class_id: Class ID of service to check.
    162         @param attr_id: ID of attribute to check.
    163         @param expected_value: The expected value for the attribute.
    164         @raise error.TestFail: If the actual value differs from |expected_value|
    165         """
    166         self.assert_equal(self.get_attribute(class_id, attr_id),
    167                           expected_value)
    168 
    169 
    170     def test_browse_group_attribute(self):
    171         """Implementation of test TP/SERVER/SA/BV-08-C from SDP Specification.
    172 
    173         @raise error.TestFail: If the DUT failed the test.
    174         """
    175         self.assert_attribute_equals(self.GAP_CLASS_ID,
    176                                      self.BROWSE_GROUP_LIST_ATTR_ID,
    177                                      [self.PUBLIC_BROWSE_ROOT])
    178 
    179 
    180     def test_icon_url_attribute(self):
    181         """Implementation of test TP/SERVER/SA/BV-11-C from SDP Specification.
    182 
    183         @raise error.TestFail: If the DUT failed the test.
    184         """
    185         self.assert_attribute_equals(self.GAP_CLASS_ID,
    186                                      self.ICON_URL_ATTR_ID,
    187                                      self.BLUEZ_URL)
    188 
    189 
    190     def test_documentation_url_attribute(self):
    191         """Implementation of test TP/SERVER/SA/BV-18-C from SDP Specification.
    192 
    193         @raise error.TestFail: If the DUT failed the test.
    194         """
    195         self.assert_attribute_equals(self.GAP_CLASS_ID,
    196                                      self.DOCUMENTATION_URL_ATTR_ID,
    197                                      self.BLUEZ_URL)
    198 
    199 
    200     def test_client_executable_url_attribute(self):
    201         """Implementation of test TP/SERVER/SA/BV-19-C from SDP Specification.
    202 
    203         @raise error.TestFail: If the DUT failed the test.
    204         """
    205         self.assert_attribute_equals(self.GAP_CLASS_ID,
    206                                      self.CLIENT_EXECUTABLE_URL_ATTR_ID,
    207                                      self.BLUEZ_URL)
    208 
    209 
    210     def test_protocol_descriptor_list_attribute(self):
    211         """Implementation of test TP/SERVER/SA/BV-05-C from SDP Specification.
    212 
    213         @raise error.TestFail: If the DUT failed the test.
    214         """
    215         value = self.get_attribute(self.GAP_CLASS_ID,
    216                                    self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID)
    217 
    218         # The first-layer protocol is L2CAP, using the PSM for ATT protocol.
    219         self.assert_equal(value[0], [self.L2CAP_UUID, self.ATT_PSM])
    220 
    221         # The second-layer protocol is ATT. The additional parameters are
    222         # ignored, since they may reasonably vary between implementations.
    223         self.assert_equal(value[1][0], self.ATT_UUID)
    224 
    225 
    226     def test_continuation_state(self):
    227         """Implementation of test TP/SERVER/SA/BV-03-C from SDP Specification.
    228 
    229         @raise error.TestFail: If the DUT failed the test.
    230         """
    231         record_handle = self.get_single_handle(self.PNP_INFORMATION_CLASS_ID)
    232         self.assert_nonempty_list(
    233             self.tester.service_attribute_request(
    234                 record_handle, self.MIN_ATTR_BYTE_CNT, [[0, 0xFFFF]]))
    235 
    236 
    237     def test_version_list_attribute(self):
    238         """Implementation of test TP/SERVER/SA/BV-15-C from SDP Specification.
    239 
    240         @raise error.TestFail: If the DUT failed the test.
    241         """
    242         self.assert_nonempty_list(
    243             self.get_attribute(self.SDP_SERVER_CLASS_ID,
    244                 self.VERSION_NUMBER_LIST_ATTR_ID))
    245 
    246 
    247     def test_service_database_state_attribute(self):
    248         """Implementation of test TP/SERVER/SA/BV-16-C from SDP Specification.
    249 
    250         @raise error.TestFail: If the DUT failed the test.
    251         """
    252         state = self.get_attribute(self.SDP_SERVER_CLASS_ID,
    253                                    self.SERVICE_DATABASE_STATE_ATTR_ID)
    254         if not isinstance(state, int):
    255             raise error.TestFail('State is not an int: %s' % state)
    256 
    257 
    258     def test_profile_descriptor_list_attribute(self):
    259         """Implementation of test TP/SERVER/SA/BV-17-C from SDP Specification.
    260 
    261         @raise error.TestFail: If list attribute not correct form.
    262 
    263         """
    264         profile_list = self.get_attribute(self.PNP_INFORMATION_CLASS_ID,
    265                                           self.PROFILE_DESCRIPTOR_LIST_ATTR_ID)
    266 
    267         if not isinstance(profile_list, list):
    268             raise error.TestFail('Value is not a list')
    269         self.assert_equal(len(profile_list), 1)
    270 
    271         if not isinstance(profile_list[0], list):
    272             raise error.TestFail('Item is not a list')
    273         self.assert_equal(len(profile_list[0]), 2)
    274 
    275         self.assert_equal(profile_list[0][0], self.PNP_INFORMATION_CLASS_ID)
    276 
    277 
    278     def test_additional_protocol_descriptor_list_attribute(self):
    279         """Implementation of test TP/SERVER/SA/BV-21-C from SDP Specification.
    280 
    281         @raise error.TestFail: If the DUT failed the test.
    282 
    283         """
    284 
    285         """AVRCP is not supported by Chromebook and no need to run this test
    286         self.assert_nonempty_list(
    287             self.get_attribute(self.AVRCP_TG_CLASS_ID,
    288                 self.ADDITIONAL_PROTOCOLLIST_ATTR_ID))
    289         """
    290 
    291     def test_non_existing_attribute(self):
    292         """Implementation of test TP/SERVER/SA/BV-20-C from SDP Specification.
    293 
    294         @raise error.TestFail: If the DUT failed the test.
    295         """
    296         record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID)
    297         res = self.tester.service_attribute_request(
    298                   record_handle, self.MAX_ATTR_BYTE_CNT,
    299                   [self.NON_EXISTING_ATTRIBUTE_ID])
    300         self.assert_equal(res, [])
    301 
    302 
    303     def test_fake_attributes(self):
    304         """Test values of attributes of the fake service record.
    305 
    306         @raise error.TestFail: If the DUT failed the test.
    307         """
    308         for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
    309             self.assert_attribute_equals(self.FAKE_SERVICE_CLASS_ID,
    310                                          attr_id, self.FAKE_ATTRIBUTE_VALUE)
    311 
    312         for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
    313             record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID)
    314 
    315             lang_base = self.tester.service_attribute_request(
    316                             record_handle, self.MAX_ATTR_BYTE_CNT,
    317                             [self.LANGUAGE_BASE_ATTRIBUTE_ID])
    318             attr_id = lang_base[1] + offset
    319 
    320             response = self.tester.service_attribute_request(
    321                            record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id])
    322             self.assert_equal(response, [attr_id, self.FAKE_ATTRIBUTE_VALUE])
    323 
    324 
    325     def test_invalid_record_handle(self):
    326         """Implementation of test TP/SERVER/SA/BI-01-C from SDP Specification.
    327 
    328         @raise error.TestFail: If the DUT failed the test.
    329         """
    330         res = self.tester.service_attribute_request(
    331                   self.INVALID_RECORD_HANDLE, self.MAX_ATTR_BYTE_CNT,
    332                   [self.NON_EXISTING_ATTRIBUTE_ID])
    333         self.assert_equal(res, self.ERROR_CODE_INVALID_RECORD_HANDLE)
    334 
    335 
    336     def test_invalid_request_syntax(self):
    337         """Implementation of test TP/SERVER/SA/BI-02-C from SDP Specification.
    338 
    339         @raise error.TestFail: If the DUT failed the test.
    340         """
    341         record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID)
    342         res = self.tester.service_attribute_request(
    343                   record_handle,
    344                   self.MAX_ATTR_BYTE_CNT,
    345                   [self.SERVICE_RECORD_HANDLE_ATTR_ID],
    346                   invalid_request=self.INVALID_SYNTAX_REQUEST)
    347         self.assert_equal(res, self.ERROR_CODE_INVALID_SYNTAX)
    348 
    349 
    350     def test_invalid_pdu_size(self):
    351         """Implementation of test TP/SERVER/SA/BI-03-C from SDP Specification.
    352 
    353         @raise error.TestFail: If the DUT failed the test.
    354         """
    355         record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID)
    356         res = self.tester.service_attribute_request(
    357                   record_handle,
    358                   self.MAX_ATTR_BYTE_CNT,
    359                   [self.SERVICE_RECORD_HANDLE_ATTR_ID],
    360                   forced_pdu_size=self.INVALID_PDU_SIZE)
    361         self.assert_equal(res, self.ERROR_CODE_INVALID_PDU_SIZE)
    362 
    363 
    364     def correct_request(self):
    365         """Run basic tests for Service Attribute Request."""
    366         # Connect to the DUT via L2CAP using SDP socket.
    367         self.tester.connect(self.adapter['Address'])
    368         self.test_record_handle_attribute()
    369         self.test_browse_group_attribute()
    370         self.test_icon_url_attribute()
    371         self.test_documentation_url_attribute()
    372         self.test_client_executable_url_attribute()
    373         self.test_protocol_descriptor_list_attribute()
    374         self.test_continuation_state()
    375         self.test_version_list_attribute()
    376         self.test_service_database_state_attribute()
    377         self.test_profile_descriptor_list_attribute()
    378         self.test_additional_protocol_descriptor_list_attribute()
    379         self.test_fake_attributes()
    380         self.test_non_existing_attribute()
    381         self.test_invalid_record_handle()
    382         self.test_invalid_request_syntax()
    383         self.test_invalid_pdu_size()
    384 
    385 
    386     def build_service_record(self):
    387         """Build SDP record manually for the fake service.
    388 
    389         @return resulting record as string
    390 
    391         """
    392         value = ET.Element('uint16', {'value': str(self.FAKE_ATTRIBUTE_VALUE)})
    393 
    394         sdp_record = ET.Element('record')
    395 
    396         service_id_attr = ET.Element(
    397             'attribute', {'id': str(self.SERVICE_CLASS_ID_ATTR_ID)})
    398         service_id_attr.append(
    399             ET.Element('uuid', {'value': '0x%X' % self.FAKE_SERVICE_CLASS_ID}))
    400         sdp_record.append(service_id_attr)
    401 
    402         for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS:
    403             attr = ET.Element('attribute', {'id': str(attr_id)})
    404             attr.append(value)
    405             sdp_record.append(attr)
    406 
    407         for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS:
    408             attr_id = self.FAKE_ATTRIBUTE_VALUE + offset
    409             attr = ET.Element('attribute', {'id': str(attr_id)})
    410             attr.append(value)
    411             sdp_record.append(attr)
    412 
    413         sdp_record_str = ('<?xml version="1.0" encoding="UTF-8"?>' +
    414                           ET.tostring(sdp_record))
    415         return sdp_record_str
    416 
    417 
    418     def run_once(self):
    419         # Reset the adapter to the powered on, discoverable state.
    420         if not self.device.reset_on():
    421             raise error.TestFail('DUT adapter could not be powered on')
    422         if not self.device.set_discoverable(True):
    423             raise error.TestFail('DUT could not be set as discoverable')
    424 
    425         self.adapter = self.device.get_adapter_properties()
    426 
    427         # Create a fake service record in order to test attributes,
    428         # that are not present in any of existing services.
    429         uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) +
    430                    self.BLUETOOTH_BASE_UUID)
    431         uuid_str = str(uuid.UUID(int=uuid128))
    432         sdp_record = self.build_service_record()
    433         self.device.register_profile(self.FAKE_SERVICE_PATH,
    434                                      uuid_str,
    435                                      {"ServiceRecord": sdp_record})
    436 
    437         # Setup the tester as a generic computer.
    438         if not self.tester.setup('computer'):
    439             raise error.TestFail('Tester could not be initialized')
    440 
    441         self.correct_request()
    442