mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Delete old BLE code for Apple platforms
PiperOrigin-RevId: 559515626
This commit is contained in:
committed by
Copybara-Service
parent
2c4bf0471f
commit
387393f1d6
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user