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(
std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(
api::BluetoothAdapter& adapter) {
// TODO (hansberry): Inject bluetooth::mojom::Adapter into BleMedium.
return std::make_unique<chrome::BleMedium>();
// Ignore the provided |adapter| argument. It provides no interface useful
// 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(
......
......@@ -4,11 +4,13 @@
#include "chrome/services/sharing/nearby/platform_v2/ble_medium.h"
#include "chrome/services/sharing/nearby/platform_v2/bluetooth_device.h"
namespace location {
namespace nearby {
namespace chrome {
BleMedium::BleMedium() = default;
BleMedium::BleMedium(bluetooth::mojom::Adapter* adapter) : adapter_(adapter) {}
BleMedium::~BleMedium() = default;
......@@ -28,15 +30,59 @@ bool BleMedium::StopAdvertising(const std::string& service_id) {
bool BleMedium::StartScanning(const std::string& service_id,
api::BleMedium::DiscoveredPeripheralCallback
discovered_peripheral_callback) {
// TODO(b/154848193): Implement this method.
NOTIMPLEMENTED();
auto service_uuid = device::BluetoothUUID(service_id);
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;
}
bool BleMedium::StopScanning(const std::string& service_id) {
// TODO(b/154848193): Implement this method.
NOTIMPLEMENTED();
return false;
discovered_peripheral_callbacks_map_.erase(device::BluetoothUUID(service_id));
if (!discovered_peripheral_callbacks_map_.empty())
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(
......@@ -62,6 +108,127 @@ std::unique_ptr<api::BleSocket> BleMedium::Connect(
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 nearby
} // namespace location
......@@ -7,7 +7,10 @@
#include <string>
#include "chrome/services/sharing/nearby/platform_v2/ble_peripheral.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"
namespace location {
......@@ -15,9 +18,12 @@ namespace nearby {
namespace chrome {
// 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:
BleMedium();
explicit BleMedium(bluetooth::mojom::Adapter* adapter);
~BleMedium() override;
BleMedium(const BleMedium&) = delete;
......@@ -38,6 +44,44 @@ class BleMedium : public api::BleMedium {
std::unique_ptr<api::BleSocket> Connect(
api::BlePeripheral& ble_peripheral,
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
......
......@@ -8,19 +8,31 @@ namespace location {
namespace nearby {
namespace chrome {
BlePeripheral::BlePeripheral(api::BluetoothDevice& bluetooth_device) {}
BlePeripheral::BlePeripheral(bluetooth::mojom::DeviceInfoPtr device_info)
: device_info_(std::move(device_info)) {}
BlePeripheral::~BlePeripheral() = default;
std::string BlePeripheral::GetName() const {
// TODO(hansberry): Implement.
return std::string();
return device_info_->name_for_display;
}
ByteArray BlePeripheral::GetAdvertisementBytes(
const std::string& service_id) const {
// TODO(hansberry): Implement.
const auto& service_data_map = device_info_->service_data_map;
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
......
......@@ -5,6 +5,7 @@
#ifndef 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"
namespace location {
......@@ -14,7 +15,7 @@ namespace chrome {
// Concrete BlePeripheral implementation.
class BlePeripheral : public api::BlePeripheral {
public:
explicit BlePeripheral(api::BluetoothDevice& bluetooth_device);
explicit BlePeripheral(bluetooth::mojom::DeviceInfoPtr device_info);
~BlePeripheral() override;
BlePeripheral(const BlePeripheral&) = delete;
......@@ -23,6 +24,11 @@ class BlePeripheral : public api::BlePeripheral {
// api::BlePeripheral:
std::string GetName() 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
......
......@@ -262,6 +262,7 @@ BluetoothInternalsTest.prototype = {
nameForDisplay: 'AAA',
rssi: {value: -40},
isGattConnected: false,
serviceDataMap: {},
services: [],
};
},
......@@ -277,6 +278,7 @@ BluetoothInternalsTest.prototype = {
nameForDisplay: 'BBB',
rssi: null,
isGattConnected: false,
serviceDataMap: {},
services: [],
};
},
......@@ -291,6 +293,7 @@ BluetoothInternalsTest.prototype = {
address: 'CC:CC:84:96:92:84',
name: 'CCC',
nameForDisplay: 'CCC',
serviceDataMap: {},
isGattConnected: false,
};
},
......
......@@ -45,6 +45,9 @@ mojom::DeviceInfoPtr Device::ConstructDeviceInfoStruct(
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;
}
......
......@@ -60,6 +60,13 @@ struct DeviceInfo {
string address;
bool is_gatt_connected;
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 {
......
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