Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ constexpr auto kMediumMaxAllowedReadBytes =
// Disable/Enable refactor of BLE/L2CAP in Nearby Connections SDK.
constexpr auto kRefactorBleL2cap =
flags::Flag<bool>(kConfigPackage, "45737079", false);
// Enable/Disable usage of shared CBPeripheralManager for GATT and L2CAP
// servers.
constexpr auto kEnableSharedPeripheralManager =
flags::Flag<bool>(kConfigPackage, "45770787", false);
// Set the safe-to-disconnect version.
// 0. Disabled all. 1. safe-to-disconnect 2. reserved 3. auto-reconnect
// 4. auto-resume 5. non-distance-constraint-recovery 6. payload_ack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@
/** Checks whether BLE L2CAP refactor is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL refactorBleL2capEnabled;

/** Checks whether shared peripheral manager is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL sharedPeripheralManagerEnabled;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ + (BOOL)refactorBleL2capEnabled {
kRefactorBleL2cap);
}

+ (BOOL)sharedPeripheralManagerEnabled {
return nearby::NearbyFlags::GetInstance().GetBoolFlag(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ - (void)tearDown {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::kEnableBleL2cap,
false);
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::kRefactorBleL2cap,
false);
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
false);

[super tearDown];
}
Expand Down Expand Up @@ -78,4 +85,34 @@ - (void)testBleL2capEnabled_WhenFlagIsFalse {
XCTAssertFalse([GNCFeatureFlags bleL2capEnabled]);
}

- (void)testRefactorBleL2capEnabled_WhenFlagIsTrue {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::kRefactorBleL2cap,
YES);
XCTAssertTrue([GNCFeatureFlags refactorBleL2capEnabled]);
}

- (void)testRefactorBleL2capEnabled_WhenFlagIsFalse {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::kRefactorBleL2cap,
NO);
XCTAssertFalse([GNCFeatureFlags refactorBleL2capEnabled]);
}

- (void)testSharedPeripheralManagerEnabled_WhenFlagIsTrue {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
YES);
XCTAssertTrue([GNCFeatureFlags sharedPeripheralManagerEnabled]);
}

- (void)testSharedPeripheralManagerEnabled_WhenFlagIsFalse {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
NO);
XCTAssertFalse([GNCFeatureFlags sharedPeripheralManagerEnabled]);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>

#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"

@class GNCBLEGATTCharacteristic;

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -57,7 +59,18 @@ typedef void (^GNCStopAdvertisingCompletionHandler)(NSError *_Nullable error);
*
* @note The public APIs of this class are thread safe.
*/
@interface GNCBLEGATTServer : NSObject
@interface GNCBLEGATTServer : NSObject <GNCPeripheralManagerDelegate>

/**
* Initializes the GATT server.
*
* @param peripheralManager The peripheral manager to use.
* @param queue The queue to use for delegate callbacks and internal operations.
*/
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(nullable dispatch_queue_t)queue;

- (instancetype)init NS_UNAVAILABLE;

