// 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_gatt_server.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/services/sharing/nearby/platform/bluetooth_utils.h"
#include "chrome/services/sharing/nearby/platform/count_down_latch.h"
#include "chrome/services/sharing/nearby/platform/nearby_platform_metrics.h"
#include "device/bluetooth/bluetooth_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_gatt_service.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
namespace {
device::BluetoothGattCharacteristic::Permissions ConvertPermission(
nearby::api::ble_v2::GattCharacteristic::Permission permission) {
switch (permission) {
case nearby::api::ble_v2::GattCharacteristic::Permission::kNone:
return device::BluetoothGattCharacteristic::Permission::PERMISSION_NONE;
case nearby::api::ble_v2::GattCharacteristic::Permission::kRead:
return device::BluetoothGattCharacteristic::Permission::PERMISSION_READ;
case nearby::api::ble_v2::GattCharacteristic::Permission::kWrite:
return device::BluetoothGattCharacteristic::Permission::PERMISSION_WRITE;
case nearby::api::ble_v2::GattCharacteristic::Permission::kLast:
NOTREACHED();
}
}
device::BluetoothGattCharacteristic::Properties ConvertProperty(
nearby::api::ble_v2::GattCharacteristic::Property property) {
switch (property) {
case nearby::api::ble_v2::GattCharacteristic::Property::kNone:
return device::BluetoothGattCharacteristic::Property::PROPERTY_NONE;
case nearby::api::ble_v2::GattCharacteristic::Property::kRead:
return device::BluetoothGattCharacteristic::Property::PROPERTY_READ;
case nearby::api::ble_v2::GattCharacteristic::Property::kWrite:
return device::BluetoothGattCharacteristic::Property::PROPERTY_WRITE;
case nearby::api::ble_v2::GattCharacteristic::Property::kIndicate:
return device::BluetoothGattCharacteristic::Property::PROPERTY_INDICATE;
case nearby::api::ble_v2::GattCharacteristic::Property::kNotify:
return device::BluetoothGattCharacteristic::Property::PROPERTY_NOTIFY;
case nearby::api::ble_v2::GattCharacteristic::Property::kLast:
NOTREACHED();
}
}
std::string_view GattErrorCodeToString(
device::BluetoothGattService::GattErrorCode error_code) {
switch (error_code) {
case device::BluetoothGattService::GattErrorCode::kUnknown:
return "Unknown";
case device::BluetoothGattService::GattErrorCode::kFailed:
return "Failed";
case device::BluetoothGattService::GattErrorCode::kInProgress:
return "In Progress";
case device::BluetoothGattService::GattErrorCode::kInvalidLength:
return "Invalid Length";
case device::BluetoothGattService::GattErrorCode::kNotPermitted:
return "Not Permitted";
case device::BluetoothGattService::GattErrorCode::kNotAuthorized:
return "Not Authorized";
case device::BluetoothGattService::GattErrorCode::kNotPaired:
return "Not Paired";
case device::BluetoothGattService::GattErrorCode::kNotSupported:
return "Not Supported";
}
}
} // namespace
namespace nearby::chrome {
std::unique_ptr<BleV2GattServer::GattService>
BleV2GattServer::GattService::Factory::Create() {
return base::WrapUnique(new BleV2GattServer::GattService());
}
BleV2GattServer::GattService::Factory::~Factory() = default;
BleV2GattServer::GattService::GattService() = default;
BleV2GattServer::GattService::~GattService() = default;
BleV2GattServer::BleV2GattServer(
const mojo::SharedRemote<bluetooth::mojom::Adapter>& adapter,
std::unique_ptr<GattService::Factory> gatt_service_factory)
: task_runner_(
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
gatt_service_factory_(std::move(gatt_service_factory)),
bluetooth_adapter_(std::make_unique<BluetoothAdapter>(adapter)),
adapter_remote_(adapter) {
CHECK(adapter_remote_.is_bound());
}
BleV2GattServer::~BleV2GattServer() = default;
BluetoothAdapter& BleV2GattServer::GetBlePeripheral() {
CHECK(bluetooth_adapter_);
return *bluetooth_adapter_;
}
std::optional<api::ble_v2::GattCharacteristic>
BleV2GattServer::CreateCharacteristic(
const Uuid& service_uuid,
const Uuid& characteristic_uuid,
api::ble_v2::GattCharacteristic::Permission permission,
api::ble_v2::GattCharacteristic::Property property) {
// Characteristics can only be created and added to a `GattService` before
// registration has begun.
CHECK(!has_registration_started_);
VLOG(1) << __func__;
// If there isn't a GATT Service that already exists for `service_uuid`,
// create one in the browser process before creating a characteristic at
// `characteristic_uuid` tied to the `service_uuid`.
auto service_it = uuid_to_gatt_service_map_.find(service_uuid);
if (service_it == uuid_to_gatt_service_map_.end()) {
mojo::PendingRemote<bluetooth::mojom::GattService>
gatt_service_pending_remote;
device::BluetoothUUID bluetooth_service_uuid{std::string(service_uuid)};
adapter_remote_->CreateLocalGattService(
/*service_uuid=*/bluetooth_service_uuid,
/*observer=*/
gatt_service_observer_.BindNewPipeAndPassRemote(),
&gatt_service_pending_remote);
if (!gatt_service_pending_remote) {
LOG(WARNING) << __func__ << ": Unable to get or create GATT service";
metrics::RecordCreateLocalGattServiceResult(/*success=*/false);
return std::nullopt;
}
metrics::RecordCreateLocalGattServiceResult(/*success=*/true);
auto gatt_service = gatt_service_factory_->Create();
gatt_service->gatt_service_remote.Bind(
std::move(gatt_service_pending_remote),
/*bind_task_runner=*/task_runner_);
gatt_service->gatt_service_remote.set_disconnect_handler(
base::BindOnce(&BleV2GattServer::OnGattServiceDisconnected,
base::Unretained(this), service_uuid),
/*handler_task_runner=*/task_runner_);
service_it =
uuid_to_gatt_service_map_.emplace(service_uuid, std::move(gatt_service))
.first;
}
// If a characteristic at `characteristic_uuid` already exists in a GATT
// service tied to `service_uuid`, return it to callers, and do not
// attempt to create on in the GATT server. This will only happen if the
// `GATT service` was not created in the block above because it will only
// happen if a previous call to `BleV2GattServer::CreateCharacteristic()`
// created the characteristic.
auto* gatt_service = service_it->second.get();
const auto& char_map =
gatt_service->characteristic_uuid_to_characteristic_map;
auto char_it = char_map.find(characteristic_uuid);
if (char_it != char_map.end()) {
VLOG(1) << __func__ << ": characteristic already exists";
return char_it->second;
}
// Trigger a call in the browser process to create a GATT characteristic in
// the local device's GATT server. The current implementation of BLE V2
// in Nearby Connections only supports a single permission or property type
// for a characteristic, even though the Bluetooth Adapter in the platform
// layer can support multiple properties using bitwise operations. In order
// future proof the BLE V2 layer, and keep implementation details of Nearby
// Connections contained in this class, `BleV2GattServer` converts a single
// nearby::api::ble_v2::GattCharacteristic Property/Permission into a
// device::BluetoothGattCharacteristic Permissions/Properties, which
// only contain a single value.
CHECK(gatt_service->gatt_service_remote.is_bound());
bool create_characteristic_success;
device::BluetoothUUID bluetooth_characteristic_uuid{
std::string(characteristic_uuid)};
gatt_service->gatt_service_remote->CreateCharacteristic(
/*characteristic_uuid=*/bluetooth_characteristic_uuid,
/*permissions=*/ConvertPermission(permission),
/*properties=*/ConvertProperty(property),
/*out_success=*/&create_characteristic_success);
if (!create_characteristic_success) {
LOG(WARNING) << __func__ << ": Unable to create GATT characteristic";
metrics::RecordCreateLocalGattCharacteristicResult(/*success=*/false);
return std::nullopt;
}
// If successful in creating the GATT characteristic, create a corresponding
// representation of the GATT characteristic to return back to the Nearby
// Connections library. This will be used to trigger requests to notify or
// update the GATT characteristic in other methods. The browser process
// retrieves the corresponding GATT characteristic by `charactertistic_uuid`.
metrics::RecordCreateLocalGattCharacteristicResult(/*success=*/true);
api::ble_v2::GattCharacteristic gatt_characteristic = {
characteristic_uuid, service_uuid, permission, property};
gatt_service->characteristic_uuid_to_characteristic_map.insert_or_assign(
characteristic_uuid, gatt_characteristic);
return gatt_characteristic;
}
bool BleV2GattServer::UpdateCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic,
const nearby::ByteArray& value) {
VLOG(1) << __func__;
auto service_it = uuid_to_gatt_service_map_.find(characteristic.service_uuid);
if (service_it == uuid_to_gatt_service_map_.end()) {
LOG(WARNING) << __func__
<< ": trying to update a characteristic in a service that "
"doesn't exist";
metrics::RecordUpdateCharacteristicResult(/*success=*/false);
return false;
}
auto* gatt_service = service_it->second.get();
const auto& char_map =
gatt_service->characteristic_uuid_to_characteristic_map;
auto char_it = char_map.find(characteristic.uuid);
if (char_it == char_map.end()) {
LOG(WARNING) << __func__
<< ": trying to update a characteristic that doesn't exist in "
"the GATT service";
metrics::RecordUpdateCharacteristicResult(/*success=*/false);
return false;
}
// //device/bluetooth is not responsible for storing the value of a GATT
// characteristic -- it is the responsibility of the `GattService`'s Delegate.
// The `GattService` will relay corresponding messages on its Delegate
// to `BleV2GattServer`, so the `BleV2GattServer` is responsible for storing
// the value of the GATT characteristic and providing it when a read
// is requested by a GATT client in `OnLocalCharacteristicRead()`.
VLOG(1) << __func__ << ": storing value for a characteristic at UUID = "
<< characteristic.uuid.Get16BitAsString();
gatt_service->characteristic_uuid_to_value_map.emplace(characteristic.uuid,
value);
metrics::RecordUpdateCharacteristicResult(/*success=*/true);
return true;
}
absl::Status BleV2GattServer::NotifyCharacteristicChanged(
const api::ble_v2::GattCharacteristic& characteristic,
bool confirm,
const nearby::ByteArray& new_value) {
// TODO(b/311430390): Implement to call on the Mojo remote to update the value
// of the GATT Characteristic, and notify remote devices, and returns the
// resulting Status of the operation. When implementing, consider if
// `UpdateCharacteristic()` needs to be called before calling
// `NotifyCharacteristicChanged()` in the library, or in this class, so
// `BleV2GattServer` holds onto the updated value for any READ requests.
NOTIMPLEMENTED();
return absl::Status();
}
void BleV2GattServer::Stop() {
VLOG(1) << __func__;
// Clearing the `uuid_to_gatt_service_map_` destroys all `GattService`s owned
// by `BleV2GattServer`, which also includes destroying their underlying
// `GattService` Mojo remotes.
uuid_to_gatt_service_map_.clear();
}
void BleV2GattServer::RegisterGattServices(
base::OnceCallback<void(bool)> on_registration_complete_callback) {
// `RegisterGattServices()` is expected to only be called once during the
// lifetime of its class to kick off registration of the GATT services.
CHECK(!has_registration_started_);
has_registration_started_ = true;
VLOG(1) << __func__;
if (uuid_to_gatt_service_map_.empty()) {
VLOG(1) << __func__ << ": no GATT services to register; returning success";
std::move(on_registration_complete_callback_).Run(/*success=*/true);
return;
}
on_registration_complete_callback_ =
std::move(on_registration_complete_callback);
registration_barrier_ =
std::make_unique<base::AtomicRefCount>(uuid_to_gatt_service_map_.size());
for (auto it = uuid_to_gatt_service_map_.begin();
it != uuid_to_gatt_service_map_.end(); it++) {
DoRegisterGattService(it->second.get());
}
}
void BleV2GattServer::DoRegisterGattService(GattService* gatt_service) {
CHECK(gatt_service->gatt_service_remote.is_bound());
gatt_service->gatt_service_remote->Register(base::BindOnce(
&BleV2GattServer::OnRegisterGattService, base::Unretained(this)));
}
void BleV2GattServer::OnRegisterGattService(
std::optional<device::BluetoothGattService::GattErrorCode> error_code) {
// If there are multiple GATT services being registered, even though the
// GATT server ultimately returns failure if any single one fails to be
// registered, we continue the registration of all the GATT services and
// do not early return to prevent a case where a GATT service is
// destroyed before its registration completes asynchronously.
if (error_code) {
LOG(WARNING) << __func__ << ": failed due to error = "
<< GattErrorCodeToString(*error_code);
did_any_gatt_services_fail_to_register_ = true;
metrics::RecordGattServiceRegistrationErrorReason(error_code.value());
}
if (!registration_barrier_->Decrement()) {
VLOG(1) << __func__ << ": registration result = "
<< (!did_any_gatt_services_fail_to_register_ ? "success"
: "failure");
metrics::RecordGattServiceRegistrationResult(
/*success=*/!did_any_gatt_services_fail_to_register_);
CHECK(on_registration_complete_callback_);
std::move(on_registration_complete_callback_)
.Run(/*success=*/!did_any_gatt_services_fail_to_register_);
}
}
void BleV2GattServer::OnLocalCharacteristicRead(
bluetooth::mojom::DeviceInfoPtr remote_device,
const device::BluetoothUUID& characteristic_uuid,
const device::BluetoothUUID& service_uuid,
uint32_t offset,
OnLocalCharacteristicReadCallback callback) {
VLOG(1) << __func__;
Uuid nearby_service_uuid = BluetoothUuidToNearbyUuid(service_uuid);
Uuid nearby_characteristic_uuid =
BluetoothUuidToNearbyUuid(characteristic_uuid);
// Expect that `OnLocalCharacteristicRead()` is called for a
// characteristic that already exists in the `uuid_to_gatt_service_map_` of
// the corresponding `GattService`. If this isn't true, it means the
// corresponding GATT service in the browser process and this
// `BleV2GattServer` have gotten out of sync.
auto service_it = uuid_to_gatt_service_map_.find(nearby_service_uuid);
CHECK(service_it != uuid_to_gatt_service_map_.end());
auto* gatt_service = service_it->second.get();
const auto& char_map =
gatt_service->characteristic_uuid_to_characteristic_map;
auto char_it = char_map.find(nearby_characteristic_uuid);
CHECK(char_it != char_map.end());
// Return an error if the property and permission of the characteristic does
// not support read requests. `nearby::api::ble_v2::GattCharacteristic` only
// support a single property and permission.
if ((char_it->second.property !=
nearby::api::ble_v2::GattCharacteristic::Property::kRead) ||
(char_it->second.permission !=
nearby::api::ble_v2::GattCharacteristic::Permission::kRead)) {
LOG(WARNING) << __func__
<< ": trying to read a characteristic that does not support "
"read requests";
metrics::RecordOnLocalCharacteristicReadResult(/*success=*/false);
std::move(callback).Run(
bluetooth::mojom::LocalCharacteristicReadResult::NewErrorCode(
device::BluetoothGattService::GattErrorCode::kNotPermitted));
return;
}
// When a characteristic has a value set with
// `BleV2GattServer::UpdateCharacteristic()`, then reading from the
// characteristic yields that value. If there isn't a value in the
// map for this characteristic, it means that it wasn't set correctly by
// callers of `BleV2GattServer`.
const auto& new_value_map = gatt_service->characteristic_uuid_to_value_map;
auto new_value_it = new_value_map.find(nearby_characteristic_uuid);
if (new_value_it == new_value_map.end()) {
LOG(WARNING) << __func__
<< ": value for the characteristic read request not found";
metrics::RecordOnLocalCharacteristicReadResult(/*success=*/false);
std::move(callback).Run(
bluetooth::mojom::LocalCharacteristicReadResult::NewErrorCode(
device::BluetoothGattService::GattErrorCode::kNotSupported));
return;
}
const ByteArray& data = new_value_it->second;
if (offset >= data.size()) {
LOG(WARNING) << __func__ << ": invalid offset";
std::move(callback).Run(
bluetooth::mojom::LocalCharacteristicReadResult::NewErrorCode(
device::BluetoothGattService::GattErrorCode::kInvalidLength));
return;
}
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data.data());
std::vector<uint8_t> read_value(bytes + offset, bytes + data.size());
metrics::RecordOnLocalCharacteristicReadResult(/*success=*/true);
std::move(callback).Run(
bluetooth::mojom::LocalCharacteristicReadResult::NewData(
std::move(read_value)));
}
void BleV2GattServer::OnGattServiceDisconnected(const Uuid& gatt_service_id) {
LOG(WARNING) << __func__ << ": GATT service at "
<< gatt_service_id.Get16BitAsString()
<< ": unexpectedly disconnected.";
uuid_to_gatt_service_map_.erase(gatt_service_id);
}
} // namespace nearby::chrome