Skip to content

Commit

Permalink
bluetooth: Expose service data from BlueZ
Browse files Browse the repository at this point in the history
BlueZ exposed Bluetooth device's ServiceData as a{sv} property[1]
where the dict value variant is an array of byte.

This CL exposes that to upper layer by
- Add support to map<string, vector<uint8_t>> in dbus::Property
- Add new service_data property in BluetoothDeviceClient
- Implement GetServiceDataUUIDs() and GetServiceDataForUUID()
  in BluetoothDeviceBlueZ
- Fix misc style issues in original code to make linter happy

[1] http://git.kernel.org/cgit/bluetooth/bluez.git/tree/src/device.c#n2551

BUG=618442,653310,b:28670943
TEST=Manually tested.

Review-Url: https://codereview.chromium.org/2369423003
Cr-Commit-Position: refs/heads/master@{#423434}
  • Loading branch information
puthik authored and Commit bot committed Oct 6, 2016
1 parent 824ec59 commit 0dd82b1
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 2 deletions.
67 changes: 67 additions & 0 deletions dbus/property.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <stddef.h>

#include <memory>

#include "base/bind.h"
#include "base/logging.h"

Expand Down Expand Up @@ -659,6 +661,70 @@ void Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>::
writer->CloseContainer(&variant_writer);
}

//
// Property<std::map<std::string, std::vector<uint8_t>>>
// specialization.
//

template <>
bool Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
PopValueFromReader(MessageReader* reader) {
MessageReader variant_reader(nullptr);
MessageReader dict_reader(nullptr);
if (!reader->PopVariant(&variant_reader) ||
!variant_reader.PopArray(&dict_reader))
return false;

value_.clear();
while (dict_reader.HasMoreData()) {
MessageReader entry_reader(nullptr);
if (!dict_reader.PopDictEntry(&entry_reader))
return false;

std::string key;
MessageReader value_varient_reader(nullptr);
if (!entry_reader.PopString(&key) ||
!entry_reader.PopVariant(&value_varient_reader))
return false;

const uint8_t* bytes = nullptr;
size_t length = 0;
if (!value_varient_reader.PopArrayOfBytes(&bytes, &length))
return false;

value_[key].assign(bytes, bytes + length);
}
return true;
}

template <>
void Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
AppendSetValueToWriter(MessageWriter* writer) {
MessageWriter variant_writer(nullptr);
MessageWriter dict_writer(nullptr);

writer->OpenVariant("a{sv}", &variant_writer);
variant_writer.OpenArray("{sv}", &dict_writer);

for (const auto& pair : set_value_) {
MessageWriter entry_writer(nullptr);
dict_writer.OpenDictEntry(&entry_writer);

entry_writer.AppendString(pair.first);

MessageWriter value_varient_writer(nullptr);
entry_writer.OpenVariant("ay", &value_varient_writer);
value_varient_writer.AppendArrayOfBytes(pair.second.data(),
pair.second.size());
entry_writer.CloseContainer(&value_varient_writer);

dict_writer.CloseContainer(&entry_writer);
}

variant_writer.CloseContainer(&dict_writer);
writer->CloseContainer(&variant_writer);
}

template class Property<uint8_t>;
template class Property<bool>;
template class Property<int16_t>;
Expand All @@ -675,5 +741,6 @@ template class Property<std::vector<ObjectPath> >;
template class Property<std::vector<uint8_t>>;
template class Property<std::map<std::string, std::string>>;
template class Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>;
template class Property<std::unordered_map<std::string, std::vector<uint8_t>>>;

} // namespace dbus
12 changes: 12 additions & 0 deletions dbus/property.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <map>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -610,6 +611,17 @@ Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>::
extern template class CHROME_DBUS_EXPORT
Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>;

template <>
CHROME_DBUS_EXPORT bool
Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
PopValueFromReader(MessageReader* reader);
template <>
CHROME_DBUS_EXPORT void
Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
AppendSetValueToWriter(MessageWriter* writer);
extern template class CHROME_DBUS_EXPORT
Property<std::unordered_map<std::string, std::vector<uint8_t>>>;

