Refactor the GNCBLEL2CAPServer.

PiperOrigin-RevId: 745855701
This commit is contained in:
Edwin Wu
2025-04-09 21:48:02 -07:00
committed by Copybara-Service
parent 7fc2066be0
commit 675e4f738f
7 changed files with 194 additions and 66 deletions
@@ -27,4 +27,5 @@ typedef NS_ERROR_ENUM(GNCBLEErrorDomain, GNCBLEError){
GNCBLEErrorAlreadyDiscoveringSpecifiedCharacteristics,
GNCBLEErrorAlreadyReadingCharacteristic,
GNCBLEErrorAlreadyScanning,
GNCBLEErrorL2CAPListeningOnQueueNil,
};
@@ -15,8 +15,17 @@
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
@protocol GNCPeripheralManager;
NS_ASSUME_NONNULL_BEGIN
/**
* Completion handler for starting listening for an L2CAP channel.
*
* @param error The cause of the failure, or @c nil if no error occurred.
*/
typedef void (^GNCStartListeningL2CAPChannelCompletionHandler)(NSError *_Nullable error);
/**
* An object that publishes the @c PSM value.
*
@@ -28,11 +37,27 @@ NS_ASSUME_NONNULL_BEGIN
@property(atomic, readonly) CBL2CAPPSM PSM;
/**
* Retrieves the L2CAP channel.
* Initializes the L2CAP server.
*
* @return The L2CAP channel, or nil if the channel is not available.
* @param peripheralManager The peripheral manager to use. If @c nil, a default peripheral manager
* will be used.
* @param queue The queue to use. If @c nil, then by default it will be DISPATCH_QUEUE_SERIAL.
*/
- (nullable CBL2CAPChannel *)getChannel;
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(nullable dispatch_queue_t)queue;
/**
* Starts listening for an L2CAP channel.
*
* @param completionHandler The completion handler to call when the L2CAP channel is started.
*/
- (void)startListeningChannelWithCompletionHandler:
(GNCStartListeningL2CAPChannelCompletionHandler)completionHandler;
/**
* Closes the L2CAP channel.
*/
- (void)close;
@end
@@ -23,7 +23,7 @@
NS_ASSUME_NONNULL_BEGIN
static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer";
static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAPServer";
@interface GNCBLEL2CAPServer () <GNCPeripheralManagerDelegate>
@property(atomic, readwrite) CBL2CAPPSM PSM;
@@ -32,37 +32,67 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer"
@implementation GNCBLEL2CAPServer {
dispatch_queue_t _queue;
id<GNCPeripheralManager> _peripheralManager;
NSString *_serviceID;
GNCStartListeningL2CAPChannelCompletionHandler _startListeningL2CAPChannelcompletionHandler;
// The L2CAP channel that is used to send and receive data.
CBL2CAPChannel *_channel;
CBL2CAPChannel *_l2CAPChannel;
/// Whether start call has been performed when the peripheral was off.
BOOL _alreadyStartedWhenPeripheralPoweredOff;
}
- (instancetype)init {
self = [super init];
if (self) {
_queue = dispatch_queue_create(kGNCBLEL2CAPServerQueueLabel, DISPATCH_QUEUE_SERIAL);
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil queue:_queue];
// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
}
return self;
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 {
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(nullable dispatch_queue_t)queue {
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;
_queue = queue ?: dispatch_queue_create(kGNCBLEL2CAPServerQueueLabel, DISPATCH_QUEUE_SERIAL);
if (peripheralManager) {
_peripheralManager = peripheralManager;
// Set for @c GNCPeripheralManager to be able to forward callbacks.
_peripheralManager.peripheralDelegate = self;
}
}
return self;
}
- (nullable CBL2CAPChannel *)getChannel {
return _channel;
- (void)startListeningChannelWithCompletionHandler:
(GNCStartListeningL2CAPChannelCompletionHandler)completionHandler {
_startListeningL2CAPChannelcompletionHandler = [completionHandler copy];
if (!_queue) {
_startListeningL2CAPChannelcompletionHandler([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 (_peripheralManager.state == CBManagerStatePoweredOn) {
// Bluetooth link is already encrypted, however encryption is not required here to avoid getting
// insufficient authentication errors due to initialization order.
[_peripheralManager publishL2CAPChannelWithEncryption:NO];
} else {
GTMLoggerInfo(@"[NEARBY] Peripheral must be on to start, waiting.");
_alreadyStartedWhenPeripheralPoweredOff = YES;
}
}
- (void)close {
[self shutDown];
}
#pragma mark - GNCPeripheralManagerDelegate
@@ -70,10 +100,18 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer"
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
dispatch_assert_queue(_queue);
if (_peripheralManager.state == CBManagerStatePoweredOn) {
[_peripheralManager publishL2CAPChannelWithEncryption:NO];
if (_alreadyStartedWhenPeripheralPoweredOff) {
// Only setup once so that toggling Bluetooth does not cause an L2CAP channel to be
// published every time since a new instance is created for each new channel.
_alreadyStartedWhenPeripheralPoweredOff = NO;
// Bluetooth link is already encrypted, however encryption is not required here to avoid
// getting insufficient authentication errors due to initialization order.
[_peripheralManager publishL2CAPChannelWithEncryption:NO];
}
}
if (_peripheralManager.state == CBManagerStatePoweredOff) {
[_peripheralManager unpublishL2CAPChannel:_PSM];
[self shutDown];
}
}
@@ -81,19 +119,27 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer"
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
dispatch_assert_queue(_queue);
GTMLoggerDebug(@"[NEARBY] didPublishL2CAPChannel with PSM: %@", @(PSM));
if (error) {
GTMLoggerError(@"Failed to publish L2CAP channel: %@", error);
GTMLoggerError(@"[NEARBY] Failed to publish L2CAP channel: %@", error);
if (_startListeningL2CAPChannelcompletionHandler) {
_startListeningL2CAPChannelcompletionHandler(error);
}
return;
}
GTMLoggerInfo(@"Published L2CAP channel with PSM: %d", PSM);
_PSM = PSM;
if (_startListeningL2CAPChannelcompletionHandler) {
_startListeningL2CAPChannelcompletionHandler(nil);
}
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(NSError *)error {
dispatch_assert_queue(_queue);
GTMLoggerDebug(@"[NEARBY] didUnpublishL2CAPChannel on PSM %@", @(PSM));
if (error) {
GTMLoggerError(@"Failed to unpublish L2CAP channel: %@", error);
GTMLoggerError(@"[NEARBY] Failed to unpublish L2CAP channel: %@", error);
}
_PSM = 0;
}
@@ -102,50 +148,52 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer"
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
dispatch_assert_queue(_queue);
GTMLoggerDebug(@"[NEARBY] didOpenL2CAPChannel");
if (error) {
GTMLoggerError(@"Failed to open L2CAP channel: %@", error);
GTMLoggerError(@"[NEARBY] Failed to open L2CAP channel: %@", error);
return;
}
GTMLoggerInfo(@"Opened L2CAP channel with PSM: %d", channel.PSM);
_channel = channel;
// TODO: b/399815436 - Implement to wrap up l2cap channel.
// Cleanup older references.
if (_l2CAPChannel) {
// The device may establish a new L2CAP socket connection while the old socket is still
// connected if sysproxy stopped for a reason other than Bluetooth disconnection. Closing the
// channel here ensures the server and the client state is reset between the two
// connection sessions.
[self closeL2CAPChannel];
}
_l2CAPChannel = channel;
// TODO: b/399815436 - Implement to wrap up l2cap channel with |GNCBLEL2CAPStream|.
}
#pragma mark - CBPeripheralManagerDelegate
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
dispatch_async(_queue, ^{
[self gnc_peripheralManagerDidUpdateState:peripheral];
});
[self gnc_peripheralManagerDidUpdateState:peripheral];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
dispatch_async(_queue, ^{
[self gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
});
[self gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
dispatch_async(_queue, ^{
[self gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
});
[self gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
dispatch_async(_queue, ^{
[self gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
});
[self gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
}
#pragma mark Private
- (void)tearDown {
- (void)shutDown {
if (_PSM > 0) {
[_peripheralManager unpublishL2CAPChannel:_PSM];
}
@@ -153,6 +201,10 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.nearby.GNCBLEL2CAPServer"
_peripheralManager = nil;
}
- (void)closeL2CAPChannel {
_l2CAPChannel = nil;
}
@end
NS_ASSUME_NONNULL_END
@@ -30,7 +30,6 @@ objc_library(
"GNCBLEGATTServerTest.m",
"GNCBLEL2CAPFakeInputOutputStream.h",
"GNCBLEL2CAPFakeInputOutputStream.m",
"GNCBLEL2CAPServer+Testing.h",
"GNCBLEL2CAPServerTest.m",
"GNCBLEL2CAPStreamTest.m",
"GNCBLEMedium+Testing.h",
@@ -18,58 +18,102 @@
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Tests/GNCBLEL2CAPServer+Testing.h"
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.h"
static const int kPSM = 192;
@interface GNCBLEL2CAPServerTest : XCTestCase
@end
@implementation GNCBLEL2CAPServerTest
#pragma mark - Publish L2CAP channel
#pragma mark Tests
- (void)testPublishL2CAPChannel {
- (void)testPublishL2CAPChannelWhenStartListeningChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
fakePeripheralManager.PSM = kPSM;
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager];
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
XCTAssertEqual([l2capServer PSM], kPSM);
XCTAssertNotNil([l2capServer getChannel]);
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
}
- (void)testFailedToPublishL2CAPChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
fakePeripheralManager.didPublishL2CAPChannelError = [NSError errorWithDomain:@"fake"
code:0
userInfo:nil];
fakePeripheralManager.PSM = kPSM;
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager];
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
XCTAssertNotEqual([l2capServer PSM], kPSM);
XCTAssertNil([l2capServer getChannel]);
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, fakePeripheralManager.didPublishL2CAPChannelError);
XCTAssertEqual([l2capServer PSM], 0);
}];
}
- (void)testUnPublishL2CAPChannel {
- (void)testPoweredOffUnpublishesChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
fakePeripheralManager.PSM = kPSM;
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager];
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOff];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:0.0];
}
- (void)testStartPeripheralManagerInitiallyOff {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOff];
}
- (void)testClose {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[l2capServer close];
XCTAssertEqual([l2capServer PSM], 0);
}
- (void)testCloseDoesNotUnpublishesChannelIfNotConnected {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
fakePeripheralManager.unpublishExpectation.inverted = YES;
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[l2capServer close];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:1.0];
}
@end
@@ -44,6 +44,9 @@ NS_ASSUME_NONNULL_BEGIN
/** Expectation fulfilled when peripheral responds to a request with an error. */
@property(nonatomic, readonly) XCTestExpectation *respondToRequestErrorExpectation;
/** Expectation fulfilled when peripheral unpublishes an L2CAP channel. */
@property(nonatomic, readonly) XCTestExpectation *unpublishExpectation;
/**
* Similates an @c addService: error.
*
@@ -72,10 +72,13 @@
initWithDescription:@"Fulfilled when peripheral responds to a request with success."];
_respondToRequestErrorExpectation = [[XCTestExpectation alloc]
initWithDescription:@"Fulfilled when peripheral responds to a request with an error."];
_unpublishExpectation = [[XCTestExpectation alloc]
initWithDescription:@"Fulfilled when L2CAP channel is unpublished."];
_isAdvertising = NO;
_state = CBManagerStateUnknown;
_advertisementData = nil;
_services = [[NSMutableArray alloc] init];
_PSM = 192;
}
return self;
}
@@ -141,6 +144,7 @@
[peripheralDelegate gnc_peripheralManager:self
didUnpublishL2CAPChannel:_PSM
error:_didUnPublishL2CAPChannelError];
[_unpublishExpectation fulfill];
}
#pragma mark - Testing Helpers