/**
* Creates a characteristic and adds it to the GATT server.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,30 +59,33 @@ @implementation GNCBLEGATTServer {
NSDictionary<NSString *, id> *_advertisementData;
}

- (instancetype)init {
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(nullable dispatch_queue_t)queue {
self = [super init];
if (self) {
_queue = dispatch_queue_create(kGNCBLEGATTServerQueueLabel, DISPATCH_QUEUE_SERIAL);
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil queue:_queue];
// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
_services = [[NSMutableDictionary alloc] init];
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
_characteristicValues = [[NSMutableDictionary alloc] init];
_advertisementData = nil;
}
return self;
}
_queue = queue ?: dispatch_queue_create(kGNCBLEGATTServerQueueLabel, DISPATCH_QUEUE_SERIAL);
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:self
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;
}

// This is private and should only be used for tests. The provided peripheral manager must call
// delegate methods on the main queue.
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager {
self = [super init];
if (self) {
_queue = dispatch_get_main_queue();
_peripheralManager = peripheralManager;
// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
_services = [[NSMutableDictionary alloc] init];
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
_characteristicValues = [[NSMutableDictionary alloc] init];
Expand Down Expand Up @@ -342,6 +345,12 @@ - (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
dispatch_assert_queue(_queue);
if (!_services[request.characteristic.service.UUID]) {
// This server does not own the requested service. Ignore the request to allow other listeners
// (or future listeners) to handle it.
return;
}

NSData *value =
_characteristicValues[request.characteristic.service.UUID][request.characteristic.UUID];
if (!value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>

#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"

@class GNCBLEL2CAPStream;
@protocol GNCPeripheralManager;

Expand Down Expand Up @@ -42,7 +44,7 @@ typedef void (^GNCOpenL2CAPServerChannelOpendCompletionHandler)(GNCBLEL2CAPStrea
*
* @note The public APIs of this class are thread safe.
*/
@interface GNCBLEL2CAPServer : NSObject
@interface GNCBLEL2CAPServer : NSObject <GNCPeripheralManagerDelegate>

// Represents a PSM (Protocol/Service Multiplexer) value for an L2CAP channel.
@property(atomic, readonly) CBL2CAPPSM PSM;
Expand All @@ -57,6 +59,8 @@ typedef void (^GNCOpenL2CAPServerChannelOpendCompletionHandler)(GNCBLEL2CAPStrea
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(nullable dispatch_queue_t)queue;

- (instancetype)init NS_UNAVAILABLE;

/**
* Starts listening for an L2CAP channel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>

#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEError.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.h"
Expand All @@ -43,21 +44,25 @@ @implementation GNCBLEL2CAPServer {
BOOL _alreadyStartedWhenPeripheralPoweredOff;
}

- (instancetype)init {
return [self initWithPeripheralManager:nil queue:nil];
}

// This is private and should only be used for tests. The provided peripheral manager must call
// delegate methods on the main queue.
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(nullable dispatch_queue_t)queue {
self = [super init];
if (self) {
_queue = queue ?: dispatch_queue_create(kGNCBLEL2CAPServerQueueLabel, DISPATCH_QUEUE_SERIAL);
if (peripheralManager) {
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;
// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
// In shared mode, do NOT set the delegate. The Multiplexer handles callbacks.
} else {
if (peripheralManager) {
_peripheralManager = peripheralManager;
// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
}
}
}
return self;
Expand All @@ -70,20 +75,36 @@ - (void)startListeningChannelWithPSMPublishedCompletionHandler:
channelOpenedCompletionHandler {
_psmPublishedCompletionHandler = [psmPublishedCompletionHandler copy];
_channelOpenedCompletionHandler = [channelOpenedCompletionHandler copy];
if (!_queue) {
_psmPublishedCompletionHandler(0, [NSError errorWithDomain:GNCBLEErrorDomain
code:GNCBLEErrorL2CAPListeningOnQueueNil
userInfo:nil]);
return;
}
if (!_peripheralManager) {
// Lazy initialization to avoid system dialog on app startup before pairing.
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil
queue:_queue
options:nil];

// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
if (!_queue) {
if (_psmPublishedCompletionHandler) {
_psmPublishedCompletionHandler(0,
[NSError errorWithDomain:GNCBLEErrorDomain
code:GNCBLEErrorL2CAPListeningOnQueueNil
userInfo:nil]);
}
return;
}
if (!_peripheralManager) {
GNCLoggerError(@"[NEARBY] Peripheral manager must not be nil.");
return;
}
} else {
if (!_queue) {
_psmPublishedCompletionHandler(0, [NSError errorWithDomain:GNCBLEErrorDomain
code:GNCBLEErrorL2CAPListeningOnQueueNil
userInfo:nil]);
return;
}
if (!_peripheralManager) {
// Lazy initialization to avoid system dialog on app startup before pairing.
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil
queue:_queue
options:nil];

// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
}
}

if (_PSM > 0) {
Expand Down
42 changes: 29 additions & 13 deletions internal/platform/implementation/apple/Mediums/BLE/GNCBLEMedium.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,6 @@
return [NSError errorWithDomain:GNCBLEErrorDomain code:GNCBLEErrorAlreadyScanning userInfo:nil];
}

static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
id<GNCPeripheralManager> _Nullable peripheralManager) {
if (!peripheralManager) {
return [[GNCBLEL2CAPServer alloc] init];
} else {
return [[GNCBLEL2CAPServer alloc] initWithPeripheralManager:peripheralManager
queue:dispatch_get_main_queue()];
}
}

@interface GNCBLEMedium () <GNCCentralManagerDelegate, CBCentralManagerDelegate>
@end

Expand Down Expand Up @@ -164,7 +154,16 @@ - (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
dispatch_async(_queue, ^{
if (!_server) {
_server = [[GNCBLEGATTServer alloc] init];
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
// TODO (edwinwu): Implement shared peripheral manager.
// For now, raise an exception.
[NSException raise:NSInvalidArgumentException
format:@"Not implemented for shared manager is enabled."];
} else {
// In legacy mode, we pass nil (or a separate manager) and do NOT add to multiplexer.
// GNCBLEGATTServer will create its own internal manager.
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:nil queue:nil];
}
}
[_server startAdvertisingData:serviceData completionHandler:completionHandler];
});
Expand Down Expand Up @@ -238,7 +237,14 @@ - (void)startGATTServerWithCompletionHandler:
(nullable GNCGATTServerCompletionHandler)completionHandler {
dispatch_async(_queue, ^{
if (!_server) {
_server = [[GNCBLEGATTServer alloc] init];
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
// TODO (edwinwu): Implement shared peripheral manager.
// For now, raise an exception.
[NSException raise:NSInvalidArgumentException
format:@"Not implemented for shared manager is enabled."];
} else {
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:nil queue:nil];
}
}
if (completionHandler) {
completionHandler(_server, nil);
Expand Down Expand Up @@ -284,7 +290,17 @@ - (void)openL2CAPServerWithPSMPublishedCompletionHandler:
(nullable id<GNCPeripheralManager>)peripheralManager {
dispatch_async(_queue, ^{
if (!_l2capServer) {
_l2capServer = CreateL2CapServer(peripheralManager);
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
// TODO (edwinwu): Implement shared peripheral manager.
// For now, raise an exception.
[NSException raise:NSInvalidArgumentException
format:@"Not implemented for shared manager is enabled."];
} else {
// Legacy mode
_l2capServer = [[GNCBLEL2CAPServer alloc]
initWithPeripheralManager:peripheralManager // Likely nil, so Server creates new one
queue:peripheralManager ? dispatch_get_main_queue() : nil];
}
}
[_l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:psmPublishedCompletionHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ objc_library(
srcs = [
"GNCBLEGATTCharacteristicTest.mm",
"GNCBLEGATTClientTest.m",
"GNCBLEGATTServerTest.m",
"GNCBLEGATTServerTest.mm",
"GNCBLEL2CAPClientTest.m",
"GNCBLEL2CAPConnectionTest.m",
"GNCBLEL2CAPFakeInputOutputStream.m",
"GNCBLEL2CAPServerTest.m",
"GNCBLEL2CAPServerTest.mm",
"GNCBLEL2CAPStreamTest.m",
"GNCBLEMediumTest.m",
"GNCFakeBLEGATTServer.m",
Expand Down Expand Up @@ -67,6 +67,8 @@ objc_library(
"GNCMFakeConnection.h",
],
deps = [
"//connections/implementation/flags:connections_flags",
"//internal/flags:nearby_flags",
"//internal/platform/implementation/apple", # buildcleaner: keep
"//internal/platform/implementation/apple/Mediums/BLE",
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Shared",
Expand Down
Loading
Loading