#pragma GCC diagnostic pop

} // namespace dbus
Expand Down
56 changes: 56 additions & 0 deletions dbus/property_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>
#include <vector>

Expand Down Expand Up @@ -431,4 +432,59 @@ TEST(PropertyTestStatic, SerializeNetAddressArray) {
EXPECT_EQ(test_list, ip_list.value());
}

TEST(PropertyTestStatic, ReadWriteStringToByteVectorMap) {
std::unique_ptr<Response> message(Response::CreateEmpty());
MessageWriter writer(message.get());
MessageWriter variant_writer(nullptr);
MessageWriter dict_writer(nullptr);

writer.OpenVariant("a{sv}", &variant_writer);
variant_writer.OpenArray("{sv}", &dict_writer);

const char* keys[] = {"One", "Two", "Three", "Four"};
const std::vector<uint8_t> values[] = {{1}, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}};
for (unsigned i = 0; i < arraysize(keys); ++i) {
MessageWriter entry_writer(nullptr);
dict_writer.OpenDictEntry(&entry_writer);

entry_writer.AppendString(keys[i]);

MessageWriter value_varient_writer(nullptr);
entry_writer.OpenVariant("ay", &value_varient_writer);
value_varient_writer.AppendArrayOfBytes(values[i].data(), values[i].size());
entry_writer.CloseContainer(&value_varient_writer);

dict_writer.CloseContainer(&entry_writer);
}

variant_writer.CloseContainer(&dict_writer);
writer.CloseContainer(&variant_writer);

MessageReader reader(message.get());
Property<std::unordered_map<std::string, std::vector<uint8_t>>> test_property;
EXPECT_TRUE(test_property.PopValueFromReader(&reader));

ASSERT_EQ(arraysize(keys), test_property.value().size());
for (unsigned i = 0; i < arraysize(keys); ++i)
EXPECT_EQ(values[i], test_property.value().at(keys[i]));
}

TEST(PropertyTestStatic, SerializeStringToByteVectorMap) {
std::unordered_map<std::string, std::vector<uint8_t>> test_map;
test_map["Hi"] = {1, 2, 3};
test_map["Map"] = {0xab, 0xcd};
test_map["Random"] = {0x0};

std::unique_ptr<Response> message(Response::CreateEmpty());
MessageWriter writer(message.get());

Property<std::unordered_map<std::string, std::vector<uint8_t>>> test_property;
test_property.ReplaceSetValueForTesting(test_map);
test_property.AppendSetValueToWriter(&writer);

MessageReader reader(message.get());
EXPECT_TRUE(test_property.PopValueFromReader(&reader));
EXPECT_EQ(test_map, test_property.value());
}

} // namespace dbus
6 changes: 5 additions & 1 deletion device/bluetooth/bluetooth_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,17 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice {
// returns the UUIDs of the device's services.
// * For dual mode devices this may be collected from both.
//
// Note: On ChromeOS and Linux, Bluez persists all services meaning if
// Note: On ChromeOS and Linux, BlueZ persists all services meaning if
// a device stops advertising a service this function will still return
// its UUID.
virtual UUIDSet GetUUIDs() const;

// Returns the last advertised Service Data. Returns an empty map if the
// adapter is not discovering.
//
// Note: On ChromeOS and Linux, BlueZ persists all service data meaning if
// a device stops advertising service data for a UUID, this function will
// still return the cached value for that UUID.
const ServiceDataMap& GetServiceData() const;

// Returns the UUIDs of services for which the device advertises Service Data.
Expand Down
6 changes: 5 additions & 1 deletion device/bluetooth/bluez/bluetooth_adapter_bluez.cc
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,9 @@ void BluetoothAdapterBlueZ::DevicePropertyChanged(
}
}

if (property_name == properties->service_data.name())
device_bluez->UpdateServiceData();

