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 uuid 6import xml.etree.ElementTree as ET 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.server.cros.bluetooth import bluetooth_test 10 11class 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