Bluetooth SDP autotest: tests for existing attributes

This CL adds the support for continuation state in Service Attribute
Requests and a test for it. It also adds some tests for existing
attributes of existing services.

BUG=chromium:329044
TEST=test_that --board ${BOARD} ${HOSTNAME} bluetooth_SDP_ServiceAttributeRequest

Change-Id: Ica7626be9b46ebb238e169ee0efea091761a35d1
Reviewed-on: https://chromium-review.googlesource.com/202788
Tested-by: Artem Rakhov <arakhov@chromium.org>
Reviewed-by: Fang Deng <fdeng@chromium.org>
Reviewed-by: Arman Uguray <armansito@chromium.org>
Commit-Queue: Artem Rakhov <arakhov@chromium.org>
diff --git a/client/common_lib/cros/bluetooth/bluetooth_sdp_socket.py b/client/common_lib/cros/bluetooth/bluetooth_sdp_socket.py
index 306db73..2c04096 100644
--- a/client/common_lib/cros/bluetooth/bluetooth_sdp_socket.py
+++ b/client/common_lib/cros/bluetooth/bluetooth_sdp_socket.py
@@ -297,9 +297,9 @@
                restrictions or if the response has an incorrect code
 
         """
-        if max_rec_cnt < 1 or max_rec_cnt > 0xFFFF:
+        if max_rec_cnt < 1 or max_rec_cnt > 65535:
             raise BluetoothSDPSocketError('MaximumServiceRecordCount must be '
-                                          'between 1 and 0xFFFF, inclusive')
+                                          'between 1 and 65535, inclusive')
 
         if len(uuids) > SDP_MAX_SSR_UUIDS_CNT:
             raise BluetoothSDPSocketError('Too many UUIDs')
@@ -446,22 +446,6 @@
             raise BluetoothSDPSocketError('Invalid size descriptor')
 
 
-    def _unpack_attr_ids(self, response):
-        """Unpack Service Attribute Response
-
-        @param response: body of raw Service Attribute Response.
-
-        @return dictionary of {attribute id : value}
-
-        """
-        byte_cnt, = struct.unpack_from('>H', response)
-        response = response[2:]
-        id_values_list, scanned = self._unpack_sdp_data_element(response)
-        if scanned != byte_cnt:
-            raise BluetoothSDPSocketError('Incorrect size of SDP data element')
-        return id_values_list, byte_cnt
-
-
     def service_attribute_request(self, handle, max_attr_byte_count, attr_ids):
         """Send a Service Attribute Request
 
@@ -477,15 +461,15 @@
                restrictions or if the response has an incorrect code
 
         """
-        if max_attr_byte_count < 7 or max_attr_byte_count > 0xFFFF:
+        if max_attr_byte_count < 7 or max_attr_byte_count > 65535:
             raise BluetoothSDPSocketError('MaximumAttributeByteCount must be '
-                                          'between 7 and 0xFFFF, inclusive')
+                                          'between 7 and 65535, inclusive')
 
         pattern = (struct.pack('>I', handle) +
                    struct.pack('>H', max_attr_byte_count) +
                    self._pack_attr_ids(attr_ids))
         cont_state = '\0'
-        id_values_list = []
+        complete_response = ''
 
         while True:
             request = pattern + cont_state
@@ -499,18 +483,19 @@
             if code != SDP_SVC_ATTR_RSP:
                 raise BluetoothSDPSocketError('Incorrect response code')
 
-            cur_values, response_byte_count = self._unpack_attr_ids(response)
-
+            response_byte_count, = struct.unpack_from('>H', response)
             if response_byte_count > max_attr_byte_count:
                 raise BluetoothSDPSocketError('AttributeListByteCount exceeds'
                                               'MaximumAttributeByteCount')
 
-            id_values_list.extend(cur_values)
+            response = response[2:]
+            complete_response += response[:response_byte_count]
+            cont_state = response[response_byte_count:]
 
-            cont_state = response[2 + response_byte_count:]
             if cont_state == '\0':
                 break
 
+        id_values_list = self._unpack_sdp_data_element(complete_response)[0]
         if len(id_values_list) % 2 == 1:
             raise BluetoothSDPSocketError('Length of returned list is odd')
 
diff --git a/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/bluetooth_SDP_ServiceAttributeRequest.py b/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/bluetooth_SDP_ServiceAttributeRequest.py
index df53ae3..ec42c76 100644
--- a/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/bluetooth_SDP_ServiceAttributeRequest.py
+++ b/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/bluetooth_SDP_ServiceAttributeRequest.py
@@ -31,6 +31,16 @@
     L2CAP_UUID                       = 0x0100
     ATT_UUID                         = 0x0007
 
+    PNP_INFORMATION_CLASS_ID         = 0x1200
+    MIN_ATTR_BYTE_CNT                = 7
+
+    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
+
 
     def get_single_handle(self, class_id):
         """Send a Service Search Request to get a handle for specific class ID.
