bluetooth: fix advertising data format

When registering an advertisement, the value format and signatures of
ManufacturerData and ServiceData has been changed in bluez 5.44.

In addition, the btmon output format has been changed when adding
and removing advertisements.

This patch fixed the format change problem.

BUG=chromium:714548
TEST=run the advertising test cases
(cros) $ test_that "${DUT_IP}" bluetooth_AdapterLEAdvertising.single
(cros) $ test_that "${DUT_IP}" bluetooth_AdapterLEAdvertising.multiple

Change-Id: Ie1967227cfd5b1aafb93f5066d0a3d02c1dadd97
Reviewed-on: https://chromium-review.googlesource.com/485540
Commit-Ready: Shyh-In Hwang <josephsih@chromium.org>
Tested-by: Shyh-In Hwang <josephsih@chromium.org>
Reviewed-by: Miao-chen Chou <mcchou@chromium.org>
diff --git a/client/cros/bluetooth/advertisement.py b/client/cros/bluetooth/advertisement.py
index 0f94fc1..be653e4 100755
--- a/client/cros/bluetooth/advertisement.py
+++ b/client/cros/bluetooth/advertisement.py
@@ -46,14 +46,31 @@
         self.service_uuids = advertisement_data.get('ServiceUUIDs', [])
         self.solicit_uuids = advertisement_data.get('SolicitUUIDs', [])
 
-        # Should convert the key of manufacturer_data from string to hex value.
-        # It is due to xmlrpclib limitation which only allows string key.
-        self.manufacturer_data = {}
+        # The xmlrpclib library requires that only string keys are allowed in
+        # python dictionary. Hence, we need to define the manufacturer data
+        # in an advertisement dictionary like
+        #    'ManufacturerData': {'0xff00': [0xa1, 0xa2, 0xa3, 0xa4, 0xa5]},
+        # in order to let autotest server transmit the advertisement to
+        # a client DUT for testing.
+        # On the other hand, the dbus method of advertising requires that
+        # the signature of the manufacturer data to be 'qv' where 'q' stands
+        # for unsigned 16-bit integer. Hence, we need to convert the key
+        # from a string, e.g., '0xff00', to its hex value, 0xff00.
+        # For signatures of the advertising properties, refer to
+        #     device_properties in src/third_party/bluez/src/device.c
+        # For explanation about signature types, refer to
+        #     https://dbus.freedesktop.org/doc/dbus-specification.html
+        self.manufacturer_data = dbus.Dictionary({}, signature='qv')
         manufacturer_data = advertisement_data.get('ManufacturerData', {})
         for key, value in manufacturer_data.items():
-            self.manufacturer_data[int(key, 16)] = value
+            self.manufacturer_data[int(key, 16)] = dbus.Array(value,
+                                                              signature='y')
 
-        self.service_data = advertisement_data.get('ServiceData')
+        self.service_data = dbus.Dictionary({}, signature='sv')
+        service_data = advertisement_data.get('ServiceData', {})
+        for uuid, data in service_data.items():
+            self.service_data[uuid] = dbus.Array(data, signature='y')
+
         self.include_tx_power = advertisement_data.get('IncludeTxPower')
 
 
@@ -90,11 +107,11 @@
                                                     signature='s')
         if self.manufacturer_data is not None:
             properties['ManufacturerData'] = dbus.Dictionary(
-                self.manufacturer_data, signature='qay')
+                self.manufacturer_data, signature='qv')
 
         if self.service_data is not None:
             properties['ServiceData'] = dbus.Dictionary(self.service_data,
-                                                        signature='say')
+                                                        signature='sv')
         if self.include_tx_power is not None:
             properties['IncludeTxPower'] = dbus.Boolean(self.include_tx_power)
 
diff --git a/server/cros/bluetooth/bluetooth_adapter_tests.py b/server/cros/bluetooth/bluetooth_adapter_tests.py
index ea903e1..36b6869 100644
--- a/server/cros/bluetooth/bluetooth_adapter_tests.py
+++ b/server/cros/bluetooth/bluetooth_adapter_tests.py
@@ -1281,8 +1281,10 @@
                 logging_timespan=logging_timespan)
 
         # Verify that a new advertisement is added.
-        advertisement_added = self.bluetooth_le_facade.btmon_find(
-                'Advertising Added: %d' % instance_id)
+        advertisement_added = (
+                self.bluetooth_le_facade.btmon_find('Advertising Added') and
+                self.bluetooth_le_facade.btmon_find('Instance: %d' %
+                                                    instance_id))
 
         # Verify that the manufacturer data could be found.
         manufacturer_data = advertisement_data.get('ManufacturerData', '')
@@ -1416,8 +1418,10 @@
                         advertisement_data))
 
         # Verify that the advertisement is removed.
-        advertisement_removed = self.bluetooth_le_facade.btmon_find(
-                'Advertising Removed: %d' % instance_id)
+        advertisement_removed = (
+                self.bluetooth_le_facade.btmon_find('Advertising Removed') and
+                self.bluetooth_le_facade.btmon_find('Instance: %d' %
+                                                    instance_id))
 
         # If advertising_disabled is True, there should be no log like
         #       'Advertising: Enabled (0x01)'
@@ -1609,17 +1613,23 @@
 
         # Verify that every advertisement is removed. When an advertisement
         # with instance id 1 is removed, the log looks like
-        #   @ Advertising Removed: 1
-        txt = 'Advertising Removed: %d'
-        for instance_id in instance_ids:
-            if not self.bluetooth_le_facade.btmon_find(txt % instance_id):
-                advertisement_removed = False
-                logging.error('Failed to remove advertisement instance: %d',
-                              instance_id)
-                break
+        #   Advertising Removed
+        #       instance: 1
+        if len(instance_ids) > 0:
+            advertisement_removed = self.bluetooth_le_facade.btmon_find(
+                    'Advertising Removed')
+            if advertisement_removed:
+                for instance_id in instance_ids:
+                    txt = 'Instance: %d' % instance_id
+                    if not self.bluetooth_le_facade.btmon_find(txt):
+                        advertisement_removed = False
+                        break
         else:
             advertisement_removed = True
 
+        if not advertisement_removed:
+            logging.error('Failed to remove advertisement')
+
         # Verify that "Reset Advertising Intervals" command has been issued.
         reset_advertising_intervals = self.bluetooth_le_facade.btmon_find(
                 'bluetoothd: Reset Advertising Intervals')