Commit ced2fad6 authored by Ryan Hansberry's avatar Ryan Hansberry Committed by Commit Bot

[Nearby] Implement BleMedium scanning.

Implement BleMedium::StartScanning() and BleMedium::StopScanning().
This requires adding a ServiceDataMap (mapping from service id to
service data) to bluetooth::mojom::DeviceInfo, to represent BLE
advertisements.

See go/nearby-chrome-bt for more details.

Bug: b:154848193
Change-Id: I40489f8128bcda9409b20b43be52e73b2a9a2240
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2370094
Commit-Queue: Ryan Hansberry <hansberry@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarJames Vecore <vecore@google.com>
Cr-Commit-Position: refs/heads/master@{#805582}
parent d3cd5999
...@@ -139,8 +139,17 @@ ImplementationPlatform::CreateBluetoothClassicMedium( ...@@ -139,8 +139,17 @@ ImplementationPlatform::CreateBluetoothClassicMedium(
std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium( std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(
api::BluetoothAdapter& adapter) { api::BluetoothAdapter& adapter) {
// TODO (hansberry): Inject bluetooth::mojom::Adapter into BleMedium. // Ignore the provided |adapter| argument. It provides no interface useful
return std::make_unique<chrome::BleMedium>(); // to implement chrome::BleMedium.
auto& connections = connections::NearbyConnections::GetInstance();
bluetooth::mojom::Adapter* bluetooth_adapter =
connections.GetBluetoothAdapter();
if (!bluetooth_adapter)
return nullptr;
return std::make_unique<chrome::BleMedium>(bluetooth_adapter);
} }
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium( std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
......
...@@ -4,11 +4,13 @@ ...@@ -4,11 +4,13 @@
#include "chrome/services/sharing/nearby/platform_v2/ble_medium.h" #include "chrome/services/sharing/nearby/platform_v2/ble_medium.h"
#include "chrome/services/sharing/nearby/platform_v2/bluetooth_device.h"
namespace location { namespace location {
namespace nearby { namespace nearby {
namespace chrome { namespace chrome {
BleMedium::BleMedium() = default; BleMedium::BleMedium(bluetooth::mojom::Adapter* adapter) : adapter_(adapter) {}
BleMedium::~BleMedium() = default; BleMedium::~BleMedium() = default;
...@@ -28,15 +30,59 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { ...@@ -28,15 +30,59 @@ bool BleMedium::StopAdvertising(const std::string& service_id) {
bool BleMedium::StartScanning(const std::string& service_id, bool BleMedium::StartScanning(const std::string& service_id,
api::BleMedium::DiscoveredPeripheralCallback api::BleMedium::DiscoveredPeripheralCallback
discovered_peripheral_callback) { discovered_peripheral_callback) {
// TODO(b/154848193): Implement this method. auto service_uuid = device::BluetoothUUID(service_id);
NOTIMPLEMENTED(); if (IsScanning() &&
base::Contains(discovered_peripheral_callbacks_map_, service_uuid)) {
return true;
}
// We only need to start discovery if no other discovery request is active.
if (discovered_peripheral_callbacks_map_.empty()) {
discovered_ble_peripherals_map_.clear();
bool success =
adapter_->AddObserver(adapter_observer_.BindNewPipeAndPassRemote());
if (!success) {
adapter_observer_.reset();
return false;
}
mojo::PendingRemote<bluetooth::mojom::DiscoverySession> discovery_session;
success = adapter_->StartDiscoverySession(&discovery_session);
if (!success || !discovery_session.is_valid()) {
adapter_observer_.reset();
return false;
}
discovery_session_.Bind(std::move(discovery_session));
discovery_session_.set_disconnect_handler(
base::BindOnce(&BleMedium::DiscoveringChanged, base::Unretained(this),
/*discovering=*/false));
}
// A different DiscoveredPeripheralCallback is being passed on each call, so
// each must be captured and associated with its |service_id|.
discovered_peripheral_callbacks_map_.insert(
{service_uuid, discovered_peripheral_callback});
return true; return true;
} }
bool BleMedium::StopScanning(const std::string& service_id) { bool BleMedium::StopScanning(const std::string& service_id) {
// TODO(b/154848193): Implement this method. discovered_peripheral_callbacks_map_.erase(device::BluetoothUUID(service_id));
NOTIMPLEMENTED(); if (!discovered_peripheral_callbacks_map_.empty())
return false; return true;
bool stop_discovery_success = true;
if (discovery_session_) {
bool message_success = discovery_session_->Stop(&stop_discovery_success);
stop_discovery_success = stop_discovery_success && message_success;
}
adapter_observer_.reset();
discovery_session_.reset();
return stop_discovery_success;
} }
bool BleMedium::StartAcceptingConnections( bool BleMedium::StartAcceptingConnections(
...@@ -62,6 +108,127 @@ std::unique_ptr<api::BleSocket> BleMedium::Connect( ...@@ -62,6 +108,127 @@ std::unique_ptr<api::BleSocket> BleMedium::Connect(
return nullptr; return nullptr;
} }
void BleMedium::PresentChanged(bool present) {
// TODO(hansberry): It is unclear to me how the API implementation can signal
// to Core that |present| has become unexpectedly false. Need to ask
// Nearby team.
if (!present)
StopScanning();
}
void BleMedium::PoweredChanged(bool powered) {
// TODO(hansberry): It is unclear to me how the API implementation can signal
// to Core that |powered| has become unexpectedly false. Need to ask
// Nearby team.
if (!powered)
StopScanning();
}
void BleMedium::DiscoverableChanged(bool discoverable) {
// Do nothing. BleMedium is not responsible for managing
// discoverable state.
}
void BleMedium::DiscoveringChanged(bool discovering) {
// TODO(hansberry): It is unclear to me how the API implementation can signal
// to Core that |discovering| has become unexpectedly false. Need to ask
// Nearby team.
if (!discovering)
StopScanning();
}
void BleMedium::DeviceAdded(bluetooth::mojom::DeviceInfoPtr device) {
if (!IsScanning())
return;
// Best-effort attempt to filter out BT Classic devices. Dual-mode (BT
// Classic and BLE) devices which the system has paired and/or connected to
// may also expose service data, but all BLE advertisements that we are
// interested in are captured in an element of |service_data_map|. See
// BluetoothClassicMedium for separate discovery of BT Classic devices.
if (device->service_data_map.empty())
return;
const std::string& address = device->address;
auto* ble_peripheral = GetDiscoveredBlePeripheral(address);
if (ble_peripheral)
ble_peripheral->UpdateDeviceInfo(std::move(device));
else
discovered_ble_peripherals_map_.emplace(address, std::move(device));
// Invoking one of the callbacks in |discovered_peripheral_callbacks_map_| may
// lead to invalidating one or all elements of
// |discovered_peripheral_callbacks_map_|, e.g., triggering StopScanning()
// while looping through it. Callbacks are copied to ensure they are not
// modified as we loop through them.
auto callbacks_map_copy = discovered_peripheral_callbacks_map_;
for (auto& it : callbacks_map_copy) {
// Must fetch |ble_peripheral| again because it may have been invalidated by
// a prior callback in this loop.
ble_peripheral = GetDiscoveredBlePeripheral(address);
if (!ble_peripheral)
break;
const auto& service_id = it.first.value();
if (ble_peripheral->GetAdvertisementBytes(service_id).Empty())
continue;
it.second.peripheral_discovered_cb(*ble_peripheral, service_id);
}
}
void BleMedium::DeviceChanged(bluetooth::mojom::DeviceInfoPtr device) {
DeviceAdded(std::move(device));
}
void BleMedium::DeviceRemoved(bluetooth::mojom::DeviceInfoPtr device) {
if (!IsScanning())
return;
const std::string& address = device->address;
if (!GetDiscoveredBlePeripheral(address))
return;
// Invoking one of the callbacks in |discovered_peripheral_callbacks_map_| may
// lead to invalidating one or all elements of
// |discovered_peripheral_callbacks_map_|, e.g., triggering StopScanning()
// while looping through it. Callbacks are copied to ensure they are not
// modified as we loop through them.
auto callbacks_map_copy = discovered_peripheral_callbacks_map_;
for (auto& it : callbacks_map_copy) {
// Must fetch |ble_peripheral| again because it may have been invalidated by
// a prior callback in this loop.
auto* ble_peripheral = GetDiscoveredBlePeripheral(address);
if (!ble_peripheral)
break;
it.second.peripheral_lost_cb(*ble_peripheral,
/*service_id=*/it.first.value());
}
discovered_ble_peripherals_map_.erase(address);
}
bool BleMedium::IsScanning() {
return adapter_observer_.is_bound() && discovery_session_.is_bound() &&
!discovered_peripheral_callbacks_map_.empty();
}
void BleMedium::StopScanning() {
// We cannot simply iterate over |discovered_peripheral_callbacks_map_|
// because StopScanning() will erase the provided element.
while (!discovered_peripheral_callbacks_map_.empty()) {
StopScanning(/*service_id=*/discovered_peripheral_callbacks_map_.begin()
->first.value());
}
}
chrome::BlePeripheral* BleMedium::GetDiscoveredBlePeripheral(
const std::string& address) {
auto it = discovered_ble_peripherals_map_.find(address);
return it == discovered_ble_peripherals_map_.end() ? nullptr : &it->second;
}
} // namespace chrome } // namespace chrome
} // namespace nearby } // namespace nearby
} // namespace location } // namespace location
...@@ -7,7 +7,10 @@ ...@@ -7,7 +7,10 @@
#include <string> #include <string>
#include "chrome/services/sharing/nearby/platform_v2/ble_peripheral.h"
#include "device/bluetooth/public/mojom/adapter.mojom.h" #include "device/bluetooth/public/mojom/adapter.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/nearby/src/cpp/platform_v2/api/ble.h" #include "third_party/nearby/src/cpp/platform_v2/api/ble.h"
namespace location { namespace location {
...@@ -15,9 +18,12 @@ namespace nearby { ...@@ -15,9 +18,12 @@ namespace nearby {
namespace chrome { namespace chrome {
// Concrete BleMedium implementation. // Concrete BleMedium implementation.
class BleMedium : public api::BleMedium { // api::BleMedium is a synchronous interface, so this implementation consumes
// the synchronous signatures of bluetooth::mojom::Adapter methods.
class BleMedium : public api::BleMedium,
public bluetooth::mojom::AdapterObserver {
public: public:
BleMedium(); explicit BleMedium(bluetooth::mojom::Adapter* adapter);
~BleMedium() override; ~BleMedium() override;
BleMedium(const BleMedium&) = delete; BleMedium(const BleMedium&) = delete;
...@@ -38,6 +44,44 @@ class BleMedium : public api::BleMedium { ...@@ -38,6 +44,44 @@ class BleMedium : public api::BleMedium {
std::unique_ptr<api::BleSocket> Connect( std::unique_ptr<api::BleSocket> Connect(
api::BlePeripheral& ble_peripheral, api::BlePeripheral& ble_peripheral,
const std::string& service_id) override; const std::string& service_id) override;
private:
// bluetooth::mojom::AdapterObserver:
void PresentChanged(bool present) override;
void PoweredChanged(bool powered) override;
void DiscoverableChanged(bool discoverable) override;
void DiscoveringChanged(bool discovering) override;
void DeviceAdded(bluetooth::mojom::DeviceInfoPtr device) override;
void DeviceChanged(bluetooth::mojom::DeviceInfoPtr device) override;
void DeviceRemoved(bluetooth::mojom::DeviceInfoPtr device) override;
// Query if any service IDs are being scanned for.
bool IsScanning();
// End discovery for all requested services.
void StopScanning();
// Returns nullptr if no BlePeripheral at |address| exists.
chrome::BlePeripheral* GetDiscoveredBlePeripheral(const std::string& address);
// This reference is owned by the top-level Nearby Connections interface and
// will always outlive this object.
bluetooth::mojom::Adapter* adapter_ = nullptr;
// |adapter_observer_| is only set and bound during active discovery so that
// events we don't care about outside of discovery don't pile up.
mojo::Receiver<bluetooth::mojom::AdapterObserver> adapter_observer_{this};
// Keyed by requested service UUID. Discovery is active as long as this map is
// non-empty.
std::map<device::BluetoothUUID, DiscoveredPeripheralCallback>
discovered_peripheral_callbacks_map_;
// Only set while discovery is active.
mojo::Remote<bluetooth::mojom::DiscoverySession> discovery_session_;
// Keyed by address of advertising remote device.
std::map<std::string, chrome::BlePeripheral> discovered_ble_peripherals_map_;
}; };
} // namespace chrome } // namespace chrome
......
...@@ -8,19 +8,31 @@ namespace location { ...@@ -8,19 +8,31 @@ namespace location {
namespace nearby { namespace nearby {
namespace chrome { namespace chrome {
BlePeripheral::BlePeripheral(api::BluetoothDevice& bluetooth_device) {} BlePeripheral::BlePeripheral(bluetooth::mojom::DeviceInfoPtr device_info)
: device_info_(std::move(device_info)) {}
BlePeripheral::~BlePeripheral() = default; BlePeripheral::~BlePeripheral() = default;
std::string BlePeripheral::GetName() const { std::string BlePeripheral::GetName() const {
// TODO(hansberry): Implement. return device_info_->name_for_display;
return std::string();
} }
ByteArray BlePeripheral::GetAdvertisementBytes( ByteArray BlePeripheral::GetAdvertisementBytes(
const std::string& service_id) const { const std::string& service_id) const {
// TODO(hansberry): Implement. const auto& service_data_map = device_info_->service_data_map;
return ByteArray();
auto it = service_data_map.find(device::BluetoothUUID(service_id));
if (it == service_data_map.end())
return ByteArray();
std::string service_data(it->second.begin(), it->second.end());
return ByteArray(service_data);
}
void BlePeripheral::UpdateDeviceInfo(
bluetooth::mojom::DeviceInfoPtr device_info) {
DCHECK_EQ(device_info_->address, device_info->address);
device_info_ = std::move(device_info);
} }
} // namespace chrome } // namespace chrome
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_BLE_PERIPHERAL_H_ #ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_BLE_PERIPHERAL_H_
#define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_BLE_PERIPHERAL_H_ #define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_V2_BLE_PERIPHERAL_H_
#include "device/bluetooth/public/mojom/adapter.mojom.h"
#include "third_party/nearby/src/cpp/platform_v2/api/ble.h" #include "third_party/nearby/src/cpp/platform_v2/api/ble.h"
namespace location { namespace location {
...@@ -14,7 +15,7 @@ namespace chrome { ...@@ -14,7 +15,7 @@ namespace chrome {
// Concrete BlePeripheral implementation. // Concrete BlePeripheral implementation.
class BlePeripheral : public api::BlePeripheral { class BlePeripheral : public api::BlePeripheral {
public: public:
explicit BlePeripheral(api::BluetoothDevice& bluetooth_device); explicit BlePeripheral(bluetooth::mojom::DeviceInfoPtr device_info);
~BlePeripheral() override; ~BlePeripheral() override;
BlePeripheral(const BlePeripheral&) = delete; BlePeripheral(const BlePeripheral&) = delete;
...@@ -23,6 +24,11 @@ class BlePeripheral : public api::BlePeripheral { ...@@ -23,6 +24,11 @@ class BlePeripheral : public api::BlePeripheral {
// api::BlePeripheral: // api::BlePeripheral:
std::string GetName() const override; std::string GetName() const override;
ByteArray GetAdvertisementBytes(const std::string& service_id) const override; ByteArray GetAdvertisementBytes(const std::string& service_id) const override;
void UpdateDeviceInfo(bluetooth::mojom::DeviceInfoPtr device_info);
private:
bluetooth::mojom::DeviceInfoPtr device_info_;
}; };
} // namespace chrome } // namespace chrome
......
...@@ -262,6 +262,7 @@ BluetoothInternalsTest.prototype = { ...@@ -262,6 +262,7 @@ BluetoothInternalsTest.prototype = {
nameForDisplay: 'AAA', nameForDisplay: 'AAA',
rssi: {value: -40}, rssi: {value: -40},
isGattConnected: false, isGattConnected: false,
serviceDataMap: {},
services: [], services: [],
}; };
}, },
...@@ -277,6 +278,7 @@ BluetoothInternalsTest.prototype = { ...@@ -277,6 +278,7 @@ BluetoothInternalsTest.prototype = {
nameForDisplay: 'BBB', nameForDisplay: 'BBB',
rssi: null, rssi: null,
isGattConnected: false, isGattConnected: false,
serviceDataMap: {},
services: [], services: [],
}; };
}, },
...@@ -291,6 +293,7 @@ BluetoothInternalsTest.prototype = { ...@@ -291,6 +293,7 @@ BluetoothInternalsTest.prototype = {
address: 'CC:CC:84:96:92:84', address: 'CC:CC:84:96:92:84',
name: 'CCC', name: 'CCC',
nameForDisplay: 'CCC', nameForDisplay: 'CCC',
serviceDataMap: {},
isGattConnected: false, isGattConnected: false,
}; };
}, },
......
...@@ -45,6 +45,9 @@ mojom::DeviceInfoPtr Device::ConstructDeviceInfoStruct( ...@@ -45,6 +45,9 @@ mojom::DeviceInfoPtr Device::ConstructDeviceInfoStruct(
device_info->rssi->value = device->GetInquiryRSSI().value(); device_info->rssi->value = device->GetInquiryRSSI().value();
} }
for (auto const& it : device->GetServiceData())
device_info->service_data_map.insert_or_assign(it.first, it.second);
return device_info; return device_info;
} }
......
...@@ -60,6 +60,13 @@ struct DeviceInfo { ...@@ -60,6 +60,13 @@ struct DeviceInfo {
string address; string address;
bool is_gatt_connected; bool is_gatt_connected;
RSSIWrapper? rssi; RSSIWrapper? rssi;
// Important note: the "service data" associated with each UUID is an
// arbitrary binary blob of data provided by a likely untrustworthy device.
// Clients are responsible for safely parsing this information; please see
// "The Rule of 2" (//docs/security/rule-of-2.md). C++ clients must parse this
// blob in a sandbox process.
map<UUID, array<uint8>> service_data_map;
}; };
struct ServiceInfo { struct ServiceInfo {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment