1# Lint as: python2, python3 2from __future__ import absolute_import 3from __future__ import division 4from __future__ import print_function 5 6import json 7import logging 8import uuid 9import xml.etree.ElementTree as ET 10 11import common 12from autotest_lib.client.common_lib import error 13from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests 14from six.moves import range 15 16 17class bluetooth_SDP_Test(object): 18 """Base class with Properties and methods common across SDP tests""" 19 version = 1 20 21 MIN_ATTR_BYTE_CNT = 7 22 MAX_ATTR_BYTE_CNT = 300 23 24 SDP_SERVER_CLASS_ID = 0x1000 25 GAP_CLASS_ID = 0x1800 26 BROWSE_GROUP_LIST_ATTR_ID = 0x0005 27 PUBLIC_BROWSE_ROOT = 0x1002 28 29 DOCUMENTATION_URL_ATTR_ID = 0x000A 30 CLIENT_EXECUTABLE_URL_ATTR_ID = 0x000B 31 ICON_URL_ATTR_ID = 0x000C 32 PROTOCOL_DESCRIPTOR_LIST_ATTR_ID = 0x0004 33 L2CAP_UUID = 0x0100 34 ATT_UUID = 0x0007 35 ATT_PSM = 0x001F 36 PNP_INFORMATION_CLASS_ID = 0x1200 37 VERSION_NUMBER_LIST_ATTR_ID = 0x0200 38 SERVICE_DATABASE_STATE_ATTR_ID = 0x0201 39 AVRCP_TG_CLASS_ID = 0x110C 40 PROFILE_DESCRIPTOR_LIST_ATTR_ID = 0x0009 41 ADDITIONAL_PROTOCOLLIST_ATTR_ID = 0x000D 42 43 FAKE_SERVICE_PATH = '/autotest/fake_service' 44 BLUEZ_URL = 'http://www.bluez.org/' 45 46 FAKE_SERVICE_CLASS_ID = 0xCDEF 47 FAKE_ATTRIBUTE_VALUE = 42 48 LANGUAGE_BASE_ATTRIBUTE_ID = 0x0006 49 50 FAKE_GENERAL_ATTRIBUTE_IDS = [ 51 0x0003, # TP/SERVER/SA/BV-04-C 52 0x0002, # TP/SERVER/SA/BV-06-C 53 0x0007, # TP/SERVER/SA/BV-07-C 54 0x0008, # TP/SERVER/SA/BV-10-C 55 # TP/SERVER/SA/BV-09-C: 56 LANGUAGE_BASE_ATTRIBUTE_ID 57 ] 58 59 FAKE_LANGUAGE_ATTRIBUTE_OFFSETS = [ 60 0x0000, # TP/SERVER/SA/BV-12-C 61 0x0001, # TP/SERVER/SA/BV-13-C 62 0x0002 # TP/SERVER/SA/BV-14-C 63 ] 64 65 BLUETOOTH_BASE_UUID = 0x0000000000001000800000805F9B34FB 66 SERVICE_CLASS_ID_ATTR_ID = 0x0001 67 ERROR_CODE_INVALID_RECORD_HANDLE = 0x0002 68 ERROR_CODE_INVALID_SYNTAX = 0x0003 69 ERROR_CODE_INVALID_PDU_SIZE = 0x0004 70 INVALID_RECORD_HANDLE = 0xFEEE 71 INVALID_SYNTAX_REQUEST = '123' 72 INVALID_PDU_SIZE = 11 73 74 75 def build_service_record(self): 76 """Build SDP record manually for the fake service. 77 78 @return resulting record as string 79 80 """ 81 value = ET.Element('uint16', {'value': str(self.FAKE_ATTRIBUTE_VALUE)}) 82 83 sdp_record = ET.Element('record') 84 85 service_id_attr = ET.Element( 86 'attribute', {'id': str(self.SERVICE_CLASS_ID_ATTR_ID)}) 87 service_id_attr.append( 88 ET.Element('uuid', {'value': '0x%X' % self.FAKE_SERVICE_CLASS_ID})) 89 sdp_record.append(service_id_attr) 90 91 for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS: 92 attr = ET.Element('attribute', {'id': str(attr_id)}) 93 attr.append(value) 94 sdp_record.append(attr) 95 96 for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS: 97 attr_id = self.FAKE_ATTRIBUTE_VALUE + offset 98 attr = ET.Element('attribute', {'id': str(attr_id)}) 99 attr.append(value) 100 sdp_record.append(attr) 101 102 sdp_record_str = ('<?xml version="1.0" encoding="UTF-8"?>' + 103 ET.tostring(sdp_record).decode('utf-8')) 104 return sdp_record_str 105 106 107class bluetooth_SDP_ServiceAttributeRequest(bluetooth_SDP_Test, 108 bluetooth_adapter_tests.BluetoothAdapterTests): 109 """ 110 Verify the correct behaviour of the device when searching for attributes of 111 services. 112 """ 113 version = 1 114 115 MAX_REC_CNT = 3 116 117 SERVICE_RECORD_HANDLE_ATTR_ID = 0x0000 118 119 NON_EXISTING_ATTRIBUTE_ID = 0xFEDC 120 121 @staticmethod 122 def assert_equal(actual, expected): 123 """Verify that |actual| is equal to |expected|. 124 125 @param actual: The value we got. 126 @param expected: The value we expected. 127 @raise error.TestFail: If the values are unequal. 128 """ 129 if actual != expected: 130 raise error.TestFail( 131 'Expected |%s|, got |%s|' % (expected, actual)) 132 133 134 @staticmethod 135 def assert_nonempty_list(value): 136 """Verify that |value| is a list, and that the list is non-empty. 137 138 @param value: The value to check. 139 @raise error.TestFail: If the value is not a list, or is empty. 140 """ 141 if not isinstance(value, list): 142 raise error.TestFail('Value is not a list. Got |%s|.' % value) 143 144 if value == []: 145 raise error.TestFail('List is empty') 146 147 148 def get_single_handle(self, class_id): 149 """Send a Service Search Request to get a handle for specific class ID. 150 151 @param class_id: The class that we want a handle for. 152 @return The record handle, as an int. 153 @raise error.TestFail: If we failed to retrieve a handle. 154 """ 155 res = json.loads(self.tester.service_search_request([class_id], 156 self.MAX_REC_CNT, dict())) 157 if not (isinstance(res, list) and len(res) > 0): 158 raise error.TestFail( 159 'Failed to retrieve handle for 0x%x' % class_id) 160 return res[0] 161 162 163 # TODO(quiche): Place this after get_attribute(), so all the tests are 164 # grouped together. 165 def test_record_handle_attribute(self): 166 """Implementation of test TP/SERVER/SA/BV-01-C from SDP Specification. 167 168 @raise error.TestFail: If the DUT failed the test. 169 """ 170 # Send Service Search Request to find out record handle for 171 # SDP Server service. 172 record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID) 173 174 # Send Service Attribute Request for Service Record Handle Attribute. 175 res = json.loads(self.tester.service_attribute_request( 176 record_handle, 177 self.MAX_ATTR_BYTE_CNT, 178 [self.SERVICE_RECORD_HANDLE_ATTR_ID], {})) 179 180 # Ensure that returned attribute is correct. 181 self.assert_equal(res, 182 [self.SERVICE_RECORD_HANDLE_ATTR_ID, record_handle]) 183 184 185 def get_attribute(self, class_id, attr_id): 186 """Get a single attribute of a single service 187 188 @param class_id: Class ID of service to check. 189 @param attr_id: ID of attribute to check. 190 @return attribute value if attribute exists, None otherwise 191 192 """ 193 record_handle = self.get_single_handle(class_id) 194 res = json.loads(self.tester.service_attribute_request( 195 record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {})) 196 if isinstance(res, list) and len(res) == 2 and res[0] == attr_id: 197 return res[1] 198 return None 199 200 201 # TODO(quiche): Move this up, to be grouped with the other |assert| 202 # methods. 203 def assert_attribute_equals(self, class_id, attr_id, expected_value): 204 """Verify that |attr_id| of service with |class_id| has |expected_value| 205 206 @param class_id: Class ID of service to check. 207 @param attr_id: ID of attribute to check. 208 @param expected_value: The expected value for the attribute. 209 @raise error.TestFail: If the actual value differs from |expected_value| 210 """ 211 self.assert_equal(self.get_attribute(class_id, attr_id), 212 expected_value) 213 214 215 def test_browse_group_attribute(self): 216 """Implementation of test TP/SERVER/SA/BV-08-C from SDP Specification. 217 218 @raise error.TestFail: If the DUT failed the test. 219 """ 220 self.assert_attribute_equals(self.GAP_CLASS_ID, 221 self.BROWSE_GROUP_LIST_ATTR_ID, 222 [self.PUBLIC_BROWSE_ROOT]) 223 224 225 def test_icon_url_attribute(self): 226 """Implementation of test TP/SERVER/SA/BV-11-C from SDP Specification. 227 228 @raise error.TestFail: If the DUT failed the test. 229 """ 230 self.assert_attribute_equals(self.GAP_CLASS_ID, 231 self.ICON_URL_ATTR_ID, 232 self.BLUEZ_URL) 233 234 235 def test_documentation_url_attribute(self): 236 """Implementation of test TP/SERVER/SA/BV-18-C from SDP Specification. 237 238 @raise error.TestFail: If the DUT failed the test. 239 """ 240 self.assert_attribute_equals(self.GAP_CLASS_ID, 241 self.DOCUMENTATION_URL_ATTR_ID, 242 self.BLUEZ_URL) 243 244 245 def test_client_executable_url_attribute(self): 246 """Implementation of test TP/SERVER/SA/BV-19-C from SDP Specification. 247 248 @raise error.TestFail: If the DUT failed the test. 249 """ 250 self.assert_attribute_equals(self.GAP_CLASS_ID, 251 self.CLIENT_EXECUTABLE_URL_ATTR_ID, 252 self.BLUEZ_URL) 253 254 255 def test_protocol_descriptor_list_attribute(self): 256 """Implementation of test TP/SERVER/SA/BV-05-C from SDP Specification. 257 258 @raise error.TestFail: If the DUT failed the test. 259 """ 260 value = self.get_attribute(self.GAP_CLASS_ID, 261 self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID) 262 263 # The first-layer protocol is L2CAP, using the PSM for ATT protocol. 264 self.assert_equal(value[0], [self.L2CAP_UUID, self.ATT_PSM]) 265 266 # The second-layer protocol is ATT. The additional parameters are 267 # ignored, since they may reasonably vary between implementations. 268 self.assert_equal(value[1][0], self.ATT_UUID) 269 270 271 def test_continuation_state(self): 272 """Implementation of test TP/SERVER/SA/BV-03-C from SDP Specification. 273 274 @raise error.TestFail: If the DUT failed the test. 275 """ 276 record_handle = self.get_single_handle(self.PNP_INFORMATION_CLASS_ID) 277 self.assert_nonempty_list( 278 json.loads(self.tester.service_attribute_request( 279 record_handle, self.MIN_ATTR_BYTE_CNT, [[0, 0xFFFF]], {}))) 280 281 282 def test_version_list_attribute(self): 283 """Implementation of test TP/SERVER/SA/BV-15-C from SDP Specification. 284 285 @raise error.TestFail: If the DUT failed the test. 286 """ 287 self.assert_nonempty_list( 288 self.get_attribute(self.SDP_SERVER_CLASS_ID, 289 self.VERSION_NUMBER_LIST_ATTR_ID)) 290 291 292 def test_service_database_state_attribute(self): 293 """Implementation of test TP/SERVER/SA/BV-16-C from SDP Specification. 294 295 @raise error.TestFail: If the DUT failed the test. 296 """ 297 state = self.get_attribute(self.SDP_SERVER_CLASS_ID, 298 self.SERVICE_DATABASE_STATE_ATTR_ID) 299 if not isinstance(state, int): 300 raise error.TestFail('State is not an int: %s' % state) 301 302 303 def test_profile_descriptor_list_attribute(self): 304 """Implementation of test TP/SERVER/SA/BV-17-C from SDP Specification. 305 306 @raise error.TestFail: If list attribute not correct form. 307 308 """ 309 profile_list = self.get_attribute(self.PNP_INFORMATION_CLASS_ID, 310 self.PROFILE_DESCRIPTOR_LIST_ATTR_ID) 311 312 if not isinstance(profile_list, list): 313 raise error.TestFail('Value is not a list') 314 self.assert_equal(len(profile_list), 1) 315 316 if not isinstance(profile_list[0], list): 317 raise error.TestFail('Item is not a list') 318 self.assert_equal(len(profile_list[0]), 2) 319 320 self.assert_equal(profile_list[0][0], self.PNP_INFORMATION_CLASS_ID) 321 322 323 def test_additional_protocol_descriptor_list_attribute(self): 324 """Implementation of test TP/SERVER/SA/BV-21-C from SDP Specification. 325 326 @raise error.TestFail: If the DUT failed the test. 327 328 """ 329 330 """AVRCP is not supported by Chromebook and no need to run this test 331 self.assert_nonempty_list( 332 self.get_attribute(self.AVRCP_TG_CLASS_ID, 333 self.ADDITIONAL_PROTOCOLLIST_ATTR_ID)) 334 """ 335 336 def test_non_existing_attribute(self): 337 """Implementation of test TP/SERVER/SA/BV-20-C from SDP Specification. 338 339 @raise error.TestFail: If the DUT failed the test. 340 """ 341 record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID) 342 res = json.loads(self.tester.service_attribute_request( 343 record_handle, self.MAX_ATTR_BYTE_CNT, 344 [self.NON_EXISTING_ATTRIBUTE_ID], {})) 345 self.assert_equal(res, []) 346 347 348 def test_fake_attributes(self): 349 """Test values of attributes of the fake service record. 350 351 @raise error.TestFail: If the DUT failed the test. 352 """ 353 for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS: 354 self.assert_attribute_equals(self.FAKE_SERVICE_CLASS_ID, 355 attr_id, self.FAKE_ATTRIBUTE_VALUE) 356 357 for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS: 358 record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID) 359 360 lang_base = json.loads(self.tester.service_attribute_request( 361 record_handle, self.MAX_ATTR_BYTE_CNT, 362 [self.LANGUAGE_BASE_ATTRIBUTE_ID], {})) 363 attr_id = lang_base[1] + offset 364 365 response = json.loads(self.tester.service_attribute_request( 366 record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {})) 367 self.assert_equal(response, [attr_id, self.FAKE_ATTRIBUTE_VALUE]) 368 369 370 def test_invalid_record_handle(self): 371 """Implementation of test TP/SERVER/SA/BI-01-C from SDP Specification. 372 373 @raise error.TestFail: If the DUT failed the test. 374 """ 375 res = json.loads(self.tester.service_attribute_request( 376 self.INVALID_RECORD_HANDLE, self.MAX_ATTR_BYTE_CNT, 377 [self.NON_EXISTING_ATTRIBUTE_ID], {})) 378 self.assert_equal(res, self.ERROR_CODE_INVALID_RECORD_HANDLE) 379 380 381 def test_invalid_request_syntax(self): 382 """Implementation of test TP/SERVER/SA/BI-02-C from SDP Specification. 383 384 @raise error.TestFail: If the DUT failed the test. 385 """ 386 record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID) 387 res = json.loads(self.tester.service_attribute_request( 388 record_handle, 389 self.MAX_ATTR_BYTE_CNT, 390 [self.SERVICE_RECORD_HANDLE_ATTR_ID], 391 {'invalid_request':self.INVALID_SYNTAX_REQUEST})) 392 self.assert_equal(res, self.ERROR_CODE_INVALID_SYNTAX) 393 394 395 def test_invalid_pdu_size(self): 396 """Implementation of test TP/SERVER/SA/BI-03-C from SDP Specification. 397 398 @raise error.TestFail: If the DUT failed the test. 399 """ 400 opts = dict({'forced_pdu_size':self.INVALID_PDU_SIZE}) 401 record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID) 402 res = json.loads(self.tester.service_attribute_request(record_handle, 403 self.MAX_ATTR_BYTE_CNT, [self.SERVICE_RECORD_HANDLE_ATTR_ID], opts)) 404 self.assert_equal(res, self.ERROR_CODE_INVALID_PDU_SIZE) 405 406 407 def correct_request_att_request_test(self): 408 """Run basic tests for Service Attribute Request.""" 409 # Connect to the DUT via L2CAP using SDP socket. 410 self.tester.connect(self.adapter['Address']) 411 412 self.test_record_handle_attribute() 413 self.test_browse_group_attribute() 414 self.test_icon_url_attribute() 415 self.test_documentation_url_attribute() 416 self.test_client_executable_url_attribute() 417 self.test_protocol_descriptor_list_attribute() 418 self.test_continuation_state() 419 self.test_version_list_attribute() 420 self.test_service_database_state_attribute() 421 self.test_profile_descriptor_list_attribute() 422 self.test_additional_protocol_descriptor_list_attribute() 423 self.test_fake_attributes() 424 self.test_non_existing_attribute() 425 self.test_invalid_record_handle() 426 self.test_invalid_pdu_size() 427 self.test_invalid_request_syntax() 428 429 430 def sdp_service_attribute_request_test(self, device): 431 """Runs service attribute request test""" 432 433 if self.host.btpeer.get_platform() != 'RASPI': 434 raise error.TestNAError('Test only runs on Raspi') 435 436 self.tester = device 437 # Reset the adapter to the powered on, discoverable state. 438 if not self.bluetooth_facade.reset_on(): 439 raise error.TestFail('DUT adapter could not be powered on') 440 if not self.bluetooth_facade.set_discoverable(True): 441 raise error.TestFail('DUT could not be set as discoverable') 442 443 self.adapter = self.bluetooth_facade.get_adapter_properties() 444 445 # Create a fake service record in order to test attributes, 446 # that are not present in any of existing services. 447 uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) + 448 self.BLUETOOTH_BASE_UUID) 449 uuid_str = str(uuid.UUID(int=uuid128)) 450 sdp_record = self.build_service_record() 451 self.bluetooth_facade.register_profile(self.FAKE_SERVICE_PATH, 452 uuid_str, 453 {"ServiceRecord": sdp_record}) 454 455 # Setup the tester as a generic computer. 456 if not self.tester.setup('computer'): 457 raise error.TestNAError('Tester could not be initialized') 458 459 self.correct_request_att_request_test() 460 461 462 463class bluetooth_SDP_ServiceBrowse(bluetooth_SDP_Test, 464 bluetooth_adapter_tests.BluetoothAdapterTests): 465 """ 466 Verify that the IUT behave correct during Service Browse procedure. 467 """ 468 version = 1 469 470 MAX_BROWSE_REC_CNT = 100 471 MAX_ATTR_BYTE_CNT = 300 472 SERVICE_CLASS_ID_LIST = 0x0001 473 BROWSE_GROUP_DESCRIPTOR = 0x1001 474 GROUP_ID = 0x0200 475 476 477 def get_attribute_ssr_sar(self, class_id, attr_id, size): 478 """Get service attributes using Service Search Request and Service 479 Attribute Request. 480 481 @param class_id: Class ID of service to check. 482 @param attr_id: ID of attribute to check. 483 @param size: Preferred size of UUID. 484 485 @return attribute value if attribute exists, None otherwise 486 487 """ 488 handles = json.loads(self.tester.service_search_request( 489 [class_id], self.MAX_BROWSE_REC_CNT, 490 {'preferred_size':size})) 491 492 if not (isinstance(handles, list) and len(handles) > 0): 493 return None 494 495 res = [] 496 for record_handle in handles: 497 value = json.loads(self.tester.service_attribute_request( 498 record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {})) 499 if not (isinstance(value, list) and len(value) == 2 and 500 value[0] == attr_id): 501 return None 502 res.append(value[1]) 503 504 return res 505 506 507 def get_attribute_ssar(self, class_id, attr_id, size): 508 """Get service attributes using Service Search Attribute Request. 509 510 @param class_id: Class ID of service to check. 511 @param attr_id: ID of attribute to check. 512 @param size: Preferred size of UUID. 513 514 @return attribute value if attribute exists, None otherwise 515 516 """ 517 response = json.loads(self.tester.service_search_attribute_request( 518 [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id], 519 {'preferred_size':size})) 520 521 if not isinstance(response, list): 522 return None 523 524 res = [] 525 for elem in response: 526 if not (isinstance(elem, list) and len(elem) == 2 and 527 elem[0] == attr_id): 528 return None 529 res.append(elem[1]) 530 531 return res 532 533 534 def test_attribute(self, class_id, attr_id, get_attribute): 535 """Test service attributes using 16-bit, 32-bit and 128-bit 536 size of UUID. 537 538 @param class_id: Class ID of service to check. 539 @param attr_id: ID of attribute to check. 540 @param get_attribute: Method to use to get an attribute value. 541 542 @return attribute value if attribute exists and values from three tests 543 are equal, None otherwise 544 545 """ 546 result_16 = get_attribute(class_id, attr_id, 16) 547 548 for size in 32, 128: 549 result_cur = get_attribute(class_id, attr_id, size) 550 if result_16 != result_cur: 551 return None 552 553 return result_16 554 555 556 def service_browse(self, get_attribute): 557 """Execute a Service Browse procedure. 558 559 @param get_attribute: Method to use to get an attribute value. 560 561 @return sorted list of unique services on the DUT, or False if browse 562 did not finish correctly 563 564 """ 565 # Find services on top of hierarchy. 566 root_services = self.test_attribute(self.PUBLIC_BROWSE_ROOT, 567 self.SERVICE_CLASS_ID_LIST, 568 get_attribute) 569 570 if not root_services: 571 return False 572 573 # Find additional browse groups. 574 group_ids = self.test_attribute(self.BROWSE_GROUP_DESCRIPTOR, 575 self.GROUP_ID, 576 get_attribute) 577 if not group_ids: 578 return False 579 580 # Find services from all browse groups. 581 all_services = [] 582 for group_id in group_ids: 583 services = self.test_attribute(group_id, 584 self.SERVICE_CLASS_ID_LIST, 585 get_attribute) 586 if not services: 587 return False 588 all_services.extend(services) 589 590 # Ensure that root services are among all services. 591 for service in root_services: 592 if service not in all_services: 593 return False 594 595 # Sort all services and remove duplicates. 596 all_services.sort() 597 last = 0 598 for service in all_services[1:]: 599 if all_services[last] != service: 600 last += 1 601 all_services[last] = service 602 603 return all_services[:last + 1] 604 605 606 def correct_request_browse_test(self): 607 """Run basic tests for Service Browse procedure. 608 609 @return True if all tests finishes correctly, False otherwise 610 611 """ 612 613 # Connect to the DUT via L2CAP using SDP socket. 614 self.tester.connect(self.adapter['Address']) 615 616 browse_ssar = self.service_browse(self.get_attribute_ssar) 617 if not browse_ssar: 618 return False 619 620 browse_ssr_sar = self.service_browse(self.get_attribute_ssr_sar) 621 622 623 # Ensure that two different browse methods return the same results. 624 return browse_ssar == browse_ssr_sar 625 626 627 def sdp_service_browse_test(self, device): 628 """Runs service browse test""" 629 630 if self.host.btpeer.get_platform() != 'RASPI': 631 raise error.TestNAError('Test only runs on Raspi') 632 633 self.tester = device 634 # Reset the adapter to the powered on, discoverable state. 635 if not (self.bluetooth_facade.reset_on() and 636 self.bluetooth_facade.set_discoverable(True)): 637 raise error.TestFail('DUT could not be reset to initial state') 638 639 self.adapter = self.bluetooth_facade.get_adapter_properties() 640 641 # Setup the tester as a generic computer. 642 if not self.tester.setup('computer'): 643 raise error.TestNAError('Tester could not be initialized') 644 645 # Since radio is involved, this test is not 100% reliable; instead we 646 # repeat a few times until it succeeds. 647 for failed_attempts in range(0, 5): 648 if self.correct_request_browse_test(): 649 break 650 else: 651 raise error.TestFail('Expected device was not found') 652 653 # Record how many attempts this took, hopefully we'll one day figure out 654 # a way to reduce this to zero and then the loop above can go away. 655 self.write_perf_keyval({'failed_attempts': failed_attempts }) 656 657 658class bluetooth_SDP_ServiceSearchAttributeRequest(bluetooth_SDP_Test, 659 bluetooth_adapter_tests.BluetoothAdapterTests): 660 """ 661 Verify the correct behaviour of the device when searching for services and 662 attributes. 663 """ 664 665 666 NON_EXISTING_SERVICE_CLASS_ID = 0x9875 667 PUBLIC_BROWSE_GROUP_CLASS_ID = 0x1002 668 669 NON_EXISTING_ATTRIBUTE_ID = 0xABCD 670 SERVICE_CLASS_ID_ATTRIBUTE_ID = 0x0001 671 VERSION_NUMBER_LIST_ATTR_ID = 0x0200 672 673 674 def fail_test(self, testname, value): 675 """Raise an error for a particular SDP test. 676 677 @param testname: a string representation of the test name. 678 @param value: the value that did not pass muster. 679 680 """ 681 raise error.TestFail('SDP test %s failed: got %s.' % (testname, value)) 682 683 684 def test_non_existing(self, class_id, attr_id): 685 """Check that a single attribute of a single service does not exist 686 687 @param class_id: Class ID of service to check. 688 @param attr_id: ID of attribute to check. 689 690 @raises error.TestFail if service or attribute does exists. 691 692 """ 693 for size in 16, 32, 128: 694 result = json.loads(self.tester.service_search_attribute_request( 695 [class_id], 696 self.MAX_ATTR_BYTE_CNT, 697 [attr_id], 698 {'preferred_size':size})) 699 if result != []: 700 raise error.TestFail('Attribute %s of class %s exists when it ' 701 'should not!' % (class_id, attr_id)) 702 703 704 def get_attribute_sssar(self, class_id, attr_id, size): 705 """Get a single attribute of a single service using Service Search 706 Attribute Request. 707 708 @param class_id: Class ID of service to check. 709 @param attr_id: ID of attribute to check. 710 @param size: Preferred size of UUID. 711 712 @return attribute value if attribute exists 713 714 @raises error.TestFail if attribute does not exist 715 716 """ 717 res = json.loads(self.tester.service_search_attribute_request( 718 [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id], 719 {'preferred_size':size})) 720 721 if (isinstance(res, list) and len(res) == 1 and 722 isinstance(res[0], list) and len(res[0]) == 2 and 723 res[0][0] == attr_id): 724 return res[0][1] 725 726 raise error.TestFail('Attribute %s of class %s does not exist! (size ' 727 '%s)' % (class_id, attr_id, size)) 728 729 730 def test_attribute_sssar(self, class_id, attr_id): 731 """Test a single attribute of a single service using 16-bit, 32-bit and 732 128-bit size of UUID. 733 734 @param class_id: Class ID of service to check. 735 @param attr_id: ID of attribute to check. 736 737 @return attribute value if attribute exists and values from three tests 738 are equal 739 740 @raises error.TestFail if attribute doesn't exist or values not equal 741 742 """ 743 result_16 = self.get_attribute_sssar(class_id, attr_id, 16) 744 for size in 32, 128: 745 result_cur = self.get_attribute_sssar(class_id, attr_id, size) 746 if result_16 != result_cur: 747 raise error.TestFail('Attribute test failed %s: expected %s, ' 748 'got %s' % (size, result_16, result_cur)) 749 750 return result_16 751 752 753 def test_non_existing_service(self): 754 """Implementation of test TP/SERVER/SSA/BV-01-C from SDP Specification. 755 756 @raises error.TestFail if test fails 757 758 """ 759 self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID, 760 self.SERVICE_CLASS_ID_ATTRIBUTE_ID) 761 762 763 def test_non_existing_attribute_sssar(self): 764 """Implementation of test TP/SERVER/SSA/BV-02-C from SDP Specification. 765 766 @raises error.TestFail if test fails 767 768 """ 769 self.test_non_existing(self.PUBLIC_BROWSE_GROUP_CLASS_ID, 770 self.NON_EXISTING_ATTRIBUTE_ID) 771 772 773 def test_non_existing_service_attribute(self): 774 """Implementation of test TP/SERVER/SSA/BV-03-C from SDP Specification. 775 776 @raises error.TestFail if test fails 777 778 """ 779 self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID, 780 self.NON_EXISTING_ATTRIBUTE_ID) 781 782 783 def test_existing_service_attribute(self): 784 """Implementation of test TP/SERVER/SSA/BV-04-C from SDP Specification. 785 786 @raises error.TestFail if test fails 787 788 """ 789 value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID, 790 self.SERVICE_CLASS_ID_ATTRIBUTE_ID) 791 if not value == [self.SDP_SERVER_CLASS_ID]: 792 self.fail_test('TP/SERVER/SSA/BV-04-C', value) 793 794 795 def test_service_database_state_attribute_sssar(self): 796 """Implementation of test TP/SERVER/SSA/BV-08-C from SDP Specification. 797 798 @raises error.TestFail if test fails 799 800 """ 801 value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID, 802 self.SERVICE_DATABASE_STATE_ATTR_ID) 803 if not isinstance(value, int): 804 self.fail_test('TP/SERVER/SSA/BV-08-C', value) 805 806 807 def test_protocol_descriptor_list_attribute_sssar(self): 808 """Implementation of test TP/SERVER/SSA/BV-11-C from SDP Specification. 809 810 @raises error.TestFail if test fails 811 812 """ 813 value = self.test_attribute_sssar(self.GAP_CLASS_ID, 814 self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID) 815 816 # The first-layer protocol is L2CAP, using the PSM for ATT protocol. 817 if value[0] != [self.L2CAP_UUID, self.ATT_PSM]: 818 self.fail_test('TP/SERVER/SSA/BV-11-C', value) 819 820 # The second-layer protocol is ATT. The additional parameters are 821 # ignored, since they may reasonably vary between implementations. 822 if value[1][0] != self.ATT_UUID: 823 self.fail_test('TP/SERVER/SSA/BV-11-C', value) 824 825 826 827 def test_browse_group_attribute_sssar(self): 828 """Implementation of test TP/SERVER/SSA/BV-12-C from SDP Specification. 829 830 @raises error.TestFail if test fails 831 832 """ 833 value = self.test_attribute_sssar(self.GAP_CLASS_ID, 834 self.BROWSE_GROUP_LIST_ATTR_ID) 835 if not value == [self.PUBLIC_BROWSE_ROOT]: 836 self.fail_test('TP/SERVER/SSA/BV-12-C', value) 837 838 839 def test_icon_url_attribute_sssar(self): 840 """Implementation of test TP/SERVER/SSA/BV-15-C from SDP Specification. 841 842 @raises error.TestFail if test fails 843 844 """ 845 value = self.test_attribute_sssar(self.GAP_CLASS_ID, 846 self.ICON_URL_ATTR_ID) 847 if not value == self.BLUEZ_URL: 848 self.fail_test('TP/SERVER/SSA/BV-15-C', value) 849 850 851 def test_version_list_attribute_sssar(self): 852 """Implementation of test TP/SERVER/SSA/BV-19-C from SDP Specification. 853 854 @raises error.TestFail if test fails 855 856 """ 857 value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID, 858 self.VERSION_NUMBER_LIST_ATTR_ID) 859 if not isinstance(value, list) and value != []: 860 self.fail_test('TP/SERVER/SSA/BV-19-C', value) 861 862 863 def test_profile_descriptor_list_attribute_sssar(self): 864 """Implementation of test TP/SERVER/SSA/BV-20-C from SDP Specification. 865 866 @raises error.TestFail if test fails 867 868 """ 869 value = self.test_attribute_sssar(self.PNP_INFORMATION_CLASS_ID, 870 self.PROFILE_DESCRIPTOR_LIST_ATTR_ID) 871 if not (isinstance(value, list) and len(value) == 1 and 872 isinstance(value[0], list) and len(value[0]) == 2 and 873 value[0][0] == self.PNP_INFORMATION_CLASS_ID): 874 self.fail_test('TP/SERVER/SSA/BV-20-C', value) 875 876 877 def test_documentation_url_attribute_sssar(self): 878 """Implementation of test TP/SERVER/SSA/BV-21-C from SDP Specification. 879 880 @raises error.TestFail if test fails 881 882 """ 883 value = self.test_attribute_sssar(self.GAP_CLASS_ID, 884 self.DOCUMENTATION_URL_ATTR_ID) 885 if not value == self.BLUEZ_URL: 886 self.fail_test('TP/SERVER/SSA/BV-21-C', value) 887 888 889 def test_client_executable_url_attribute_sssar(self): 890 """Implementation of test TP/SERVER/SSA/BV-22-C from SDP Specification. 891 892 @raises error.TestFail if test fails 893 894 """ 895 value = self.test_attribute_sssar(self.GAP_CLASS_ID, 896 self.CLIENT_EXECUTABLE_URL_ATTR_ID) 897 if not value == self.BLUEZ_URL: 898 self.fail_test('TP/SERVER/SSA/BV-22-C', value) 899 900 901 def test_additional_protocol_descriptor_list_attribute_sssar(self): 902 """Implementation of test TP/SERVER/SSA/BV-23-C from SDP Specification. 903 904 @raises error.TestFail if test fails 905 906 """ 907 908 """AVRCP is not supported by Chromebook and no need to run this test 909 value = self.test_attribute_sssar(self.AVRCP_TG_CLASS_ID, 910 self.ADDITIONAL_PROTOCOLLIST_ATTR_ID) 911 if not isinstance(value, list) and value != []: 912 self.fail_test('TP/SERVER/SSA/BV-23-C', value) 913 """ 914 915 def test_fake_attributes_sssar(self): 916 """Test values of attributes of the fake service record. 917 918 @raises error.TestFail if test fails 919 920 """ 921 for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS: 922 value = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID, 923 attr_id) 924 if value != self.FAKE_ATTRIBUTE_VALUE: 925 self.fail_test('fake service attributes', value) 926 927 for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS: 928 lang_base = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID, 929 self.LANGUAGE_BASE_ATTRIBUTE_ID) 930 attr_id = lang_base + offset 931 value = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID, 932 attr_id) 933 if value != self.FAKE_ATTRIBUTE_VALUE: 934 self.fail_test('fake service attributes', value) 935 936 937 def test_continuation_state_sssar(self): 938 """Implementation of test TP/SERVER/SSA/BV-06-C from SDP Specification. 939 940 @raises error.TestFail if test fails 941 942 """ 943 for size in 16, 32, 128: 944 # This request should generate a long response, which will be 945 # split into 98 chunks. 946 value = json.loads(self.tester.service_search_attribute_request( 947 [self.PUBLIC_BROWSE_GROUP_CLASS_ID], 948 self.MIN_ATTR_BYTE_CNT, 949 [[0, 0xFFFF]], {'preferred_size':size})) 950 if not isinstance(value, list) or value == []: 951 self.fail_test('TP/SERVER/SSA/BV-06-C', value) 952 953 954 def test_invalid_request_syntax_sssar(self): 955 """Implementation of test TP/SERVER/SSA/BI-01-C from SDP Specification. 956 957 @raises error.TestFail if test fails 958 959 """ 960 for size in 16, 32, 128: 961 value = json.loads(self.tester.service_search_attribute_request( 962 [self.SDP_SERVER_CLASS_ID], 963 self.MAX_ATTR_BYTE_CNT, 964 [self.SERVICE_CLASS_ID_ATTRIBUTE_ID], 965 {'preferred_size':size, 966 'invalid_request':'9875'})) 967 if value != self.ERROR_CODE_INVALID_SYNTAX: 968 self.fail_test('TP/SERVER/SSA/BI-01-C', value) 969 970 971 def test_invalid_pdu_size_sssar(self): 972 """Implementation of test TP/SERVER/SSA/BI-02-C from SDP Specification. 973 974 @raises error.TestFail if test fails 975 976 """ 977 for size in 16, 32, 128: 978 value = json.loads(self.tester.service_search_attribute_request( 979 [self.SDP_SERVER_CLASS_ID], 980 self.MAX_ATTR_BYTE_CNT, 981 [self.SERVICE_CLASS_ID_ATTRIBUTE_ID], 982 {'preferred_size':size, 983 'forced_pdu_size':100})) 984 if value != self.ERROR_CODE_INVALID_PDU_SIZE: 985 self.fail_test('TP/SERVER/SSA/BI-02-C', value) 986 987 988 def correct_request_search_att_test(self): 989 """Run tests for Service Search Attribute request. 990 991 @raises error.TestFail if any test fails 992 993 """ 994 # connect to the DUT via L2CAP using SDP socket 995 self.tester.connect(self.adapter['Address']) 996 997 self.test_non_existing_service() 998 self.test_non_existing_attribute_sssar() 999 self.test_non_existing_service_attribute() 1000 #self.test_existing_service_attribute() 1001 self.test_service_database_state_attribute_sssar() 1002 self.test_protocol_descriptor_list_attribute_sssar() 1003 self.test_browse_group_attribute_sssar() 1004 self.test_icon_url_attribute_sssar() 1005 self.test_version_list_attribute_sssar() 1006 self.test_profile_descriptor_list_attribute_sssar() 1007 self.test_documentation_url_attribute_sssar() 1008 self.test_client_executable_url_attribute_sssar() 1009 self.test_additional_protocol_descriptor_list_attribute_sssar() 1010 self.test_fake_attributes_sssar() 1011 self.test_continuation_state_sssar() 1012 self.test_invalid_request_syntax_sssar() 1013 self.test_invalid_pdu_size_sssar() 1014 logging.info('correct_request finished successfully!') 1015 1016 1017 def sdp_service_search_attribute_request_test(self, device): 1018 """Runs service search attribute request test""" 1019 1020 if self.host.btpeer.get_platform() != 'RASPI': 1021 raise error.TestNAError('Test only runs on Raspi') 1022 1023 self.tester = device 1024 # Reset the adapter to the powered on, discoverable state. 1025 if not self.bluetooth_facade.reset_on(): 1026 raise error.TestFail('DUT adapter could not be powered on') 1027 if not self.bluetooth_facade.set_discoverable(True): 1028 raise error.TestFail('DUT could not be set as discoverable') 1029 1030 self.adapter = self.bluetooth_facade.get_adapter_properties() 1031 1032 # Create a fake service record in order to test attributes, 1033 # that are not present in any of existing services. 1034 uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) + 1035 self.BLUETOOTH_BASE_UUID) 1036 uuid_str = str(uuid.UUID(int=uuid128)) 1037 sdp_record = self.build_service_record() 1038 self.bluetooth_facade.register_profile(self.FAKE_SERVICE_PATH, 1039 uuid_str, 1040 {"ServiceRecord": sdp_record}) 1041 1042 # Setup the tester as a generic computer. 1043 if not self.tester.setup('computer'): 1044 raise error.TestNAError('Tester could not be initialized') 1045 1046 # Since radio is involved, this test is not 100% reliable; instead we 1047 # repeat a few times until it succeeds. 1048 passing = False 1049 for failed_attempts in range(0, 4): 1050 try: 1051 self.correct_request_search_att_test() 1052 passing = True 1053 except error.TestFail as e: 1054 logging.warning('Ignoring error: %s', e) 1055 if passing: 1056 break 1057 else: 1058 self.correct_request_search_att_test() 1059 1060 # Record how many attempts this took, hopefully we'll one day figure out 1061 # a way to reduce this to zero and then the loop above can go away. 1062 self.write_perf_keyval({'failed_attempts': failed_attempts}) 1063 1064 1065class bluetooth_SDP_ServiceSearchRequestBasic( 1066 bluetooth_adapter_tests.BluetoothAdapterTests): 1067 """ 1068 Verify the correct behaviour of the device when searching for services. 1069 """ 1070 version = 1 1071 1072 SDP_SERVER_CLASS_ID = 0x1000 1073 NO_EXISTING_SERVICE_CLASS_ID = 0x0001 1074 FAKE_SERVICES_CNT = 300 1075 FAKE_SERVICES_PATH = '/autotest/fake_service_' 1076 FAKE_SERVICES_CLASS_ID = 0xABCD 1077 BLUETOOTH_BASE_UUID = 0x0000000000001000800000805F9B34FB 1078 SSRB_INVALID_PDU_SIZE = 9875 1079 ERROR_CODE_INVALID_REQUEST_SYNTAX = 0x0003 1080 ERROR_CODE_INVALID_PDU_SIZE = 0x0004 1081 1082 1083 def correct_request_basic_test(self): 1084 """Search the existing service on the DUT using the Tester. 1085 1086 @return True if found, False if not found 1087 1088 """ 1089 # connect to the DUT via L2CAP using SDP socket 1090 self.tester.connect(self.adapter['Address']) 1091 1092 for size in 16, 32, 128: 1093 # test case TP/SERVER/SS/BV-01-C: 1094 # at least the SDP server service exists 1095 resp = json.loads(self.tester.service_search_request( 1096 [self.SDP_SERVER_CLASS_ID], 3, 1097 {'preferred_size':size})) 1098 if resp != [0]: 1099 return False 1100 # test case TP/SERVER/SS/BV-04-C: 1101 # Service with Class ID = 0x0001 should never exist, as this UUID is 1102 # reserved as Bluetooth Core Specification UUID 1103 resp = json.loads(self.tester.service_search_request( 1104 [self.NO_EXISTING_SERVICE_CLASS_ID], 3, 1105 {'preferred_size':size})) 1106 if resp != []: 1107 return False 1108 # test case TP/SERVER/SS/BV-03-C: 1109 # request the fake services' Class ID to force SDP to use 1110 # continuation state 1111 resp = json.loads(self.tester.service_search_request( 1112 [self.FAKE_SERVICES_CLASS_ID], 1113 self.FAKE_SERVICES_CNT * 2, 1114 {'preferred_size':size})) 1115 if len(resp) != self.FAKE_SERVICES_CNT: 1116 return False 1117 # test case TP/SERVER/SS/BI-01-C: 1118 # send a Service Search Request with intentionally invalid PDU size 1119 resp = json.loads(self.tester.service_search_request( 1120 [self.SDP_SERVER_CLASS_ID], 3, 1121 {'preferred_size':size, 1122 'forced_pdu_size':self.SSRB_INVALID_PDU_SIZE})) 1123 if resp != self.ERROR_CODE_INVALID_PDU_SIZE: 1124 return False 1125 # test case TP/SERVER/SS/BI-02-C: 1126 # send a Service Search Request with invalid syntax 1127 resp = json.loads(self.tester.service_search_request( 1128 [self.SDP_SERVER_CLASS_ID], 3, 1129 {'preferred_size':size, 'invalid_request':True})) 1130 if resp != self.ERROR_CODE_INVALID_REQUEST_SYNTAX: 1131 return False 1132 1133 return True 1134 1135 1136 def sdp_service_search_request_basic_test(self, device): 1137 """Runs service search request basic test""" 1138 1139 if self.host.btpeer.get_platform() != 'RASPI': 1140 raise error.TestNAError('Test only runs on Raspi') 1141 1142 self.tester = device 1143 # Reset the adapter to the powered on, discoverable state. 1144 if not (self.bluetooth_facade.reset_on() and 1145 self.bluetooth_facade.set_discoverable(True)): 1146 raise error.TestFail('DUT could not be reset to initial state') 1147 1148 self.adapter = self.bluetooth_facade.get_adapter_properties() 1149 1150 # Setup the tester as a generic computer. 1151 if not self.tester.setup('computer'): 1152 raise error.TestNAError('Tester could not be initialized') 1153 1154 # Create many fake services with the same Class ID 1155 for num in range(0, self.FAKE_SERVICES_CNT): 1156 path_str = self.FAKE_SERVICES_PATH + str(num) 1157 uuid128 = ((self.FAKE_SERVICES_CLASS_ID << 96) + 1158 self.BLUETOOTH_BASE_UUID) 1159 uuid_str = str(uuid.UUID(int=uuid128)) 1160 self.bluetooth_facade.register_profile(path_str, uuid_str, {}) 1161 1162 # Since radio is involved, this test is not 100% reliable; instead we 1163 # repeat a few times until it succeeds. 1164 for failed_attempts in range(0, 5): 1165 if self.correct_request_basic_test(): 1166 break 1167 else: 1168 raise error.TestFail('Expected device was not found') 1169 1170 # Record how many attempts this took, hopefully we'll one day figure out 1171 # a way to reduce this to zero and then the loop above can go away. 1172 self.write_perf_keyval({'failed_attempts': failed_attempts }) 1173 1174 1175class BluetoothSDPTests(bluetooth_SDP_ServiceAttributeRequest, 1176 bluetooth_SDP_ServiceBrowse, 1177 bluetooth_SDP_ServiceSearchAttributeRequest, 1178 bluetooth_SDP_ServiceSearchRequestBasic): 1179 """Master class that simplifies inheritance of sdp tests""" 1180 pass 1181