// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/services/sharing/nearby/platform/ble_v2_medium.h"
#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h"
#include "chrome/services/sharing/nearby/platform/ble_v2_gatt_server.h"
#include "chrome/services/sharing/nearby/platform/ble_v2_remote_peripheral.h"
#include "chrome/services/sharing/nearby/platform/ble_v2_server_socket.h"
#include "chrome/services/sharing/nearby/platform/bluetooth_utils.h"
#include "chrome/services/sharing/nearby/platform/nearby_platform_metrics.h"
#include "components/cross_device/logging/logging.h"
#include "components/cross_device/nearby/nearby_features.h"
#include "third_party/nearby/src/internal/platform/byte_array.h"
#include "third_party/nearby/src/internal/platform/implementation/ble_v2.h"
namespace nearby::chrome {
namespace {
// Max times trying to generate unique scan session id.
static constexpr int kGenerateSessionIdRetryLimit = 3;
// Indicating failed to generate unique scan session id.
static constexpr uint64_t kFailedGenerateSessionId = 0;
// Client name for logging in BLE scanning.
static constexpr char kScanClientName[] = "NearbyBleV2";
std::string TxPowerLevelToName(api::ble_v2::TxPowerLevel tx_power_level) {
switch (tx_power_level) {
case api::ble_v2::TxPowerLevel::kUltraLow:
return "UltraLow";
case api::ble_v2::TxPowerLevel::kLow:
return "Low";
case api::ble_v2::TxPowerLevel::kMedium:
return "Medium";
case api::ble_v2::TxPowerLevel::kHigh:
return "High";
case api::ble_v2::TxPowerLevel::kUnknown:
return "Unknown";
}
}
void CancelPendingTasks(
base::flat_set<raw_ptr<base::WaitableEvent>>& events_to_cancel) {
if (!events_to_cancel.empty()) {
DVLOG(1) << __func__ << ": Canceling " << events_to_cancel.size()
<< " pending calls.";
}
for (base::WaitableEvent* event : std::move(events_to_cancel)) {
event->Signal();
}
}
std::string_view ConnectResultToString(bluetooth::mojom::ConnectResult result) {
switch (result) {
case bluetooth::mojom::ConnectResult::SUCCESS:
return "Success";
case bluetooth::mojom::ConnectResult::AUTH_CANCELED:
return "Auth Canceled";
case bluetooth::mojom::ConnectResult::AUTH_FAILED:
return "Auth Failed";
case bluetooth::mojom::ConnectResult::AUTH_REJECTED:
return "Auth Rejected";
case bluetooth::mojom::ConnectResult::AUTH_TIMEOUT:
return "Auth Timeout";
case bluetooth::mojom::ConnectResult::FAILED:
return "Failed";
case bluetooth::mojom::ConnectResult::INPROGRESS:
return "In Progress";
case bluetooth::mojom::ConnectResult::UNKNOWN:
return "Unknown";
case bluetooth::mojom::ConnectResult::UNSUPPORTED_DEVICE:
return "Unsupported Device";
case bluetooth::mojom::ConnectResult::DEVICE_NO_LONGER_IN_RANGE:
return "Device No Longer In Range";
case bluetooth::mojom::ConnectResult::NOT_READY:
return "Not Ready";
case bluetooth::mojom::ConnectResult::ALREADY_CONNECTED:
return "Already Connected";
case bluetooth::mojom::ConnectResult::ALREADY_EXISTS:
return "Already Exists";
case bluetooth::mojom::ConnectResult::NOT_CONNECTED:
return "Not Connected";
case bluetooth::mojom::ConnectResult::DOES_NOT_EXIST:
return "Does Not Exist";
case bluetooth::mojom::ConnectResult::INVALID_ARGS:
return "Invalid Args";
case bluetooth::mojom::ConnectResult::NON_AUTH_TIMEOUT:
return "Non Auth Timeout";
case bluetooth::mojom::ConnectResult::NO_MEMORY:
return "No Memory";
case bluetooth::mojom::ConnectResult::JNI_ENVIRONMENT:
return "JNI Environment";
case bluetooth::mojom::ConnectResult::JNI_THREAD_ATTACH:
return "JNI Thread Attach";
case bluetooth::mojom::ConnectResult::WAKELOCK:
return "Wakelock";
case bluetooth::mojom::ConnectResult::UNEXPECTED_STATE:
return "Unexpected State";
case bluetooth::mojom::ConnectResult::SOCKET:
return "Socket Error";
}
NOTREACHED();
}
} // namespace
BleV2Medium::BleV2Medium(
const mojo::SharedRemote<bluetooth::mojom::Adapter>& adapter)
: task_runner_(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
adapter_(adapter) {
CHECK(adapter_.is_bound());
}
BleV2Medium::~BleV2Medium() {
// For thread safety, shut down on the |task_runner_|.
base::WaitableEvent shutdown_waitable_event;
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&BleV2Medium::Shutdown, base::Unretained(this),
&shutdown_waitable_event));
shutdown_waitable_event.Wait();
}
bool BleV2Medium::StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters) {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return false;
}
// Before starting the advertising, register the GATT Services if supported
// to make GATT advertisements available. To accommodate the asynchronous
// nature of registering the GATT services via `RegisterGattServices()`,
// block until registration succeeds or fails.
if (gatt_server_) {
DVLOG(1)
<< __func__
<< ": attempting to register GATT Services before starting advertising";
base::WaitableEvent register_gatt_services_waitable_event;
bool registration_success;
// The `WeakPtr` to the `BleV2GattServer` cannot be dereferenced on a
// different sequence than `StartAdvertising()` due to thread safety
// enforcement in `WeakPtr`. Therefore, pass a raw pointer to the member
// object to trigger registration.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&BleV2Medium::DoRegisterGattServices,
base::Unretained(this), gatt_server_.get(),
®istration_success,
®ister_gatt_services_waitable_event));
base::ScopedAllowBaseSyncPrimitives allow_wait;
register_gatt_services_waitable_event.Wait();
if (!registration_success) {
DLOG(WARNING)
<< __func__
<< ": failed register GATT Services before starting advertising; "
"stopping advertising";
metrics::RecordStartAdvertisingResult(
/*success=*/false,
/*is_extended_advertisement=*/false);
metrics::RecordStartAdvertisingFailureReason(
/*reason=*/metrics::StartAdvertisingFailureReason::
kFailedToRegisterGattServices,
/*is_extended_advertisement=*/false);
return false;
}
}
std::string service_data_info;
for (auto it = advertising_data.service_data.begin();
it != advertising_data.service_data.end(); it++) {
service_data_info +=
"{UUID:" + std::string(it->first) +
",data size:" + base::NumberToString(it->second.size()) + ",data=0x" +
base::HexEncode(std::vector<uint8_t>(
it->second.data(), it->second.data() + it->second.size())) +
(std::next(it) == advertising_data.service_data.end() ? "}" : "}, ");
}
DVLOG(1) << __func__
<< "BLE_v2 StartAdvertising: "
"advertising_data.is_extended_advertisement="
<< advertising_data.is_extended_advertisement
<< ", advertising_data.service_data=" << service_data_info
<< ", tx_power_level="
<< TxPowerLevelToName(advertise_set_parameters.tx_power_level)
<< ", is_connectable=" << advertise_set_parameters.is_connectable;
if (advertising_data.is_extended_advertisement &&
!IsExtendedAdvertisementsAvailable()) {
// Nearby Connections is expected to pass us extended advertisements without
// first checking if we have support. In that case we are expected to return
// false.
DLOG(WARNING) << __func__
<< " Extended advertising is not supported, "
"not registering extended adv.";
metrics::RecordStartAdvertisingResult(
/*success=*/false,
/*is_extended_advertisement=*/advertising_data
.is_extended_advertisement);
metrics::RecordStartAdvertisingFailureReason(
/*reason=*/metrics::StartAdvertisingFailureReason::
kNoExtendedAdvertisementSupport,
/*is_extended_advertisement=*/advertising_data
.is_extended_advertisement);
return false;
}
// There are 3 types of advertisements that Nearby Connections will ask us
// to broadcast. All 3 are connectable, but there are a few other
// differences.
// 1. Extended Advertisements - These do not have ScanResponse data, and
// contain their full payload in the AdvertisementData. This is limited by
// hardware support.
// 2. Regular legacy GATT advertisements - These do use ScanResponse data.
// This can either contain real information about our GATT Server, or
// contain "dummy" info that signals that this device couldn't start the
// GATT Server (which is also limited by hardware support.)
// 3. Fast advertisements - These do use ScanResponse data, and are shorter
// than GATT advertisements. These are expected to always be supported by
// hardware.
std::map<device::BluetoothUUID,
mojo::PendingRemote<bluetooth::mojom::Advertisement>>
registered_advertisements;
for (const auto& entry : advertising_data.service_data) {
bool use_scan_response = true;
if (advertising_data.is_extended_advertisement) {
use_scan_response = false;
}
auto service_uuid = device::BluetoothUUID(std::string(entry.first));
mojo::PendingRemote<bluetooth::mojom::Advertisement> pending_advertisement;
bool success = adapter_->RegisterAdvertisement(
service_uuid,
std::vector<uint8_t>(entry.second.data(),
entry.second.data() + entry.second.size()),
/*use_scan_data=*/use_scan_response,
/*connectable=*/advertise_set_parameters.is_connectable,
&pending_advertisement);
if (!success || !pending_advertisement.is_valid()) {
// Return early when failing to register an advertisement, even if
// there are multiple sets of advertising data, as Nearby Connections
// expects all advertisements to be registered on success.
DLOG(WARNING) << __func__ << " Failed to register advertisement.";
metrics::RecordStartAdvertisingResult(
/*success=*/false,
/*is_extended_advertisement=*/advertising_data
.is_extended_advertisement);
metrics::RecordStartAdvertisingFailureReason(
/*reason=*/metrics::StartAdvertisingFailureReason::
kAdapterRegisterAdvertisementFailed,
/*is_extended_advertisement=*/advertising_data
.is_extended_advertisement);
return false;
}
registered_advertisements.emplace(service_uuid,
std::move(pending_advertisement));
}
// Only save registered advertisements into the map after all registrations
// succeed. Note that api::ble_v2::BleAdvertisementData enforces one
// advertisement per UUID, but Nearby Connections expects us to handle
// multiple registered advertisements per UUID.
for (auto& entry : registered_advertisements) {
registered_advertisements_map_[entry.first].emplace_back(
std::move(entry.second), task_runner_);
}
DVLOG(1) << __func__ << " Started advertising.";
metrics::RecordStartAdvertisingResult(
/*success=*/true,
/*is_extended_advertisement=*/advertising_data.is_extended_advertisement);
return true;
}
std::unique_ptr<BleV2Medium::AdvertisingSession> BleV2Medium::StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters,
BleV2Medium::AdvertisingCallback callback) {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return nullptr;
}
// TODO(b/318839357): deprecate the 'bool StartAdvertising' function.
if (StartAdvertising(advertising_data, advertise_set_parameters)) {
if (callback.start_advertising_result) {
callback.start_advertising_result(absl::OkStatus());
}
} else {
if (callback.start_advertising_result) {
callback.start_advertising_result(
absl::InternalError("Failed to start advertising."));
}
return nullptr;
}
return std::make_unique<BleV2Medium::AdvertisingSession>(
BleV2Medium::AdvertisingSession{
.stop_advertising =
[this]() {
if (StopAdvertising()) {
return absl::OkStatus();
} else {
return absl::InternalError("Failed to stop advertising.");
}
},
});
}
bool BleV2Medium::StopAdvertising() {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return false;
}
CD_LOG(INFO, Feature::NEARBY_INFRA)
<< __func__ << " Clearing registered advertisements.";
registered_advertisements_map_.clear();
return true;
}
bool BleV2Medium::StartScanning(const Uuid& service_uuid,
api::ble_v2::TxPowerLevel tx_power_level,
BleV2Medium::ScanCallback callback) {
NOTIMPLEMENTED();
return false;
}
bool BleV2Medium::StopScanning() {
NOTIMPLEMENTED();
return false;
}
std::unique_ptr<BleV2Medium::ScanningSession> BleV2Medium::StartScanning(
const Uuid& service_uuid,
api::ble_v2::TxPowerLevel tx_power_level,
BleV2Medium::ScanningCallback callback) {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return nullptr;
}
if (!IsScanning()) {
discovered_ble_peripherals_map_.clear();
service_uuid_to_session_ids_map_.clear();
session_id_to_scanning_callback_map_.clear();
bool success =
adapter_->AddObserver(adapter_observer_.BindNewPipeAndPassRemote());
if (!success) {
adapter_observer_.reset();
metrics::RecordStartScanningResult(
/*success=*/false);
metrics::RecordStartScanningFailureReason(
/*reason=*/metrics::StartScanningFailureReason::
kAdapterObserverationFailed);
return nullptr;
}
mojo::PendingRemote<bluetooth::mojom::DiscoverySession> discovery_session;
success =
adapter_->StartDiscoverySession(kScanClientName, &discovery_session);
if (!success || !discovery_session.is_valid()) {
adapter_observer_.reset();
metrics::RecordStartScanningResult(
/*success=*/false);
metrics::RecordStartScanningFailureReason(
/*reason=*/metrics::StartScanningFailureReason::
kStartDiscoverySessionFailed);
return nullptr;
}
discovery_session_.Bind(std::move(discovery_session));
discovery_session_.set_disconnect_handler(
base::BindOnce(&BleV2Medium::DiscoveringChanged, base::Unretained(this),
/*discovering=*/false));
}
if (callback.start_scanning_result) {
callback.start_scanning_result(absl::OkStatus());
}
// A "service" refers to high-level libraries like Connections, Presence,
// Fast Pair, etc. Each service has a unique `service_uuid`, and each client
// application that consumes that service will be making requests to
// `StartScanning()` with the same `service_uuid`. In order to disambiguate
// multiple clients using the same `service_uuid`, we create a `session_id`
// here for each scan request.
uint64_t session_id = GenerateUniqueSessionId();
device::BluetoothUUID bluetooth_service_uuid{std::string(service_uuid)};
// Save session id, service id and callback for this scan session.
session_id_to_scanning_callback_map_.insert(
{session_id, std::move(callback)});
auto iter = service_uuid_to_session_ids_map_.find(bluetooth_service_uuid);
if (iter == service_uuid_to_session_ids_map_.end()) {
service_uuid_to_session_ids_map_.insert(
{bluetooth_service_uuid, {session_id}});
} else {
iter->second.insert(session_id);
}
metrics::RecordStartScanningResult(
/*success=*/true);
// Generate and return ScanningSession.
return std::make_unique<BleV2Medium::ScanningSession>(
BleV2Medium::ScanningSession{
.stop_scanning =
[this, session_id, bluetooth_service_uuid]() {
size_t num_erased_from_callback_map =
session_id_to_scanning_callback_map_.erase(session_id);
size_t num_erased_from_service_and_session_map = 0u;
auto iter = service_uuid_to_session_ids_map_.find(
bluetooth_service_uuid);
if (iter != service_uuid_to_session_ids_map_.end()) {
num_erased_from_service_and_session_map =
iter->second.erase(session_id);
}
if (num_erased_from_callback_map != 1u ||
num_erased_from_service_and_session_map != 1u) {
return absl::NotFoundError(
"Can't find the provided internal session");
}
session_id_to_scanning_callback_map_.erase(session_id);
service_uuid_to_session_ids_map_[bluetooth_service_uuid].erase(
session_id);
if (service_uuid_to_session_ids_map_[bluetooth_service_uuid]
.empty()) {
service_uuid_to_session_ids_map_.erase(
bluetooth_service_uuid);
}
// Stop discovery if there's no more on-going scan sessions.
if (session_id_to_scanning_callback_map_.empty()) {
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();
if (!stop_discovery_success) {
return absl::InternalError(
"Discovery is not fully stopped");
}
}
return absl::OkStatus();
},
});
}
std::unique_ptr<api::ble_v2::GattServer> BleV2Medium::StartGattServer(
api::ble_v2::ServerGattConnectionCallback callback) {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return nullptr;
}
if (!features::IsNearbyBleV2GattServerEnabled()) {
return nullptr;
}
bool is_dual_role_supported;
adapter_->IsLeScatternetDualRoleSupported(&is_dual_role_supported);
metrics::RecordGattServerScatternetDualRoleSupported(is_dual_role_supported);
if (!is_dual_role_supported) {
return nullptr;
}
auto gatt_server = std::make_unique<BleV2GattServer>(adapter_);
// TODO(b/335753061): Revisit the design pattern of holding onto a
// `GattServer` pointer when Nearby Connections adjusts the BLE V2 APIs.
gatt_server_ = gatt_server->GetWeakPtr();
return gatt_server;
}
std::unique_ptr<api::ble_v2::GattClient> BleV2Medium::ConnectToGattServer(
api::ble_v2::BlePeripheral& peripheral,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return nullptr;
}
base::WaitableEvent connect_to_gatt_server_waitable_event;
CHECK(adapter_.is_bound());
mojo::PendingRemote<bluetooth::mojom::Device> device;
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&BleV2Medium::DoConnectToGattServer,
base::Unretained(this), &device, peripheral.GetAddress(),
&connect_to_gatt_server_waitable_event));
base::ScopedAllowBaseSyncPrimitives allow_wait;
connect_to_gatt_server_waitable_event.Wait();
if (!device) {
LOG(WARNING) << __func__ << ": could not connect to the GATT server";
metrics::RecordConnectToRemoteGattServerResult(/*success=*/false);
return nullptr;
}
metrics::RecordConnectToRemoteGattServerResult(/*success=*/true);
// `tx_power_level` has no equivalent parameter in the Bluetooth Adapter
// layer, so it is ignored.
//
// TODO(b/311430390): When Nearby Connections uses
// `ClientGattConnectionCallback`, pass it into `BleV2GattClient` to trigger
// events for characteristic subscription and disconnect.
return std::make_unique<nearby::chrome::BleV2GattClient>(std::move(device));
}
std::unique_ptr<api::ble_v2::BleServerSocket> BleV2Medium::OpenServerSocket(
const std::string& service_id) {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return nullptr;
}
// TODO(b/320554697): This function has no purpose in BLE V2 and can be
// removed once implementation of the GATT Server advertising is complete.
// Note that other platforms still use this function for now.
return std::make_unique<BleV2ServerSocket>();
}
std::unique_ptr<api::ble_v2::BleSocket> BleV2Medium::Connect(
const std::string& service_id,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BlePeripheral& peripheral,
CancellationFlag* cancellation_flag) {
NOTIMPLEMENTED();
return nullptr;
}
bool BleV2Medium::IsExtendedAdvertisementsAvailable() {
if (!features::IsNearbyBleV2Enabled()) {
DVLOG(1) << __func__ << ": BleV2 is disabled.";
return false;
}
if (!features::IsNearbyBleV2ExtendedAdvertisingEnabled()) {
return false;
}
bluetooth::mojom::AdapterInfoPtr info;
bool success = adapter_->GetInfo(&info);
return success && info->extended_advertisement_support;
}
bool BleV2Medium::GetRemotePeripheral(const std::string& mac_address,
GetRemotePeripheralCallback callback) {
NOTIMPLEMENTED();
return false;
}
bool BleV2Medium::GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
GetRemotePeripheralCallback callback) {
auto it =
std::find_if(discovered_ble_peripherals_map_.begin(),
discovered_ble_peripherals_map_.end(),
[&](const auto& address_device_pair) {
return address_device_pair.second.GetUniqueId() == id;
});
if (it == discovered_ble_peripherals_map_.end()) {
LOG(WARNING) << __func__ << ": no match for device at id = " << id;
return false;
}
std::move(callback)(it->second);
return true;
}
void BleV2Medium::PresentChanged(bool present) {
NOTIMPLEMENTED();
}
void BleV2Medium::PoweredChanged(bool powered) {
NOTIMPLEMENTED();
}
void BleV2Medium::DiscoverableChanged(bool discoverable) {
NOTIMPLEMENTED();
}
void BleV2Medium::DiscoveringChanged(bool discovering) {
if (!discovering) {
StopScanning();
}
}
void BleV2Medium::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.is_null() || device->service_data_map.empty()) {
return;
}
if (device.is_null()) {
CD_LOG(WARNING, Feature::NEARBY_INFRA) << __func__ << " Device is empty.";
return;
}
// Extract Advertisement Data.
auto advertisement_data = api::ble_v2::BleAdvertisementData{
.is_extended_advertisement = false,
.service_data = {},
};
base::flat_set<device::BluetoothUUID> bluetooth_service_set;
for (const auto& service_data_pair : device->service_data_map) {
bluetooth_service_set.insert(service_data_pair.first);
advertisement_data.service_data.insert(
{BluetoothUuidToNearbyUuid(service_data_pair.first),
ByteArray{std::string(service_data_pair.second.begin(),
service_data_pair.second.end())}});
}
// Add a new or update the existing discovered peripheral. Note: Because
// BleV2RemotePeripherals are passed by reference to NearbyConnections, if a
// BleV2RemotePeripheral already exists with the given address, the reference
// should not be invalidated, the update functions should be called instead.
const std::string& address = device->address;
auto* existing_ble_peripheral = GetDiscoveredBlePeripheral(address);
if (existing_ble_peripheral) {
existing_ble_peripheral->UpdateDeviceInfo(std::move(device));
} else {
discovered_ble_peripherals_map_.emplace(
address, chrome::BleV2RemotePeripheral(std::move(device)));
}
for (const auto& service_uuid : bluetooth_service_set) {
auto iter = service_uuid_to_session_ids_map_.find(service_uuid);
if (iter == service_uuid_to_session_ids_map_.end()) {
continue;
}
for (auto session_id : iter->second) {
const auto scanning_callback_iter =
session_id_to_scanning_callback_map_.find(session_id);
if (scanning_callback_iter ==
session_id_to_scanning_callback_map_.end()) {
continue;
}
// Fetch the BleV2RemotePeripheral with the same `address` again because
// previously fetched pointers may have been invalidated while iterating
// through the IDs.
auto* ble_peripheral = GetDiscoveredBlePeripheral(address);
if (!ble_peripheral) {
CD_LOG(WARNING, Feature::NEARBY_INFRA)
<< __func__ << " Can't find previously discovered ble peripheral.";
continue;
}
if (scanning_callback_iter->second.advertisement_found_cb) {
scanning_callback_iter->second.advertisement_found_cb(
*ble_peripheral, advertisement_data);
}
}
}
}
void BleV2Medium::DeviceChanged(bluetooth::mojom::DeviceInfoPtr device) {
DeviceAdded(std::move(device));
}
void BleV2Medium::DeviceRemoved(bluetooth::mojom::DeviceInfoPtr device) {
// TODO we also need this when productionize the ble medium code.
NOTIMPLEMENTED();
}
bool BleV2Medium::IsScanning() {
return adapter_observer_.is_bound() &&
!service_uuid_to_session_ids_map_.empty() &&
!session_id_to_scanning_callback_map_.empty();
}
chrome::BleV2RemotePeripheral* BleV2Medium::GetDiscoveredBlePeripheral(
const std::string& address) {
auto it = discovered_ble_peripherals_map_.find(address);
return it == discovered_ble_peripherals_map_.end() ? nullptr : &it->second;
}
uint64_t BleV2Medium::GenerateUniqueSessionId() {
for (int i = 0; i < kGenerateSessionIdRetryLimit; i++) {
uint64_t session_id = base::RandUint64();
if (session_id != kFailedGenerateSessionId &&
session_id_to_scanning_callback_map_.find(session_id) ==
session_id_to_scanning_callback_map_.end()) {
return session_id;
}
}
return kFailedGenerateSessionId;
}
void BleV2Medium::DoRegisterGattServices(
BleV2GattServer* gatt_server,
bool* registration_success,
base::WaitableEvent* register_gatt_services_waitable_event) {
CHECK(task_runner_->RunsTasksInCurrentSequence());
pending_register_gatt_services_waitable_events_.insert(
register_gatt_services_waitable_event);
CHECK(gatt_server);
gatt_server->RegisterGattServices(base::BindOnce(
&BleV2Medium::OnRegisterGattServices, base::Unretained(this),
registration_success, register_gatt_services_waitable_event));
}
void BleV2Medium::OnRegisterGattServices(
bool* out_registration_success,
base::WaitableEvent* register_gatt_services_waitable_event,
bool in_registration_success) {
CHECK(task_runner_->RunsTasksInCurrentSequence());
if (!pending_register_gatt_services_waitable_events_.contains(
register_gatt_services_waitable_event)) {
// The event has already been signaled.
return;
}
*out_registration_success = in_registration_success;
DVLOG(1) << "BleV2Medium::" << __func__
<< ": GATT Services registration result = "
<< (*out_registration_success ? "success" : "failure");
if (!register_gatt_services_waitable_event->IsSignaled()) {
register_gatt_services_waitable_event->Signal();
pending_register_gatt_services_waitable_events_.erase(
register_gatt_services_waitable_event);
}
}
void BleV2Medium::DoConnectToGattServer(
mojo::PendingRemote<bluetooth::mojom::Device>* device,
const std::string& address,
base::WaitableEvent* connect_to_gatt_server_waitable_event) {
CHECK(task_runner_->RunsTasksInCurrentSequence());
pending_connect_to_gatt_server_waitable_events_.insert(
connect_to_gatt_server_waitable_event);
CHECK(adapter_.is_bound());
adapter_->ConnectToDevice(
address, base::BindOnce(
&BleV2Medium::OnConnectToGattServer, base::Unretained(this),
/*gatt_connection_start_time*/ base::TimeTicks::Now(),
device, connect_to_gatt_server_waitable_event));
}
void BleV2Medium::OnConnectToGattServer(
base::TimeTicks gatt_connection_start_time,
mojo::PendingRemote<bluetooth::mojom::Device>* out_device,
base::WaitableEvent* connect_to_gatt_server_waitable_event,
bluetooth::mojom::ConnectResult result,
mojo::PendingRemote<bluetooth::mojom::Device> in_device) {
CHECK(task_runner_->RunsTasksInCurrentSequence());
if (!pending_connect_to_gatt_server_waitable_events_.contains(
connect_to_gatt_server_waitable_event)) {
// The event has already been signaled.
return;
}
*out_device = std::move(in_device);
VLOG(1) << __func__
<< ": ConnectToDevice() result = " << ConnectResultToString(result);
if (result != bluetooth::mojom::ConnectResult::SUCCESS) {
CHECK(!in_device);
metrics::RecordConnectToRemoteGattServerFailureReason(result);
} else {
metrics::RecordConnectToRemoteGattServerDuration(
/*duration=*/base::TimeTicks::Now() - gatt_connection_start_time);
}
if (!connect_to_gatt_server_waitable_event->IsSignaled()) {
connect_to_gatt_server_waitable_event->Signal();
pending_connect_to_gatt_server_waitable_events_.erase(
connect_to_gatt_server_waitable_event);
}
}
void BleV2Medium::Shutdown(base::WaitableEvent* shutdown_waitable_event) {
CHECK(task_runner_->RunsTasksInCurrentSequence());
// Note that resetting the Remote will cancel any pending callbacks, including
// those already in the task queue.
gatt_server_.reset();
adapter_.reset();
discovery_session_.reset();
discovered_ble_peripherals_map_.clear();
session_id_to_scanning_callback_map_.clear();
service_uuid_to_session_ids_map_.clear();
registered_advertisements_map_.clear();
// Cancel all pending connect/listen calls. This is sequence safe because all
// changes to the pending-event sets are sequenced.
CancelPendingTasks(pending_register_gatt_services_waitable_events_);
CancelPendingTasks(pending_connect_to_gatt_server_waitable_events_);
shutdown_waitable_event->Signal();
}
} // namespace nearby::chrome