if (property_name == properties->bluetooth_class.name() ||
property_name == properties->appearance.name() ||
property_name == properties->address.name() ||
Expand All @@ -599,7 +602,8 @@ void BluetoothAdapterBlueZ::DevicePropertyChanged(
property_name == properties->connected.name() ||
property_name == properties->uuids.name() ||
property_name == properties->rssi.name() ||
property_name == properties->tx_power.name()) {
property_name == properties->tx_power.name() ||
property_name == properties->service_data.name()) {
NotifyDeviceChanged(device_bluez);
}

Expand Down
63 changes: 63 additions & 0 deletions device/bluetooth/bluez/bluetooth_bluez_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4487,4 +4487,67 @@ TEST_F(BluetoothBlueZTest, Shutdown_OnStopDiscoveryError) {
EXPECT_EQ(1 + kNumberOfDiscoverySessions, error_callback_count_);
}

TEST_F(BluetoothBlueZTest, ServiceDataChanged) {
// Simulate a change of service data of a device.
GetAdapter();

BluetoothDevice* device = adapter_->GetDevice(
bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress);

// Install an observer; expect the DeviceChanged method to be called
// when we change the service data.
TestBluetoothAdapterObserver observer(adapter_);

bluez::FakeBluetoothDeviceClient::Properties* properties =
fake_bluetooth_device_client_->GetProperties(dbus::ObjectPath(
bluez::FakeBluetoothDeviceClient::kPairedDevicePath));

properties->service_data.set_valid(true);

// Check that ServiceDataChanged is correctly invoke.
properties->service_data.ReplaceValue({{kGapUuid, {1, 2, 3}}});
EXPECT_EQ(1, observer.device_changed_count());
EXPECT_EQ(device, observer.last_device());
EXPECT_EQ(
BluetoothDevice::ServiceDataMap({{BluetoothUUID(kGapUuid), {1, 2, 3}}}),
device->GetServiceData());
EXPECT_EQ(BluetoothDevice::UUIDSet({BluetoothUUID(kGapUuid)}),
device->GetServiceDataUUIDs());
EXPECT_EQ(std::vector<uint8_t>({1, 2, 3}),
*(device->GetServiceDataForUUID(BluetoothUUID(kGapUuid))));

// Check that we can update service data with same uuid / add more uuid.
properties->service_data.ReplaceValue(
{{kGapUuid, {3, 2, 1}}, {kGattUuid, {1}}});
EXPECT_EQ(2, observer.device_changed_count());
EXPECT_EQ(device, observer.last_device());

EXPECT_EQ(
BluetoothDevice::ServiceDataMap({{BluetoothUUID(kGapUuid), {3, 2, 1}},
{BluetoothUUID(kGattUuid), {1}}}),
device->GetServiceData());
EXPECT_EQ(BluetoothDevice::UUIDSet(
{BluetoothUUID(kGapUuid), BluetoothUUID(kGattUuid)}),
device->GetServiceDataUUIDs());
EXPECT_EQ(std::vector<uint8_t>({3, 2, 1}),
*(device->GetServiceDataForUUID(BluetoothUUID(kGapUuid))));
EXPECT_EQ(std::vector<uint8_t>({1}),
*(device->GetServiceDataForUUID(BluetoothUUID(kGattUuid))));

// Check that we can remove uuid / change uuid with same data.
properties->service_data.ReplaceValue({{kPnpUuid, {3, 2, 1}}});
EXPECT_EQ(3, observer.device_changed_count());
EXPECT_EQ(device, observer.last_device());

EXPECT_EQ(
BluetoothDevice::ServiceDataMap({{BluetoothUUID(kPnpUuid), {3, 2, 1}}}),
device->GetServiceData());
EXPECT_EQ(BluetoothDevice::UUIDSet({BluetoothUUID(kPnpUuid)}),
device->GetServiceDataUUIDs());
EXPECT_EQ(std::vector<uint8_t>({3, 2, 1}),
*(device->GetServiceDataForUUID(BluetoothUUID(kPnpUuid))));
EXPECT_EQ(nullptr, device->GetServiceDataForUUID(BluetoothUUID(kGapUuid)));
EXPECT_EQ(nullptr, device->GetServiceDataForUUID(BluetoothUUID(kGattUuid)));
}

} // namespace bluez
12 changes: 12 additions & 0 deletions device/bluetooth/bluez/bluetooth_device_bluez.cc
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,18 @@ void BluetoothDeviceBlueZ::GetServiceRecords(
weak_ptr_factory_.GetWeakPtr(), error_callback));
}

