Files
2025-06-19 09:54:00 -07:00

649 lines
25 KiB
C++

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_BLE_V2_H_
#define PLATFORM_API_BLE_V2_H_
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/exception.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/mac_address.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/uuid.h"
namespace nearby {
namespace api {
namespace ble_v2 {
// Coarse representation of power settings throughout all BLE operations.
enum class TxPowerLevel {
kUnknown = 0,
kUltraLow = 1,
kLow = 2,
kMedium = 3,
kHigh = 4,
};
// https://developer.android.com/reference/android/bluetooth/le/AdvertisingSetParameters.Builder
//
// The preferences for Advertising.
struct AdvertiseParameters {
// The transmission power level for the advertising.
TxPowerLevel tx_power_level;
// Whether the advertisement type should be connectable or non-connectable.
bool is_connectable;
};
// https://developer.android.com/reference/android/bluetooth/le/AdvertiseData
//
// Bundle of data found in a BLE advertisement.
//
// All service UUIDs will conform to the 16-bit Bluetooth base UUID,
// 0000xxxx-0000-1000-8000-00805F9B34FB. This makes it possible to store two
// byte service UUIDs in the advertisement.
struct BleAdvertisementData {
// Broadcasts a BLE extended advertisement if it is true.
bool is_extended_advertisement;
// Maps service UUIDs to their service data.
//
// For each platform should follow to set the service UUID(key) and service
// data(value):
//
// iOS : 16 bit service UUID (type=0x03) + LocalName data (type=0x08)
// Windows: Service data (type=0x16)
// Android: 16 bit service UUID (type=0x03) + Service data (type=0x16)
absl::flat_hash_map<Uuid, nearby::ByteArray> service_data;
};
// Opaque wrapper over a BLE peripheral. Must be able to uniquely identify a
// peripheral so that we can connect to its GATT server.
class BlePeripheral {
public:
using UniqueId = std::uint64_t;
BlePeripheral(UniqueId unique_id, MacAddress address)
: unique_id_(unique_id), address_(std::move(address)) {}
explicit BlePeripheral(UniqueId unique_id = 0) : unique_id_(unique_id) {}
virtual ~BlePeripheral() = default;
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice#getAddress()
//
// Returns the current address.
//
// This will always be an empty string on Apple platforms.
virtual std::string GetAddress() const {
if (address_.IsSet()) {
return address_.ToString();
}
return "";
}
// Returns an immutable unique identifier. The identifier must not change when
// the BLE address is rotated.
virtual UniqueId GetUniqueId() const { return unique_id_; }
// Sets platform specific data that can be retrieved by `GetPlatformData()`.
void SetPlatformData(void* platform_data) { platform_data_ = platform_data; }
void* GetPlatformData() const { return platform_data_; }
bool IsSet() const { return GetUniqueId() != 0 || !GetAddress().empty(); }
private:
UniqueId unique_id_ = 0;
MacAddress address_;
void* platform_data_ = nullptr;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic
//
// Representation of a GATT characteristic.
struct GattCharacteristic {
// Represents the GATT characteristic permissions
// This enumeration supports a bitwise combination of its member values.
// |, &, |= of the values are legal.
enum class Permission {
kNone = 0,
kRead = 1 << 0,
kWrite = 1 << 1,
kLast,
};
// Represents the GATT characteristic properties
// This enumeration supports a bitwise combination of its member values.
// |, &, |= of the values are legal.
enum class Property {
kNone = 0,
kRead = 1 << 0,
kWrite = 1 << 1,
kIndicate = 1 << 2,
kNotify = 1 << 3,
kLast,
};
Uuid uuid;
Uuid service_uuid;
Permission permission;
Property property;
// overloading operator for enum class Permission and Property
friend inline Permission operator|(Permission a, Permission b) {
return static_cast<Permission>(static_cast<int>(a) | static_cast<int>(b));
}
friend inline Permission operator&(Permission a, Permission b) {
return static_cast<Permission>(static_cast<int>(a) & static_cast<int>(b));
}
friend inline Permission& operator|=(Permission& a, Permission b) {
a = a | b;
return a;
}
friend inline Property operator|(Property a, Property b) {
return static_cast<Property>(static_cast<int>(a) | static_cast<int>(b));
}
friend inline Property operator&(Property a, Property b) {
return static_cast<Property>(static_cast<int>(a) & static_cast<int>(b));
}
friend inline Property& operator|=(Property& a, Property b) {
a = a | b;
return a;
}
// Hashable
template <typename H>
friend H AbslHashValue(H h, const GattCharacteristic& s) {
return H::combine(std::move(h), s.uuid, s.service_uuid, s.permission,
s.property);
}
bool operator==(const GattCharacteristic& rhs) const {
return this->uuid == rhs.uuid && this->service_uuid == rhs.service_uuid &&
this->permission == rhs.permission && this->property == rhs.property;
}
template <typename Sink>
friend void AbslStringify(Sink& sink, const GattCharacteristic& c) {
absl::Format(&sink, "(service:%s, uuid:%s, permission:%v, property%v)",
std::string(c.service_uuid), std::string(c.uuid), c.permission,
c.property);
}
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt
//
// Representation of a client GATT connection to a remote GATT server.
class GattClient {
public:
// Specifies what type of GATT write should be performed.
enum class WriteType {
kWithResponse = 0,
kWithoutResponse = 1,
};
virtual ~GattClient() = default;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices()
//
// Discovers available service and characteristics on this connection.
// Returns whether or not discovery finished successfully.
//
// This function should block until discovery has finished.
virtual bool DiscoverServiceAndCharacteristics(
const Uuid& service_uuid,
const std::vector<Uuid>& characteristic_uuids) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID)
//
// Retrieves a GATT characteristic. On error, does not return a value.
//
// DiscoverServiceAndCharacteristics() should be called before this method to
// fetch all available services and characteristics first.
//
// It is okay for duplicate services to exist, as long as the specified
// characteristic UUID is unique among all services of the same UUID.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
virtual absl::optional<GattCharacteristic> GetCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue()
// NOLINTNEXTLINE(google3-legacy-absl-backports)
virtual absl::optional<std::string> ReadCharacteristic(
const GattCharacteristic& characteristic) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
//
// Sends a remote characteristic write request to the server and returns
// whether or not it was successful.
virtual bool WriteCharacteristic(const GattCharacteristic& characteristic,
absl::string_view value, WriteType type) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic,%20boolean)
//
// Enable or disable notifications/indications for a given characteristic.
virtual bool SetCharacteristicSubscription(
const GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect()
virtual void Disconnect() = 0;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer
//
// Representation of a BLE GATT server.
// LINT.IfChange
class GattServer {
public:
virtual ~GattServer() = default;
// Creates a characteristic and adds it to the GATT server under the given
// characteristic and service UUIDs. Returns no value upon error.
//
// Characteristics of the same service UUID should be put under one
// service rather than many services with the same UUID.
//
// If the INDICATE property is included, the characteristic should include
// the official Bluetooth Client Characteristic Configuration descriptor
// with UUID 0x2902 and a WRITE permission. This allows remote clients to
// write to this descriptor and subscribe for characteristic changes. For
// more information about this descriptor, please go to:
// https://www.bluetooth.com/specifications/Gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.Gatt.client_characteristic_configuration.xml
// NOLINTNEXTLINE(google3-legacy-absl-backports)
virtual absl::optional<GattCharacteristic> CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
GattCharacteristic::Permission permission,
GattCharacteristic::Property property) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
//
// Locally updates the value of a characteristic and returns whether or not
// it was successful.
virtual bool UpdateCharacteristic(const GattCharacteristic& characteristic,
const nearby::ByteArray& value) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice,%20android.bluetooth.BluetoothGattCharacteristic,%20boolean,%20byte[])
//
// Send a notification or indication that a local characteristic has been
// updated and returns an absl::Status indicating success or what went wrong.
virtual absl::Status NotifyCharacteristicChanged(
const GattCharacteristic& characteristic, bool confirm,
const ByteArray& new_value) = 0;
// Stops a GATT server.
virtual void Stop() = 0;
};
// LINT.ThenChange(//depot/google3/third_party/nearby/internal/platform/ble_v2.h)
// Callback for asynchronous events on the client side of a GATT connection.
struct ClientGattConnectionCallback {
public:
// Called when the characteristic is changed
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb = [](absl::string_view) {};
// Called when the client is disconnected from the GATT server.
absl::AnyInvocable<void()> disconnected_cb = []() {};
};
// Callback for asynchronous events on the server side of a GATT connection.
struct ServerGattConnectionCallback {
using ReadValueCallback =
absl::AnyInvocable<void(absl::StatusOr<absl::string_view> data)>;
using WriteValueCallback = absl::AnyInvocable<void(absl::Status result)>;
// Called when a remote peripheral connected to us and subscribed to one of
// our characteristics.
absl::AnyInvocable<void(const GattCharacteristic& characteristic)>
characteristic_subscription_cb;
// Called when a remote peripheral unsubscribed from one of our
// characteristics.
absl::AnyInvocable<void(const GattCharacteristic& characteristic)>
characteristic_unsubscription_cb;
// Called when a gatt client is reading from the characteristic.
// Must call `callback` with the read result.
// When a characteristic has a static value set with
// `GattServer::UpdateCharacteristic()`, then reading from the characteristic
// yields that static value. The read callback is not called.
// Otherwise, the gatt server calls the read callback to get the value.
absl::AnyInvocable<void(const BlePeripheral::UniqueId remote_device_id,
const GattCharacteristic& characteristic, int offset,
ReadValueCallback callback)>
on_characteristic_read_cb;
// Called when a gatt client is writing to the characteristic.
// Must call `callback` with the write result.
absl::AnyInvocable<void(const BlePeripheral::UniqueId remote_device_id,
const GattCharacteristic& characteristic, int offset,
absl::string_view data, WriteValueCallback callback)>
on_characteristic_write_cb;
};
// A BLE GATT client socket for requesting GATT socket.
class BleSocket {
public:
virtual ~BleSocket() = default;
// Returns the InputStream of the BleSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleSocket object is destroyed.
virtual InputStream& GetInputStream() = 0;
// Returns the OutputStream of the BleSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleSocket object is destroyed.
virtual OutputStream& GetOutputStream() = 0;
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
virtual Exception Close() = 0;
// Returns BlePeripheral::UniqueId that is connected to this socket.
virtual BlePeripheral::UniqueId GetRemotePeripheralId() = 0;
};
// A BLE GATT server socket for listening incoming GATT socket.
class BleServerSocket {
public:
virtual ~BleServerSocket() = default;
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
// On success, returns connected socket, ready to exchange data.
// Returns nullptr on error.
// Once error is reported, it is permanent, and ServerSocket has to be closed.
virtual std::unique_ptr<BleSocket> Accept() = 0;
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
virtual Exception Close() = 0;
};
// A BLE L2CAP client socket for requesting L2CAP socket.
class BleL2capSocket {
public:
virtual ~BleL2capSocket() = default;
// Returns the InputStream of the BleL2capSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleL2capSocket object is destroyed.
virtual InputStream& GetInputStream() = 0;
// Returns the OutputStream of the BleL2capSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleL2capSocket object is destroyed.
virtual OutputStream& GetOutputStream() = 0;
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
virtual Exception Close() = 0;
// Sets the close notifier by client side.
virtual void SetCloseNotifier(absl::AnyInvocable<void()> notifier) {}
// Returns valid BlePeripheral pointer if there is a connection, and
// nullptr otherwise.
virtual BlePeripheral::UniqueId GetRemotePeripheralId() = 0;
};
// A BLE L2CAP server socket for listening incoming L2CAP socket.
class BleL2capServerSocket {
public:
virtual ~BleL2capServerSocket() = default;
// Gets PSM value has been published by the server.
virtual int GetPSM() const = 0;
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
// On success, returns connected socket, ready to exchange data.
// Returns nullptr on error.
// Once error is reported, it is permanent, and L2CAP ServerSocket has to be
// closed.
virtual std::unique_ptr<BleL2capSocket> Accept() = 0;
// Closes the L2CAP server socket.
virtual Exception Close() = 0;
};
// The main BLE medium used inside of Nearby. This serves as the entry point
// for all BLE and GATT related operations.
class BleMedium {
public:
virtual ~BleMedium() = default;
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#startAdvertising(android.bluetooth.le.AdvertiseSettings,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseCallback)
//
// Starts BLE advertising and returns whether or not it was successful.
virtual bool StartAdvertising(
const BleAdvertisementData& advertising_data,
AdvertiseParameters advertise_set_parameters) = 0;
struct AdvertisingCallback {
absl::AnyInvocable<void(absl::Status)> start_advertising_result;
};
struct AdvertisingSession {
absl::AnyInvocable<absl::Status()> stop_advertising;
};
// Async interface for StartAdvertising.
// Result status will be passed to start_advertising_result callback.
// To stop advertising, invoke the stop_advertising callback in
// AdvertisingSession.
virtual std::unique_ptr<AdvertisingSession> StartAdvertising(
const BleAdvertisementData& advertising_data,
AdvertiseParameters advertise_set_parameters,
AdvertisingCallback callback) = 0;
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#stopAdvertising(android.bluetooth.le.AdvertiseCallback)
//
// Stops advertising.
virtual bool StopAdvertising() = 0;
// https://developer.android.com/reference/android/bluetooth/le/ScanCallback
//
// Callback for BLE scan results.
//
// The passed in advertisement_data is the merged combination of both
// advertisement data and scan response.
//
// Every discovery of an advertisement should be reported, even if the
// advertisement was discovered before.
//
// The peripheral is owned by platform implementation and it should outlive
// for the whole peripheral(device) connection life cycle.
struct ScanCallback {
absl::AnyInvocable<void(BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data)>
advertisement_found_cb =
[](BlePeripheral::UniqueId, BleAdvertisementData) {};
};
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#startScan(java.util.List%3Candroid.bluetooth.le.ScanFilter%3E,%20android.bluetooth.le.ScanSettings,%20android.bluetooth.le.ScanCallback)
//
// Starts scanning and returns whether or not it was successful.
//
// TX Power level should be interpreted in the following way:
// LOW:
// - Scan window = ~512ms
// - Scan interval = ~5120ms
// HIGH:
// - Scan window = ~4096ms
// - Scan interval = ~4096ms
virtual bool StartScanning(const Uuid& service_uuid,
TxPowerLevel tx_power_level,
ScanCallback callback) = 0;
// Starts multiple services scanning and returns whether or not it was
// successful.
//
// service_uuids: The UUIDs of the services to scan for. They are logical OR'd
// together.
// tx_power_level: The power level to use for the scan.
// callback: The callback to invoke for every discovered advertisement.
virtual bool StartMultipleServicesScanning(
const std::vector<Uuid>& service_uuids, TxPowerLevel tx_power_level,
ScanCallback callback) {
return false;
}
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#stopScan(android.bluetooth.le.ScanCallback)
//
// Stops scanning.
virtual bool StopScanning() = 0;
virtual bool PauseMediumScanning() { return true; }
virtual bool ResumeMediumScanning() { return true; }
struct ScanningSession {
absl::AnyInvocable<absl::Status()> stop_scanning;
};
struct ScanningCallback {
absl::AnyInvocable<void(absl::Status)> start_scanning_result =
[](absl::Status) {};
absl::AnyInvocable<void(BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data)>
advertisement_found_cb =
[](BlePeripheral::UniqueId, BleAdvertisementData) {};
absl::AnyInvocable<void(BlePeripheral::UniqueId peripheral_id)>
advertisement_lost_cb = [](BlePeripheral::UniqueId) {};
};
// Async interface for StartScanning.
// Result status will be passed to start_scanning_result callback.
// To stop scanning, invoke the stop_scanning callback in
// ScanningSession.
virtual std::unique_ptr<ScanningSession> StartScanning(
const Uuid& service_uuid, TxPowerLevel tx_power_level,
ScanningCallback callback) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothManager#openGattServer(android.content.Context,%20android.bluetooth.BluetoothGattServerCallback)
//
// Starts a GATT server. Returns a nullptr upon error.
virtual std::unique_ptr<GattServer> StartGattServer(
ServerGattConnectionCallback callback) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#connectGatt(android.content.Context,%20boolean,%20android.bluetooth.BluetoothGattCallback)
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestConnectionPriority(int)
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestMtu(int)
//
// Connects to a GATT server and negotiates the specified connection
// parameters. Returns nullptr upon error.
//
// TX Power level should be interpreted in the following way:
// HIGH:
// - Connection interval = ~11.25ms - 15ms
// LOW:
// - Connection interval = ~100ms - 125ms
virtual std::unique_ptr<GattClient> ConnectToGattServer(
BlePeripheral::UniqueId peripheral_id, TxPowerLevel tx_power_level,
ClientGattConnectionCallback callback) = 0;
// Opens a BLE server socket based on service ID.
//
// On success, returns a new BleServerSocket.
// On error, returns nullptr.
virtual std::unique_ptr<BleServerSocket> OpenServerSocket(
const std::string& service_id) = 0;
// Opens a BLE l2cap server socket based on service ID.
//
// On success, returns a new BleL2capServerSocket.
// On error, returns nullptr.
// Platform implementation should override this method if it supports L2CAP.
virtual std::unique_ptr<BleL2capServerSocket> OpenL2capServerSocket(
const std::string& service_id) {
return nullptr;
}
// Connects to a BLE peripheral.
//
// On success, returns a new BleSocket.
// On error, returns nullptr.
virtual std::unique_ptr<BleSocket> Connect(
const std::string& service_id, TxPowerLevel tx_power_level,
BlePeripheral::UniqueId peripheral_id,
CancellationFlag* cancellation_flag) = 0;
// Connects to a BLE peripheral over L2CAP.
//
// On success, returns a new BleL2capSocket.
// On error, returns nullptr.
// Platform implementation should override this method if it supports L2CAP.
virtual std::unique_ptr<BleL2capSocket> ConnectOverL2cap(
int psm, const std::string& service_id, TxPowerLevel tx_power_level,
BlePeripheral::UniqueId peripheral_id,
CancellationFlag* cancellation_flag) {
return nullptr;
}
// Requests if support extended advertisement.
virtual bool IsExtendedAdvertisementsAvailable() = 0;
virtual void AddAlternateUuidForService(uint16_t uuid,
const std::string& service_id) {}
// Retrieves a BlePeripheral ID from a native BLE peripheral id.
// The native ID is platform specific and is used to identify a BLE
// peripheral. On iOS, it should be NSUUID in string format. On other
// platforms, it should be the Bluetooth address in string format
// "XX:XX:XX:XX:XX:XX".
//
// Returns std::nullopt if cannot retrieve the BlePeripheral from the native
// BLE peripheral id.
virtual std::optional<BlePeripheral::UniqueId>
RetrieveBlePeripheralIdFromNativeId(
const std::string& ble_peripheral_native_id) {
return std::nullopt;
}
};
} // namespace ble_v2
} // namespace api
} // namespace nearby
#endif // PLATFORM_API_BLE_V2_H_