@@ -66,6 +76,27 @@
         return 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)
+        if record_handle == -1:
+            return False
+
+        res = 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
+
+
     def test_attribute(self, class_id, attr_id, attr_value):
         """Test a single attribute of a single service
 
@@ -142,6 +173,69 @@
                                     [self.ATT_UUID, 1, 8]])
 
 
+    def test_continuation_state(self):
+        """Implementation of test TP/SERVER/SA/BV-03-C from SDP Specification.
+
+        @return True if test passes, False if test fails
+
+        """
+        record_handle = self.get_single_handle(self.PNP_INFORMATION_CLASS_ID)
+        if record_handle == -1:
+            return False
+
+        res = self.tester.service_attribute_request(
+                  record_handle, self.MIN_ATTR_BYTE_CNT, [[0, 0xFFFF]])
+
+        return isinstance(res, list) and res != []
+
+
+    def test_version_list_attribute(self):
+        """Implementation of test TP/SERVER/SA/BV-15-C from SDP Specification.
+
+        @return True if test passes, False if test fails
+
+        """
+        version_list = self.get_attribute(self.SDP_SERVER_CLASS_ID,
+                                          self.VERSION_NUMBER_LIST_ATTR_ID)
+        return isinstance(version_list, list) and version_list != []
+
+
+    def test_service_database_state_attribute(self):
+        """Implementation of test TP/SERVER/SA/BV-16-C from SDP Specification.
+
+        @return True if test passes, False if test fails
+
+        """
+        state = self.get_attribute(self.SDP_SERVER_CLASS_ID,
+                                   self.SERVICE_DATABASE_STATE_ATTR_ID)
+        return isinstance(state, int)
+
+
+    def test_profile_descriptor_list_attribute(self):
+        """Implementation of test TP/SERVER/SA/BV-17-C from SDP Specification.
+
+        @return True if test passes, False if test fails
+
+        """
+        profile_list = self.get_attribute(self.PNP_INFORMATION_CLASS_ID,
+                                          self.PROFILE_DESCRIPTOR_LIST_ATTR_ID)
+        return (isinstance(profile_list, list) and len(profile_list) == 1 and
+                isinstance(profile_list[0], list) and
+                len(profile_list[0]) == 2 and
+                profile_list[0][0] == self.PNP_INFORMATION_CLASS_ID)
+
+
+    def test_additional_protocol_descriptor_list_attribute(self):
+        """Implementation of test TP/SERVER/SA/BV-18-C from SDP Specification.
+
+        @return True if test passes, False if test fails
+
+        """
+        protocol_list = self.get_attribute(self.AVRCP_TG_CLASS_ID,
+                                           self.ADDITIONAL_PROTOCOLLIST_ATTR_ID)
+        return isinstance(protocol_list, list) and protocol_list != []
+
+
     def correct_request(self):
         """Run basic tests for Service Attribute Request.
 
@@ -156,7 +250,12 @@
                 self.test_icon_url_attribute() and
                 self.test_documentation_url_attribute() and
                 self.test_client_executable_url_attribute() and
-                self.test_protocol_descriptor_list_attribute())
+                self.test_protocol_descriptor_list_attribute() and
+                self.test_continuation_state() and
+                self.test_version_list_attribute() and
+                self.test_service_database_state_attribute() and
+                self.test_profile_descriptor_list_attribute() and
+                self.test_additional_protocol_descriptor_list_attribute())
 
 
     def run_once(self):
diff --git a/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/control b/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/control
index c0f9e8f..ea89914 100644
--- a/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/control
+++ b/server/site_tests/bluetooth_SDP_ServiceAttributeRequest/control
@@ -17,15 +17,22 @@
 The tester sends Service Search Request and Service Attribute Request to the DUT
 to ensure that it is able to response with different attributes:
 ServiceRecordHandle, BrowseGroupList, DocumentationURL, ClientExecutableURL,
-IconURL, ProtocolDescriptorList.
+IconURL, ProtocolDescriptorList, VersionNumberList, ServiceDatabaseState,
+BluetoothProfileDescriptorList, AdditionalProtocolDescriptorLists.
+It also checks that responses with continuation state work properly.
 
 This test covers the Bluetooth SIG test cases:
 TP/SERVER/SA/BV-01-C
+TP/SERVER/SA/BV-03-C
+TP/SERVER/SA/BV-05-C
 TP/SERVER/SA/BV-08-C
 TP/SERVER/SA/BV-11-C
+TP/SERVER/SA/BV-15-C
+TP/SERVER/SA/BV-16-C
+TP/SERVER/SA/BV-17-C
 TP/SERVER/SA/BV-18-C
 TP/SERVER/SA/BV-19-C
-TP/SERVER/SA/BV-05-C
+TP/SERVER/SA/BV-21-C
 """
 
 from autotest_lib.server.cros.bluetooth import bluetooth_tester