[BLE Refactor] Implement iOS Advertising modules.

PiperOrigin-RevId: 455005322
This commit is contained in:
edwinwu
2022-06-14 18:34:01 -07:00
committed by Copybara-Service
parent a9d7061794
commit 160a224510
8 changed files with 305 additions and 34 deletions
+1 -1
View File
@@ -168,7 +168,7 @@ class GattServer final {
return impl_->UpdateCharacteristic(characteristic, value);
}
void Stop() { return impl_->Stop(); }
void Stop() { if (impl_) return impl_->Stop(); }
// Returns true if a gatt_server is usable. If this method returns false,
// it is not safe to call any other method.
+13 -2
View File
@@ -15,6 +15,7 @@
#ifndef PLATFORM_API_BLE_V2_H_
#define PLATFORM_API_BLE_V2_H_
#include <algorithm>
#include <cstdint>
#include <functional>
#include <limits>
@@ -113,14 +114,24 @@ struct GattCharacteristic {
Uuid uuid;
Uuid service_uuid;
std::vector<Permission> permissions;
std::vector<Property> properties;
// Hashable
template <typename H>
friend H AbslHashValue(H h, const GattCharacteristic& s) {
return H::combine(std::move(h), s.uuid, s.service_uuid);
return H::combine(std::move(h), s.uuid, s.service_uuid, s.permissions,
s.properties);
}
bool operator==(const GattCharacteristic& rhs) const {
return this->uuid == rhs.uuid && this->service_uuid == rhs.service_uuid;
bool has_equal_permissions =
std::is_permutation(this->permissions.begin(), this->permissions.end(),
rhs.permissions.begin(), rhs.permissions.end());
bool has_equal_properties =
std::is_permutation(this->properties.begin(), this->properties.end(),
rhs.properties.begin(), rhs.properties.end());
return this->uuid == rhs.uuid && this->service_uuid == rhs.service_uuid &&
has_equal_permissions && has_equal_properties;
}
};
@@ -66,7 +66,6 @@ objc_library(
"GNCUtils.h",
],
deps = [
"//third_party/objective_c/google_toolbox_for_mac:GTM_StringEncoding",
"@aappleby_smhasher//:libmurmur3",
],
)
@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
@class CBCharacteristic;
@class CBUUID;
NS_ASSUME_NONNULL_BEGIN
@@ -26,16 +29,43 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface GNCMBlePeripheral : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
* Initializes an `GNCMBlePeripheral` object.
* Adds GATT CBService.
*
* @param serviceUUID A GATT service ID to advertise for.
*/
- (void)addCBServiceWithUUID:(CBUUID *)serviceUUID;
/**
* Adds GATT CBCharacteristic.
*
* @param characteristic A characteristic CBUUID.
*/
- (void)addCharacteristic:(CBCharacteristic *)characteristic;
/**
* Updates GATT CBCharacteristic with value.
*
* @param value The NSData to advertise.
* @param characteristicUUID A characteristic CBUUID.
*/
- (void)updateValue:(NSData *)value forCharacteristic:(CBUUID *)characteristicUUID;
/**
* Stops GATT server service.
*/
- (void)stopGATTService;
/**
* Starts advertising with service UUID and advertisement data.
*
* @param serviceUUID A string that uniquely identifies the advertised service to search for.
* @param advertisementData The data to advertise.
*/
- (instancetype)initWithServiceUUID:(NSString *)serviceUUID
advertisementData:(NSData *)advertisementData NS_DESIGNATED_INITIALIZER;
- (BOOL)startAdvertisingWithServiceUUID:(NSString *)serviceUUID
advertisementData:(NSData *)advertisementData;
@end
@@ -14,34 +14,41 @@
#import "internal/platform/implementation/ios/Mediums/Ble/GNCMBlePeripheral.h"
#include <CoreBluetooth/CoreBluetooth.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import "internal/platform/implementation/ios/GNCUtils.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
GNCMPeripheralStateStopped,
GNCMPeripheralStateAdvertising,
};
@interface GNCMBlePeripheral () <CBPeripheralManagerDelegate>
@end
@implementation GNCMBlePeripheral {
/** GATT service for advertisement. */
/** Service UUID for advertisement. */
CBMutableService *_advertisementService;
/** GATT service for GATT connection. */
CBMutableService *_GATTService;
/** GATT characteristics for GATT connection. */
NSMutableArray<CBCharacteristic *> *_gattCharacteristics;
/** CBUUID characteristic to NSData value dictionary for GATT connection. */
NSMutableDictionary<CBUUID *, NSData *> *_gattCharacteristicValues;
/** Data to be advertised. */
NSData *_advertisementData;
/** Peripheral manager used to advertise or connect to peripherals. */
CBPeripheralManager *_peripheralManager;
/** Serial background queue for |peripheralManager|. */
dispatch_queue_t _selfQueue;
/** Peripheral state for stop or advertising. */
GNCMPeripheralState _state;
}
- (instancetype)initWithServiceUUID:(NSString *)serviceUUID
advertisementData:(NSData *)advertisementData {
self = [super init];
if (self) {
_advertisementService =
[[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:serviceUUID] primary:YES];
_advertisementData = [advertisementData copy];
- (instancetype)init {
if (self = [super init]) {
// To make this class thread-safe, use a serial queue for all state changes, and have Core
// Bluetooth also use this queue.
_selfQueue = dispatch_queue_create("GNCPeripheralManagerQueue", DISPATCH_QUEUE_SERIAL);
@@ -51,6 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
initWithDelegate:self
queue:_selfQueue
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
_state = GNCMPeripheralStateStopped;
}
return self;
}
@@ -60,22 +68,68 @@ NS_ASSUME_NONNULL_BEGIN
// dispatch_sync. This means delloc must be called from an external queue, which means |self|
// must never be captured by any escaping block used in this class.
dispatch_sync(_selfQueue, ^{
[self stopAdvertising];
[self stopAdvertisingInternal];
});
}
- (void)addCBServiceWithUUID:(CBUUID *)serviceUUID {
if (!_GATTService) {
// If it has been called, then don't do it again. Initialize one time.
_GATTService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
_gattCharacteristics = [[NSMutableArray alloc] init];
_gattCharacteristicValues = [[NSMutableDictionary alloc] init];
}
}
- (void)addCharacteristic:(CBCharacteristic *)characteristic {
if (_gattCharacteristics) {
[_gattCharacteristics addObject:characteristic];
}
if (_gattCharacteristicValues) {
[_gattCharacteristicValues setObject:[[NSData alloc] init] forKey:characteristic.UUID];
}
}
- (void)updateValue:(NSData *)value forCharacteristic:(CBUUID *)characteristicUUID {
if ([_gattCharacteristicValues objectForKey:characteristicUUID]) {
[_gattCharacteristicValues setObject:value forKey:characteristicUUID];
}
}
- (void)stopGATTService {
if (!_GATTService) return;
dispatch_sync(_selfQueue, ^{
[_peripheralManager removeService:_GATTService];
});
}
- (BOOL)startAdvertisingWithServiceUUID:(NSString *)serviceUUID
advertisementData:(NSData *)advertisementData {
NSLog(@"[NEARBY] Client rquests startAdvertising");
_advertisementService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:serviceUUID]
primary:YES];
_advertisementData = [advertisementData copy];
if (_GATTService) {
if (_gattCharacteristics && _gattCharacteristics.count > 0) {
_GATTService.characteristics = _gattCharacteristics;
}
}
_state = GNCMPeripheralStateAdvertising;
return YES;
}
#pragma mark CBPeripheralManagerDelegate
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
if (peripheral.state == CBManagerStatePoweredOn) {
NSLog(@"[NEARBY] peripheralManagerDidUpdateState %li", (long)peripheral.state);
if (peripheral.state == CBManagerStatePoweredOn && !peripheral.isAdvertising &&
_state == GNCMPeripheralStateAdvertising) {
NSLog(@"[NEARBY] CBPeripheralManager powered on; starting advertising");
[_peripheralManager startAdvertising:@{
CBAdvertisementDataServiceUUIDsKey : @[ _advertisementService.UUID ],
CBAdvertisementDataLocalNameKey : _advertisementData
}];
[self startAdvertisingInternal];
} else {
NSLog(@"[NEARBY] CBPeripheralManager not powered on; stopping advertising");
[self stopAdvertising];
[self stopAdvertisingInternal];
}
}
@@ -85,7 +139,7 @@ NS_ASSUME_NONNULL_BEGIN
NSLog(@"[NEARBY] Error starting advertising: %@,", [error localizedDescription]);
return;
}
if (_peripheralManager.state != CBPeripheralManagerStatePoweredOn) {
if (_peripheralManager.state != CBManagerStatePoweredOn) {
NSLog(@"[NEARBY] Error starting advertising: peripheral manager not on!");
return;
}
@@ -93,11 +147,53 @@ NS_ASSUME_NONNULL_BEGIN
NSLog(@"[NEARBY] Peripheral manager started advertising");
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
NSLog(@"[NEARBY] peripheralManager:didReceiveReadRequest");
// This is called when a central asks to read a characteristic's value.
CBATTError error = CBATTErrorAttributeNotFound;
NSData *value = _gattCharacteristicValues[request.characteristic.UUID];
if (value != nil && value.length > 0) {
if (request.offset > value.length) {
error = CBATTErrorInvalidOffset;
} else {
// Reply with the advertisement data.
NSRange rangeFromOffset = NSMakeRange(request.offset, value.length - request.offset);
request.value = [value subdataWithRange:rangeFromOffset];
error = CBATTErrorSuccess;
}
}
[_peripheralManager respondToRequest:request withResult:error];
}
#pragma mark Private
/** Signals the peripheral manager to start advertising. Must be called on _selfQueue */
- (void)startAdvertisingInternal {
if (![_peripheralManager isAdvertising]) {
NSLog(@"[NEARBY] startAdvertisingInternal");
if (_GATTService) {
[_peripheralManager addService:_GATTService];
}
[_peripheralManager startAdvertising:@{
CBAdvertisementDataServiceUUIDsKey : @[ _advertisementService.UUID ],
CBAdvertisementDataLocalNameKey : _advertisementData
}];
}
}
/** Signals the peripheral manager to stop advertising. Must be called on _selfQueue */
- (void)stopAdvertising {
[_peripheralManager stopAdvertising];
- (void)stopAdvertisingInternal {
if ([_peripheralManager isAdvertising]) {
NSLog(@"[NEARBY] stopAdvertisingInternal");
_state = GNCMPeripheralStateStopped;
if (_GATTService) {
[_peripheralManager removeService:_GATTService];
}
[_peripheralManager stopAdvertising];
}
}
@end
@@ -28,6 +28,7 @@ using ::location::nearby::api::BluetoothAdapter;
using ::location::nearby::api::ImplementationPlatform;
using ::location::nearby::api::ble_v2::BleAdvertisementData;
using ::location::nearby::api::ble_v2::BleMedium;
using ::location::nearby::api::ble_v2::GattCharacteristic;
using ::location::nearby::api::ble_v2::TxPowerLevel;
static const char *const kAdvertisementString = "\x0a\x0b\x0c\x0d";
@@ -74,4 +75,30 @@ static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
XCTAssertTrue(_ble->StopScanning());
}
- (void)testGattServerWorking {
// Test creating gatt_server.
auto gatt_server = _ble->StartGattServer(/*ServerGattConnectionCallback=*/{});
XCTAssert(gatt_server != nullptr);
// Test creating characteristic.
Uuid service_uuid(1234, 5678);
Uuid characteristic_uuid(5678, 1234);
std::vector<GattCharacteristic::Permission> permissions = {
GattCharacteristic::Permission::kRead};
std::vector<GattCharacteristic::Property> properties = {
GattCharacteristic::Property::kRead};
// NOLINTNEXTLINE
absl::optional<GattCharacteristic> gatt_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid, permissions, properties);
XCTAssertTrue(gatt_characteristic.has_value());
// Test updating characteristic.
ByteArray any_byte("any");
XCTAssertTrue(
gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
gatt_server->Stop();
}
@end
@@ -15,10 +15,12 @@
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_IOS_BLE_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_IOS_BLE_H_
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#include <string>
#include "absl/types/optional.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
@@ -56,6 +58,25 @@ class BleMedium : public api::ble_v2::BleMedium {
bool IsExtendedAdvertisementsAvailable() override;
private:
// A concrete implemenation for GattServer.
class GattServer : public api::ble_v2::GattServer {
public:
GattServer() = default;
explicit GattServer(GNCMBlePeripheral* peripheral) : peripheral_(peripheral) {}
absl::optional<api::ble_v2::GattCharacteristic> CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
const std::vector<api::ble_v2::GattCharacteristic::Permission>& permissions,
const std::vector<api::ble_v2::GattCharacteristic::Property>& properties) override;
bool UpdateCharacteristic(const api::ble_v2::GattCharacteristic& characteristic,
const location::nearby::ByteArray& value) override;
void Stop() override;
private:
GNCMBlePeripheral* peripheral_;
};
BluetoothAdapter* adapter_;
GNCMBlePeripheral* peripheral_;
GNCMBleCentral* central_;
+92 -5
View File
@@ -27,6 +27,51 @@ namespace location {
namespace nearby {
namespace ios {
namespace {
CBAttributePermissions PermissionToCBPermissions(
const std::vector<api::ble_v2::GattCharacteristic::Permission>& permissions) {
CBAttributePermissions characteristPermissions = 0;
for (const auto& permission : permissions) {
switch (permission) {
case api::ble_v2::GattCharacteristic::Permission::kRead:
characteristPermissions |= CBAttributePermissionsReadable;
break;
case api::ble_v2::GattCharacteristic::Permission::kWrite:
characteristPermissions |= CBAttributePermissionsWriteable;
break;
case api::ble_v2::GattCharacteristic::Permission::kLast:
case api::ble_v2::GattCharacteristic::Permission::kUnknown:
default:; // fall through
}
}
return characteristPermissions;
}
CBCharacteristicProperties PropertiesToCBProperties(
const std::vector<api::ble_v2::GattCharacteristic::Property>& properties) {
CBCharacteristicProperties characteristicProperties = 0;
for (const auto& property : properties) {
switch (property) {
case api::ble_v2::GattCharacteristic::Property::kRead:
characteristicProperties |= CBCharacteristicPropertyRead;
break;
case api::ble_v2::GattCharacteristic::Property::kWrite:
characteristicProperties |= CBCharacteristicPropertyWrite;
break;
case api::ble_v2::GattCharacteristic::Property::kIndicate:
characteristicProperties |= CBCharacteristicPropertyIndicate;
break;
case api::ble_v2::GattCharacteristic::Property::kLast:
case api::ble_v2::GattCharacteristic::Property::kUnknown:
default:; // fall through
}
}
return characteristicProperties;
}
} // namespace
using ::location::nearby::api::ble_v2::BleAdvertisementData;
using ::location::nearby::api::ble_v2::TxPowerLevel;
using ScanCallback = ::location::nearby::api::ble_v2::BleMedium::ScanCallback;
@@ -40,12 +85,15 @@ bool BleMedium::StartAdvertising(
if (advertising_data.service_data.empty()) {
return false;
}
const std::string& service_uuid = advertising_data.service_data.begin()->first.Get16BitAsString();
const auto& service_uuid = advertising_data.service_data.begin()->first.Get16BitAsString();
const ByteArray& service_data_bytes = advertising_data.service_data.begin()->second;
peripheral_ =
[[GNCMBlePeripheral alloc] initWithServiceUUID:ObjCStringFromCppString(service_uuid)
advertisementData:NSDataFromByteArray(service_data_bytes)];
if (!peripheral_) {
peripheral_ = [[GNCMBlePeripheral alloc] init];
}
[peripheral_ startAdvertisingWithServiceUUID:ObjCStringFromCppString(service_uuid)
advertisementData:NSDataFromByteArray(service_data_bytes)];
return true;
}
@@ -72,7 +120,10 @@ bool BleMedium::StopScanning() {
std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
api::ble_v2::ServerGattConnectionCallback callback) {
return nullptr;
if (!peripheral_) {
peripheral_ = [[GNCMBlePeripheral alloc] init];
}
return std::make_unique<GattServer>(peripheral_);
}
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
@@ -95,6 +146,42 @@ std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string& se
bool BleMedium::IsExtendedAdvertisementsAvailable() { return false; }
// NOLINTNEXTLINE
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattServer::CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
const std::vector<api::ble_v2::GattCharacteristic::Permission>& permissions,
const std::vector<api::ble_v2::GattCharacteristic::Property>& properties) {
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
.service_uuid = service_uuid,
.permissions = permissions,
.properties = properties};
[peripheral_
addCBServiceWithUUID:[CBUUID
UUIDWithString:ObjCStringFromCppString(
characteristic.service_uuid.Get16BitAsString())]];
[peripheral_
addCharacteristic:[[CBMutableCharacteristic alloc]
initWithType:[CBUUID UUIDWithString:ObjCStringFromCppString(std::string(
characteristic.uuid))]
properties:PropertiesToCBProperties(characteristic.properties)
value:nil
permissions:PermissionToCBPermissions(characteristic.permissions)]];
return characteristic;
}
bool BleMedium::GattServer::UpdateCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic,
const location::nearby::ByteArray& value) {
[peripheral_ updateValue:NSDataFromByteArray(value)
forCharacteristic:[CBUUID UUIDWithString:ObjCStringFromCppString(
std::string(characteristic.uuid))]];
return true;
}
void BleMedium::GattServer::Stop() {
[peripheral_ stopGATTService];
}
} // namespace ios
} // namespace nearby
} // namespace location