From 4911d49f82e6a5f3108bbb5b9b87a58c870946f4 Mon Sep 17 00:00:00 2001 From: Edwin Wu Date: Fri, 18 Apr 2025 11:23:40 -0700 Subject: [PATCH] Implementation of BLE L2CAP connecting flow. PiperOrigin-RevId: 749092197 --- internal/platform/ble_v2.cc | 10 ++++++++-- internal/platform/ble_v2.h | 1 + .../apple/Mediums/BLEv2/GNCBLEL2CAPClient.h | 8 ++++++++ .../apple/Mediums/BLEv2/GNCBLEL2CAPClient.m | 8 ++++++-- .../apple/Mediums/BLEv2/GNCBLEMedium.h | 9 +++++++++ .../apple/Mediums/BLEv2/GNCBLEMedium.m | 14 ++++++++++++++ .../apple/Mediums/BLEv2/GNCPeripheral.h | 12 ++++++++++++ .../apple/Tests/GNCFakePeripheral.h | 9 +++++++++ .../apple/Tests/GNCFakePeripheral.m | 12 ++++++++++++ .../implementation/apple/ble_medium.h | 12 ++++++++++++ .../implementation/apple/ble_medium.mm | 19 +++++++++++++++++++ internal/platform/implementation/ble_v2.h | 3 ++- 12 files changed, 112 insertions(+), 5 deletions(-) diff --git a/internal/platform/ble_v2.cc b/internal/platform/ble_v2.cc index d7a61775..cf2d93c1 100644 --- a/internal/platform/ble_v2.cc +++ b/internal/platform/ble_v2.cc @@ -284,8 +284,14 @@ BleV2Socket BleV2Medium::Connect(const std::string& service_id, BleL2capSocket BleV2Medium::ConnectOverL2cap( const std::string& service_id, TxPowerLevel tx_power_level, const BleV2Peripheral& peripheral, CancellationFlag* cancellation_flag) { - // TODO(mingshiouwu): Replace with a real implementation connecting flow. - return BleL2capSocket(peripheral, nullptr); + BleL2capSocket socket; + peripheral.GetImpl([&](api::ble_v2::BlePeripheral& device) { + socket = BleL2capSocket( + peripheral, + impl_->ConnectOverL2cap(peripheral.GetPsm(), service_id, tx_power_level, + device, cancellation_flag)); + }); + return socket; } bool BleV2Medium::IsExtendedAdvertisementsAvailable() { diff --git a/internal/platform/ble_v2.h b/internal/platform/ble_v2.h index 01534672..48ffe1b8 100644 --- a/internal/platform/ble_v2.h +++ b/internal/platform/ble_v2.h @@ -336,6 +336,7 @@ class GattClient final { class BleL2capSocket final { public: + BleL2capSocket() = default; BleL2capSocket(BleV2Peripheral peripheral, std::unique_ptr socket) : peripheral_(peripheral) {} diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h index 703b9504..28cb790f 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h @@ -45,6 +45,14 @@ typedef void (^GNCRequestDisconnectionHandler)(id peripheral); - (instancetype)initWithPeripheral:(id)peripheral requestDisconnectionHandler:(GNCRequestDisconnectionHandler)requestDisconnectionHandler; +/** + * Opens a L2CAP channel with the @c PSM. + * + * @param PSM The PSM to use for opening the L2CAP channel. + */ +// TODO: b/399815436 - Add CompletionHandler for this method. +- (void)openL2CAPChannelWithPSM:(uint16_t)PSM; + /** Cancels an active or pending local connection to a peripheral. */ - (void)disconnect; diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.m index 01b9e8ea..6bf09ceb 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.m @@ -32,7 +32,7 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC GNCRequestDisconnectionHandler _requestDisconnectionHandler; // The L2CAP channel that is used to send and receive data. - CBL2CAPChannel *_channel; + CBL2CAPChannel *_l2CAPChannel; } - (instancetype)initWithPeripheral:(id)peripheral @@ -58,6 +58,10 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC return self; }; +- (void)openL2CAPChannelWithPSM:(uint16_t)PSM { + [_peripheral openL2CAPChannelWithPSM:PSM]; +} + - (void)disconnect { dispatch_async(_queue, ^{ _requestDisconnectionHandler(_peripheral); @@ -74,7 +78,7 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC return; } GTMLoggerInfo(@"[NEARBY] Opened L2CAP channel: %@", channel); - _channel = channel; + _l2CAPChannel = channel; // TODO: b/399815436 - Implement to wrap up l2cap channel. } diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h index ac11a381..d08d1cc1 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h @@ -211,6 +211,15 @@ typedef void (^GNCOpenL2CAPServerCompletionHandler)(GNCBLEL2CAPServer *_Nullable - (void)openL2CAPServerWithCompletionHandler:(GNCOpenL2CAPServerCompletionHandler)completionHandler peripheralManager:(nullable id)peripheralManager; +/** + * Opens a L2CAP channel with the @c PSM on the remote peripheral. + * + * @param PSM The PSM to use for opening the L2CAP channel. + * @param remotePeripheral The peripheral to which the L2CAP channel is being opened. + */ +// TODO: b/399815436 - Add CompletionHandler for this method. +- (void)openL2CAPChannelWithPSM:(uint16_t)PSM peripheral:(id)remotePeripheral; + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m index c60d0771..164aef71 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.m @@ -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/GNCBLEL2CAPServer.h" +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h" #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCCentralManager.h" #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h" #import "internal/platform/implementation/apple/Mediums/BLEv2/NSData+GNCBase85.h" @@ -54,6 +55,7 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer( // The active GATT server, or @nil if one hasn't been started yet. GNCBLEGATTServer *_server; GNCBLEL2CAPServer *_l2capServer; + GNCBLEL2CAPClient *_l2capClient; // The services that is being actively scanned for. NSMutableArray *_scanningServiceUUIDs; @@ -223,6 +225,18 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer( }); } +- (void)openL2CAPChannelWithPSM:(uint16_t)PSM peripheral:(id)remotePeripheral { + dispatch_async(_queue, ^{ + if (!_l2capClient) { + _l2capClient = + [[GNCBLEL2CAPClient alloc] initWithPeripheral:remotePeripheral + requestDisconnectionHandler:^(id _Nonnull peripheral){ + }]; + } + [_l2capClient openL2CAPChannelWithPSM:PSM]; + }); +} + #pragma mark - Internal - (void)internalStartScanningIfPoweredOn { diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h index a061d106..31d872d0 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h @@ -112,6 +112,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)readValueForCharacteristic:(CBCharacteristic *)characteristic; +/** + * Opens an L2CAP channel with the specified PSM. + * + * When you call this method to open an L2CAP channel, the peripheral calls the + * @c peripheral:didOpenL2CAPChannel:error: method of its delegate object. If the peripheral + * successfully opens the L2CAP channel, you can access it through the peripheral’s @c l2capChannels + * property. + * + * @param PSM The PSM value to use for the L2CAP channel. + */ +- (void)openL2CAPChannelWithPSM:(uint16_t)PSM; + @end /** diff --git a/internal/platform/implementation/apple/Tests/GNCFakePeripheral.h b/internal/platform/implementation/apple/Tests/GNCFakePeripheral.h index 0d62478d..f6750a2d 100644 --- a/internal/platform/implementation/apple/Tests/GNCFakePeripheral.h +++ b/internal/platform/implementation/apple/Tests/GNCFakePeripheral.h @@ -55,6 +55,15 @@ NS_ASSUME_NONNULL_BEGIN /** Similates a delay in all delegate calls by the specified amount. */ @property(nonatomic, readwrite) NSTimeInterval delegateDelay; +/** + * Similates a @c openL2CAPChannelWithPSM: error. + * + * Setting this error to a value other than @c nil will simulate a failure when calling + * @c openL2CAPChannelWithPSM: and will call the + * @c gnc_peripheral:didOpenL2CAPChannel:error: delegate method with the provided error. + */ +@property(nonatomic, nullable, readwrite) NSError *openL2CAPChannelError; + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Tests/GNCFakePeripheral.m b/internal/platform/implementation/apple/Tests/GNCFakePeripheral.m index ebed3f24..b421e455 100644 --- a/internal/platform/implementation/apple/Tests/GNCFakePeripheral.m +++ b/internal/platform/implementation/apple/Tests/GNCFakePeripheral.m @@ -119,6 +119,18 @@ NS_ASSUME_NONNULL_BEGIN } } +- (void)openL2CAPChannelWithPSM:(uint16_t)PSM { + [self delayDelegateUsingBlock:^() { + if (_openL2CAPChannelError) { + CBL2CAPChannel *channel = [[CBL2CAPChannel alloc] init]; + [peripheralDelegate gnc_peripheral:self + didOpenL2CAPChannel:channel + error:_openL2CAPChannelError]; + } + // TODO: b/399815436 - Add testing for L2CAP channels if error is nil. + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/ble_medium.h b/internal/platform/implementation/apple/ble_medium.h index c7a6c4f5..86354c93 100644 --- a/internal/platform/implementation/apple/ble_medium.h +++ b/internal/platform/implementation/apple/ble_medium.h @@ -147,6 +147,18 @@ class BleMedium : public api::ble_v2::BleMedium { api::ble_v2::BlePeripheral &peripheral, CancellationFlag *cancellation_flag) override; + // TODO(b/290385712): cancellation_flag support is not yet implemented. + // + // Connects to a BLE peripheral over L2CAP. + // + // The peripheral must outlive the socket or undefined behavior will occur. The peripheral + // should not be modified by this method. + // + // On success, returns a new BleL2capSocket. On error, returns nullptr. + std::unique_ptr ConnectOverL2cap( + int psm, const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral &peripheral, CancellationFlag *cancellation_flag) override; + // Returns whether the hardware supports BOTH advertising extensions and extended scans. // // This is currently always false for all Apple hardware. diff --git a/internal/platform/implementation/apple/ble_medium.mm b/internal/platform/implementation/apple/ble_medium.mm index 2efefa21..5e07279b 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -33,6 +33,7 @@ #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h" #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h" #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h" +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h" #import "internal/platform/implementation/apple/ble_gatt_client.h" #import "internal/platform/implementation/apple/ble_gatt_server.h" #import "internal/platform/implementation/apple/ble_l2cap_server_socket.h" @@ -461,6 +462,24 @@ std::unique_ptr BleMedium::Connect(const std::string &se return std::move(socket); } +std::unique_ptr BleMedium::ConnectOverL2cap( + int psm, const std::string &service_id, api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::BlePeripheral &peripheral, CancellationFlag *cancellation_flag) { + // Check that the @c api::ble_v2::BlePeripheral is a @c nearby::apple::BlePeripheral and not a + // @c nearby::apple::EmptyBlePeripheral instance, so we can retreive the CBPeripheral object. + BlePeripheral *non_empty_peripheral = dynamic_cast(&peripheral); + if (non_empty_peripheral == nullptr) { + GTMLoggerError(@"[NEARBY] Failed to connect over L2CAP: peripheral is empty."); + return nullptr; + } + + // TODO: b/399815436 - Continue to add implementation for this method when BleL2capSocket is + // ready. + [medium_ openL2CAPChannelWithPSM:psm peripheral:non_empty_peripheral->GetPeripheral()]; + + return nullptr; +} + bool BleMedium::IsExtendedAdvertisementsAvailable() { return [medium_ supportsExtendedAdvertisements]; } diff --git a/internal/platform/implementation/ble_v2.h b/internal/platform/implementation/ble_v2.h index 6ca3d9a7..993c22d5 100644 --- a/internal/platform/implementation/ble_v2.h +++ b/internal/platform/implementation/ble_v2.h @@ -589,8 +589,9 @@ class BleMedium { // // On success, returns a new BleL2capSocket. // On error, returns nullptr. + // Platform implementation should override this method if it supports L2CAP. virtual std::unique_ptr ConnectOverL2cap( - const std::string& service_id, TxPowerLevel tx_power_level, + int psm, const std::string& service_id, TxPowerLevel tx_power_level, BlePeripheral& peripheral, CancellationFlag* cancellation_flag) { return nullptr; }