[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
@@ -52,6 +52,9 @@ constexpr auto kEnableBleL2cap =
// Disable/Enable BLE v2 in Nearby Connections SDK.
constexpr auto kEnableBleV2 =
flags::Flag<bool>(kConfigPackage, "45401515", true);
// When true, refactor the BLE/L2CAP logic in Nearby Connections SDK.
constexpr auto kRefactorBleL2cap =
flags::Flag<bool>(kConfigPackage, "45713654", false);
// Enable/Disable DCT advertising/scanning specification.
constexpr auto kEnableDct =
flags::Flag<bool>(kConfigPackage, "45697202", false);
@@ -226,8 +226,7 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl(
if (ble_medium_.StartLegacyAdvertising(
service_id, local_endpoint_id,
advertising_options.fast_advertisement_service_uuid)) {
LOG(INFO) << __func__
<< "Ble started advertising for legacy device.";
LOG(INFO) << __func__ << "Ble started advertising for legacy device.";
mediums_started_successfully.push_back(bluetooth_medium);
VLOG(1) << __func__ << "After Ble, BT added";
bluetooth_classic_advertiser_client_id_ = client->GetClientId();
@@ -2379,49 +2378,68 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BleConnectImpl(
config_package_nearby::nearby_connections_feature::kEnableBleL2cap) &&
peripheral.GetPsm() !=
mediums::BleAdvertisementHeader::kDefaultPsmValue) {
ErrorOr<BleL2capSocket> ble_l2cap_socket_result =
ble_medium_.ConnectOverL2cap(
endpoint->service_id, peripheral,
client->GetCancellationFlag(endpoint->endpoint_id));
if (!ble_l2cap_socket_result.has_error()) {
LOG(INFO) << "In BleConnectImpl(), connected to Ble L2CAP device "
<< absl::BytesToHexString(peripheral.GetId().data())
<< " for endpoint(id=" << endpoint->endpoint_id << ").";
auto channel = std::make_unique<BleL2capEndpointChannel>(
endpoint->service_id, /*channel_name=*/endpoint->endpoint_id,
ble_l2cap_socket_result.value());
return BasePcpHandler::ConnectImplResult{
.medium = BLE,
.status = {Status::kSuccess},
.operation_result_code = OperationResultCode::DETAIL_SUCCESS,
.endpoint_channel = std::move(channel),
};
if (NearbyFlags::GetInstance().GetBoolFlag(
config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap)) {
// TODO(edwinwu): Implement refactored Ble L2CAP flow in next cl.
LOG(WARNING)
<< "In BleConnectImpl(), failed to connect to Ble L2CAP "
"device due to refactor Ble L2CAP flow is not implemented yet.";
} else {
LOG(WARNING) << "In BleConnectImpl(), failed to connect to Ble L2CAP "
"device "
<< absl::BytesToHexString(peripheral.GetId().data())
<< " for endpoint(id=" << endpoint->endpoint_id << ").";
ErrorOr<BleL2capSocket> ble_l2cap_socket_result =
ble_medium_.ConnectOverL2cap(
endpoint->service_id, peripheral,
client->GetCancellationFlag(endpoint->endpoint_id));
if (!ble_l2cap_socket_result.has_error()) {
LOG(INFO) << "In BleConnectImpl(), connected to Ble L2CAP device "
<< absl::BytesToHexString(peripheral.GetId().data())
<< " for endpoint(id=" << endpoint->endpoint_id << ").";
auto channel = std::make_unique<BleL2capEndpointChannel>(
endpoint->service_id, /*channel_name=*/endpoint->endpoint_id,
ble_l2cap_socket_result.value());
return BasePcpHandler::ConnectImplResult{
.medium = BLE,
.status = {Status::kSuccess},
.operation_result_code = OperationResultCode::DETAIL_SUCCESS,
.endpoint_channel = std::move(channel),
};
} else {
LOG(WARNING) << "In BleConnectImpl(), failed to connect to Ble L2CAP "
"device "
<< absl::BytesToHexString(peripheral.GetId().data())
<< " for endpoint(id=" << endpoint->endpoint_id << ").";
}
}
}
ErrorOr<BleSocket> ble_socket_result =
ble_medium_.Connect(endpoint->service_id, peripheral,
client->GetCancellationFlag(endpoint->endpoint_id));
if (ble_socket_result.has_error()) {
LOG(ERROR) << "In BleConnectImpl(), failed to connect to BLE device "
<< absl::BytesToHexString(peripheral.GetId().data())
<< " for endpoint(id=" << endpoint->endpoint_id << ").";
std::unique_ptr<BleEndpointChannel> channel = nullptr;
if (NearbyFlags::GetInstance().GetBoolFlag(
config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap)) {
// TODO(edwinwu): Implement refactored Ble L2CAP flow in next cl.
return BasePcpHandler::ConnectImplResult{
.status = {Status::kBleError},
.operation_result_code =
ble_socket_result.error().operation_result_code().value(),
.operation_result_code = OperationResultCode::DETAIL_UNKNOWN,
};
} else {
ErrorOr<BleSocket> ble_socket_result =
ble_medium_.Connect(endpoint->service_id, peripheral,
client->GetCancellationFlag(endpoint->endpoint_id));
if (ble_socket_result.has_error()) {
LOG(ERROR) << "In BleConnectImpl(), failed to connect to BLE device "
<< absl::BytesToHexString(peripheral.GetId().data())
<< " for endpoint(id=" << endpoint->endpoint_id << ").";
return BasePcpHandler::ConnectImplResult{
.status = {Status::kBleError},
.operation_result_code =
ble_socket_result.error().operation_result_code().value(),
};
}
channel = std::make_unique<BleEndpointChannel>(
endpoint->service_id, /*channel_name=*/endpoint->endpoint_id,
ble_socket_result.value());
}
auto channel = std::make_unique<BleEndpointChannel>(
endpoint->service_id, /*channel_name=*/endpoint->endpoint_id,
ble_socket_result.value());
return BasePcpHandler::ConnectImplResult{
.medium = BLE,
.status = {Status::kSuccess},
@@ -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) {