Delete old BLE code for Apple platforms

PiperOrigin-RevId: 559515626
This commit is contained in:
Nick Bourdakos
2023-08-23 12:54:15 -07:00
committed by Copybara-Service
parent 2c4bf0471f
commit 387393f1d6
16 changed files with 20 additions and 2200 deletions
-8
View File
@@ -570,14 +570,6 @@ let package = Package(
"connections/implementation/mediums/webrtc",
// This breaks the build, but seems to work fine without it?
"internal/platform/medium_environment.cc",
// Temporarily ignore BLEv2 source files.
// TODO(b/293283024): Stop ignoring these files when BLEv2 migration is complete.
"internal/platform/implementation/apple/ble_gatt_server.mm",
"internal/platform/implementation/apple/ble_gatt_client.mm",
"internal/platform/implementation/apple/ble_medium.mm",
"internal/platform/implementation/apple/ble_peripheral.mm",
"internal/platform/implementation/apple/ble_server_socket.mm",
"internal/platform/implementation/apple/ble_socket.mm",
],
sources: [
"compiled_proto",
+9 -37
View File
@@ -23,7 +23,6 @@ package(default_visibility = [
objc_library(
name = "apple",
srcs = [
"ble_utils.mm",
"crypto.mm",
"device_info.mm",
"log_message.mm",
@@ -35,7 +34,6 @@ objc_library(
"wifi_lan.mm",
],
hdrs = [
"ble_utils.h",
"device_info.h",
"log_message.h",
"multi_thread_executor.h",
@@ -51,7 +49,7 @@ objc_library(
deps = [
":Platform_cc",
":Shared",
":ble",
":ble_v2",
"//internal/platform:base",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
@@ -77,55 +75,29 @@ objc_library(
}),
)
objc_library(
name = "ble",
srcs = [
"ble.mm",
"bluetooth_adapter.mm",
"utils.mm",
],
hdrs = [
"ble.h",
"bluetooth_adapter.h",
"utils.h",
],
# Prevent Objective-C++ headers from being pulled into swift.
aspect_hints = ["//tools/build_defs/swift:no_module"],
deps = [
"//internal/platform:base",
"//internal/platform:cancellation_flag",
"//internal/platform/implementation:comm",
"//internal/platform/implementation/apple/Mediums",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:optional",
],
)
objc_library(
name = "ble_v2",
srcs = [
"ble_gatt_client.h",
"ble_gatt_client.mm",
"ble_gatt_server.h",
"ble_gatt_server.mm",
"ble_medium.h",
"ble_medium.mm",
"ble_peripheral.h",
"ble_peripheral.mm",
"ble_server_socket.h",
"ble_server_socket.mm",
"ble_socket.h",
"ble_socket.mm",
"ble_utils.mm",
"bluetooth_adapter_v2.h",
"bluetooth_adapter_v2.mm",
"utils.mm",
],
hdrs = [
"ble_gatt_client.h",
"ble_gatt_server.h",
"ble_medium.h",
"ble_peripheral.h",
"ble_server_socket.h",
"ble_socket.h",
"ble.h",
"ble_utils.h",
"bluetooth_adapter_v2.h",
"utils.h",
],
# Prevent Objective-C++ headers from being pulled into swift.
@@ -27,9 +27,7 @@ objc_library(
"BLEv2/GNCPeripheral.m",
"BLEv2/GNCPeripheralManager.m",
"BLEv2/NSData+GNCWebSafeBase64.m",
"Ble/GNCMBleCentral.m",
"Ble/GNCMBleConnection.m",
"Ble/GNCMBlePeripheral.m",
"Ble/GNCMBleUtils.mm",
"GNCLeaks.h",
"GNCLeaks.m",
@@ -51,9 +49,7 @@ objc_library(
"BLEv2/GNCPeripheral.h",
"BLEv2/GNCPeripheralManager.h",
"BLEv2/NSData+GNCWebSafeBase64.h",
"Ble/GNCMBleCentral.h",
"Ble/GNCMBleConnection.h",
"Ble/GNCMBlePeripheral.h",
"Ble/GNCMBleUtils.h",
"GNCMConnection.h",
"WiFiLAN/GNCIPv4Address.h",
@@ -64,8 +60,6 @@ objc_library(
],
deps = [
"//internal/platform/implementation/apple:Shared",
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Central",
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Peripheral",
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Shared",
"//proto/mediums:ble_frames_cc_proto",
"//third_party/apple_frameworks:CoreBluetooth",
@@ -1,114 +0,0 @@
// Copyright 2022 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.
#import <CoreBluetooth/CoreBluetooth.h>
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
@class CBUUID;
NS_ASSUME_NONNULL_BEGIN
/**
* This handler is called on a discover when a nearby advertising endpoint is discovered.
*/
typedef void (^GNCMScanResultHandler)(NSString *peripheralID, NSData *serviceData);
/**
* This handler is called on a discovery when a nearby advertising endpoint is connected.
*/
typedef void (^GNCMGATTConnectionResultHandler)(NSError *_Nullable error);
/**
* This handler is called on a discovery when a nearby advertising endpoint is connected. The input
* is a discovered set of characteristics.
*/
typedef void (^GNCMGATTDiscoverResultHandler)(
NSOrderedSet<CBCharacteristic *> *_Nullable characteristicValues);
/**
* This handler is called by a discovering endpoint to request a connection with an an advertising
* endpoint.
*/
typedef void (^GNCMBleConnectionRequester)(NSString *serviceID,
GNCMConnectionHandler connectionHandler);
/**
* This handler is called on a discoverer when a nearby advertising endpoint is discovered.
* Call |requestConnection| to request a connection with the advertiser.
*/
typedef void (^GNCMBleRequestConnectionHandler)(GNCMBleConnectionRequester requestConnection);
/**
* GNCMBleCentral discovers devices advertising the specified service UUID via BLE (using the
* GNCMBlePeripheral class) and calls the specififed scanning result handler when one is found.
*
* This class is thread-safe. Any calls made to it (and to the objects/closures it passes back via
* callbacks) can be made from any thread/queue. Callbacks made from this class are called on the
* specified queue.
*/
@interface GNCMBleCentral : NSObject
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
* Starts scanning with service UUID.
*
* @param serviceUUID A string that uniquely identifies the scanning services to search for.
* @param scanResultHandler The handler that is called when an endpoint advertising the service
* UUID is discovered.
* @param requestConnectionHandler The handler that is called when an endpoint is discovered.
* @param callbackQueue The queue on which all callbacks are made.
*/
- (BOOL)startScanningWithServiceUUID:(NSString *)serviceUUID
scanResultHandler:(GNCMScanResultHandler)scanResultHandler
requestConnectionHandler:(GNCMBleRequestConnectionHandler)requestConnectionHandler
callbackQueue:(nullable dispatch_queue_t)callbackQueue;
/**
* Sets up a GATT connection.
*
* @param peripheralID A string that uniquely identifies the peripheral.
* @param gattConnectionResultHandler The handler that is called when an endpoint is
* connected.
*/
- (void)connectGattServerWithPeripheralID:(NSString *)peripheralID
gattConnectionResultHandler:
(GNCMGATTConnectionResultHandler)gattConnectionResultHandler;
/**
* Discovers GATT service and its associated characteristics with values.
*
* @param serviceUUID A CBUUID for service to discover.
* @param gattCharacteristics Array of CBUUID for characteristic to discover.
* @param peripheralID A string that uniquely identifies the peripheral.
* @param gattDiscoverResultHandler This handler is called on a discovery for a discovered map of
* characteristic values when a nearby advertising endpoint is
* connected.
*/
- (void)discoverGattService:(CBUUID *)serviceUUID
gattCharacteristics:(NSArray<CBUUID *> *)characteristicUUIDs
peripheralID:(NSString *)peripheralID
gattDiscoverResultHandler:(GNCMGATTDiscoverResultHandler)gattDiscoverResultHandler;
/**
* Disconnects GATT connection.
*
* @param peripheralID A string that uniquely identifies the peripheral.
*/
- (void)disconnectGattServiceWithPeripheralID:(NSString *)peripheralID;
@end
NS_ASSUME_NONNULL_END
@@ -1,510 +0,0 @@
// Copyright 2022 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.
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleCentral.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleUtils.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralPeerManager.h"
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, GNCMCentralState) {
GNCMCentralStateStopped,
GNCMCentralStateScanning,
};
typedef void (^GNCMBleCharacteristicsHandler)(NSArray<CBCharacteristic *> *characteristics,
NSError *error);
typedef void (^GNCMBleCharacteristicValueHandler)(CBCharacteristic *characteristic, NSError *error);
typedef void (^GNCIntHandler)(int);
/** This lets a GNCIntHandler call itself. */
GNCIntHandler GNCRecursiveIntHandler(void (^block)(GNCIntHandler blockSelf, int i)) {
return ^(int i) {
return block(GNCRecursiveIntHandler(block), i);
};
}
/** This represents a discovered peripheral. */
@interface GNCMPeripheralInfo : NSObject
@property(nonatomic) CBPeripheral *peripheral;
@property(nonatomic, copy) NSDictionary<NSString *, id> *advertisementData;
/** Called when characteristics are discovered. */
@property(nonatomic, nullable) GNCMBleCharacteristicsHandler charsHandler;
/** Called when a characteristic value is read. */
@property(nonatomic, nullable) GNCMBleCharacteristicValueHandler charValueHandler;
@end
@implementation GNCMPeripheralInfo
- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary<NSString *, id> *)advertisementData {
self = [super init];
if (self) {
_peripheral = peripheral;
_advertisementData = [advertisementData copy];
}
return self;
}
- (BOOL)isEqual:(id)object {
// There is always exactly one info object per peripheral, so compare by identity. This is needed
// for maintenance of the peripherals stored in multiple maps.
return self == object;
}
- (NSUInteger)hash {
return (NSUInteger)self;
}
@end
@interface GNCMBleCentral () <CBCentralManagerDelegate,
CBPeripheralDelegate,
GNSCentralManagerDelegate>
@end
@implementation GNCMBleCentral {
/** Service UUID the central is scanning for. */
CBUUID *_serviceUUID;
/** The scan result callback handler. */
GNCMScanResultHandler _scanResultHandler;
/** Central manager used to scan or connect to peripherals. */
CBCentralManager *_centralManager;
/** Serial background queue for |centralManager|. */
dispatch_queue_t _selfQueue;
/** Central state for stop or scanning. */
GNCMCentralState _centralState;
/** The dictionary keyed by CBPeripheral identifier to value GNCMPeripheralInfo. */
NSMutableDictionary<NSUUID *, GNCMPeripheralInfo *> *_nearbyPeripheralsByID;
/** Array of characteristic UUID used for discovering. */
NSArray<CBUUID *> *_characteristicUUIDs;
/** GATT connection result handler. */
GNCMGATTConnectionResultHandler _gattConnectionResultHandler;
/** GATT service and characteristic discovery result hanadler. */
GNCMGATTDiscoverResultHandler _gattDiscoverResultHandler;
/** The discovered characteristic values map used to callback for `_gattDiscoverResultHandler`. */
NSMutableOrderedSet<CBCharacteristic *> *_gattCharacteristicValues;
/** Central manager used for socket connection based on weave protocol. */
GNSCentralManager *_socketCentralManager;
/** A callback handler to reuqest connection on the discovered advertiser. */
GNCMBleRequestConnectionHandler _requestConnectionHandler;
/** Client callback queue. If client doesn't assign it, then use main queue. */
dispatch_queue_t _clientCallbackQueue;
/** Internal async priority queue. */
dispatch_queue_t _internalCallbackQueue;
/** Flag to disable callback for dealloc. */
BOOL _callbacksEnabled;
}
- (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("GNCCentralManagerQueue", DISPATCH_QUEUE_SERIAL);
_nearbyPeripheralsByID = [NSMutableDictionary dictionary];
_gattCharacteristicValues = [[NSMutableOrderedSet alloc] init];
_centralState = GNCMCentralStateStopped;
}
return self;
}
- (void)dealloc {
// These calls must be made on |selfQueue|. Can't capture |self| in an async block, so must use
// dispatch_sync. This means dealloc 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 stopScanningInternal];
[_socketCentralManager stopNoScanMode];
_callbacksEnabled = NO;
});
}
- (BOOL)startScanningWithServiceUUID:(NSString *)serviceUUID
scanResultHandler:(GNCMScanResultHandler)scanResultHandler
requestConnectionHandler:(GNCMBleRequestConnectionHandler)requestConnectionHandler
callbackQueue:(nullable dispatch_queue_t)callbackQueue {
NSLog(@"[NEARBY] Client rquests startScanning");
_serviceUUID = [CBUUID UUIDWithString:serviceUUID];
_scanResultHandler = scanResultHandler;
_requestConnectionHandler = requestConnectionHandler;
// The client may be using the callback queue for other purposes, so wrap it with a private
// queue to know with certainty when all callbacks are done.
_clientCallbackQueue = callbackQueue ?: dispatch_get_main_queue();
_internalCallbackQueue =
dispatch_queue_create("GNCMBleCentralCallbackQueue", DISPATCH_QUEUE_PRIORITY_DEFAULT);
_callbacksEnabled = YES;
// Set up the central manager for scanning.
_centralManager =
[[CBCentralManager alloc] initWithDelegate:self
queue:_selfQueue
options:@{CBCentralManagerOptionShowPowerAlertKey : @NO}];
// Set up the central manager for the socket.
_socketCentralManager = [[GNSCentralManager alloc] initWithSocketServiceUUID:_serviceUUID
queue:_selfQueue];
_socketCentralManager.delegate = self;
[_socketCentralManager startNoScanModeWithAdvertisedServiceUUIDs:@[ _serviceUUID ]];
_centralState = GNCMCentralStateScanning;
return YES;
}
- (void)connectGattServerWithPeripheralID:(NSString *)peripheralID
gattConnectionResultHandler:
(GNCMGATTConnectionResultHandler)gattConnectionResultHandler {
_gattConnectionResultHandler = gattConnectionResultHandler;
dispatch_sync(_selfQueue, ^{
GNCMPeripheralInfo *peripheralInfo =
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
if (!peripheralInfo) return;
[_centralManager connectPeripheral:peripheralInfo.peripheral options:nil];
});
}
- (void)discoverGattService:(CBUUID *)serviceUUID
gattCharacteristics:(NSArray<CBUUID *> *)characteristicUUIDs
peripheralID:(NSString *)peripheralID
gattDiscoverResultHandler:(GNCMGATTDiscoverResultHandler)gattDiscoverResultHandler {
_gattDiscoverResultHandler = gattDiscoverResultHandler;
[_gattCharacteristicValues removeAllObjects];
dispatch_sync(_selfQueue, ^{
GNCMPeripheralInfo *peripheralInfo =
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
if (!peripheralInfo) return;
_characteristicUUIDs = [characteristicUUIDs copy];
// Start to discover service and the delegate will get its characteristics and read their values
// recursively
[peripheralInfo.peripheral discoverServices:@[ serviceUUID ]];
});
}
- (void)disconnectGattServiceWithPeripheralID:(NSString *)peripheralID {
dispatch_sync(_selfQueue, ^{
GNCMPeripheralInfo *peripheralInfo =
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
if (!peripheralInfo) return;
_gattConnectionResultHandler = nil;
_gattDiscoverResultHandler = nil;
[_centralManager cancelPeripheralConnection:peripheralInfo.peripheral];
});
}
#pragma mark CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
if (central.state == CBManagerStatePoweredOn && !central.isScanning &&
_centralState == GNCMCentralStateScanning) {
NSLog(@"[NEARBY] CBCentralManager powered on; starting scan");
[self startScanningInternal];
} else {
NSLog(@"[NEARBY] CBCentralManager not powered on; stopping scan");
[self stopScanningInternal];
}
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
RSSI:(NSNumber *)RSSI {
NSNumber *connectable = advertisementData[CBAdvertisementDataIsConnectable];
if (![connectable boolValue]) return;
// Look for the NC advertisement header in either the service data (from non-iOS) or the
// advertised name (from iOS).
NSData *serviceData = advertisementData[CBAdvertisementDataServiceDataKey][_serviceUUID]
?: advertisementData[CBAdvertisementDataLocalNameKey];
// Try to look up the peripheral by ID.
GNCMPeripheralInfo *info = _nearbyPeripheralsByID[peripheral.identifier];
if (!info) {
NSLog(@"[NEARBY] New peripheral: %@", peripheral);
// This is a new peripheral, so create a new peripheral info object.
info = [[GNCMPeripheralInfo alloc] initWithPeripheral:peripheral
advertisementData:advertisementData];
} else {
info.peripheral = peripheral;
}
_nearbyPeripheralsByID[peripheral.identifier] = info;
_scanResultHandler(peripheral.identifier.UUIDString, serviceData);
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"[NEARBY] Connected to peripheral: %@", peripheral);
peripheral.delegate = self;
// Tell the caller the connection is done.
_gattConnectionResultHandler(nil);
}
- (void)centralManager:(CBCentralManager *)central
didFailToConnectPeripheral:(CBPeripheral *)peripheral
error:(nullable NSError *)error {
NSLog(@"[NEARBY] Failed to connect to peripheral: %@, error: %@", peripheral, error);
// Tell the caller the connection failed.
_gattConnectionResultHandler(error);
}
- (void)centralManager:(CBCentralManager *)central
didDisconnectPeripheral:(CBPeripheral *)peripheral
error:(nullable NSError *)error {
NSLog(@"[NEARBY] Disconnected to peripheral: %@, error: %@", peripheral, error);
}
#pragma mark CBPeripheralDelegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error {
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
if (!peripheralInfo) return;
if (error || (peripheral.services.count == 0)) {
NSLog(@"[NEARBY] Error reading advertisement: unable to discover services.");
_gattDiscoverResultHandler(nil);
} else {
NSLog(@"[NEARBY] Discovered services for %@: %@", peripheral.name, peripheral.services);
// Helper functions for discovering characteristics and reading their values.
void (^discoverChars)(CBService *, GNCMBleCharacteristicsHandler) =
^(CBService *service, GNCMBleCharacteristicsHandler handler) {
NSAssert(!peripheralInfo.charsHandler, @"Unexpected characteristic handler");
peripheralInfo.charsHandler = handler;
// Discover all characteristics that may contain the advertisement.
[peripheral discoverCharacteristics:_characteristicUUIDs forService:service];
};
void (^readCharValue)(CBCharacteristic *, GNCMBleCharacteristicValueHandler) =
^(CBCharacteristic *characteristic, GNCMBleCharacteristicValueHandler handler) {
NSAssert(!peripheralInfo.charValueHandler, @"Unexpected characteristic value handler");
peripheralInfo.charValueHandler = handler;
[peripheral readValueForCharacteristic:characteristic];
};
// Multiple services may have the same UUID, so find the right service by searching for the
// characteristic containing the advertisement with a matching service ID hash.
__weak __typeof__(self) weakSelf = self;
void (^tryService)(int) = GNCRecursiveIntHandler(^(GNCIntHandler tryService, int serviceIndex) {
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) return;
// We've tried all the services, report the discovered characteristics and their values.
if (serviceIndex == peripheral.services.count) {
NSLog(@"[NEARBY] Done traversing all services or no services to traverse.");
_gattDiscoverResultHandler(_gattCharacteristicValues);
if (_gattCharacteristicValues.count > 0) {
CBCharacteristic *characteristic = [_gattCharacteristicValues objectAtIndex:0];
[strongSelf completeGATTReadWithData:characteristic.value
forPeripheralInfo:peripheralInfo];
}
return;
}
NSLog(@"[NEARBY] Trying service: %@", peripheral.services[serviceIndex]);
discoverChars(
peripheral.services[serviceIndex], ^(NSArray<CBCharacteristic *> *chars, NSError *error) {
void (^tryNextService)() = ^{
tryService(serviceIndex + 1);
};
// If there was an error or there are no characteristics on this service, try next one.
if (error || (chars.count == 0)) {
tryNextService();
return;
}
// Read each characteristic.
void (^tryChar)(int) = GNCRecursiveIntHandler(^(GNCIntHandler tryChar, int charIndex) {
// We've tried all characteristics on this service without error, try next service.
if (charIndex == chars.count) {
NSLog(@"[NEARBY] No matching advertisement found");
tryNextService();
return;
}
NSLog(@"[NEARBY] Trying characteristic: %@", chars[charIndex]);
readCharValue(chars[charIndex], ^(CBCharacteristic *characteristic, NSError *error) {
if (error) {
tryNextService();
} else {
// We've found the characteristic and its non-nil value. Store it.
if (characteristic.value.length != 0) {
[_gattCharacteristicValues addObject:characteristic];
}
tryChar(charIndex + 1);
}
});
});
// Start searching the characteristics for the current service.
tryChar(0);
});
});
// Start searching the services.
tryService(0);
}
}
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(nullable NSError *)error {
NSLog(@"[NEARBY] Discovered characteristics: %@ error: %@", service.characteristics, error);
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
if (!peripheralInfo) return;
GNCMBleCharacteristicsHandler charsHandler = peripheralInfo.charsHandler;
peripheralInfo.charsHandler = nil;
charsHandler(service.characteristics, error);
}
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(nullable NSError *)error {
NSLog(@"[NEARBY] Read characteristic value: %@ error: %@", characteristic, error);
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
if (!peripheralInfo) return;
GNCMBleCharacteristicValueHandler valueHandler = peripheralInfo.charValueHandler;
peripheralInfo.charValueHandler = nil;
valueHandler(characteristic, error);
}
#pragma mark GNSCentralManagerDelegate
- (void)centralManager:(GNSCentralManager *)centralManager
didDiscoverPeer:(GNSCentralPeerManager *)centralPeerManager
advertisementData:(nullable NSDictionary<NSString *, id> *)advertisementData {
if (!_callbacksEnabled) return;
// Retrieve the cached peripheral info.
NSUUID *peerId = centralPeerManager.identifier;
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peerId];
if (!peripheralInfo) return;
__weak __typeof__(self) weakSelf = self;
[self callbackAsync:^{
_requestConnectionHandler(^(NSString *serviceID, GNCMConnectionHandler connectionHandler) {
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) return;
void (^callConnectionHandler)(GNSSocket *__nullable) = ^(GNSSocket *__nullable socket) {
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf->_callbacksEnabled) return;
[strongSelf callbackAsync:^{
if (!socket) {
NSLog(@"[NEARBY] Central failed to create BLE socket");
connectionHandler(nil);
} else {
GNCMBleConnection *connection =
[GNCMBleConnection connectionWithSocket:socket
serviceID:serviceID
expectedIntroPacket:NO
callbackQueue:strongSelf->_clientCallbackQueue];
connection.connectionHandlers = connectionHandler(connection);
}
}];
};
dispatch_async(strongSelf->_selfQueue, ^{
// A connection is being requested, so establish a BLE socket. Make sure to use the most
// up-to-date GNSCentralPeerManager in case the MAC address rotated. The cached
// peripheral info should be the single source of truth for which MAC address to use.
GNSCentralPeerManager *updatedCentralPeerManager =
[centralManager retrieveCentralPeerWithIdentifier:peripheralInfo.peripheral.identifier];
if (!updatedCentralPeerManager) {
callConnectionHandler(nil);
}
// Make a socket connection.
[updatedCentralPeerManager
socketWithPairingCharacteristic:NO
completion:^(GNSSocket *socket, NSError *error) {
__strong __typeof__(self) strongSelf = weakSelf;
if (!strongSelf) return;
dispatch_async(strongSelf->_selfQueue, ^{
if (!error) {
// Call the connection handler when the socket has
// connected or fails to connect.
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
callConnectionHandler(didConnect ? socket : nil);
});
} else {
callConnectionHandler(nil);
}
});
}];
});
});
}];
}
- (void)centralManagerDidUpdateBleState:(GNSCentralManager *)centralManager {
// No op.
}
#pragma mark Private
/** This method assumes it's being called on selfQueue. */
- (void)completeGATTReadWithData:(NSData *)advertisement
forPeripheralInfo:(GNCMPeripheralInfo *)peripheralInfo {
NSAssert(peripheralInfo, @"Nil peripheralInfo");
if (peripheralInfo) {
CBPeripheral *peripheral = peripheralInfo.peripheral;
NSUUID *peripheralID = peripheral.identifier;
// Store the data for the socket callback, and report the peripheral to the socket library.
[_socketCentralManager retrievePeripheralWithIdentifier:peripheralID
advertisementData:peripheralInfo.advertisementData];
}
}
/** Signals the central manager to start scanning. Must be called on _selfQueue. */
- (void)startScanningInternal {
if (![_centralManager isScanning]) {
NSLog(@"[NEARBY] startScanningInternal");
[_centralManager
scanForPeripheralsWithServices:@[ _serviceUUID ]
options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}];
}
}
/** Signals the central manager to stop scanning. Must be called on _selfQueue */
- (void)stopScanningInternal {
if ([_centralManager isScanning]) {
NSLog(@"[NEARBY] stopScanningInternal");
_centralState = GNCMCentralStateStopped;
[_centralManager stopScan];
}
}
/** Calls the specified block on the callback queue. */
- (void)callbackAsync:(dispatch_block_t)block {
dispatch_queue_t clientCallbackQueue = _clientCallbackQueue; // don't capture |self|
dispatch_async(_internalCallbackQueue, ^{
dispatch_sync(clientCallbackQueue, block);
});
}
@end
NS_ASSUME_NONNULL_END
@@ -1,79 +0,0 @@
// Copyright 2022 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.
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
@class CBCharacteristic;
@class CBUUID;
NS_ASSUME_NONNULL_BEGIN
/**
* GNCMBlePeripheral advertises the specified service UUID via Ble for the purpose of being
* discovered by a central using the GNCMBleCentral class.
*
* This class is thread-safe. Any calls made to it (and to the objects/closures it passes back via
* callbacks) can be made from any thread/queue. Callbacks made from this class are called on the
* specified queue.
*/
@interface GNCMBlePeripheral : NSObject
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
* 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.
* @param endpointconnectedHandler The handler that is called when a discoverer connects.
* @param callbackQueue The queue on which all callbacks are made. If |callbackQueue| is not
* provided, then the main queue is used in the function.
*/
- (BOOL)startAdvertisingWithServiceUUID:(NSString *)serviceUUID
advertisementData:(NSData *)advertisementData
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler
callbackQueue:(nullable dispatch_queue_t)callbackQueue;
@end
NS_ASSUME_NONNULL_END
@@ -1,293 +0,0 @@
// Copyright 2022 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.
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBlePeripheral.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import "internal/platform/implementation/apple/GNCUtils.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleUtils.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
GNCMPeripheralStateStopped,
GNCMPeripheralStateAdvertising,
};
@interface GNCMBlePeripheral () <CBPeripheralManagerDelegate>
@end
@implementation GNCMBlePeripheral {
/** 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;
/** Peripheral manager used for socket connection based on weave protocol. */
GNSPeripheralManager *_socketPeripheralManager;
/** Peripheral service manager used to manage one BLE service. */
GNSPeripheralServiceManager *_socketPeripheralServiceManager;
/** Client callback queue. If client doesn't assign it, then use main queue. */
dispatch_queue_t _clientCallbackQueue;
/** Internal async priority queue. */
dispatch_queue_t _internalCallbackQueue;
/** Flag to disable callback for dealloc. */
BOOL _callbacksEnabled;
}
- (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);
_state = GNCMPeripheralStateStopped;
}
return self;
}
- (void)dealloc {
// These calls must be made on |selfQueue|. Can't capture |self| in an async block, so must use
// 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 stopAdvertisingInternal];
_callbacksEnabled = NO;
});
}
- (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
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler
callbackQueue:(nullable dispatch_queue_t)callbackQueue {
NSLog(@"[NEARBY] Client rquests startAdvertising");
// The client may be using the callback queue for other purposes, so wrap it with a private
// queue to know with certainty when all callbacks are done.
_clientCallbackQueue = callbackQueue ?: dispatch_get_main_queue();
_internalCallbackQueue =
dispatch_queue_create("GNCMBlePeripheralCallbackQueue", DISPATCH_QUEUE_PRIORITY_DEFAULT);
_callbacksEnabled = YES;
_advertisementService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:serviceUUID]
primary:YES];
_advertisementData = [advertisementData copy];
// 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);
__weak __typeof__(self) weakSelf = self;
// Set up the peripheral manager for the socket. This must be done before creating the
// peripheral manager for the advertisement data because it's started/stopped in the
// -peripheralManagerDidUpdateState: callback.
_socketPeripheralServiceManager = [[GNSPeripheralServiceManager alloc]
initWithBleServiceUUID:_advertisementService.UUID
addPairingCharacteristic:NO
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
// Call the connection handler when the socket has connected or fails to connect.
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
[weakSelf establishConnectionWithSocket:socket
didConnect:didConnect
endpointConnectedHandler:endpointConnectedHandler];
});
return YES;
}
queue:_selfQueue];
_socketPeripheralManager = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil
queue:_selfQueue];
[_socketPeripheralManager addPeripheralServiceManager:_socketPeripheralServiceManager
bleServiceAddedCompletion:^(NSError *error) {
NSLog(@"Failed to add service");
}];
// Set up the peripheral manager for the advertisement data.
_peripheralManager = [[CBPeripheralManager alloc]
initWithDelegate:self
queue:_selfQueue
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
if (_GATTService) {
if (_gattCharacteristics && _gattCharacteristics.count > 0) {
_GATTService.characteristics = _gattCharacteristics;
}
}
_state = GNCMPeripheralStateAdvertising;
return YES;
}
#pragma mark CBPeripheralManagerDelegate
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
NSLog(@"[NEARBY] peripheralManagerDidUpdateState %li", (long)peripheral.state);
if (peripheral.state == CBManagerStatePoweredOn && !peripheral.isAdvertising &&
_state == GNCMPeripheralStateAdvertising) {
NSLog(@"[NEARBY] CBPeripheralManager powered on; starting advertising");
[_socketPeripheralManager start];
[self startAdvertisingInternal];
} else {
NSLog(@"[NEARBY] CBPeripheralManager not powered on; stopping advertising");
[self stopAdvertisingInternal];
}
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(nullable NSError *)error {
if (error) {
NSLog(@"[NEARBY] Error starting advertising: %@,", [error localizedDescription]);
return;
}
if (_peripheralManager.state != CBManagerStatePoweredOn) {
NSLog(@"[NEARBY] Error starting advertising: peripheral manager not on!");
return;
}
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)stopAdvertisingInternal {
if ([_peripheralManager isAdvertising]) {
NSLog(@"[NEARBY] stopAdvertisingInternal");
_state = GNCMPeripheralStateStopped;
if (_GATTService) {
[_peripheralManager removeService:_GATTService];
}
[_peripheralManager stopAdvertising];
}
}
/**
* Connects with socket and callback the |GNCMBleConnection| is established or nil if it is not.
*/
- (void)establishConnectionWithSocket:(GNSSocket *)socket
didConnect:(BOOL)didConnect
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler {
if (!_callbacksEnabled) {
return;
}
[self callbackAsync:^{
if (!didConnect) {
NSLog(@"[NEARBY] Peripheral failed to create BLE socket");
endpointConnectedHandler(nil);
} else {
GNCMBleConnection *connection = [GNCMBleConnection connectionWithSocket:socket
serviceID:nil
expectedIntroPacket:YES
callbackQueue:_clientCallbackQueue];
connection.connectionHandlers = endpointConnectedHandler(connection);
}
}];
}
/**
* Calls the specified block on the callback queue, preventing it from being dispatched to the
* client callback queue when callbacks are disabled. And without capturing |self|, since
* callbacks are disabled in dealloc.
*/
- (void)callbackAsync:(dispatch_block_t)block {
dispatch_queue_t clientCallbackQueue = _clientCallbackQueue; // don't capture |self|
dispatch_async(_internalCallbackQueue, ^{
dispatch_sync(clientCallbackQueue, block);
});
}
@end
NS_ASSUME_NONNULL_END
@@ -31,8 +31,6 @@ objc_library(
"GNCBLEMedium+Testing.h",
"GNCBLEMediumTest.m",
"GNCBLEUtilsTest.mm",
"GNCBleTest.mm",
"GNCBluetoothAdapterTest.mm",
"GNCCryptoTest.mm",
"GNCFakeCentralManager.h",
"GNCFakeCentralManager.m",
@@ -53,8 +51,8 @@ objc_library(
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation/apple",
"//internal/platform/implementation/apple:ble",
"//internal/platform/implementation/apple", # buildcleaner: keep
"//internal/platform/implementation/apple:ble_v2",
"//internal/platform/implementation/apple/Mediums",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
@@ -1,112 +0,0 @@
// Copyright 2022 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.
#import <XCTest/XCTest.h>
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
#include <memory>
#include <string>
#include <utility>
#include "internal/platform/byte_array.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#include "internal/platform/implementation/platform.h"
using ::nearby::ByteArray;
using ::nearby::Uuid;
using ::nearby::api::BluetoothAdapter;
using ::nearby::api::ImplementationPlatform;
using ::nearby::api::ble_v2::BleAdvertisementData;
using ::nearby::api::ble_v2::BleMedium;
using ::nearby::api::ble_v2::GattCharacteristic;
using ::nearby::api::ble_v2::TxPowerLevel;
using IOSBluetoothAdapter = ::nearby::apple::BluetoothAdapter;
static const char *const kAdvertisementString = "\x0a\x0b\x0c\x0d";
static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
@interface GNCBleTest : XCTestCase
@end
// TODO(b/222392304): More tests on GNCBleTest.
@implementation GNCBleTest {
std::unique_ptr<BluetoothAdapter> _adapter;
std::unique_ptr<BleMedium> _ble;
}
- (void)setUp {
[super setUp];
_adapter = ImplementationPlatform::CreateBluetoothAdapter();
_ble = ImplementationPlatform::CreateBleV2Medium(*_adapter);
}
- (void)testStartandStopAdvertising {
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
// Assemble regular advertisement data.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
XCTAssertTrue(_ble->StartAdvertising(advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
[NSThread sleepForTimeInterval:0.1];
XCTAssertTrue(_ble->StopAdvertising());
}
- (void)testStartandStopScanning {
Uuid service_uuid(1234, 5678);
XCTAssertTrue(_ble->StartScanning(service_uuid, kTxPowerLevel, BleMedium::ScanCallback{}));
[NSThread sleepForTimeInterval:0.1];
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);
GattCharacteristic::Permission permission = GattCharacteristic::Permission::kRead;
GattCharacteristic::Property property = GattCharacteristic::Property::kRead;
// NOLINTNEXTLINE
absl::optional<GattCharacteristic> gatt_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid, permission, property);
XCTAssertTrue(gatt_characteristic.has_value());
// Test updating characteristic.
ByteArray any_byte("any");
XCTAssertTrue(gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
gatt_server->Stop();
}
- (void)testCreateGattClient {
IOSBluetoothAdapter *adapter = static_cast<IOSBluetoothAdapter *>(_adapter.get());
auto gatt_client = _ble->ConnectToGattServer(adapter->GetPeripheral(), kTxPowerLevel, {});
XCTAssert(gatt_client != nullptr);
}
@end
@@ -1,87 +0,0 @@
// Copyright 2022 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.
#import <XCTest/XCTest.h>
#include <string>
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
using ::nearby::apple::BlePeripheral;
using ::nearby::apple::BluetoothAdapter;
using ScanMode = ::nearby::api::BluetoothAdapter::ScanMode;
using Status = ::nearby::api::BluetoothAdapter::Status;
static const char kAdapterName[] = "MyBtAdapter";
static const char kMacAddress[] = "4C:8B:1D:CE:BA:D1";
@interface GNCBluetoothAdapterTest : XCTestCase
@end
@implementation GNCBluetoothAdapterTest
- (void)testName {
BluetoothAdapter adapter;
XCTAssertTrue(adapter.SetName(kAdapterName));
XCTAssertEqual(adapter.GetName(), std::string(kAdapterName));
}
- (void)testGetScanMode {
BluetoothAdapter adapter;
// Always return kNone as ScanMode is not supported .
XCTAssertEqual(adapter.GetScanMode(), ScanMode::kNone);
}
- (void)testSetScanMode_DefaultUnsupported {
BluetoothAdapter adapter;
XCTAssertFalse(adapter.SetScanMode(ScanMode::kNone));
XCTAssertFalse(adapter.SetScanMode(ScanMode::kConnectable));
XCTAssertFalse(adapter.SetScanMode(ScanMode::kConnectableDiscoverable));
}
- (void)testSetStatus {
BluetoothAdapter adapter;
XCTAssertTrue(adapter.SetStatus(Status::kDisabled));
XCTAssertFalse(adapter.IsEnabled());
XCTAssertTrue(adapter.SetStatus(Status::kEnabled));
XCTAssertTrue(adapter.IsEnabled());
}
- (void)testMacAddress {
BluetoothAdapter adapter;
adapter.SetMacAddress(kMacAddress);
XCTAssertEqual(adapter.GetMacAddress(), std::string(kMacAddress));
}
- (void)testGetPeripheral {
BluetoothAdapter adapter;
adapter.SetMacAddress(kMacAddress);
// The peripheral from adapter, the MAC address is the same.
BlePeripheral& peripheral = adapter.GetPeripheral();
XCTAssertEqual(adapter.GetMacAddress(), peripheral.GetAddress());
}
@end
+7 -212
View File
@@ -12,218 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLE_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLE_H_
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#include <memory>
#include <string>
#include <vector>
#include "absl/types/optional.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
@class GNCMBlePeripheral, GNCMBleCentral;
namespace nearby {
namespace apple {
/** InputStream that reads from GNCMConnection. */
class BleInputStream : public InputStream {
public:
BleInputStream();
~BleInputStream() override;
ExceptionOr<ByteArray> Read(std::int64_t size) override;
Exception Close() override;
GNCMConnectionHandlers *GetConnectionHandlers() { return connectionHandlers_; }
private:
GNCMConnectionHandlers *connectionHandlers_;
NSMutableArray<NSData *> *newDataPackets_;
NSMutableData *accumulatedData_;
NSCondition *condition_;
};
/** OutputStream that writes to GNCMConnection. */
class BleOutputStream : public OutputStream {
public:
explicit BleOutputStream(id<GNCMConnection> connection)
: connection_(connection), condition_([[NSCondition alloc] init]) {}
~BleOutputStream() override;
Exception Write(const ByteArray &data) override;
Exception Flush() override;
Exception Close() override;
private:
id<GNCMConnection> connection_;
NSCondition *condition_;
};
/** Concrete BleSocket implementation. */
class BleSocket : public api::ble_v2::BleSocket {
public:
BleSocket(id<GNCMConnection> connection, BlePeripheral *peripheral);
~BleSocket() override;
InputStream &GetInputStream() override { return *input_stream_; }
OutputStream &GetOutputStream() override { return *output_stream_; }
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
BlePeripheral *GetRemotePeripheral() override { return peripheral_; }
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
private:
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable absl::Mutex mutex_;
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
std::unique_ptr<BleInputStream> input_stream_;
std::unique_ptr<BleOutputStream> output_stream_;
BlePeripheral *peripheral_;
};
/** Concrete BleServerSocket implementation. */
class BleServerSocket : public api::ble_v2::BleServerSocket {
public:
~BleServerSocket() override;
std::unique_ptr<api::ble_v2::BleSocket> Accept() override ABSL_LOCKS_EXCLUDED(mutex_);
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
bool Connect(std::unique_ptr<BleSocket> socket) ABSL_LOCKS_EXCLUDED(mutex_);
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) ABSL_LOCKS_EXCLUDED(mutex_);
private:
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable absl::Mutex mutex_;
absl::CondVar cond_;
absl::flat_hash_set<std::unique_ptr<BleSocket>> pending_sockets_ ABSL_GUARDED_BY(mutex_);
absl::AnyInvocable<void()> close_notifier_ ABSL_GUARDED_BY(mutex_);
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
};
/** Concrete BleMedium implementation. */
class BleMedium : public api::ble_v2::BleMedium {
public:
explicit BleMedium(api::BluetoothAdapter &adapter);
// api::BleMedium:
bool StartAdvertising(const api::ble_v2::BleAdvertisementData &advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters) override;
bool StopAdvertising() override;
std::unique_ptr<AdvertisingSession> StartAdvertising(
const api::ble_v2::BleAdvertisementData &advertising_data,
api::ble_v2::AdvertiseParameters advertise_parameters, AdvertisingCallback callback) override;
bool StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BleMedium::ScanCallback scan_callback) override;
bool StopScanning() override;
std::unique_ptr<ScanningSession> StartScanning(const Uuid &service_uuid,
api::ble_v2::TxPowerLevel tx_power_level,
ScanningCallback callback) override;
std::unique_ptr<api::ble_v2::GattServer> StartGattServer(
api::ble_v2::ServerGattConnectionCallback callback) override;
std::unique_ptr<api::ble_v2::GattClient> ConnectToGattServer(
api::ble_v2::BlePeripheral &peripheral, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) override;
std::unique_ptr<api::ble_v2::BleServerSocket> OpenServerSocket(
const std::string &service_id) override;
std::unique_ptr<api::ble_v2::BleSocket> Connect(const std::string &service_id,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BlePeripheral &peripheral,
CancellationFlag *cancellation_flag) override;
bool IsExtendedAdvertisementsAvailable() override;
bool GetRemotePeripheral(const std::string& mac_address,
GetRemotePeripheralCallback callback) override {
return false;
}
bool GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
GetRemotePeripheralCallback callback) override {
return false;
}
private:
// A concrete implemenation for GattServer.
class GattServer : public api::ble_v2::GattServer {
public:
GattServer() = default;
GattServer(BluetoothAdapter *adapter, GNCMBlePeripheral *peripheral)
: adapter_(adapter), peripheral_(peripheral) {}
absl::optional<api::ble_v2::GattCharacteristic> CreateCharacteristic(
const Uuid &service_uuid, const Uuid &characteristic_uuid,
api::ble_v2::GattCharacteristic::Permission permission,
api::ble_v2::GattCharacteristic::Property property) override;
bool UpdateCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
const nearby::ByteArray &value) override;
absl::Status NotifyCharacteristicChanged(const api::ble_v2::GattCharacteristic &characteristic,
bool confirm, const ByteArray &new_value) override;
void Stop() override;
BlePeripheral &GetBlePeripheral() override { return adapter_->GetPeripheral(); }
private:
BluetoothAdapter *adapter_ = nullptr;
GNCMBlePeripheral *peripheral_;
};
// A concrete implemenation for GattClient.
class GattClient : public api::ble_v2::GattClient {
public:
GattClient() = default;
explicit GattClient(GNCMBleCentral *central, const std::string &peripheral_id)
: central_(central), peripheral_id_(peripheral_id) {}
bool DiscoverServiceAndCharacteristics(const Uuid &service_uuid,
const std::vector<Uuid> &characteristic_uuids) override;
// NOLINTNEXTLINE
absl::optional<api::ble_v2::GattCharacteristic> GetCharacteristic(
const Uuid &service_uuid, const Uuid &characteristic_uuid) override;
// NOLINTNEXTLINE
absl::optional<std::string> ReadCharacteristic(
const api::ble_v2::GattCharacteristic &characteristic) override;
bool WriteCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
absl::string_view value,
api::ble_v2::GattClient::WriteType write_type) override;
bool SetCharacteristicSubscription(
const api::ble_v2::GattCharacteristic &characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) override;
void Disconnect() override;
private:
GNCMBleCentral *central_;
std::string peripheral_id_;
absl::flat_hash_map<api::ble_v2::GattCharacteristic, const std::string>
gatt_characteristic_values_;
};
absl::Mutex mutex_;
BluetoothAdapter *adapter_;
GNCMBlePeripheral *peripheral_;
GNCMBleCentral *central_;
absl::flat_hash_map<std::string, BleServerSocket *> server_sockets_ ABSL_GUARDED_BY(mutex_);
dispatch_queue_t callback_queue_;
};
} // namespace apple
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLE_H_
#import "internal/platform/implementation/apple/ble_medium.h"
@@ -1,600 +0,0 @@
// Copyright 2022 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.
#import "internal/platform/implementation/apple/ble.h"
#include <CoreBluetooth/CoreBluetooth.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
#include "internal/platform/implementation/apple/utils.h"
#include "internal/platform/implementation/ble_v2.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleCentral.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBlePeripheral.h"
namespace nearby {
namespace apple {
using Permission = api::ble_v2::GattCharacteristic::Permission;
using Property = api::ble_v2::GattCharacteristic::Property;
namespace {
CBAttributePermissions PermissionToCBPermissions(Permission permission) {
CBAttributePermissions characteristPermissions = 0;
if ((permission & Permission::kRead) != Permission::kNone) {
characteristPermissions |= CBAttributePermissionsReadable;
}
if ((permission & Permission::kWrite) != Permission::kNone) {
characteristPermissions |= CBAttributePermissionsWriteable;
}
return characteristPermissions;
}
CBCharacteristicProperties PropertiesToCBProperties(Property property) {
CBCharacteristicProperties characteristicProperties = 0;
if ((property & Property::kRead) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyRead;
}
if ((property & Property::kWrite) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyWrite;
}
if ((property & Property::kIndicate) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyIndicate;
}
if ((property & Property::kNotify) != Property::kNone) {
characteristicProperties |= CBCharacteristicPropertyNotify;
}
return characteristicProperties;
}
} // namespace
using ::nearby::api::ble_v2::BleAdvertisementData;
using ::nearby::api::ble_v2::TxPowerLevel;
using ScanCallback = ::nearby::api::ble_v2::BleMedium::ScanCallback;
/** InputStream that reads from GNCMConnection. */
BleInputStream::BleInputStream()
: newDataPackets_([NSMutableArray array]),
accumulatedData_([NSMutableData data]),
condition_([[NSCondition alloc] init]) {
// Create the handlers of incoming data from the remote endpoint.
connectionHandlers_ = [GNCMConnectionHandlers
payloadHandler:^(NSData* data) {
[condition_ lock];
// Add the incoming data to the data packet array to be processed in read() below.
[newDataPackets_ addObject:data];
[condition_ signal];
[condition_ unlock];
}
disconnectedHandler:^{
[condition_ lock];
// Release the data packet array, meaning the stream has been closed or severed.
newDataPackets_ = nil;
[condition_ signal];
[condition_ unlock];
}];
}
BleInputStream::~BleInputStream() {
NSCAssert(!newDataPackets_, @"BleInputStream not closed before destruction");
}
ExceptionOr<ByteArray> BleInputStream::Read(std::int64_t size) {
// Block until either (a) the connection has been closed, (b) we have enough data to return.
NSData* dataToReturn;
[condition_ lock];
while (true) {
// Check if the stream has been closed or severed.
if (!newDataPackets_) break;
if (newDataPackets_.count > 0) {
// Add the packet data to the accumulated data.
for (NSData* data in newDataPackets_) {
if (data.length > 0) {
[accumulatedData_ appendData:data];
}
}
[newDataPackets_ removeAllObjects];
}
if ((size == -1) && (accumulatedData_.length > 0)) {
// Return all of the data.
dataToReturn = accumulatedData_;
accumulatedData_ = [NSMutableData data];
break;
} else if (accumulatedData_.length > 0) {
// Return up to |size| bytes of the data.
std::int64_t sizeToReturn = (accumulatedData_.length < size) ? accumulatedData_.length : size;
NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn);
dataToReturn = [accumulatedData_ subdataWithRange:range];
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
break;
}
[condition_ wait];
}
[condition_ unlock];
if (dataToReturn) {
NSLog(@"[NEARBY] Input stream: Received data of size: %lu", (unsigned long)dataToReturn.length);
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
}
Exception BleInputStream::Close() {
// Unblock pending read operation.
[condition_ lock];
newDataPackets_ = nil;
[condition_ signal];
[condition_ unlock];
return {Exception::kSuccess};
}
/** OutputStream that writes to GNCMConnection. */
BleOutputStream::~BleOutputStream() {
NSCAssert(!connection_, @"BleOutputStream not closed before destruction");
}
Exception BleOutputStream::Write(const ByteArray& data) {
[condition_ lock];
NSLog(@"[NEARBY] Sending data of size: %lu", (unsigned long)NSDataFromByteArray(data).length);
NSMutableData* packet = [NSMutableData dataWithData:NSDataFromByteArray(data)];
// Send the data, blocking until the completion handler is called.
__block GNCMPayloadResult sendResult = GNCMPayloadResultFailure;
__block bool isComplete = NO;
NSCondition* condition = condition_; // don't capture |this| in completion
// Check if connection_ is nil, then just don't wait and return as failure.
if (connection_ != nil) {
[connection_ sendData:packet
progressHandler:^(size_t count) {
}
completion:^(GNCMPayloadResult result) {
// Make sure we haven't already reported completion before. This prevents a crash
// where we try leaving a dispatch group more times than we entered it.
// b/79095653.
if (isComplete) {
return;
}
isComplete = YES;
sendResult = result;
[condition lock];
[condition signal];
[condition unlock];
}];
[condition_ wait];
[condition_ unlock];
} else {
sendResult = GNCMPayloadResultFailure;
[condition_ unlock];
}
if (sendResult == GNCMPayloadResultSuccess) {
return {Exception::kSuccess};
} else {
return {Exception::kIo};
}
}
Exception BleOutputStream::Flush() {
// The write() function blocks until the data is received by the remote endpoint, so there's
// nothing to do here.
return {Exception::kSuccess};
}
Exception BleOutputStream::Close() {
// Unblock pending write operation.
[condition_ lock];
connection_ = nil;
[condition_ signal];
[condition_ unlock];
return {Exception::kSuccess};
}
/** BleSocket implementation.*/
BleSocket::BleSocket(id<GNCMConnection> connection, BlePeripheral* peripheral)
: input_stream_(new BleInputStream()),
output_stream_(new BleOutputStream(connection)),
peripheral_(peripheral) {}
BleSocket::~BleSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
bool BleSocket::IsClosed() const {
absl::MutexLock lock(&mutex_);
return closed_;
}
Exception BleSocket::Close() {
absl::MutexLock lock(&mutex_);
DoClose();
return {Exception::kSuccess};
}
void BleSocket::DoClose() {
if (!closed_) {
input_stream_->Close();
output_stream_->Close();
closed_ = true;
}
}
/** WifiLanServerSocket implementation. */
BleServerSocket::~BleServerSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
std::unique_ptr<api::ble_v2::BleSocket> BleServerSocket::Accept() {
absl::MutexLock lock(&mutex_);
while (!closed_ && pending_sockets_.empty()) {
cond_.Wait(&mutex_);
}
// Return early if closed.
if (closed_) return {};
auto remote_socket = std::move(pending_sockets_.extract(pending_sockets_.begin()).value());
return std::move(remote_socket);
}
bool BleServerSocket::Connect(std::unique_ptr<BleSocket> socket) {
absl::MutexLock lock(&mutex_);
if (closed_) {
return false;
}
// add client socket to the pending list
pending_sockets_.insert(std::move(socket));
cond_.SignalAll();
if (closed_) {
return false;
}
return true;
}
void BleServerSocket::SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
absl::MutexLock lock(&mutex_);
close_notifier_ = std::move(notifier);
}
Exception BleServerSocket::Close() {
absl::MutexLock lock(&mutex_);
return DoClose();
}
Exception BleServerSocket::DoClose() {
bool should_notify = !closed_;
closed_ = true;
if (should_notify) {
cond_.SignalAll();
if (close_notifier_) {
auto notifier = std::move(close_notifier_);
mutex_.Unlock();
// Notifier may contain calls to public API, and may cause deadlock, if
// mutex_ is held during the call.
notifier();
mutex_.Lock();
}
}
return {Exception::kSuccess};
}
/** BleMedium implementation. */
BleMedium::BleMedium(::nearby::api::BluetoothAdapter& adapter)
: adapter_(static_cast<BluetoothAdapter*>(&adapter)) {}
bool BleMedium::StartAdvertising(
const BleAdvertisementData& advertising_data,
::nearby::api::ble_v2::AdvertiseParameters advertise_set_parameters) {
if (advertising_data.service_data.empty()) {
return false;
}
const auto& service_uuid = advertising_data.service_data.begin()->first.Get16BitAsString();
const ByteArray& service_data_bytes = advertising_data.service_data.begin()->second;
if (!peripheral_) {
peripheral_ = [[GNCMBlePeripheral alloc] init];
}
auto& peripheral = adapter_->GetPeripheral();
[peripheral_
startAdvertisingWithServiceUUID:ObjCStringFromCppString(service_uuid)
advertisementData:NSDataFromByteArray(service_data_bytes)
endpointConnectedHandler:^GNCMConnectionHandlers*(id<GNCMConnection> connection) {
// TODO(edwinwu): This server_socket is supposed to be gotten from the map by key of
// servcie_id. We now always get the first iteration since we don't know the key now.
// Try the way to move the Ble socket frame verification up to one layer.
std::string service_id;
BleServerSocket* server_socket;
if (!server_sockets_.empty()) {
service_id = server_sockets_.begin()->first;
server_socket = server_sockets_.begin()->second;
} else {
return nil;
}
auto socket = std::make_unique<BleSocket>(connection, &peripheral);
GNCMConnectionHandlers* connectionHandlers =
static_cast<BleInputStream&>(socket->GetInputStream()).GetConnectionHandlers();
server_socket->Connect(std::move(socket));
return connectionHandlers;
}
callbackQueue:callback_queue_];
return true;
}
bool BleMedium::StopAdvertising() {
peripheral_ = nil;
return true;
}
std::unique_ptr<BleMedium::AdvertisingSession> BleMedium::StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_parameters,
BleMedium::AdvertisingCallback callback) {
// TODO(hais): add real impl for iOs StartAdvertising
return std::make_unique<AdvertisingSession>(AdvertisingSession{});
}
bool BleMedium::StartScanning(const Uuid& service_uuid, TxPowerLevel tx_power_level,
ScanCallback scan_callback) {
if (!central_) {
central_ = [[GNCMBleCentral alloc] init];
}
__block ScanCallback callback = std::move(scan_callback);
[central_ startScanningWithServiceUUID:ObjCStringFromCppString(service_uuid.Get16BitAsString())
scanResultHandler:^(NSString* peripheralID, NSData* serviceData) {
BleAdvertisementData advertisement_data;
advertisement_data.service_data = {{service_uuid, ByteArrayFromNSData(serviceData)}};
BlePeripheral& peripheral = adapter_->GetPeripheral();
peripheral.SetPeripheralId(CppStringFromObjCString(peripheralID));
callback.advertisement_found_cb(peripheral, advertisement_data);
}
requestConnectionHandler:^(GNCMBleConnectionRequester connectionRequester) {
BlePeripheral& peripheral = adapter_->GetPeripheral();
peripheral.SetConnectionRequester(connectionRequester);
}
callbackQueue:callback_queue_];
return true;
}
bool BleMedium::StopScanning() {
central_ = nil;
return true;
}
std::unique_ptr<BleMedium::ScanningSession> BleMedium::StartScanning(
const Uuid& service_uuid, TxPowerLevel tx_power_level, BleMedium::ScanningCallback callback) {
// TODO(hais): add real impl for windows StartScanning.
return std::make_unique<ScanningSession>(ScanningSession{});
}
std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
api::ble_v2::ServerGattConnectionCallback callback) {
if (!peripheral_) {
peripheral_ = [[GNCMBlePeripheral alloc] init];
}
return std::make_unique<GattServer>(adapter_, peripheral_);
}
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
api::ble_v2::BlePeripheral& peripheral, TxPowerLevel tx_power_level,
api::ble_v2::ClientGattConnectionCallback callback) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError* connectedError;
BlePeripheral iosPeripheral = static_cast<BlePeripheral&>(peripheral);
std::string peripheral_id = iosPeripheral.GetPeripheralId();
[central_ connectGattServerWithPeripheralID:ObjCStringFromCppString(peripheral_id)
gattConnectionResultHandler:^(NSError* _Nullable error) {
connectedError = error;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
if (connectedError) {
return nullptr;
}
return std::make_unique<GattClient>(central_, peripheral_id);
}
std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
const std::string& service_id) {
auto server_socket = std::make_unique<BleServerSocket>();
server_socket->SetCloseNotifier([this, service_id]() {
absl::MutexLock lock(&mutex_);
server_sockets_.erase(service_id);
});
absl::MutexLock lock(&mutex_);
server_sockets_.insert({service_id, server_socket.get()});
return server_socket;
}
std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string& service_id,
TxPowerLevel tx_power_level,
api::ble_v2::BlePeripheral& peripheral,
CancellationFlag* cancellation_flag) {
NSString* serviceID = ObjCStringFromCppString(service_id);
__block std::unique_ptr<BleSocket> socket;
__block BlePeripheral ios_peripheral = static_cast<BlePeripheral&>(peripheral);
GNCMBleConnectionRequester connection_requester = ios_peripheral.GetConnectionRequester();
if (!connection_requester) return {};
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
if (cancellation_flag->Cancelled()) {
NSLog(@"[NEARBY] BLE Connect: Has been cancelled: service_id=%@", serviceID);
dispatch_group_leave(group); // unblock
return {};
}
connection_requester(serviceID, ^(id<GNCMConnection> connection) {
// If the connection wasn't successfully established, return a NULL socket.
if (connection) {
socket = std::make_unique<BleSocket>(connection, &ios_peripheral);
}
dispatch_group_leave(group); // unblock
return socket != nullptr
? static_cast<BleInputStream&>(socket->GetInputStream()).GetConnectionHandlers()
: nullptr;
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Send the (empty) intro packet, which the BLE advertiser is expecting.
if (socket != nullptr) {
socket->GetOutputStream().Write(ByteArray());
}
return std::move(socket);
}
bool BleMedium::IsExtendedAdvertisementsAvailable() { return false; }
// NOLINTNEXTLINE
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattServer::CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
api::ble_v2::GattCharacteristic::Permission permission,
api::ble_v2::GattCharacteristic::Property property) {
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
.service_uuid = service_uuid,
.permission = permission,
.property = property};
[peripheral_
addCBServiceWithUUID:[CBUUID
UUIDWithString:ObjCStringFromCppString(
characteristic.service_uuid.Get16BitAsString())]];
[peripheral_
addCharacteristic:[[CBMutableCharacteristic alloc]
initWithType:[CBUUID UUIDWithString:ObjCStringFromCppString(std::string(
characteristic.uuid))]
properties:PropertiesToCBProperties(characteristic.property)
value:nil
permissions:PermissionToCBPermissions(characteristic.permission)]];
return characteristic;
}
bool BleMedium::GattServer::UpdateCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic, const nearby::ByteArray& value) {
[peripheral_ updateValue:NSDataFromByteArray(value)
forCharacteristic:[CBUUID UUIDWithString:ObjCStringFromCppString(
std::string(characteristic.uuid))]];
return true;
}
absl::Status BleMedium::GattServer::NotifyCharacteristicChanged(
const api::ble_v2::GattCharacteristic& characteristic, bool confirm,
const ByteArray& new_value) {
// no-op because client cannot request notifications.
return absl::UnimplementedError("Unimplemented!");
}
void BleMedium::GattServer::Stop() { [peripheral_ stopGATTService]; }
bool BleMedium::GattClient::DiscoverServiceAndCharacteristics(
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
// Discover all characteristics that may contain the advertisement.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
gatt_characteristic_values_.clear();
CBUUID* serviceUUID = [CBUUID UUIDWithString:ObjCStringFromCppString(std::string(service_uuid))];
absl::flat_hash_map<std::string, Uuid> gatt_characteristics;
NSMutableArray<CBUUID*>* characteristicUUIDs =
[NSMutableArray arrayWithCapacity:characteristic_uuids.size()];
for (const auto& characteristic_uuid : characteristic_uuids) {
[characteristicUUIDs addObject:[CBUUID UUIDWithString:ObjCStringFromCppString(
std::string(characteristic_uuid))]];
gatt_characteristics.insert({std::string(characteristic_uuid), characteristic_uuid});
}
[central_ discoverGattService:serviceUUID
gattCharacteristics:characteristicUUIDs
peripheralID:ObjCStringFromCppString(peripheral_id_)
gattDiscoverResultHandler:^(NSOrderedSet<CBCharacteristic*>* _Nullable cb_characteristics) {
if (cb_characteristics != nil) {
for (CBCharacteristic* cb_characteristic in cb_characteristics) {
auto const& it = gatt_characteristics.find(
CppStringFromObjCString(cb_characteristic.UUID.UUIDString));
if (it == gatt_characteristics.end()) continue;
api::ble_v2::GattCharacteristic characteristic = {.uuid = it->second,
.service_uuid = service_uuid};
gatt_characteristic_values_.insert(
{characteristic, ByteArrayFromNSData(cb_characteristic.value).string_data()});
}
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
if (gatt_characteristic_values_.empty()) {
return false;
}
return true;
}
// NOLINTNEXTLINE
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattClient::GetCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) {
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
.service_uuid = service_uuid};
auto const it = gatt_characteristic_values_.find(characteristic);
if (it == gatt_characteristic_values_.end()) {
return absl::nullopt; // NOLINT
}
return it->first;
}
// NOLINTNEXTLINE
absl::optional<std::string> BleMedium::GattClient::ReadCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic) {
auto const it = gatt_characteristic_values_.find(characteristic);
if (it == gatt_characteristic_values_.end()) {
return absl::nullopt; // NOLINT
}
return it->second;
}
bool BleMedium::GattClient::WriteCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic, absl::string_view value,
api::ble_v2::GattClient::WriteType write_type) {
// No op.
return false;
}
bool BleMedium::GattClient::SetCharacteristicSubscription(
const api::ble_v2::GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) {
// No op since we can't write characteristics.
return false;
}
void BleMedium::GattClient::Disconnect() {
[central_ disconnectGattServiceWithPeripheralID:ObjCStringFromCppString(peripheral_id_)];
}
} // namespace apple
} // namespace nearby
@@ -71,7 +71,7 @@ class GattServer : public api::ble_v2::GattServer {
private:
GNCBLEGATTServer *gatt_server_;
BlePeripheral peripheral_;
EmptyBlePeripheral peripheral_;
};
} // namespace apple
@@ -1,108 +0,0 @@
// Copyright 2022 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLUETOOTH_ADAPTER_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLUETOOTH_ADAPTER_H_
#include <string>
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleCentral.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#include "internal/platform/prng.h"
namespace nearby {
namespace apple {
class BluetoothAdapter;
// Concrete BlePeripheral implementation.
class BlePeripheral : public api::ble_v2::BlePeripheral {
public:
std::string GetAddress() const override;
api::ble_v2::BlePeripheral::UniqueId GetUniqueId() const override {
return unique_id_;
}
std::string GetPeripheralId() const { return peripheral_id_; }
void SetPeripheralId(const std::string& peripheral_id) {
peripheral_id_ = peripheral_id;
}
void SetConnectionRequester(GNCMBleConnectionRequester connection_requester) {
connection_requester_ = connection_requester;
}
GNCMBleConnectionRequester GetConnectionRequester() {
return connection_requester_;
}
private:
// Only BluetoothAdapter may instantiate BlePeripheral.
friend class BluetoothAdapter;
explicit BlePeripheral(BluetoothAdapter* adapter) : adapter_(*adapter) {
unique_id_ = Prng().NextInt64();
}
BluetoothAdapter& adapter_;
std::string peripheral_id_;
GNCMBleConnectionRequester connection_requester_;
api::ble_v2::BlePeripheral::UniqueId unique_id_;
};
// Concrete BluetoothAdapter implementation.
class BluetoothAdapter : public api::BluetoothAdapter {
public:
using Status = api::BluetoothAdapter::Status;
using ScanMode = api::BluetoothAdapter::ScanMode;
~BluetoothAdapter() override { SetStatus(Status::kDisabled); }
bool SetStatus(Status status) override {
enabled_ = status == Status::kEnabled;
return true;
}
bool IsEnabled() const override { return enabled_; }
ScanMode GetScanMode() const override { return mode_; }
bool SetScanMode(ScanMode mode) override { return false; }
std::string GetName() const override { return name_; }
bool SetName(absl::string_view name) {
return SetName(name, /* persist= */ true);
}
bool SetName(absl::string_view name, bool persist) override {
name_ = std::string(name);
return true;
}
std::string GetMacAddress() const override { return mac_address_; }
void SetMacAddress(absl::string_view mac_address) {
mac_address_ = std::string(mac_address);
}
BlePeripheral& GetPeripheral() { return peripheral_; }
private:
BlePeripheral peripheral_{this};
ScanMode mode_ = ScanMode::kNone;
std::string name_;
std::string mac_address_;
bool enabled_ = true;
};
} // namespace apple
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLUETOOTH_ADAPTER_H_
@@ -1,28 +0,0 @@
// Copyright 2022 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.
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
#include <string>
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
namespace nearby {
namespace apple {
std::string BlePeripheral::GetAddress() const { return adapter_.GetMacAddress(); }
} // namespace apple
} // namespace nearby
@@ -170,7 +170,7 @@ std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(api::Bluetoot
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
api::BluetoothAdapter& adapter) {
return std::make_unique<apple::BleMedium>(adapter);
return std::make_unique<apple::BleMedium>();
}
std::unique_ptr<ServerSyncMedium> ImplementationPlatform::CreateServerSyncMedium() {