# Lint as: python2, python3 from __future__ import absolute_import from __future__ import division from __future__ import print_function import json import logging import uuid import xml.etree.ElementTree as ET import common from autotest_lib.client.common_lib import error from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests from six.moves import range class bluetooth_SDP_Test(object): """Base class with Properties and methods common across SDP tests""" version = 1 MIN_ATTR_BYTE_CNT = 7 MAX_ATTR_BYTE_CNT = 300 SDP_SERVER_CLASS_ID = 0x1000 GAP_CLASS_ID = 0x1800 BROWSE_GROUP_LIST_ATTR_ID = 0x0005 PUBLIC_BROWSE_ROOT = 0x1002 DOCUMENTATION_URL_ATTR_ID = 0x000A CLIENT_EXECUTABLE_URL_ATTR_ID = 0x000B ICON_URL_ATTR_ID = 0x000C PROTOCOL_DESCRIPTOR_LIST_ATTR_ID = 0x0004 L2CAP_UUID = 0x0100 ATT_UUID = 0x0007 ATT_PSM = 0x001F PNP_INFORMATION_CLASS_ID = 0x1200 VERSION_NUMBER_LIST_ATTR_ID = 0x0200 SERVICE_DATABASE_STATE_ATTR_ID = 0x0201 AVRCP_TG_CLASS_ID = 0x110C PROFILE_DESCRIPTOR_LIST_ATTR_ID = 0x0009 ADDITIONAL_PROTOCOLLIST_ATTR_ID = 0x000D FAKE_SERVICE_PATH = '/autotest/fake_service' BLUEZ_URL = 'http://www.bluez.org/' FAKE_SERVICE_CLASS_ID = 0xCDEF FAKE_ATTRIBUTE_VALUE = 42 LANGUAGE_BASE_ATTRIBUTE_ID = 0x0006 FAKE_GENERAL_ATTRIBUTE_IDS = [ 0x0003, # TP/SERVER/SA/BV-04-C 0x0002, # TP/SERVER/SA/BV-06-C 0x0007, # TP/SERVER/SA/BV-07-C 0x0008, # TP/SERVER/SA/BV-10-C # TP/SERVER/SA/BV-09-C: LANGUAGE_BASE_ATTRIBUTE_ID ] FAKE_LANGUAGE_ATTRIBUTE_OFFSETS = [ 0x0000, # TP/SERVER/SA/BV-12-C 0x0001, # TP/SERVER/SA/BV-13-C 0x0002 # TP/SERVER/SA/BV-14-C ] BLUETOOTH_BASE_UUID = 0x0000000000001000800000805F9B34FB SERVICE_CLASS_ID_ATTR_ID = 0x0001 ERROR_CODE_INVALID_RECORD_HANDLE = 0x0002 ERROR_CODE_INVALID_SYNTAX = 0x0003 ERROR_CODE_INVALID_PDU_SIZE = 0x0004 INVALID_RECORD_HANDLE = 0xFEEE INVALID_SYNTAX_REQUEST = '123' INVALID_PDU_SIZE = 11 def build_service_record(self): """Build SDP record manually for the fake service. @return resulting record as string """ value = ET.Element('uint16', {'value': str(self.FAKE_ATTRIBUTE_VALUE)}) sdp_record = ET.Element('record') service_id_attr = ET.Element( 'attribute', {'id': str(self.SERVICE_CLASS_ID_ATTR_ID)}) service_id_attr.append( ET.Element('uuid', {'value': '0x%X' % self.FAKE_SERVICE_CLASS_ID})) sdp_record.append(service_id_attr) for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS: attr = ET.Element('attribute', {'id': str(attr_id)}) attr.append(value) sdp_record.append(attr) for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS: attr_id = self.FAKE_ATTRIBUTE_VALUE + offset attr = ET.Element('attribute', {'id': str(attr_id)}) attr.append(value) sdp_record.append(attr) sdp_record_str = ('' + ET.tostring(sdp_record).decode('utf-8')) return sdp_record_str class bluetooth_SDP_ServiceAttributeRequest(bluetooth_SDP_Test, bluetooth_adapter_tests.BluetoothAdapterTests): """ Verify the correct behaviour of the device when searching for attributes of services. """ version = 1 MAX_REC_CNT = 3 SERVICE_RECORD_HANDLE_ATTR_ID = 0x0000 NON_EXISTING_ATTRIBUTE_ID = 0xFEDC @staticmethod def assert_equal(actual, expected): """Verify that |actual| is equal to |expected|. @param actual: The value we got. @param expected: The value we expected. @raise error.TestFail: If the values are unequal. """ if actual != expected: raise error.TestFail( 'Expected |%s|, got |%s|' % (expected, actual)) @staticmethod def assert_nonempty_list(value): """Verify that |value| is a list, and that the list is non-empty. @param value: The value to check. @raise error.TestFail: If the value is not a list, or is empty. """ if not isinstance(value, list): raise error.TestFail('Value is not a list. Got |%s|.' % value) if value == []: raise error.TestFail('List is empty') def get_single_handle(self, class_id): """Send a Service Search Request to get a handle for specific class ID. @param class_id: The class that we want a handle for. @return The record handle, as an int. @raise error.TestFail: If we failed to retrieve a handle. """ res = json.loads(self.tester.service_search_request([class_id], self.MAX_REC_CNT, dict())) if not (isinstance(res, list) and len(res) > 0): raise error.TestFail( 'Failed to retrieve handle for 0x%x' % class_id) return res[0] # TODO(quiche): Place this after get_attribute(), so all the tests are # grouped together. def test_record_handle_attribute(self): """Implementation of test TP/SERVER/SA/BV-01-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ # Send Service Search Request to find out record handle for # SDP Server service. record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID) # Send Service Attribute Request for Service Record Handle Attribute. res = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [self.SERVICE_RECORD_HANDLE_ATTR_ID], {})) # Ensure that returned attribute is correct. self.assert_equal(res, [self.SERVICE_RECORD_HANDLE_ATTR_ID, record_handle]) def get_attribute(self, class_id, attr_id): """Get a single attribute of a single service @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @return attribute value if attribute exists, None otherwise """ record_handle = self.get_single_handle(class_id) res = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {})) if isinstance(res, list) and len(res) == 2 and res[0] == attr_id: return res[1] return None # TODO(quiche): Move this up, to be grouped with the other |assert| # methods. def assert_attribute_equals(self, class_id, attr_id, expected_value): """Verify that |attr_id| of service with |class_id| has |expected_value| @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @param expected_value: The expected value for the attribute. @raise error.TestFail: If the actual value differs from |expected_value| """ self.assert_equal(self.get_attribute(class_id, attr_id), expected_value) def test_browse_group_attribute(self): """Implementation of test TP/SERVER/SA/BV-08-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ self.assert_attribute_equals(self.GAP_CLASS_ID, self.BROWSE_GROUP_LIST_ATTR_ID, [self.PUBLIC_BROWSE_ROOT]) def test_icon_url_attribute(self): """Implementation of test TP/SERVER/SA/BV-11-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ self.assert_attribute_equals(self.GAP_CLASS_ID, self.ICON_URL_ATTR_ID, self.BLUEZ_URL) def test_documentation_url_attribute(self): """Implementation of test TP/SERVER/SA/BV-18-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ self.assert_attribute_equals(self.GAP_CLASS_ID, self.DOCUMENTATION_URL_ATTR_ID, self.BLUEZ_URL) def test_client_executable_url_attribute(self): """Implementation of test TP/SERVER/SA/BV-19-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ self.assert_attribute_equals(self.GAP_CLASS_ID, self.CLIENT_EXECUTABLE_URL_ATTR_ID, self.BLUEZ_URL) def test_protocol_descriptor_list_attribute(self): """Implementation of test TP/SERVER/SA/BV-05-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ value = self.get_attribute(self.GAP_CLASS_ID, self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID) # The first-layer protocol is L2CAP, using the PSM for ATT protocol. self.assert_equal(value[0], [self.L2CAP_UUID, self.ATT_PSM]) # The second-layer protocol is ATT. The additional parameters are # ignored, since they may reasonably vary between implementations. self.assert_equal(value[1][0], self.ATT_UUID) def test_continuation_state(self): """Implementation of test TP/SERVER/SA/BV-03-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ record_handle = self.get_single_handle(self.PNP_INFORMATION_CLASS_ID) self.assert_nonempty_list( json.loads(self.tester.service_attribute_request( record_handle, self.MIN_ATTR_BYTE_CNT, [[0, 0xFFFF]], {}))) def test_version_list_attribute(self): """Implementation of test TP/SERVER/SA/BV-15-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ self.assert_nonempty_list( self.get_attribute(self.SDP_SERVER_CLASS_ID, self.VERSION_NUMBER_LIST_ATTR_ID)) def test_service_database_state_attribute(self): """Implementation of test TP/SERVER/SA/BV-16-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ state = self.get_attribute(self.SDP_SERVER_CLASS_ID, self.SERVICE_DATABASE_STATE_ATTR_ID) if not isinstance(state, int): raise error.TestFail('State is not an int: %s' % state) def test_profile_descriptor_list_attribute(self): """Implementation of test TP/SERVER/SA/BV-17-C from SDP Specification. @raise error.TestFail: If list attribute not correct form. """ profile_list = self.get_attribute(self.PNP_INFORMATION_CLASS_ID, self.PROFILE_DESCRIPTOR_LIST_ATTR_ID) if not isinstance(profile_list, list): raise error.TestFail('Value is not a list') self.assert_equal(len(profile_list), 1) if not isinstance(profile_list[0], list): raise error.TestFail('Item is not a list') self.assert_equal(len(profile_list[0]), 2) self.assert_equal(profile_list[0][0], self.PNP_INFORMATION_CLASS_ID) def test_additional_protocol_descriptor_list_attribute(self): """Implementation of test TP/SERVER/SA/BV-21-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ """AVRCP is not supported by Chromebook and no need to run this test self.assert_nonempty_list( self.get_attribute(self.AVRCP_TG_CLASS_ID, self.ADDITIONAL_PROTOCOLLIST_ATTR_ID)) """ def test_non_existing_attribute(self): """Implementation of test TP/SERVER/SA/BV-20-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID) res = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [self.NON_EXISTING_ATTRIBUTE_ID], {})) self.assert_equal(res, []) def test_fake_attributes(self): """Test values of attributes of the fake service record. @raise error.TestFail: If the DUT failed the test. """ for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS: self.assert_attribute_equals(self.FAKE_SERVICE_CLASS_ID, attr_id, self.FAKE_ATTRIBUTE_VALUE) for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS: record_handle = self.get_single_handle(self.FAKE_SERVICE_CLASS_ID) lang_base = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [self.LANGUAGE_BASE_ATTRIBUTE_ID], {})) attr_id = lang_base[1] + offset response = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {})) self.assert_equal(response, [attr_id, self.FAKE_ATTRIBUTE_VALUE]) def test_invalid_record_handle(self): """Implementation of test TP/SERVER/SA/BI-01-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ res = json.loads(self.tester.service_attribute_request( self.INVALID_RECORD_HANDLE, self.MAX_ATTR_BYTE_CNT, [self.NON_EXISTING_ATTRIBUTE_ID], {})) self.assert_equal(res, self.ERROR_CODE_INVALID_RECORD_HANDLE) def test_invalid_request_syntax(self): """Implementation of test TP/SERVER/SA/BI-02-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID) res = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [self.SERVICE_RECORD_HANDLE_ATTR_ID], {'invalid_request':self.INVALID_SYNTAX_REQUEST})) self.assert_equal(res, self.ERROR_CODE_INVALID_SYNTAX) def test_invalid_pdu_size(self): """Implementation of test TP/SERVER/SA/BI-03-C from SDP Specification. @raise error.TestFail: If the DUT failed the test. """ opts = dict({'forced_pdu_size':self.INVALID_PDU_SIZE}) record_handle = self.get_single_handle(self.SDP_SERVER_CLASS_ID) res = json.loads(self.tester.service_attribute_request(record_handle, self.MAX_ATTR_BYTE_CNT, [self.SERVICE_RECORD_HANDLE_ATTR_ID], opts)) self.assert_equal(res, self.ERROR_CODE_INVALID_PDU_SIZE) def correct_request_att_request_test(self): """Run basic tests for Service Attribute Request.""" # Connect to the DUT via L2CAP using SDP socket. self.tester.connect(self.adapter['Address']) self.test_record_handle_attribute() self.test_browse_group_attribute() self.test_icon_url_attribute() self.test_documentation_url_attribute() self.test_client_executable_url_attribute() self.test_protocol_descriptor_list_attribute() self.test_continuation_state() self.test_version_list_attribute() self.test_service_database_state_attribute() self.test_profile_descriptor_list_attribute() self.test_additional_protocol_descriptor_list_attribute() self.test_fake_attributes() self.test_non_existing_attribute() self.test_invalid_record_handle() self.test_invalid_pdu_size() self.test_invalid_request_syntax() def sdp_service_attribute_request_test(self, device): """Runs service attribute request test""" if self.host.btpeer.get_platform() != 'RASPI': raise error.TestNAError('Test only runs on Raspi') self.tester = device # Reset the adapter to the powered on, discoverable state. if not self.bluetooth_facade.reset_on(): raise error.TestFail('DUT adapter could not be powered on') if not self.bluetooth_facade.set_discoverable(True): raise error.TestFail('DUT could not be set as discoverable') self.adapter = self.bluetooth_facade.get_adapter_properties() # Create a fake service record in order to test attributes, # that are not present in any of existing services. uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) + self.BLUETOOTH_BASE_UUID) uuid_str = str(uuid.UUID(int=uuid128)) sdp_record = self.build_service_record() self.bluetooth_facade.register_profile(self.FAKE_SERVICE_PATH, uuid_str, {"ServiceRecord": sdp_record}) # Setup the tester as a generic computer. if not self.tester.setup('computer'): raise error.TestNAError('Tester could not be initialized') self.correct_request_att_request_test() class bluetooth_SDP_ServiceBrowse(bluetooth_SDP_Test, bluetooth_adapter_tests.BluetoothAdapterTests): """ Verify that the IUT behave correct during Service Browse procedure. """ version = 1 MAX_BROWSE_REC_CNT = 100 MAX_ATTR_BYTE_CNT = 300 SERVICE_CLASS_ID_LIST = 0x0001 BROWSE_GROUP_DESCRIPTOR = 0x1001 GROUP_ID = 0x0200 def get_attribute_ssr_sar(self, class_id, attr_id, size): """Get service attributes using Service Search Request and Service Attribute Request. @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @param size: Preferred size of UUID. @return attribute value if attribute exists, None otherwise """ handles = json.loads(self.tester.service_search_request( [class_id], self.MAX_BROWSE_REC_CNT, {'preferred_size':size})) if not (isinstance(handles, list) and len(handles) > 0): return None res = [] for record_handle in handles: value = json.loads(self.tester.service_attribute_request( record_handle, self.MAX_ATTR_BYTE_CNT, [attr_id], {})) if not (isinstance(value, list) and len(value) == 2 and value[0] == attr_id): return None res.append(value[1]) return res def get_attribute_ssar(self, class_id, attr_id, size): """Get service attributes using Service Search Attribute Request. @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @param size: Preferred size of UUID. @return attribute value if attribute exists, None otherwise """ response = json.loads(self.tester.service_search_attribute_request( [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id], {'preferred_size':size})) if not isinstance(response, list): return None res = [] for elem in response: if not (isinstance(elem, list) and len(elem) == 2 and elem[0] == attr_id): return None res.append(elem[1]) return res def test_attribute(self, class_id, attr_id, get_attribute): """Test service attributes using 16-bit, 32-bit and 128-bit size of UUID. @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @param get_attribute: Method to use to get an attribute value. @return attribute value if attribute exists and values from three tests are equal, None otherwise """ result_16 = get_attribute(class_id, attr_id, 16) for size in 32, 128: result_cur = get_attribute(class_id, attr_id, size) if result_16 != result_cur: return None return result_16 def service_browse(self, get_attribute): """Execute a Service Browse procedure. @param get_attribute: Method to use to get an attribute value. @return sorted list of unique services on the DUT, or False if browse did not finish correctly """ # Find services on top of hierarchy. root_services = self.test_attribute(self.PUBLIC_BROWSE_ROOT, self.SERVICE_CLASS_ID_LIST, get_attribute) if not root_services: return False # Find additional browse groups. group_ids = self.test_attribute(self.BROWSE_GROUP_DESCRIPTOR, self.GROUP_ID, get_attribute) if not group_ids: return False # Find services from all browse groups. all_services = [] for group_id in group_ids: services = self.test_attribute(group_id, self.SERVICE_CLASS_ID_LIST, get_attribute) if not services: return False all_services.extend(services) # Ensure that root services are among all services. for service in root_services: if service not in all_services: return False # Sort all services and remove duplicates. all_services.sort() last = 0 for service in all_services[1:]: if all_services[last] != service: last += 1 all_services[last] = service return all_services[:last + 1] def correct_request_browse_test(self): """Run basic tests for Service Browse procedure. @return True if all tests finishes correctly, False otherwise """ # Connect to the DUT via L2CAP using SDP socket. self.tester.connect(self.adapter['Address']) browse_ssar = self.service_browse(self.get_attribute_ssar) if not browse_ssar: return False browse_ssr_sar = self.service_browse(self.get_attribute_ssr_sar) # Ensure that two different browse methods return the same results. return browse_ssar == browse_ssr_sar def sdp_service_browse_test(self, device): """Runs service browse test""" if self.host.btpeer.get_platform() != 'RASPI': raise error.TestNAError('Test only runs on Raspi') self.tester = device # Reset the adapter to the powered on, discoverable state. if not (self.bluetooth_facade.reset_on() and self.bluetooth_facade.set_discoverable(True)): raise error.TestFail('DUT could not be reset to initial state') self.adapter = self.bluetooth_facade.get_adapter_properties() # Setup the tester as a generic computer. if not self.tester.setup('computer'): raise error.TestNAError('Tester could not be initialized') # Since radio is involved, this test is not 100% reliable; instead we # repeat a few times until it succeeds. for failed_attempts in range(0, 5): if self.correct_request_browse_test(): break else: raise error.TestFail('Expected device was not found') # Record how many attempts this took, hopefully we'll one day figure out # a way to reduce this to zero and then the loop above can go away. self.write_perf_keyval({'failed_attempts': failed_attempts }) class bluetooth_SDP_ServiceSearchAttributeRequest(bluetooth_SDP_Test, bluetooth_adapter_tests.BluetoothAdapterTests): """ Verify the correct behaviour of the device when searching for services and attributes. """ NON_EXISTING_SERVICE_CLASS_ID = 0x9875 PUBLIC_BROWSE_GROUP_CLASS_ID = 0x1002 NON_EXISTING_ATTRIBUTE_ID = 0xABCD SERVICE_CLASS_ID_ATTRIBUTE_ID = 0x0001 VERSION_NUMBER_LIST_ATTR_ID = 0x0200 def fail_test(self, testname, value): """Raise an error for a particular SDP test. @param testname: a string representation of the test name. @param value: the value that did not pass muster. """ raise error.TestFail('SDP test %s failed: got %s.' % (testname, value)) def test_non_existing(self, class_id, attr_id): """Check that a single attribute of a single service does not exist @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @raises error.TestFail if service or attribute does exists. """ for size in 16, 32, 128: result = json.loads(self.tester.service_search_attribute_request( [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id], {'preferred_size':size})) if result != []: raise error.TestFail('Attribute %s of class %s exists when it ' 'should not!' % (class_id, attr_id)) def get_attribute_sssar(self, class_id, attr_id, size): """Get a single attribute of a single service using Service Search Attribute Request. @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @param size: Preferred size of UUID. @return attribute value if attribute exists @raises error.TestFail if attribute does not exist """ res = json.loads(self.tester.service_search_attribute_request( [class_id], self.MAX_ATTR_BYTE_CNT, [attr_id], {'preferred_size':size})) if (isinstance(res, list) and len(res) == 1 and isinstance(res[0], list) and len(res[0]) == 2 and res[0][0] == attr_id): return res[0][1] raise error.TestFail('Attribute %s of class %s does not exist! (size ' '%s)' % (class_id, attr_id, size)) def test_attribute_sssar(self, class_id, attr_id): """Test a single attribute of a single service using 16-bit, 32-bit and 128-bit size of UUID. @param class_id: Class ID of service to check. @param attr_id: ID of attribute to check. @return attribute value if attribute exists and values from three tests are equal @raises error.TestFail if attribute doesn't exist or values not equal """ result_16 = self.get_attribute_sssar(class_id, attr_id, 16) for size in 32, 128: result_cur = self.get_attribute_sssar(class_id, attr_id, size) if result_16 != result_cur: raise error.TestFail('Attribute test failed %s: expected %s, ' 'got %s' % (size, result_16, result_cur)) return result_16 def test_non_existing_service(self): """Implementation of test TP/SERVER/SSA/BV-01-C from SDP Specification. @raises error.TestFail if test fails """ self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID, self.SERVICE_CLASS_ID_ATTRIBUTE_ID) def test_non_existing_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-02-C from SDP Specification. @raises error.TestFail if test fails """ self.test_non_existing(self.PUBLIC_BROWSE_GROUP_CLASS_ID, self.NON_EXISTING_ATTRIBUTE_ID) def test_non_existing_service_attribute(self): """Implementation of test TP/SERVER/SSA/BV-03-C from SDP Specification. @raises error.TestFail if test fails """ self.test_non_existing(self.NON_EXISTING_SERVICE_CLASS_ID, self.NON_EXISTING_ATTRIBUTE_ID) def test_existing_service_attribute(self): """Implementation of test TP/SERVER/SSA/BV-04-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID, self.SERVICE_CLASS_ID_ATTRIBUTE_ID) if not value == [self.SDP_SERVER_CLASS_ID]: self.fail_test('TP/SERVER/SSA/BV-04-C', value) def test_service_database_state_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-08-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID, self.SERVICE_DATABASE_STATE_ATTR_ID) if not isinstance(value, int): self.fail_test('TP/SERVER/SSA/BV-08-C', value) def test_protocol_descriptor_list_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-11-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.GAP_CLASS_ID, self.PROTOCOL_DESCRIPTOR_LIST_ATTR_ID) # The first-layer protocol is L2CAP, using the PSM for ATT protocol. if value[0] != [self.L2CAP_UUID, self.ATT_PSM]: self.fail_test('TP/SERVER/SSA/BV-11-C', value) # The second-layer protocol is ATT. The additional parameters are # ignored, since they may reasonably vary between implementations. if value[1][0] != self.ATT_UUID: self.fail_test('TP/SERVER/SSA/BV-11-C', value) def test_browse_group_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-12-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.GAP_CLASS_ID, self.BROWSE_GROUP_LIST_ATTR_ID) if not value == [self.PUBLIC_BROWSE_ROOT]: self.fail_test('TP/SERVER/SSA/BV-12-C', value) def test_icon_url_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-15-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.GAP_CLASS_ID, self.ICON_URL_ATTR_ID) if not value == self.BLUEZ_URL: self.fail_test('TP/SERVER/SSA/BV-15-C', value) def test_version_list_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-19-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.SDP_SERVER_CLASS_ID, self.VERSION_NUMBER_LIST_ATTR_ID) if not isinstance(value, list) and value != []: self.fail_test('TP/SERVER/SSA/BV-19-C', value) def test_profile_descriptor_list_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-20-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.PNP_INFORMATION_CLASS_ID, self.PROFILE_DESCRIPTOR_LIST_ATTR_ID) if not (isinstance(value, list) and len(value) == 1 and isinstance(value[0], list) and len(value[0]) == 2 and value[0][0] == self.PNP_INFORMATION_CLASS_ID): self.fail_test('TP/SERVER/SSA/BV-20-C', value) def test_documentation_url_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-21-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.GAP_CLASS_ID, self.DOCUMENTATION_URL_ATTR_ID) if not value == self.BLUEZ_URL: self.fail_test('TP/SERVER/SSA/BV-21-C', value) def test_client_executable_url_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-22-C from SDP Specification. @raises error.TestFail if test fails """ value = self.test_attribute_sssar(self.GAP_CLASS_ID, self.CLIENT_EXECUTABLE_URL_ATTR_ID) if not value == self.BLUEZ_URL: self.fail_test('TP/SERVER/SSA/BV-22-C', value) def test_additional_protocol_descriptor_list_attribute_sssar(self): """Implementation of test TP/SERVER/SSA/BV-23-C from SDP Specification. @raises error.TestFail if test fails """ """AVRCP is not supported by Chromebook and no need to run this test value = self.test_attribute_sssar(self.AVRCP_TG_CLASS_ID, self.ADDITIONAL_PROTOCOLLIST_ATTR_ID) if not isinstance(value, list) and value != []: self.fail_test('TP/SERVER/SSA/BV-23-C', value) """ def test_fake_attributes_sssar(self): """Test values of attributes of the fake service record. @raises error.TestFail if test fails """ for attr_id in self.FAKE_GENERAL_ATTRIBUTE_IDS: value = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID, attr_id) if value != self.FAKE_ATTRIBUTE_VALUE: self.fail_test('fake service attributes', value) for offset in self.FAKE_LANGUAGE_ATTRIBUTE_OFFSETS: lang_base = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID, self.LANGUAGE_BASE_ATTRIBUTE_ID) attr_id = lang_base + offset value = self.test_attribute_sssar(self.FAKE_SERVICE_CLASS_ID, attr_id) if value != self.FAKE_ATTRIBUTE_VALUE: self.fail_test('fake service attributes', value) def test_continuation_state_sssar(self): """Implementation of test TP/SERVER/SSA/BV-06-C from SDP Specification. @raises error.TestFail if test fails """ for size in 16, 32, 128: # This request should generate a long response, which will be # split into 98 chunks. value = json.loads(self.tester.service_search_attribute_request( [self.PUBLIC_BROWSE_GROUP_CLASS_ID], self.MIN_ATTR_BYTE_CNT, [[0, 0xFFFF]], {'preferred_size':size})) if not isinstance(value, list) or value == []: self.fail_test('TP/SERVER/SSA/BV-06-C', value) def test_invalid_request_syntax_sssar(self): """Implementation of test TP/SERVER/SSA/BI-01-C from SDP Specification. @raises error.TestFail if test fails """ for size in 16, 32, 128: value = json.loads(self.tester.service_search_attribute_request( [self.SDP_SERVER_CLASS_ID], self.MAX_ATTR_BYTE_CNT, [self.SERVICE_CLASS_ID_ATTRIBUTE_ID], {'preferred_size':size, 'invalid_request':'9875'})) if value != self.ERROR_CODE_INVALID_SYNTAX: self.fail_test('TP/SERVER/SSA/BI-01-C', value) def test_invalid_pdu_size_sssar(self): """Implementation of test TP/SERVER/SSA/BI-02-C from SDP Specification. @raises error.TestFail if test fails """ for size in 16, 32, 128: value = json.loads(self.tester.service_search_attribute_request( [self.SDP_SERVER_CLASS_ID], self.MAX_ATTR_BYTE_CNT, [self.SERVICE_CLASS_ID_ATTRIBUTE_ID], {'preferred_size':size, 'forced_pdu_size':100})) if value != self.ERROR_CODE_INVALID_PDU_SIZE: self.fail_test('TP/SERVER/SSA/BI-02-C', value) def correct_request_search_att_test(self): """Run tests for Service Search Attribute request. @raises error.TestFail if any test fails """ # connect to the DUT via L2CAP using SDP socket self.tester.connect(self.adapter['Address']) self.test_non_existing_service() self.test_non_existing_attribute_sssar() self.test_non_existing_service_attribute() #self.test_existing_service_attribute() self.test_service_database_state_attribute_sssar() self.test_protocol_descriptor_list_attribute_sssar() self.test_browse_group_attribute_sssar() self.test_icon_url_attribute_sssar() self.test_version_list_attribute_sssar() self.test_profile_descriptor_list_attribute_sssar() self.test_documentation_url_attribute_sssar() self.test_client_executable_url_attribute_sssar() self.test_additional_protocol_descriptor_list_attribute_sssar() self.test_fake_attributes_sssar() self.test_continuation_state_sssar() self.test_invalid_request_syntax_sssar() self.test_invalid_pdu_size_sssar() logging.info('correct_request finished successfully!') def sdp_service_search_attribute_request_test(self, device): """Runs service search attribute request test""" if self.host.btpeer.get_platform() != 'RASPI': raise error.TestNAError('Test only runs on Raspi') self.tester = device # Reset the adapter to the powered on, discoverable state. if not self.bluetooth_facade.reset_on(): raise error.TestFail('DUT adapter could not be powered on') if not self.bluetooth_facade.set_discoverable(True): raise error.TestFail('DUT could not be set as discoverable') self.adapter = self.bluetooth_facade.get_adapter_properties() # Create a fake service record in order to test attributes, # that are not present in any of existing services. uuid128 = ((self.FAKE_SERVICE_CLASS_ID << 96) + self.BLUETOOTH_BASE_UUID) uuid_str = str(uuid.UUID(int=uuid128)) sdp_record = self.build_service_record() self.bluetooth_facade.register_profile(self.FAKE_SERVICE_PATH, uuid_str, {"ServiceRecord": sdp_record}) # Setup the tester as a generic computer. if not self.tester.setup('computer'): raise error.TestNAError('Tester could not be initialized') # Since radio is involved, this test is not 100% reliable; instead we # repeat a few times until it succeeds. passing = False for failed_attempts in range(0, 4): try: self.correct_request_search_att_test() passing = True except error.TestFail as e: logging.warning('Ignoring error: %s', e) if passing: break else: self.correct_request_search_att_test() # Record how many attempts this took, hopefully we'll one day figure out # a way to reduce this to zero and then the loop above can go away. self.write_perf_keyval({'failed_attempts': failed_attempts}) class bluetooth_SDP_ServiceSearchRequestBasic( bluetooth_adapter_tests.BluetoothAdapterTests): """ Verify the correct behaviour of the device when searching for services. """ version = 1 SDP_SERVER_CLASS_ID = 0x1000 NO_EXISTING_SERVICE_CLASS_ID = 0x0001 FAKE_SERVICES_CNT = 300 FAKE_SERVICES_PATH = '/autotest/fake_service_' FAKE_SERVICES_CLASS_ID = 0xABCD BLUETOOTH_BASE_UUID = 0x0000000000001000800000805F9B34FB SSRB_INVALID_PDU_SIZE = 9875 ERROR_CODE_INVALID_REQUEST_SYNTAX = 0x0003 ERROR_CODE_INVALID_PDU_SIZE = 0x0004 def correct_request_basic_test(self): """Search the existing service on the DUT using the Tester. @return True if found, False if not found """ # connect to the DUT via L2CAP using SDP socket self.tester.connect(self.adapter['Address']) for size in 16, 32, 128: # test case TP/SERVER/SS/BV-01-C: # at least the SDP server service exists resp = json.loads(self.tester.service_search_request( [self.SDP_SERVER_CLASS_ID], 3, {'preferred_size':size})) if resp != [0]: return False # test case TP/SERVER/SS/BV-04-C: # Service with Class ID = 0x0001 should never exist, as this UUID is # reserved as Bluetooth Core Specification UUID resp = json.loads(self.tester.service_search_request( [self.NO_EXISTING_SERVICE_CLASS_ID], 3, {'preferred_size':size})) if resp != []: return False # test case TP/SERVER/SS/BV-03-C: # request the fake services' Class ID to force SDP to use # continuation state resp = json.loads(self.tester.service_search_request( [self.FAKE_SERVICES_CLASS_ID], self.FAKE_SERVICES_CNT * 2, {'preferred_size':size})) if len(resp) != self.FAKE_SERVICES_CNT: return False # test case TP/SERVER/SS/BI-01-C: # send a Service Search Request with intentionally invalid PDU size resp = json.loads(self.tester.service_search_request( [self.SDP_SERVER_CLASS_ID], 3, {'preferred_size':size, 'forced_pdu_size':self.SSRB_INVALID_PDU_SIZE})) if resp != self.ERROR_CODE_INVALID_PDU_SIZE: return False # test case TP/SERVER/SS/BI-02-C: # send a Service Search Request with invalid syntax resp = json.loads(self.tester.service_search_request( [self.SDP_SERVER_CLASS_ID], 3, {'preferred_size':size, 'invalid_request':True})) if resp != self.ERROR_CODE_INVALID_REQUEST_SYNTAX: return False return True def sdp_service_search_request_basic_test(self, device): """Runs service search request basic test""" if self.host.btpeer.get_platform() != 'RASPI': raise error.TestNAError('Test only runs on Raspi') self.tester = device # Reset the adapter to the powered on, discoverable state. if not (self.bluetooth_facade.reset_on() and self.bluetooth_facade.set_discoverable(True)): raise error.TestFail('DUT could not be reset to initial state') self.adapter = self.bluetooth_facade.get_adapter_properties() # Setup the tester as a generic computer. if not self.tester.setup('computer'): raise error.TestNAError('Tester could not be initialized') # Create many fake services with the same Class ID for num in range(0, self.FAKE_SERVICES_CNT): path_str = self.FAKE_SERVICES_PATH + str(num) uuid128 = ((self.FAKE_SERVICES_CLASS_ID << 96) + self.BLUETOOTH_BASE_UUID) uuid_str = str(uuid.UUID(int=uuid128)) self.bluetooth_facade.register_profile(path_str, uuid_str, {}) # Since radio is involved, this test is not 100% reliable; instead we # repeat a few times until it succeeds. for failed_attempts in range(0, 5): if self.correct_request_basic_test(): break else: raise error.TestFail('Expected device was not found') # Record how many attempts this took, hopefully we'll one day figure out # a way to reduce this to zero and then the loop above can go away. self.write_perf_keyval({'failed_attempts': failed_attempts }) class BluetoothSDPTests(bluetooth_SDP_ServiceAttributeRequest, bluetooth_SDP_ServiceBrowse, bluetooth_SDP_ServiceSearchAttributeRequest, bluetooth_SDP_ServiceSearchRequestBasic): """Master class that simplifies inheritance of sdp tests""" pass