From e9aef7153a98b7354a1eb0855a3ef0cc10d598b1 Mon Sep 17 00:00:00 2001 From: Edwin Wu Date: Wed, 30 Apr 2025 21:54:05 -0700 Subject: [PATCH] Add request data connection before data transmission for L2CAP. PiperOrigin-RevId: 753434675 --- .../Mediums/BLEv2/GNCBLEL2CAPConnection.h | 8 +++++ .../Mediums/BLEv2/GNCBLEL2CAPConnection.m | 36 +++++++++++++++++-- .../implementation/apple/ble_medium.mm | 23 +++++++++--- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.h index 71a0a108..cf5d5b55 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.h @@ -47,6 +47,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)sendData:(NSData *)payload completion:(void (^)(BOOL))completion; +/** + * Requests data connection from the remote device. + * + * @param completion A block that is called when the data connection is ready. The block takes a + * BOOL parameter indicating whether the data connection is ready. + */ +- (void)requestDataConnectionWithCompletion:(void (^)(BOOL))completion; + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.m index a3844225..1776d3f3 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.m @@ -23,6 +23,8 @@ #import "GoogleToolboxForMac/GTMLogger.h" enum { kL2CAPPacketLength = 4 }; +static const CGFloat kRequestDataConnectionDelayInSeconds = 0.0; +static const UInt8 kRequestDataConnectionTimeoutInSeconds = 5; static char *const kGNCBLEL2CAPConnectionQueueLabel = "com.google.nearby.GNCBLEL2CAPConnection"; @@ -60,6 +62,7 @@ static NSData *PrefixLengthData(NSData *data) { @property(nonatomic) NSUInteger expectedDataLength; @property(nonatomic) NSMutableData *undeliveredData; @property(nonatomic) BOOL verboseLoggingEnabled; +@property(nonatomic) NSCondition *requestDataConnectionCondition; @end @implementation GNCBLEL2CAPConnection @@ -80,6 +83,7 @@ static NSData *PrefixLengthData(NSData *data) { [connection setIncomingConnection:incomingConnection]; [connection setUndeliveredData:[NSMutableData data]]; [connection setExpectedDataLength:0]; + [connection setRequestDataConnectionCondition:[[NSCondition alloc] init]]; return connection; } @@ -108,6 +112,31 @@ static NSData *PrefixLengthData(NSData *data) { }); } +- (void)requestDataConnectionWithCompletion:(void (^)(BOOL))completion { + GTMLoggerInfo(@"[NEARBY] Sending l2cap packet request data connection"); + // TODO b/399815436 - A bug is causing channel has written to the socket but the remote does not + // receive it. Add a delay to make sure the data is written to the socket. Remove the delay once + // the bug is fixed. + dispatch_time_t requestTime = + dispatch_time(DISPATCH_TIME_NOW, kRequestDataConnectionDelayInSeconds * NSEC_PER_SEC); + dispatch_after(requestTime, _selfQueue, ^(void) { + [_requestDataConnectionCondition lock]; + NSData *requestDataConnectionPacket = + GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommandRequestDataConnection, nil); + [_stream sendData:PrefixLengthData(requestDataConnectionPacket) + completionBlock:^(BOOL result){ + + }]; + dispatch_async(_callbackQueue, ^{ + NSDate *requestDataConnectionTimeout = + [NSDate dateWithTimeIntervalSinceNow:kRequestDataConnectionTimeoutInSeconds]; + BOOL result = [_requestDataConnectionCondition waitUntilDate:requestDataConnectionTimeout]; + completion(result); + [_requestDataConnectionCondition unlock]; + }); + }); +} + #pragma mark GNCBLEL2CAPStreamDelegate - (void)stream:(GNCBLEL2CAPStream *)stream didReceiveData:(NSData *)data { @@ -263,12 +292,15 @@ static NSData *PrefixLengthData(NSData *data) { } switch (l2capPacket.command) { case GNCMBLEL2CAPCommandResponseDataConnectionReady: { - _handledReceivedL2CAPResponseDataConnectionReadyPacket = YES; + [_requestDataConnectionCondition lock]; dispatch_async(_selfQueue, ^{ [_stream sendData:PrefixLengthData(GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash)) - completionBlock:^(BOOL result){ + completionBlock:^(BOOL result) { + [_requestDataConnectionCondition broadcast]; + [_requestDataConnectionCondition unlock]; }]; }); + _handledReceivedL2CAPResponseDataConnectionReadyPacket = YES; } break; case GNCMBLEL2CAPCommandRequestAdvertisement: case GNCMBLEL2CAPCommandRequestDataConnection: diff --git a/internal/platform/implementation/apple/ble_medium.mm b/internal/platform/implementation/apple/ble_medium.mm index f4a6c99b..16a1e597 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -55,6 +55,7 @@ #import "GoogleToolboxForMac/GTMLogger.h" static NSString *const kWeaveServiceUUID = @"FEF3"; +static const UInt8 kRequestConnectionTimeoutInSeconds = 10; namespace nearby { namespace apple { @@ -492,8 +493,10 @@ std::unique_ptr BleMedium::ConnectOverL2cap( } dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + dispatch_time_t timeout = + dispatch_time(DISPATCH_TIME_NOW, kRequestConnectionTimeoutInSeconds * NSEC_PER_SEC); __block std::unique_ptr socket; - std::string service_id_str = service_id; + const std::string &service_id_str = service_id; [medium_ openL2CAPChannelWithPSM:psm peripheral:non_empty_peripheral->GetPeripheral() completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) { @@ -506,10 +509,22 @@ std::unique_ptr BleMedium::ConnectOverL2cap( serviceID:@(service_id_str.c_str()) incomingConnection:NO callbackQueue:dispatch_get_main_queue()]; - socket = std::make_unique(connection, non_empty_peripheral); - dispatch_semaphore_signal(semaphore); + // Blocked call to wait for the packet validation result. + // TODO: b/399815436 - Remove this once the packet validation is moved to the + // Connections layer. + [connection requestDataConnectionWithCompletion:^(BOOL result) { + if (result) { + socket = std::make_unique(connection, non_empty_peripheral); + } + GTMLoggerInfo(result ? @"[NEARBY] Request data connection is ok" + : @"[NEARBY] Request data connection is not ok"); + dispatch_semaphore_signal(semaphore); + }]; }]; - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + if (dispatch_semaphore_wait(semaphore, timeout) != 0) { + GTMLoggerError(@"[NEARBY] Failed to connect over L2CAP: timeout."); + return nullptr; + } if (socket == nullptr) { return nullptr; }