[BLEREFACTOR]: Add refactor flow for Ble/L2CAP in Apple platform layer.

PiperOrigin-RevId: 829179079
This commit is contained in:
Edwin Wu
2025-11-06 17:51:10 -08:00
committed by Copybara-Service
parent 5caa639fcf
commit d7a3d13fe2
7 changed files with 186 additions and 122 deletions
@@ -26,4 +26,7 @@
/** Checks whether BLE L2CAP is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL bleL2capEnabled;
/** Checks whether BLE L2CAP refactor is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL refactorBleL2capEnabled;
@end
@@ -36,4 +36,10 @@
kEnableBleL2cap);
}
+ (BOOL)refactorBleL2capEnabled {
return nearby::NearbyFlags::GetInstance().GetBoolFlag(
nearby::connections::config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap);
}
@end
@@ -16,6 +16,7 @@
#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/GNCBLEL2CAPStream.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCLeaks.h"
@@ -98,8 +99,12 @@ static NSData *PrefixLengthData(NSData *data) {
dispatch_async(_selfQueue, ^{
NSData *packet;
// Prefix the service ID hash.
packet = PrefixLengthData(PrefixDataWithServiceIDHash(_serviceIDHash, data));
if (GNCFeatureFlags.refactorBleL2capEnabled) {
packet = PrefixLengthData(data);
} else {
// Prefix the service ID hash.
packet = PrefixLengthData(PrefixDataWithServiceIDHash(_serviceIDHash, data));
}
if (_verboseLoggingEnabled) {
GNCLoggerDebug(@"GNCBLEL2CAPConnection data to be sent: %@", [packet description]);
}
@@ -202,42 +207,49 @@ static NSData *PrefixLengthData(NSData *data) {
}
bytesProcessed = realData.length + kL2CAPPacketLength;
// TODO: b/399815436 - Refactor the validation logic to connections layer.
if ([self handleL2CAPPacketFromData:realData]) {
return bytesProcessed;
}
if (GNCFeatureFlags.refactorBleL2capEnabled) {
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler([realData copy]);
});
}
} else {
// TODO: b/399815436 - Refactor the validation logic to connections layer.
if ([self handleL2CAPPacketFromData:realData]) {
return bytesProcessed;
}
// TODO: b/399815436 - All BLE control packets should be handled here and not passed to
// upper layer. Need to refine the flow after refactoring.
if (_incomingConnection && !_handledReceivedBLEIntroPacket) {
[self handleBLEIntroPacketFromData:realData];
return bytesProcessed;
}
// TODO: b/399815436 - All BLE control packets should be handled here and not passed to
// upper layer. Need to refine the flow after refactoring.
if (_incomingConnection && !_handledReceivedBLEIntroPacket) {
[self handleBLEIntroPacketFromData:realData];
return bytesProcessed;
}
if (realData.length < _serviceIDHash.length) {
GNCLoggerError(@"Data length mismatch. Expected size: > %lu, Data: %@", _serviceIDHash.length,
realData);
return bytesProcessed;
}
if (realData.length < _serviceIDHash.length) {
GNCLoggerError(@"Data length mismatch. Expected size: > %lu, Data: %@", _serviceIDHash.length,
realData);
return bytesProcessed;
}
// Extract the service ID prefix from each data packet and validate it.
NSUInteger prefixLength = _serviceIDHash.length;
if (![[realData subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
return bytesProcessed;
}
// Extract the service ID prefix from each data packet and validate it.
NSUInteger prefixLength = _serviceIDHash.length;
if (![[realData subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
return bytesProcessed;
}
dispatch_async(_selfQueue, ^{
[_stream sendData:PrefixLengthData(GNCMGenerateBLEFramesPacketAcknowledgementPacket(
_serviceIDHash, realData.length))
completionBlock:^(BOOL result){
}];
});
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler([NSData
dataWithData:[realData subdataWithRange:NSMakeRange(prefixLength,
realData.length - prefixLength)]]);
dispatch_async(_selfQueue, ^{
[_stream sendData:PrefixLengthData(GNCMGenerateBLEFramesPacketAcknowledgementPacket(
_serviceIDHash, realData.length))
completionBlock:^(BOOL result){
}];
});
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler(
[realData subdataWithRange:NSMakeRange(prefixLength, realData.length - prefixLength)]);
});
}
}
return bytesProcessed;
}
@@ -14,6 +14,7 @@
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMBleConnection.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCLeaks.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.h"
@@ -58,14 +59,18 @@ NS_ASSUME_NONNULL_BEGIN
completion:(GNCMPayloadResultHandler)completion {
dispatch_async(_selfQueue, ^{
NSMutableData *packet;
if (data.length == 0) {
// Get the Control introduction packet if data length is 0.
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
packet = [NSMutableData dataWithData:introData];
if (GNCFeatureFlags.refactorBleL2capEnabled) {
packet = [NSMutableData dataWithData:data];
} else {
// Prefix the service ID hash.
packet = [NSMutableData dataWithData:_serviceIDHash];
[packet appendData:data];
if (data.length == 0) {
// Get the Control introduction packet if data length is 0.
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
packet = [NSMutableData dataWithData:introData];
} else {
// Prefix the service ID hash.
packet = [NSMutableData dataWithData:_serviceIDHash];
[packet appendData:data];
}
}
[_socket sendData:packet
@@ -100,46 +105,55 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)socket:(GNSSocket *)socket didReceiveData:(NSData *)data {
// Extract the service ID prefix from each data packet.
NSMutableData *packet;
NSUInteger prefixLength = _serviceIDHash.length;
if (_expectedIntroPacket && !_receivedIntroPacket) {
// Check if the first packet is intro packet.
if (!_serviceIDHash) {
// If _serviceIdHash is nil, then we need to parse the first incoming packet if it conforms to
// introducion packet and extract the serviceIdHash for coming packets.
NSData *serviceIDHash = GNCMParseBLEFramesIntroductionPacket(data);
if (serviceIDHash) {
_serviceIDHash = serviceIDHash;
_receivedIntroPacket = YES;
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
if (GNCFeatureFlags.refactorBleL2capEnabled) {
dispatch_async(_selfQueue, ^{
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler([data copy]);
});
}
} else {
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
if ([data isEqual:introData]) {
_receivedIntroPacket = YES;
});
} else {
// Extract the service ID prefix from each data packet.
NSData *packet;
NSUInteger prefixLength = _serviceIDHash.length;
if (_expectedIntroPacket && !_receivedIntroPacket) {
// Check if the first packet is intro packet.
if (!_serviceIDHash) {
// If _serviceIdHash is nil, then we need to parse the first incoming packet if it conforms
// to introducion packet and extract the serviceIdHash for coming packets.
NSData *serviceIDHash = GNCMParseBLEFramesIntroductionPacket(data);
if (serviceIDHash) {
_serviceIDHash = serviceIDHash;
_receivedIntroPacket = YES;
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
}
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
if ([data isEqual:introData]) {
_receivedIntroPacket = YES;
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
}
}
return;
}
return;
}
if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded");
return;
}
packet = [NSMutableData
dataWithData:[data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)]];
dispatch_async(_selfQueue, ^{
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler(packet);
});
if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded");
return;
}
});
packet = [data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)];
dispatch_async(_selfQueue, ^{
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler(packet);
});
}
});
}
}
@end
@@ -27,6 +27,7 @@
#include "internal/platform/implementation/ble.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTCharacteristic.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
@@ -628,8 +629,10 @@ std::unique_ptr<api::ble::BleSocket> BleMedium::Connect(
return nullptr;
}
// Send the (empty) intro packet, which the BLE advertiser is expecting.
socket->GetOutputStream().Write(ByteArray());
if (!GNCFeatureFlags.refactorBleL2capEnabled) {
// Send the (empty) intro packet, which the BLE advertiser is expecting.
socket->GetOutputStream().Write(ByteArray());
}
return std::move(socket);
}
@@ -658,17 +661,22 @@ std::unique_ptr<api::ble::BleL2capSocket> BleMedium::ConnectOverL2cap(
serviceID:@(service_id_str.c_str())
incomingConnection:NO
callbackQueue:connection_callback_queue_];
// Blocked call to wait for the packet validation result.
// TODO: b/419654808 - Remove this once the packet validation is moved to the
// Connections layer.
[connection requestDataConnectionWithCompletion:^(BOOL result) {
if (result) {
socket = std::make_unique<BleL2capSocket>(connection, peripheral_id);
}
GNCLoggerInfo(result ? @"[NEARBY] Request data connection is ok"
: @"[NEARBY] Request data connection is not ok");
if (GNCFeatureFlags.refactorBleL2capEnabled) {
socket = std::make_unique<BleL2capSocket>(connection, peripheral_id);
dispatch_semaphore_signal(semaphore);
}];
} else {
// Blocked call to wait for the packet validation result.
// TODO: b/419654808 - Remove this once the packet validation is moved to the
// Connections layer.
[connection requestDataConnectionWithCompletion:^(BOOL result) {
if (result) {
socket = std::make_unique<BleL2capSocket>(connection, peripheral_id);
}
GNCLoggerInfo(result ? @"[NEARBY] Request data connection is ok"
: @"[NEARBY] Request data connection is not ok");
dispatch_semaphore_signal(semaphore);
}];
}
}];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, kApiTimeoutInSeconds * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {