Fix L2CAP stream connection problem.

PiperOrigin-RevId: 761848851
This commit is contained in:
Edwin Wu
2025-05-22 01:10:12 -07:00
committed by Copybara-Service
parent 90b7e76a24
commit ec7dbcbf30
3 changed files with 104 additions and 23 deletions
@@ -55,9 +55,16 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
// The active GATT server, or @nil if one hasn't been started yet.
GNCBLEGATTServer *_server;
// The active L2CAP server, or @nil if one hasn't been started yet.
GNCBLEL2CAPServer *_l2capServer;
// The active L2CAP client, or @nil if one hasn't been started yet.
GNCBLEL2CAPClient *_l2capClient;
// The PSM number of the remote peripheral's L2CAP server.
uint16_t _l2capPSM;
// The services that is being actively scanned for.
NSMutableArray<CBUUID *> *_scanningServiceUUIDs;
@@ -68,11 +75,18 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
// A peripheral to connection completion handler map. Used to track connection attempts. When a
// connection attempt has succeeded or failed, the completion handler is called and removed from
// the map.
NSMutableDictionary<NSUUID *, GNCGATTConnectionCompletionHandler> *_connectionCompletionHandlers;
NSMutableDictionary<NSUUID *, GNCGATTConnectionCompletionHandler>
*_gattConnectionCompletionHandlers;
// A peripheral to disconnection handler map. Used to track when a peripheral becomes
// disconnected. Once disconnected, the completion handler is called and removed from the map.
NSMutableDictionary<NSUUID *, GNCGATTDisconnectionHandler> *_disconnectionHandlers;
// disconnected. Once disconnected, the disconnection handler is called and removed from the map.
NSMutableDictionary<NSUUID *, GNCGATTDisconnectionHandler> *_gattDisconnectionHandlers;
// A peripheral to L2CAP stream completion handler map. Used to track L2CAP stream attempts. When
// a L2CAP stream attempt has succeeded or failed, the completion handler is called and removed
// from the map.
NSMutableDictionary<NSUUID *, GNCOpenL2CAPStreamCompletionHandler>
*_l2capStreamCompletionHandlers;
}
- (instancetype)init {
@@ -93,9 +107,11 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
_queue = queue ?: dispatch_get_main_queue();
_centralManager = centralManager;
_centralManager.centralDelegate = self;
_connectionCompletionHandlers = [NSMutableDictionary dictionary];
_disconnectionHandlers = [NSMutableDictionary dictionary];
_gattConnectionCompletionHandlers = [NSMutableDictionary dictionary];
_gattDisconnectionHandlers = [NSMutableDictionary dictionary];
_scanningServiceUUIDs = [NSMutableArray array];
_l2capStreamCompletionHandlers = [NSMutableDictionary dictionary];
_l2capPSM = 0;
}
return self;
}
@@ -199,8 +215,9 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
completionHandler:
(nullable GNCGATTConnectionCompletionHandler)completionHandler {
dispatch_async(_queue, ^{
_disconnectionHandlers[remotePeripheral.identifier] = disconnectionHandler;
_connectionCompletionHandlers[remotePeripheral.identifier] = completionHandler;
_gattDisconnectionHandlers[remotePeripheral.identifier] = disconnectionHandler;
_gattConnectionCompletionHandlers[remotePeripheral.identifier] = completionHandler;
_l2capPSM = 0;
[_centralManager connectPeripheral:remotePeripheral options:@{}];
});
}
@@ -232,19 +249,22 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral){
}];
}
[_l2capClient openL2CAPChannelWithPSM:psm
completionHandler:^void(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
if (!completionHandler) return;
if (error) {
completionHandler(nil, error);
} else {
completionHandler(stream, nil);
}
}];
_l2capStreamCompletionHandlers[remotePeripheral.identifier] = completionHandler;
_l2capPSM = psm;
// There is a Core Bluetooth problem: Either -didConnectPeripheral or
// -didFailToConnectPeripheral should be called at this point, but sometimes neither is
// called.
// TODO: b/419127415 - Investigate the root cause of this problem.
[_centralManager connectPeripheral:remotePeripheral options:@{}];
});
}
// This is private and should only be used for tests. The provided L2CAP client must call
// delegate methods on the main queue.
- (void)setL2CAPClient:(GNCBLEL2CAPClient *)l2capClient {
_l2capClient = l2capClient;
}
#pragma mark - Internal
- (void)internalStartScanningIfPoweredOn {
@@ -300,6 +320,35 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
return @{};
}
- (void)internalOpenL2CAPChannel:(id<GNCPeripheral>)remotePeripheral {
dispatch_assert_queue(_queue);
__weak __typeof__(self) weakSelf = self;
GNCOpenL2CAPStreamCompletionHandler handler =
_l2capStreamCompletionHandlers[remotePeripheral.identifier];
_l2capStreamCompletionHandlers[remotePeripheral.identifier] = nil;
if (!handler) {
return;
}
[_l2capClient
openL2CAPChannelWithPSM:_l2capPSM
completionHandler:^(GNCBLEL2CAPStream *_Nullable stream, NSError *_Nullable error) {
__typeof__(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
dispatch_async(strongSelf->_queue, ^{
if (error) {
handler(nil, error);
} else {
handler(stream, nil);
}
});
}];
}
#pragma mark - GNCCentralManagerDelegate
- (void)gnc_centralManagerDidUpdateState:(id<GNCCentralManager>)central {
@@ -320,8 +369,13 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
- (void)gnc_centralManager:(id<GNCCentralManager>)central
didConnectPeripheral:(id<GNCPeripheral>)peripheral {
dispatch_assert_queue(_queue);
GNCGATTConnectionCompletionHandler handler = _connectionCompletionHandlers[peripheral.identifier];
_connectionCompletionHandlers[peripheral.identifier] = nil;
if (_l2capPSM > 0) {
[self internalOpenL2CAPChannel:peripheral];
return;
}
GNCGATTConnectionCompletionHandler handler =
_gattConnectionCompletionHandlers[peripheral.identifier];
_gattConnectionCompletionHandlers[peripheral.identifier] = nil;
if (handler) {
GNCBLEGATTClient *client =
[[GNCBLEGATTClient alloc] initWithPeripheral:peripheral
@@ -338,8 +392,9 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
didFailToConnectPeripheral:(id<GNCPeripheral>)peripheral
error:(nullable NSError *)error {
dispatch_assert_queue(_queue);
GNCGATTConnectionCompletionHandler handler = _connectionCompletionHandlers[peripheral.identifier];
_connectionCompletionHandlers[peripheral.identifier] = nil;
GNCGATTConnectionCompletionHandler handler =
_gattConnectionCompletionHandlers[peripheral.identifier];
_gattConnectionCompletionHandlers[peripheral.identifier] = nil;
if (handler) {
handler(nil, error);
}
@@ -349,8 +404,8 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
didDisconnectPeripheral:(id<GNCPeripheral>)peripheral
error:(nullable NSError *)error {
dispatch_assert_queue(_queue);
GNCGATTDisconnectionHandler handler = _disconnectionHandlers[peripheral.identifier];
_disconnectionHandlers[peripheral.identifier] = nil;
GNCGATTDisconnectionHandler handler = _gattDisconnectionHandlers[peripheral.identifier];
_gattDisconnectionHandlers[peripheral.identifier] = nil;
if (handler) {
handler();
}
@@ -38,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN
- (NSDictionary<CBUUID *, NSData *> *)decodeAdvertisementData:
(NSDictionary<NSString *, id> *)advertisementData;
- (void)setL2CAPClient:(GNCBLEL2CAPClient *)l2capClient;
@end
NS_ASSUME_NONNULL_END
@@ -21,6 +21,7 @@
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
#import "internal/platform/implementation/apple/Tests/GNCBLEL2CAPClient+Testing.h"
#import "internal/platform/implementation/apple/Tests/GNCBLEMedium+Testing.h"
#import "internal/platform/implementation/apple/Tests/GNCFakeCentralManager.h"
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheral.h"
@@ -304,6 +305,28 @@ static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
[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] initWithPeripheral:fakePeripheral
queue: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 {