void BluetoothDeviceBlueZ::UpdateServiceData() {
bluez::BluetoothDeviceClient::Properties* properties =
bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->GetProperties(
object_path_);
DCHECK(properties);
DCHECK(properties->service_data.is_valid());

service_data_.clear();
for (const auto& pair : properties->service_data.value())
service_data_[BluetoothUUID(pair.first)] = pair.second;
}

BluetoothPairingBlueZ* BluetoothDeviceBlueZ::BeginPairing(
BluetoothDevice::PairingDelegate* pairing_delegate) {
pairing_.reset(new BluetoothPairingBlueZ(this, pairing_delegate));
Expand Down
16 changes: 16 additions & 0 deletions device/bluetooth/bluez/bluetooth_device_bluez.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include <memory>
#include <string>
#include <unordered_set>
#include <vector>

#include "base/macros.h"
#include "base/memory/ref_counted.h"
Expand Down Expand Up @@ -103,6 +105,20 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceBlueZ
void GetServiceRecords(const GetServiceRecordsCallback& callback,
const GetServiceRecordsErrorCallback& error_callback);

// Called by BluetoothAdapterBlueZ to update BluetoothDevice->service_data_
// when receive DevicePropertyChanged event for the service data property.
// Note that
// 1) BlueZ persists all service data meaning that BlueZ won't remove service
// data even when a device stops advertising service data.
// 2) BlueZ sends DevicePropertyChanged event separately for each UUID that
// service data changed. Meaning that UpdateServiceData() might get called
// multiple times when there are multiple UUIDs that service data changed.
// 3) When a device update service data for a UUID, BlueZ update data for that
// UUID if it is already exist. If not BlueZ adds that data for UUID.
// This means BlueZ won't remove service data even when a device stops
// advertising service data for a UUID.
void UpdateServiceData();

// Creates a pairing object with the given delegate |pairing_delegate| and
// establishes it as the pairing context for this device. All pairing-related
// method calls will be forwarded to this object until it is released.
Expand Down
4 changes: 4 additions & 0 deletions device/bluetooth/dbus/bluetooth_device_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

#include "device/bluetooth/dbus/bluetooth_device_client.h"

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
Expand Down Expand Up @@ -199,6 +202,7 @@ BluetoothDeviceClient::Properties::Properties(
RegisterProperty(bluetooth_device::kModaliasProperty, &modalias);
RegisterProperty(bluetooth_device::kRSSIProperty, &rssi);
RegisterProperty(bluetooth_device::kTxPowerProperty, &tx_power);
RegisterProperty(bluetooth_device::kServiceDataProperty, &service_data);
RegisterProperty(bluetooth_device::kServicesResolvedProperty,
&services_resolved);
}
Expand Down
6 changes: 6 additions & 0 deletions device/bluetooth/dbus/bluetooth_device_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <stdint.h>

#include <string>
#include <unordered_map>
#include <vector>

#include "base/callback.h"
Expand Down Expand Up @@ -108,6 +109,11 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceClient : public BluezDBusClient {
// discovered during inquiry. Read-only.
dbus::Property<int16_t> rssi;

// Service advertisement data. Keys are the UUIDs in string format followed
// by its byte array value. Read-only.
dbus::Property<std::unordered_map<std::string, std::vector<uint8_t>>>
service_data;

// Indicate whether or not service discovery has been resolved. Read-only.
dbus::Property<bool> services_resolved;

Expand Down

0 comments on commit 0dd82b1

Please sign in to comment.