mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Merge remote-tracking branch 'nearby/main' into sync-upstream
This commit is contained in:
@@ -39,6 +39,7 @@ objc_library(
|
||||
"GNCMConnection.m",
|
||||
"GNCPeripheral.m",
|
||||
"GNCPeripheralManager.m",
|
||||
"GNCPeripheralManagerMultiplexer.m",
|
||||
"NSData+GNCBase85.mm",
|
||||
"NSData+GNCWebSafeBase64.m",
|
||||
],
|
||||
@@ -59,6 +60,7 @@ objc_library(
|
||||
"GNCMConnection.h",
|
||||
"GNCPeripheral.h",
|
||||
"GNCPeripheralManager.h",
|
||||
"GNCPeripheralManagerMultiplexer.h",
|
||||
"NSData+GNCBase85.h",
|
||||
"NSData+GNCWebSafeBase64.h",
|
||||
],
|
||||
|
||||
@@ -64,6 +64,7 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = queue ?: dispatch_queue_create(kGNCBLEGATTServerQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
<<<<<<< HEAD
|
||||
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
|
||||
if (!peripheralManager) {
|
||||
// In shared mode, the peripheral manager must be injected.
|
||||
@@ -90,6 +91,34 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
|
||||
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
|
||||
_characteristicValues = [[NSMutableDictionary alloc] init];
|
||||
_advertisementData = nil;
|
||||
=======
|
||||
_services = [[NSMutableDictionary alloc] init];
|
||||
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
|
||||
_characteristicValues = [[NSMutableDictionary alloc] init];
|
||||
_advertisementData = nil;
|
||||
|
||||
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
|
||||
if (!peripheralManager) {
|
||||
// In shared mode, the peripheral manager must be injected.
|
||||
[NSException raise:NSInvalidArgumentException
|
||||
format:@"Peripheral manager cannot be nil when shared manager is enabled."];
|
||||
}
|
||||
_peripheralManager = peripheralManager;
|
||||
// In shared mode, do NOT set the delegate. The Multiplexer handles callbacks.
|
||||
} else {
|
||||
// Legacy mode: Create a new manager if one isn't provided.
|
||||
if (!peripheralManager) {
|
||||
peripheralManager = [[CBPeripheralManager alloc]
|
||||
initWithDelegate:nil
|
||||
queue:_queue
|
||||
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
|
||||
}
|
||||
_peripheralManager = peripheralManager;
|
||||
// In legacy mode, we own the manager (or use the injected one as if we own it) and set the
|
||||
// delegate.
|
||||
_peripheralManager.peripheralDelegate = self;
|
||||
}
|
||||
>>>>>>> nearby/main
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -107,6 +107,10 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
|
||||
}
|
||||
}
|
||||
|
||||
if (_PSM > 0) {
|
||||
GNCLoggerInfo((@"[NEARBY] Unpublish L2CAP channel with PSM: %@"), @(_PSM));
|
||||
[_peripheralManager unpublishL2CAPChannel:_PSM];
|
||||
}
|
||||
if (_peripheralManager.state == CBManagerStatePoweredOn) {
|
||||
// Bluetooth link is already encrypted, however encryption is not required here to avoid getting
|
||||
// insufficient authentication errors due to initialization order.
|
||||
|
||||
@@ -106,15 +106,6 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* Initializes the BLE medium with a custom central manager.
|
||||
*
|
||||
* @param centralManager The central manager to use for BLE operations.
|
||||
* @param queue The queue to use for all internal operations.
|
||||
*/
|
||||
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
|
||||
queue:(nullable dispatch_queue_t)queue;
|
||||
|
||||
/** The hardware supports BOTH advertising extensions and extended scans. */
|
||||
@property(nonatomic, readonly) BOOL supportsExtendedAdvertisements;
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEError.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCCentralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManagerMultiplexer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/NSData+GNCBase85.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/NSData+GNCWebSafeBase64.h"
|
||||
|
||||
@@ -41,11 +41,16 @@ static NSError *AlreadyScanningError() {
|
||||
}
|
||||
|
||||
@interface GNCBLEMedium () <GNCCentralManagerDelegate, CBCentralManagerDelegate>
|
||||
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
|
||||
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
|
||||
queue:(dispatch_queue_t)queue;
|
||||
@end
|
||||
|
||||
@implementation GNCBLEMedium {
|
||||
dispatch_queue_t _queue;
|
||||
id<GNCCentralManager> _centralManager;
|
||||
id<GNCPeripheralManager> _peripheralManager;
|
||||
GNCPeripheralManagerMultiplexer *_multiplexer;
|
||||
|
||||
// The active GATT server, or @nil if one hasn't been started yet.
|
||||
GNCBLEGATTServer *_server;
|
||||
@@ -89,21 +94,31 @@ static NSError *AlreadyScanningError() {
|
||||
- (instancetype)init {
|
||||
dispatch_queue_t queue = dispatch_queue_create(kBLEMediumQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
CBCentralManager *centralManager =
|
||||
[[CBCentralManager alloc] initWithDelegate:self
|
||||
[[CBCentralManager alloc] initWithDelegate:nil
|
||||
queue:queue
|
||||
options:@{CBCentralManagerOptionShowPowerAlertKey : @NO}];
|
||||
return [self initWithCentralManager:centralManager queue:queue];
|
||||
CBPeripheralManager *peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil
|
||||
queue:queue];
|
||||
return [self initWithCentralManager:centralManager
|
||||
peripheralManager:peripheralManager
|
||||
queue:queue];
|
||||
}
|
||||
|
||||
// This is private and should only be used for tests. The provided central manager must call
|
||||
// delegate methods on the main queue.
|
||||
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
|
||||
queue:(nullable dispatch_queue_t)queue {
|
||||
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
|
||||
queue:(dispatch_queue_t)queue {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = queue ?: dispatch_get_main_queue();
|
||||
_queue = queue;
|
||||
_centralManager = centralManager;
|
||||
_centralManager.centralDelegate = self;
|
||||
_peripheralManager = peripheralManager;
|
||||
if (GNCFeatureFlags.sharedPeripheralManagerEnabled && _peripheralManager) {
|
||||
_multiplexer = [[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:_queue];
|
||||
_peripheralManager.peripheralDelegate = _multiplexer;
|
||||
}
|
||||
_gattConnectionCompletionHandlers = [NSMutableDictionary dictionary];
|
||||
_gattDisconnectionHandlers = [NSMutableDictionary dictionary];
|
||||
_scanningServiceUUIDs = [NSMutableArray array];
|
||||
@@ -150,15 +165,29 @@ static NSError *AlreadyScanningError() {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_centralManager stopScan];
|
||||
_centralManager.centralDelegate = nil;
|
||||
|
||||
[_peripheralManager stopAdvertising];
|
||||
_peripheralManager.peripheralDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
|
||||
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_server) {
|
||||
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
|
||||
<<<<<<< HEAD
|
||||
// TODO (edwinwu): Implement shared peripheral manager.
|
||||
// For now, raise an exception.
|
||||
[NSException raise:NSInvalidArgumentException
|
||||
format:@"Not implemented for shared manager is enabled."];
|
||||
=======
|
||||
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:_peripheralManager
|
||||
queue:_queue];
|
||||
[_multiplexer addListener:_server];
|
||||
>>>>>>> nearby/main
|
||||
} else {
|
||||
// In legacy mode, we pass nil (or a separate manager) and do NOT add to multiplexer.
|
||||
// GNCBLEGATTServer will create its own internal manager.
|
||||
@@ -205,7 +234,7 @@ static NSError *AlreadyScanningError() {
|
||||
[_scanningServiceUUIDs addObjectsFromArray:serviceUUIDs];
|
||||
_advertisementFoundHandler = advertisementFoundHandler;
|
||||
|
||||
[self internalStartScanningIfPoweredOn];
|
||||
[self updateScanningState];
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
@@ -226,7 +255,7 @@ static NSError *AlreadyScanningError() {
|
||||
|
||||
- (void)resumeMediumScanning:(nullable GNCStartScanningCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
[self internalStartScanningIfPoweredOn];
|
||||
[self updateScanningState];
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
@@ -238,10 +267,16 @@ static NSError *AlreadyScanningError() {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_server) {
|
||||
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
|
||||
<<<<<<< HEAD
|
||||
// TODO (edwinwu): Implement shared peripheral manager.
|
||||
// For now, raise an exception.
|
||||
[NSException raise:NSInvalidArgumentException
|
||||
format:@"Not implemented for shared manager is enabled."];
|
||||
=======
|
||||
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:_peripheralManager
|
||||
queue:_queue];
|
||||
[_multiplexer addListener:_server];
|
||||
>>>>>>> nearby/main
|
||||
} else {
|
||||
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:nil queue:nil];
|
||||
}
|
||||
@@ -291,10 +326,22 @@ static NSError *AlreadyScanningError() {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_l2capServer) {
|
||||
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
|
||||
<<<<<<< HEAD
|
||||
// TODO (edwinwu): Implement shared peripheral manager.
|
||||
// For now, raise an exception.
|
||||
[NSException raise:NSInvalidArgumentException
|
||||
format:@"Not implemented for shared manager is enabled."];
|
||||
=======
|
||||
_l2capServer = [[GNCBLEL2CAPServer alloc]
|
||||
initWithPeripheralManager:peripheralManager ?: _peripheralManager
|
||||
queue:peripheralManager ? dispatch_get_main_queue() : _queue];
|
||||
// Only add to multiplexer if we are using the internal shared manager.
|
||||
// If a specific manager was passed in (e.g. for testing?), we might still need logic here.
|
||||
// But typically `peripheralManager` is nil in prod.
|
||||
if (peripheralManager == nil || peripheralManager == _peripheralManager) {
|
||||
[_multiplexer addListener:_l2capServer];
|
||||
}
|
||||
>>>>>>> nearby/main
|
||||
} else {
|
||||
// Legacy mode
|
||||
_l2capServer = [[GNCBLEL2CAPServer alloc]
|
||||
@@ -351,7 +398,7 @@ static NSError *AlreadyScanningError() {
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
- (void)internalStartScanningIfPoweredOn {
|
||||
- (void)updateScanningState {
|
||||
dispatch_assert_queue(_queue);
|
||||
// Scanning can only be done when powered on and must be restarted if bluetooth is turned off
|
||||
// then back on. This will be called anytime the central manager's state changes, so
|
||||
@@ -479,7 +526,7 @@ static NSError *AlreadyScanningError() {
|
||||
return;
|
||||
}
|
||||
dispatch_assert_queue(_queue);
|
||||
[self internalStartScanningIfPoweredOn];
|
||||
[self updateScanningState];
|
||||
}
|
||||
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
@@ -496,6 +543,7 @@ static NSError *AlreadyScanningError() {
|
||||
didConnectPeripheral:(id<GNCPeripheral>)peripheral {
|
||||
dispatch_assert_queue(_queue);
|
||||
[self cancelConnectionTimeout];
|
||||
|
||||
if (_l2capPSM > 0) {
|
||||
[self internalOpenL2CAPChannel:peripheral];
|
||||
return;
|
||||
@@ -541,6 +589,7 @@ static NSError *AlreadyScanningError() {
|
||||
didDisconnectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
|
||||
GNCGATTDisconnectionHandler handler = _gattDisconnectionHandlers[peripheral.identifier];
|
||||
_gattDisconnectionHandlers[peripheral.identifier] = nil;
|
||||
if (handler) {
|
||||
|
||||
@@ -22,8 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@implementation CBCentralManager (GNCCentralManagerAdditions)
|
||||
|
||||
- (void)setCentralDelegate:(nullable id<GNCCentralManagerDelegate>)centralDelegate {
|
||||
NSAssert([centralDelegate conformsToProtocol:@protocol(CBCentralManagerDelegate)],
|
||||
@"centralDelegate must conform to protocol CBCentralManagerDelegate");
|
||||
if (centralDelegate) {
|
||||
NSAssert([centralDelegate conformsToProtocol:@protocol(CBCentralManagerDelegate)],
|
||||
@"centralDelegate must conform to protocol CBCentralManagerDelegate");
|
||||
}
|
||||
self.delegate = (id<CBCentralManagerDelegate>)centralDelegate;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,9 +90,11 @@ NSData *_Nullable GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommand command, NSData
|
||||
|
||||
/**
|
||||
* Calls the completion handler with (a) YES if the GNSSocket connected, or (b) NO if it failed to
|
||||
* connect for any reason. The completion handler is called on the main queue.
|
||||
* connect for any reason. The completion handler is called on the given queue. If the queue is nil,
|
||||
* the completion handler is called on the main queue.
|
||||
*/
|
||||
void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion);
|
||||
void GNCMWaitForConnection(GNSSocket *socket, dispatch_queue_t _Nullable queue,
|
||||
GNCMBoolHandler completion);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
||||
@@ -235,18 +235,20 @@ NSData *_Nullable GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommand command, NSData
|
||||
|
||||
@end
|
||||
|
||||
void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion) {
|
||||
void GNCMWaitForConnection(GNSSocket *socket, dispatch_queue_t _Nullable queue,
|
||||
GNCMBoolHandler completion) {
|
||||
// This function passes YES to the completion when the socket has successfully connected, and
|
||||
// otherwise passes NO to the completion after a timeout of several seconds. We shouldn't retain
|
||||
// the completion after it's been called, so store it in a __block variable and nil it out once
|
||||
// the socket has connected.
|
||||
__block GNCMBoolHandler completionRef = completion;
|
||||
dispatch_queue_t targetQueue = queue ?: dispatch_get_main_queue();
|
||||
|
||||
// The delegate listens for the socket connection callbacks. It's retained by the block passed to
|
||||
// dispatch_after below, so it will live long enough to do its job.
|
||||
GNCMBleSocketDelegate *delegate =
|
||||
[GNCMBleSocketDelegate delegateWithConnectedHandler:^(BOOL didConnect) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
dispatch_async(targetQueue, ^{
|
||||
if (completionRef) completionRef(didConnect);
|
||||
completionRef = nil;
|
||||
});
|
||||
@@ -254,9 +256,10 @@ void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion) {
|
||||
socket.delegate = delegate;
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBleSocketConnectionTimeout * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
targetQueue, ^{
|
||||
(void)delegate; // make sure it's retained until the timeout
|
||||
if (completionRef) completionRef(NO);
|
||||
completionRef = nil;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2026 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/GNCPeripheralManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A multiplexer that forwards @c CBPeripheralManagerDelegate and @c GNCPeripheralManagerDelegate
|
||||
*/
|
||||
@interface GNCPeripheralManagerMultiplexer : NSObject <GNCPeripheralManagerDelegate>
|
||||
|
||||
/**
|
||||
* Initializes the multiplexer.
|
||||
*
|
||||
* @param callbackQueue The queue to use for forwarding delegate callbacks.
|
||||
*/
|
||||
- (instancetype)initWithCallbackQueue:(dispatch_queue_t)callbackQueue NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Adds a listener to the multiplexer. Listeners are held weakly.
|
||||
*
|
||||
* @param listener The listener to add.
|
||||
*/
|
||||
- (void)addListener:(id<GNCPeripheralManagerDelegate>)listener;
|
||||
|
||||
/**
|
||||
* Removes a listener from the multiplexer.
|
||||
*
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
- (void)removeListener:(id<GNCPeripheralManagerDelegate>)listener;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright 2026 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/GNCPeripheralManagerMultiplexer.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation GNCPeripheralManagerMultiplexer {
|
||||
NSHashTable<id<GNCPeripheralManagerDelegate>> *_listeners;
|
||||
dispatch_queue_t _callbackQueue;
|
||||
dispatch_queue_t _syncQueue;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCallbackQueue:(dispatch_queue_t)callbackQueue {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_listeners = [NSHashTable weakObjectsHashTable];
|
||||
_callbackQueue = callbackQueue;
|
||||
_syncQueue = dispatch_queue_create("com.google.nearby.GNCPeripheralManagerMultiplexerSync",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
}
|
||||
|
||||
- (void)addListener:(id<GNCPeripheralManagerDelegate>)listener {
|
||||
dispatch_async(_syncQueue, ^{
|
||||
[self->_listeners addObject:listener];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeListener:(id<GNCPeripheralManagerDelegate>)listener {
|
||||
dispatch_async(_syncQueue, ^{
|
||||
[self->_listeners removeObject:listener];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<id<GNCPeripheralManagerDelegate>> *)allListeners {
|
||||
__block NSArray<id<GNCPeripheralManagerDelegate>> *listeners;
|
||||
dispatch_sync(_syncQueue, ^{
|
||||
listeners = [self->_listeners allObjects];
|
||||
});
|
||||
return listeners;
|
||||
}
|
||||
|
||||
#pragma mark - GNCPeripheralManagerDelegate
|
||||
|
||||
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
[listener gnc_peripheralManagerDidUpdateState:peripheral];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(gnc_peripheralManagerDidStartAdvertising:error:)]) {
|
||||
[listener gnc_peripheralManagerDidStartAdvertising:peripheral error:error];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didAddService:error:)]) {
|
||||
[listener gnc_peripheralManager:peripheral didAddService:service error:error];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didReceiveReadRequest:)]) {
|
||||
[listener gnc_peripheralManager:peripheral didReceiveReadRequest:request];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didPublishL2CAPChannel:error:)]) {
|
||||
[listener gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(NSError *)error {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didUnpublishL2CAPChannel:error:)]) {
|
||||
[listener gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error {
|
||||
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didOpenL2CAPChannel:error:)]) {
|
||||
[listener gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - CBPeripheralManagerDelegate
|
||||
|
||||
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
|
||||
[self gnc_peripheralManagerDidUpdateState:peripheral];
|
||||
}
|
||||
|
||||
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManagerDidStartAdvertising:peripheral error:error];
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManager:peripheral didAddService:service error:error];
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
[self gnc_peripheralManager:peripheral didReceiveReadRequest:request];
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -30,9 +30,6 @@ objc_library(
|
||||
]) + [
|
||||
"Source/GNSCentral.h",
|
||||
],
|
||||
copts = [
|
||||
"-Wno-enum-compare", # TODO(b/418286948): Remove this when the error is fixed.
|
||||
],
|
||||
deps = [
|
||||
":Shared",
|
||||
"//internal/platform/implementation/apple/Log:GNCLogger",
|
||||
@@ -51,9 +48,6 @@ objc_library(
|
||||
]) + [
|
||||
"Source/GNSPeripheral.h",
|
||||
],
|
||||
copts = [
|
||||
"-Wno-enum-compare", # TODO(b/418286948): Remove this when the error is fixed.
|
||||
],
|
||||
deps = [
|
||||
":Shared",
|
||||
"//internal/platform/implementation/apple/Log:GNCLogger",
|
||||
|
||||
+1
-1
@@ -371,7 +371,7 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
|
||||
}
|
||||
|
||||
- (void)updateBTCrashLoopHeuristic {
|
||||
NSAssert(_cbPeripheralManager.state == CBCentralManagerStateResetting, @"Unexpected CB state %@",
|
||||
NSAssert(_cbPeripheralManager.state == CBManagerStateResetting, @"Unexpected CB state %@",
|
||||
CBManagerStateString(_cbPeripheralManager.state));
|
||||
NSDate *now = [NSDate date];
|
||||
if ([now timeIntervalSinceDate:_btCrashLastResettingDate] >
|
||||
|
||||
@@ -32,7 +32,7 @@ objc_library(
|
||||
"GNCBLEL2CAPFakeInputOutputStream.m",
|
||||
"GNCBLEL2CAPServerTest.mm",
|
||||
"GNCBLEL2CAPStreamTest.m",
|
||||
"GNCBLEMediumTest.m",
|
||||
"GNCBLEMediumTest.mm",
|
||||
"GNCFakeBLEGATTServer.m",
|
||||
"GNCFakeBLEMedium.m",
|
||||
"GNCFakeCBL2CAPChannel.m",
|
||||
@@ -44,6 +44,7 @@ objc_library(
|
||||
"GNCMBleUtilsTest.m",
|
||||
"GNCMConnectionsTest.m",
|
||||
"GNCMFakeConnection.mm",
|
||||
"GNCPeripheralManagerMultiplexerTest.m",
|
||||
"GNCPeripheralManagerTest.m",
|
||||
"GNCPeripheralTest.m",
|
||||
"NSData+GNCBase85Test.m",
|
||||
|
||||
@@ -24,16 +24,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface GNCBLEMedium (Testing)
|
||||
|
||||
/**
|
||||
* Creates a BLE Medium with a provided central manager.
|
||||
* Creates a BLE Medium with a provided central manager and peripheral manager.
|
||||
*
|
||||
* This is only exposed for testing and can be used to inject a fake central manager.
|
||||
* This is only exposed for tests and can be used to inject a fake central manager and
|
||||
* peripheral manager.
|
||||
*
|
||||
* @param centralManager The central manager instance.
|
||||
* @param peripheralManager The peripheral manager instance.
|
||||
* @param queue The queue to run on, this must match the queue that the central manager's delegate
|
||||
* is running on. Defaults to the main queue when @c nil.
|
||||
* is running on.
|
||||
*/
|
||||
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
|
||||
queue:(nullable dispatch_queue_t)queue;
|
||||
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
|
||||
queue:(dispatch_queue_t)queue;
|
||||
|
||||
- (NSDictionary<CBUUID *, NSData *> *)decodeAdvertisementData:
|
||||
(NSDictionary<NSString *, id> *)advertisementData;
|
||||
|
||||
@@ -1,404 +0,0 @@
|
||||
// Copyright 2023 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/GNCBLEMedium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPClient+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEMedium+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCentralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
|
||||
|
||||
static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
@interface GNCBLEMediumTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEMediumTest
|
||||
|
||||
#pragma mark - Supports Extended Advertisements
|
||||
|
||||
- (void)testSupportsExtendedAdvertisements {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
|
||||
XCTAssertFalse([medium supportsExtendedAdvertisements]);
|
||||
}
|
||||
|
||||
#pragma mark - Start Scanning
|
||||
|
||||
- (void)testStartScanning {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *startScanningExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
XCTestExpectation *advertisementFoundExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Advertisement found."];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
[fakeCentralManager simulateCentralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
XCTAssertEqualObjects(expected, data);
|
||||
[advertisementFoundExpectation fulfill];
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[startScanningExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ startScanningExpectation ] timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(@[ serviceUUID ], fakeCentralManager.serviceUUIDs);
|
||||
|
||||
[fakeCentralManager
|
||||
simulateCentralManagerDidDiscoverPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
advertisementData:@{
|
||||
CBAdvertisementDataLocalNameKey : @"dGVzdA",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ advertisementFoundExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testAlreadyScanning {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
|
||||
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:nil];
|
||||
|
||||
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartStopStartScanning {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[medium stopScanningWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Decode Advertisement Data
|
||||
|
||||
- (void)testDecodeAndroidStyleAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataServiceDataKey : @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
},
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeAndroidStyleAdvertisementDataWithLocalName {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"Nearby", // Just happens to be base64 decodable.
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
CBAdvertisementDataServiceDataKey : @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
},
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeAppleStyleAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"dGVzdA",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeInvalidAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"!@#$",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(@{}, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeEmptyAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:@{}];
|
||||
|
||||
XCTAssertEqualObjects(@{}, actual);
|
||||
}
|
||||
|
||||
#pragma mark - Start GATT Server
|
||||
|
||||
- (void)testStartGATTServer {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start GATT server."];
|
||||
|
||||
[medium startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
|
||||
XCTAssertNotNil(server);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Start Advertising
|
||||
|
||||
- (void)testStartAdvertising {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
// Start advertising is fully covered with @c GNCBLEGATTServer tests. We are passing invalid
|
||||
// advertising data here so we can test code paths relevant to @c GNCBLEMedium, but bail early
|
||||
// enough to avoid making actual CoreBluetooth calls.
|
||||
[medium startAdvertisingData:@{}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStopAdvertising {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Stop advertising."];
|
||||
|
||||
// Stop advertising is fully covered with @c GNCBLEGATTServer tests. We are only testing stopping
|
||||
// without having started which tests the code paths relevant to @c GNCBLEMedium.
|
||||
[medium stopAdvertisingWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Open L2CAP Channel
|
||||
|
||||
- (void)testOpenL2CAPServerSocket {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *psmPublishedexpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
|
||||
XCTestExpectation *channelOpenedexpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
// Open L2CAP server is fully covered with @c GNCBLEL2CAPServer tests.
|
||||
[medium
|
||||
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *error) {
|
||||
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
|
||||
XCTAssertNil(error);
|
||||
[psmPublishedexpectation fulfill];
|
||||
}
|
||||
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[channelOpenedexpectation fulfill];
|
||||
}
|
||||
peripheralManager:fakePeripheralManager];
|
||||
|
||||
[self waitForExpectations:@[ psmPublishedexpectation ] timeout:0.1];
|
||||
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
|
||||
}
|
||||
|
||||
- (void)testSuccessfulOpenL2CAPChannel {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Open L2CAP channel."];
|
||||
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
GNCBLEL2CAPClient *l2capClient =
|
||||
[[GNCBLEL2CAPClient alloc] initWithQueue:nil
|
||||
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral){
|
||||
}];
|
||||
[medium setL2CAPClient:l2capClient];
|
||||
[medium openL2CAPChannelWithPSM:123
|
||||
peripheral:fakePeripheral
|
||||
completionHandler:^(GNCBLEL2CAPStream *_Nullable stream, NSError *_Nullable error) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Connect
|
||||
|
||||
- (void)testSuccessfulConnect {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
disconnectionHandler:nil
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNotNil(client);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testFailedConnect {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
|
||||
fakeCentralManager.didFailToConnectPeripheralError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
disconnectionHandler:nil
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNil(client);
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testDisconnect {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *disconnectExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Disconnect."];
|
||||
|
||||
GNCFakePeripheral *peripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:peripheral
|
||||
disconnectionHandler:^() {
|
||||
[disconnectExpectation fulfill];
|
||||
}
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNotNil(client);
|
||||
XCTAssertNil(error);
|
||||
[client disconnect];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ disconnectExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testRetrievePeripheralWithIdentifier_exists {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTAssertNotNil(
|
||||
[medium retrievePeripheralWithIdentifier:
|
||||
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111111"]]);
|
||||
}
|
||||
|
||||
- (void)testRetrievePeripheralWithIdentifier_doesNotExist {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTAssertNil(
|
||||
[medium retrievePeripheralWithIdentifier:
|
||||
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111112"]]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,621 @@
|
||||
// Copyright 2023 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/GNCBLEMedium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPClient+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEMedium+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCentralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
|
||||
|
||||
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
|
||||
#include "internal/flags/nearby_flags.h"
|
||||
|
||||
static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
@interface GNCBLEMediumTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEMediumTest
|
||||
|
||||
- (void)tearDown {
|
||||
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testInit_allocatesMultiplexerWhenFlagEnabled {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
|
||||
id multiplexer = [medium valueForKey:@"_multiplexer"];
|
||||
if (enabled.boolValue) {
|
||||
XCTAssertNotNil(multiplexer);
|
||||
XCTAssertEqual(fakePeripheralManager.peripheralDelegate, multiplexer);
|
||||
} else {
|
||||
XCTAssertNil(multiplexer);
|
||||
XCTAssertNil(fakePeripheralManager.peripheralDelegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Supports Extended Advertisements
|
||||
|
||||
- (void)testSupportsExtendedAdvertisements {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
|
||||
XCTAssertFalse([medium supportsExtendedAdvertisements]);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Start Scanning
|
||||
|
||||
- (void)testStartScanning {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *startScanningExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
XCTestExpectation *advertisementFoundExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Advertisement found."];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
[fakeCentralManager simulateCentralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
XCTAssertEqualObjects(expected, data);
|
||||
[advertisementFoundExpectation fulfill];
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[startScanningExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ startScanningExpectation ] timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(@[ serviceUUID ], fakeCentralManager.serviceUUIDs);
|
||||
|
||||
[fakeCentralManager
|
||||
simulateCentralManagerDidDiscoverPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
advertisementData:@{
|
||||
CBAdvertisementDataLocalNameKey : @"dGVzdA",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ advertisementFoundExpectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testAlreadyScanning {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
|
||||
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:nil];
|
||||
|
||||
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testStartStopStartScanning {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[medium stopScanningWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Decode Advertisement Data
|
||||
|
||||
- (void)testDecodeAndroidStyleAdvertisementData {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataServiceDataKey : @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
},
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDecodeAndroidStyleAdvertisementDataWithLocalName {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"Nearby", // Just happens to be base64 decodable.
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
CBAdvertisementDataServiceDataKey : @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
},
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDecodeAppleStyleAdvertisementData {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"dGVzdA",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDecodeInvalidAdvertisementData {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"!@#$",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(@{}, actual);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDecodeEmptyAdvertisementData {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:@{}];
|
||||
|
||||
XCTAssertEqualObjects(@{}, actual);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Start GATT Server
|
||||
|
||||
- (void)testStartGATTServer {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start GATT server."];
|
||||
|
||||
[medium startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
|
||||
XCTAssertNotNil(server);
|
||||
XCTAssertNil(error);
|
||||
|
||||
// Verify internal structure based on flag
|
||||
id mediumManager = [medium valueForKey:@"_peripheralManager"];
|
||||
id serverManager = [server valueForKey:@"_peripheralManager"];
|
||||
|
||||
if (enabled.boolValue) {
|
||||
XCTAssertEqual(mediumManager, serverManager);
|
||||
id multiplexer = [medium valueForKey:@"_multiplexer"];
|
||||
XCTAssertEqual([mediumManager peripheralDelegate], multiplexer);
|
||||
} else {
|
||||
XCTAssertNotEqual(mediumManager, serverManager);
|
||||
id manager = [server valueForKey:@"_peripheralManager"];
|
||||
XCTAssertEqual([manager peripheralDelegate], server);
|
||||
}
|
||||
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Start Advertising
|
||||
|
||||
- (void)testStartAdvertising {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
// Start advertising is fully covered with @c GNCBLEGATTServer tests. We are passing invalid
|
||||
// advertising data here so we can test code paths relevant to @c GNCBLEMedium, but bail early
|
||||
// enough to avoid making actual CoreBluetooth calls.
|
||||
[medium startAdvertisingData:@{}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testStopAdvertising {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Stop advertising."];
|
||||
|
||||
// Stop advertising is fully covered with @c GNCBLEGATTServer tests. We are only testing stopping
|
||||
// without having started which tests the code paths relevant to @c GNCBLEMedium.
|
||||
[medium stopAdvertisingWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Open L2CAP Channel
|
||||
|
||||
- (void)testOpenL2CAPServerSocket {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *psmPublishedexpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
|
||||
XCTestExpectation *channelOpenedexpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
// Open L2CAP server is fully covered with @c GNCBLEL2CAPServer tests.
|
||||
[medium
|
||||
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *error) {
|
||||
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
|
||||
XCTAssertNil(error);
|
||||
[psmPublishedexpectation fulfill];
|
||||
}
|
||||
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[channelOpenedexpectation fulfill];
|
||||
}
|
||||
peripheralManager:fakePeripheralManager];
|
||||
|
||||
[self waitForExpectations:@[ psmPublishedexpectation ] timeout:0.1];
|
||||
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testSuccessfulOpenL2CAPChannel {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Open L2CAP channel."];
|
||||
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
GNCBLEL2CAPClient *l2capClient =
|
||||
[[GNCBLEL2CAPClient alloc] initWithQueue:nil
|
||||
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral){
|
||||
}];
|
||||
[medium setL2CAPClient:l2capClient];
|
||||
[medium openL2CAPChannelWithPSM:123
|
||||
peripheral:fakePeripheral
|
||||
completionHandler:^(GNCBLEL2CAPStream *_Nullable stream, NSError *_Nullable error) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Connect
|
||||
|
||||
- (void)testSuccessfulConnect {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
disconnectionHandler:nil
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNotNil(client);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testFailedConnect {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
|
||||
fakeCentralManager.didFailToConnectPeripheralError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
disconnectionHandler:nil
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNil(client);
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testDisconnect {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTestExpectation *disconnectExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Disconnect."];
|
||||
|
||||
GNCFakePeripheral *peripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:peripheral
|
||||
disconnectionHandler:^() {
|
||||
[disconnectExpectation fulfill];
|
||||
}
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNotNil(client);
|
||||
XCTAssertNil(error);
|
||||
[client disconnect];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ disconnectExpectation ] timeout:3];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRetrievePeripheralWithIdentifier_exists {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTAssertNotNil(
|
||||
[medium retrievePeripheralWithIdentifier:
|
||||
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111111"]]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testRetrievePeripheralWithIdentifier_doesNotExist {
|
||||
for (NSNumber *enabled in @[ @NO, @YES ]) {
|
||||
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
|
||||
nearby::connections::config_package_nearby::nearby_connections_feature::
|
||||
kEnableSharedPeripheralManager,
|
||||
enabled.boolValue);
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
|
||||
peripheralManager:fakePeripheralManager
|
||||
queue:dispatch_get_main_queue()];
|
||||
XCTAssertNil(
|
||||
[medium retrievePeripheralWithIdentifier:
|
||||
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111112"]]);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/** The socket file descriptor for the L2CAP channel. */
|
||||
@property(nonatomic, readonly) int socketFD;
|
||||
/** The PSM (Protocol/Service Multiplexer) of the L2CAP channel. */
|
||||
@property(nonatomic, readonly) CBL2CAPPSM PSM;
|
||||
@property(nonatomic, readwrite) CBL2CAPPSM PSM;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ static const uint16_t kPSM = 192;
|
||||
GNCFakeCBL2CAPChannel *fakeChannel = [[GNCFakeCBL2CAPChannel alloc] init];
|
||||
fakeChannel.inputStream = fakeStream.inputStream;
|
||||
fakeChannel.outputStream = fakeStream.outputStream;
|
||||
fakeChannel.PSM = _PSM;
|
||||
[_peripheralDelegate gnc_peripheralManager:self
|
||||
didOpenL2CAPChannel:(CBL2CAPChannel *)fakeChannel
|
||||
error:_didOpenL2CAPChannelError];
|
||||
|
||||
@@ -131,7 +131,7 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
|
||||
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection success"];
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, nil, ^(BOOL flag) {
|
||||
XCTAssertTrue(flag);
|
||||
[expectation fulfill];
|
||||
});
|
||||
@@ -142,12 +142,31 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
|
||||
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
|
||||
}
|
||||
|
||||
- (void)testWaitForConnection_CustomQueue {
|
||||
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
|
||||
dispatch_queue_t customQueue = dispatch_queue_create("com.google.nearby.testQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection success on custom queue"];
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, customQueue, ^(BOOL flag) {
|
||||
XCTAssertTrue(flag);
|
||||
// Verify that we are on the custom queue
|
||||
const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
|
||||
XCTAssertEqual(strcmp(label, "com.google.nearby.testQueue"), 0);
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
// Simulate the connection callback
|
||||
[fakeSocket simulateSocketDidConnect];
|
||||
|
||||
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
|
||||
}
|
||||
|
||||
- (void)testWaitForConnection_Failure_Disconnect {
|
||||
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Connection failed on disconnect"];
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, nil, ^(BOOL flag) {
|
||||
XCTAssertFalse(flag);
|
||||
[expectation fulfill];
|
||||
});
|
||||
@@ -164,7 +183,7 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"Connection failed on timeout"];
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
|
||||
GNCMWaitForConnection((GNSSocket *)fakeSocket, nil, ^(BOOL flag) {
|
||||
XCTAssertFalse(flag);
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
+495
@@ -0,0 +1,495 @@
|
||||
// Copyright 2026 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/GNCPeripheralManagerMultiplexer.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
|
||||
|
||||
@interface GNCPeripheralManagerMultiplexerTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface FakePeripheralManagerDelegate : NSObject <GNCPeripheralManagerDelegate>
|
||||
@property(nonatomic) XCTestExpectation *expectation;
|
||||
@property(nonatomic) BOOL didUpdateStateCalled;
|
||||
@property(nonatomic) CBManagerState state;
|
||||
|
||||
@property(nonatomic) BOOL didStartAdvertisingCalled;
|
||||
@property(nonatomic) NSError *startAdvertisingError;
|
||||
|
||||
@property(nonatomic) BOOL didAddServiceCalled;
|
||||
@property(nonatomic) CBService *addedService;
|
||||
@property(nonatomic) NSError *addServiceError;
|
||||
|
||||
@property(nonatomic) BOOL didReceiveReadRequestCalled;
|
||||
@property(nonatomic) CBATTRequest *readRequest;
|
||||
|
||||
@property(nonatomic) BOOL didPublishL2CAPChannelCalled;
|
||||
@property(nonatomic) CBL2CAPPSM publishedPSM;
|
||||
@property(nonatomic) NSError *publishL2CAPChannelError;
|
||||
|
||||
@property(nonatomic) BOOL didUnpublishL2CAPChannelCalled;
|
||||
@property(nonatomic) CBL2CAPPSM unpublishedPSM;
|
||||
@property(nonatomic) NSError *unpublishL2CAPChannelError;
|
||||
|
||||
@property(nonatomic) BOOL didOpenL2CAPChannelCalled;
|
||||
@property(nonatomic) CBL2CAPChannel *openedChannel;
|
||||
@property(nonatomic) NSError *openL2CAPChannelError;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FakePeripheralManagerDelegate
|
||||
|
||||
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
|
||||
_didUpdateStateCalled = YES;
|
||||
_state = peripheral.state;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
_didStartAdvertisingCalled = YES;
|
||||
_startAdvertisingError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
_didAddServiceCalled = YES;
|
||||
_addedService = service;
|
||||
_addServiceError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
_didReceiveReadRequestCalled = YES;
|
||||
_readRequest = request;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
_didPublishL2CAPChannelCalled = YES;
|
||||
_publishedPSM = PSM;
|
||||
_publishL2CAPChannelError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(NSError *)error {
|
||||
_didUnpublishL2CAPChannelCalled = YES;
|
||||
_unpublishedPSM = PSM;
|
||||
_unpublishL2CAPChannelError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error {
|
||||
_didOpenL2CAPChannelCalled = YES;
|
||||
_openedChannel = channel;
|
||||
_openL2CAPChannelError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManagerDidUpdateState:(nonnull CBPeripheralManager *)peripheral {
|
||||
_didUpdateStateCalled = YES;
|
||||
_state = peripheral.state;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
_didStartAdvertisingCalled = YES;
|
||||
_startAdvertisingError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
_didAddServiceCalled = YES;
|
||||
_addedService = service;
|
||||
_addServiceError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
_didReceiveReadRequestCalled = YES;
|
||||
_readRequest = request;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
_didPublishL2CAPChannelCalled = YES;
|
||||
_publishedPSM = PSM;
|
||||
_publishL2CAPChannelError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
|
||||
error:(nullable NSError *)error {
|
||||
_didUnpublishL2CAPChannelCalled = YES;
|
||||
_unpublishedPSM = PSM;
|
||||
_unpublishL2CAPChannelError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
|
||||
error:(nullable NSError *)error {
|
||||
_didOpenL2CAPChannelCalled = YES;
|
||||
_openedChannel = channel;
|
||||
_openL2CAPChannelError = error;
|
||||
if (_expectation) {
|
||||
[_expectation fulfill];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GNCPeripheralManagerMultiplexerTest
|
||||
|
||||
- (void)testMultiplexerForwardsCallbacks {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate1 = [[FakePeripheralManagerDelegate alloc] init];
|
||||
FakePeripheralManagerDelegate *delegate2 = [[FakePeripheralManagerDelegate alloc] init];
|
||||
|
||||
delegate1.expectation = [self expectationWithDescription:@"Delegate 1 called"];
|
||||
delegate2.expectation = [self expectationWithDescription:@"Delegate 2 called"];
|
||||
|
||||
[multiplexer addListener:delegate1];
|
||||
[multiplexer addListener:delegate2];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
fakeManager.state = CBManagerStatePoweredOn;
|
||||
|
||||
[multiplexer gnc_peripheralManagerDidUpdateState:fakeManager];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
|
||||
XCTAssertTrue(delegate1.didUpdateStateCalled);
|
||||
XCTAssertTrue(delegate2.didUpdateStateCalled);
|
||||
XCTAssertEqual(delegate1.state, CBManagerStatePoweredOn);
|
||||
XCTAssertEqual(delegate2.state, CBManagerStatePoweredOn);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerRemovesListener {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate1 = [[FakePeripheralManagerDelegate alloc] init];
|
||||
|
||||
delegate1.expectation = [self expectationWithDescription:@"Delegate 1 called"];
|
||||
|
||||
[multiplexer addListener:delegate1];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
fakeManager.state = CBManagerStatePoweredOn;
|
||||
|
||||
[multiplexer removeListener:delegate1];
|
||||
[multiplexer gnc_peripheralManagerDidUpdateState:fakeManager];
|
||||
|
||||
// We expect delegate1 NOT to be called.
|
||||
// Since removals are async, we wait a bit to ensure it had a chance (or didn't).
|
||||
XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ delegate1.expectation ] timeout:0.5];
|
||||
XCTAssertEqual(result, XCTWaiterResultTimedOut);
|
||||
XCTAssertFalse(delegate1.didUpdateStateCalled);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerForwardsDidStartAdvertising {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:1 userInfo:nil];
|
||||
|
||||
[multiplexer gnc_peripheralManagerDidStartAdvertising:fakeManager error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didStartAdvertisingCalled);
|
||||
XCTAssertEqualObjects(delegate.startAdvertisingError, error);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerForwardsDidAddService {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
CBMutableService *service =
|
||||
[[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"180D"] primary:YES];
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:2 userInfo:nil];
|
||||
|
||||
[multiplexer gnc_peripheralManager:fakeManager didAddService:service error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didAddServiceCalled);
|
||||
XCTAssertEqualObjects(delegate.addedService, service);
|
||||
XCTAssertEqualObjects(delegate.addServiceError, error);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerForwardsDidReceiveReadRequest {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
id request = [NSNull null]; // Use NSNull or any object as placeholder since we can't create
|
||||
// CBATTRequest
|
||||
|
||||
[multiplexer gnc_peripheralManager:fakeManager didReceiveReadRequest:request];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didReceiveReadRequestCalled);
|
||||
XCTAssertEqual(delegate.readRequest, request);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerForwardsDidPublishL2CAPChannel {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
CBL2CAPPSM psm = 42;
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:3 userInfo:nil];
|
||||
|
||||
[multiplexer gnc_peripheralManager:fakeManager didPublishL2CAPChannel:psm error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didPublishL2CAPChannelCalled);
|
||||
XCTAssertEqual(delegate.publishedPSM, psm);
|
||||
XCTAssertEqualObjects(delegate.publishL2CAPChannelError, error);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerForwardsDidUnpublishL2CAPChannel {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
CBL2CAPPSM psm = 42;
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:4 userInfo:nil];
|
||||
|
||||
[multiplexer gnc_peripheralManager:fakeManager didUnpublishL2CAPChannel:psm error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didUnpublishL2CAPChannelCalled);
|
||||
XCTAssertEqual(delegate.unpublishedPSM, psm);
|
||||
XCTAssertEqualObjects(delegate.unpublishL2CAPChannelError, error);
|
||||
}
|
||||
|
||||
- (void)testMultiplexerForwardsDidOpenL2CAPChannel {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
id channel = [NSNull null]; // Placeholder
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:5 userInfo:nil];
|
||||
|
||||
[multiplexer gnc_peripheralManager:fakeManager didOpenL2CAPChannel:channel error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didOpenL2CAPChannelCalled);
|
||||
XCTAssertEqual(delegate.openedChannel, channel);
|
||||
XCTAssertEqualObjects(delegate.openL2CAPChannelError, error);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidUpdateState {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
fakeManager.state = CBManagerStatePoweredOn;
|
||||
|
||||
[multiplexer peripheralManagerDidUpdateState:(CBPeripheralManager *)fakeManager];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didUpdateStateCalled);
|
||||
XCTAssertEqual(delegate.state, CBManagerStatePoweredOn);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidStartAdvertising {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:10 userInfo:nil];
|
||||
|
||||
[multiplexer peripheralManagerDidStartAdvertising:(CBPeripheralManager *)fakeManager error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didStartAdvertisingCalled);
|
||||
XCTAssertEqualObjects(delegate.startAdvertisingError, error);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidAddService {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
CBMutableService *service =
|
||||
[[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"180F"] primary:YES];
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:11 userInfo:nil];
|
||||
|
||||
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager didAddService:service error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didAddServiceCalled);
|
||||
XCTAssertEqualObjects(delegate.addedService, service);
|
||||
XCTAssertEqualObjects(delegate.addServiceError, error);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidReceiveReadRequest {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
id request = [NSNull null];
|
||||
|
||||
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager didReceiveReadRequest:request];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didReceiveReadRequestCalled);
|
||||
XCTAssertEqual(delegate.readRequest, request);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidPublishL2CAPChannel {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
CBL2CAPPSM psm = 100;
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:13 userInfo:nil];
|
||||
|
||||
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager
|
||||
didPublishL2CAPChannel:psm
|
||||
error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didPublishL2CAPChannelCalled);
|
||||
XCTAssertEqual(delegate.publishedPSM, psm);
|
||||
XCTAssertEqualObjects(delegate.publishL2CAPChannelError, error);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidUnpublishL2CAPChannel {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
CBL2CAPPSM psm = 101;
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:14 userInfo:nil];
|
||||
|
||||
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager
|
||||
didUnpublishL2CAPChannel:psm
|
||||
error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didUnpublishL2CAPChannelCalled);
|
||||
XCTAssertEqual(delegate.unpublishedPSM, psm);
|
||||
XCTAssertEqualObjects(delegate.unpublishL2CAPChannelError, error);
|
||||
}
|
||||
|
||||
- (void)testCBPeripheralManagerDelegateDidOpenL2CAPChannel {
|
||||
GNCPeripheralManagerMultiplexer *multiplexer =
|
||||
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
|
||||
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
|
||||
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
|
||||
[multiplexer addListener:delegate];
|
||||
|
||||
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
|
||||
id channel = [NSNull null];
|
||||
NSError *error = [NSError errorWithDomain:@"test" code:15 userInfo:nil];
|
||||
|
||||
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager
|
||||
didOpenL2CAPChannel:channel
|
||||
error:error];
|
||||
|
||||
[self waitForExpectationsWithTimeout:1 handler:nil];
|
||||
XCTAssertTrue(delegate.didOpenL2CAPChannelCalled);
|
||||
XCTAssertEqual(delegate.openedChannel, channel);
|
||||
XCTAssertEqualObjects(delegate.openL2CAPChannelError, error);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user