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