mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Add completion handler to opened L2CAP channel for iOS client.
PiperOrigin-RevId: 753363177
This commit is contained in:
committed by
Copybara-Service
parent
541bffc858
commit
fc0768f2c4
@@ -14,10 +14,16 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h"
|
||||
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Completion handler for opening an L2CAP stream.
|
||||
typedef void (^GNCOpenL2CAPStreamCompletionHandler)(GNCBLEL2CAPStream *_Nullable stream,
|
||||
NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked after a call to @c disconnect, requesting that the local connection to the
|
||||
* remote peripheral be cancelled.
|
||||
@@ -49,9 +55,11 @@ typedef void (^GNCRequestDisconnectionHandler)(id<GNCPeripheral> peripheral);
|
||||
* Opens a L2CAP channel with the @c PSM.
|
||||
*
|
||||
* @param PSM The PSM to use for opening the L2CAP channel.
|
||||
* @param completionHandler Called on a private queue with the opened L2CAP stream if successfully
|
||||
* opened or an error if one has occurred.
|
||||
*/
|
||||
// TODO: b/399815436 - Add CompletionHandler for this method.
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM;
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM
|
||||
completionHandler:(GNCOpenL2CAPStreamCompletionHandler)completionHandler;
|
||||
|
||||
/** Cancels an active or pending local connection to a peripheral. */
|
||||
- (void)disconnect;
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPClient";
|
||||
static char *const kGNCBLEL2CAPClientQueueLabel = "com.google.nearby.GNCBLEL2CAPClient";
|
||||
|
||||
@interface GNCBLEL2CAPClient () <GNCPeripheralDelegate>
|
||||
@end
|
||||
@@ -29,10 +30,11 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC
|
||||
@implementation GNCBLEL2CAPClient {
|
||||
dispatch_queue_t _queue;
|
||||
id<GNCPeripheral> _peripheral;
|
||||
GNCOpenL2CAPStreamCompletionHandler _completionHandler;
|
||||
GNCRequestDisconnectionHandler _requestDisconnectionHandler;
|
||||
|
||||
// The L2CAP channel that is used to send and receive data.
|
||||
CBL2CAPChannel *_l2CAPChannel;
|
||||
GNCBLEL2CAPStream *_l2CAPStream;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPeripheral:(id<GNCPeripheral>)peripheral
|
||||
@@ -58,8 +60,11 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC
|
||||
return self;
|
||||
};
|
||||
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM {
|
||||
[_peripheral openL2CAPChannelWithPSM:PSM];
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM
|
||||
completionHandler:(GNCOpenL2CAPStreamCompletionHandler)completionHandler {
|
||||
GTMLoggerInfo(@"[NEARBY] openL2CAPChannelWithPSM = %d", PSM);
|
||||
_completionHandler = [completionHandler copy];
|
||||
[_peripheral openL2CAPChannel:(CBL2CAPPSM)PSM];
|
||||
}
|
||||
|
||||
- (void)disconnect {
|
||||
@@ -70,16 +75,45 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC
|
||||
|
||||
#pragma mark - GNCPeripheralDelegate
|
||||
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral didOpenL2CAPChannel:(CBL2CAPChannel *)channel
|
||||
error:(NSError *)error {
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral
|
||||
didOpenL2CAPChannel:(CBL2CAPChannel *)channel
|
||||
error:(NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
if (error) {
|
||||
GTMLoggerError(@"[NEARBY] Failed to open L2CAP channel: %@", error);
|
||||
GTMLoggerDebug(
|
||||
@"[NEARBY] didOpenL2CAPChannel, channel: %@, inputStream: %@, outputStream: %@, error: %@",
|
||||
channel, channel.inputStream, channel.outputStream, error);
|
||||
// TODO: b/399815436 - channel.inputStream is null when doing testing. Refactor tests in the
|
||||
// future.
|
||||
if (error || (channel && (!channel.inputStream || !channel.outputStream))) {
|
||||
if (_completionHandler) {
|
||||
_completionHandler(nil, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
GTMLoggerInfo(@"[NEARBY] Opened L2CAP channel: %@", channel);
|
||||
|
||||
// Cleanup older references.
|
||||
if (_l2CAPStream || _l2CAPChannel) {
|
||||
// The watch may establish a new L2CAP socket connection while the old socket is still
|
||||
// connected if sysproxy stopped for a reason other than Bluetooth disconnection. Closing the
|
||||
// channel here ensures the controller and the client state is reset between the two
|
||||
// connection sessions.
|
||||
[self closeL2CAPChannel];
|
||||
}
|
||||
|
||||
_l2CAPChannel = channel;
|
||||
// TODO: b/399815436 - Implement to wrap up l2cap channel.
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
_l2CAPStream = [[GNCBLEL2CAPStream alloc]
|
||||
initWithClosedBlock:^{
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
// Indicates the L2CAP socket is closed. Clean up the resources used for the old
|
||||
// connection so that a new one can be established.
|
||||
[strongSelf closeL2CAPChannel];
|
||||
}
|
||||
inputStream:_l2CAPChannel.inputStream
|
||||
outputStream:_l2CAPChannel.outputStream];
|
||||
if (_completionHandler) {
|
||||
_completionHandler(_l2CAPStream, nil);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - CBPeripheralDelegate
|
||||
@@ -87,9 +121,24 @@ static char *const kGNCBLEL2CAPClientQueueLabel = "com.googlenearby.GNCBLEL2CAPC
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didOpenL2CAPChannel:(CBL2CAPChannel *)channel
|
||||
error:(NSError *)error {
|
||||
if ([self respondsToSelector:@selector(gnc_peripheral:didOpenL2CAPChannel:error:)]) {
|
||||
[self gnc_peripheral:peripheral didOpenL2CAPChannel:channel error:error];
|
||||
}
|
||||
dispatch_async(_queue, ^{
|
||||
if ([self respondsToSelector:@selector(gnc_peripheral:didOpenL2CAPChannel:error:)]) {
|
||||
[self gnc_peripheral:peripheral didOpenL2CAPChannel:channel error:error];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)shutDown {
|
||||
_peripheral = nil;
|
||||
_peripheral.peripheralDelegate = nil;
|
||||
}
|
||||
|
||||
- (void)closeL2CAPChannel {
|
||||
[_l2CAPStream tearDown];
|
||||
_l2CAPStream = nil;
|
||||
_l2CAPChannel = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -77,7 +77,6 @@ enum { READ_BUFFER_SIZE = 409600 };
|
||||
inputStream:(NSInputStream *)inputStream
|
||||
outputStream:(NSOutputStream *)outputStream {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_streamQueue = dispatch_queue_create("com.google.nearby.GNCBLEL2CAPStream",
|
||||
dispatch_queue_attr_make_with_qos_class(
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h"
|
||||
// #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h"
|
||||
|
||||
@class GNCBLEGATTServer;
|
||||
@class GNCBLEGATTClient;
|
||||
@@ -217,9 +218,12 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c
|
||||
*
|
||||
* @param PSM The PSM to use for opening the L2CAP channel.
|
||||
* @param remotePeripheral The peripheral to which the L2CAP channel is being opened.
|
||||
* @param completionHandler Called on a private queue with the opened L2CAP stream if successfully
|
||||
* opened or an error if one has occurred.
|
||||
*/
|
||||
// TODO: b/399815436 - Add CompletionHandler for this method.
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM peripheral:(id<GNCPeripheral>)remotePeripheral;
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM
|
||||
peripheral:(id<GNCPeripheral>)remotePeripheral
|
||||
completionHandler:(nullable GNCOpenL2CAPStreamCompletionHandler)completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
#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/GNCBLEL2CAPServer.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"
|
||||
@@ -221,7 +221,9 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
|
||||
});
|
||||
}
|
||||
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM peripheral:(id<GNCPeripheral>)remotePeripheral {
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)psm
|
||||
peripheral:(id<GNCPeripheral>)remotePeripheral
|
||||
completionHandler:(nullable GNCOpenL2CAPStreamCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_l2capClient) {
|
||||
_l2capClient =
|
||||
@@ -229,7 +231,16 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
|
||||
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral){
|
||||
}];
|
||||
}
|
||||
[_l2capClient openL2CAPChannelWithPSM:PSM];
|
||||
[_l2capClient openL2CAPChannelWithPSM:psm
|
||||
completionHandler:^void(GNCBLEL2CAPStream *_Nullable stream,
|
||||
NSError *_Nullable error) {
|
||||
if (!completionHandler) return;
|
||||
if (error) {
|
||||
completionHandler(nil, error);
|
||||
} else {
|
||||
completionHandler(stream, nil);
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @param PSM The PSM value to use for the L2CAP channel.
|
||||
*/
|
||||
- (void)openL2CAPChannelWithPSM:(uint16_t)PSM;
|
||||
- (void)openL2CAPChannel:(CBL2CAPPSM)PSM;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ objc_library(
|
||||
"GNCBLEGATTClientTest.m",
|
||||
"GNCBLEGATTServer+Testing.h",
|
||||
"GNCBLEGATTServerTest.m",
|
||||
"GNCBLEL2CAPClient+Testing.h",
|
||||
"GNCBLEL2CAPClientTest.m",
|
||||
"GNCBLEL2CAPConnectionTest.m",
|
||||
"GNCBLEL2CAPFakeInputOutputStream.h",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPClient.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCBLEL2CAPClient (Testing)
|
||||
|
||||
/**
|
||||
* Creates a L2CAP client with a provided peripheral.
|
||||
*
|
||||
* This is only exposed for testing and can be used to inject a fake peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral instance.
|
||||
* @param queue The queue to run on, this must match the queue that the peripheral's delegate is
|
||||
* running on. Defaults to the main queue when @c nil.
|
||||
* @param requestDisconnectionHandler Called on a private queue with @c peripheral when the
|
||||
* connection to the peripheral should be cancelled.
|
||||
*/
|
||||
-(instancetype)initWithPeripheral:(id<GNCPeripheral>)peripheral
|
||||
queue:(nullable dispatch_queue_t)queue
|
||||
requestDisconnectionHandler:(GNCRequestDisconnectionHandler)requestDisconnectionHandler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -19,30 +19,75 @@
|
||||
#import <XCTest/XCTest.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/GNCFakePeripheral.h"
|
||||
|
||||
@interface GNCBLEL2CAPClientTest : XCTestCase
|
||||
@property(nonatomic) GNCFakePeripheral *fakePeripheral;
|
||||
@property(nonatomic) GNCBLEL2CAPClient *l2capClient;
|
||||
@property(nonatomic) XCTestExpectation *requestDisconnectionHandlerExpectation;
|
||||
@end
|
||||
|
||||
@implementation GNCBLEL2CAPClientTest
|
||||
|
||||
#pragma mark - Tests
|
||||
|
||||
- (void)testDisconnect {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Disconnect."];
|
||||
|
||||
GNCBLEL2CAPClient *l2capClient =
|
||||
[[GNCBLEL2CAPClient alloc] initWithPeripheral:fakePeripheral
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
self.fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
self.l2capClient =
|
||||
[[GNCBLEL2CAPClient alloc] initWithPeripheral:self.fakePeripheral
|
||||
queue:nil
|
||||
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral) {
|
||||
XCTAssertNotNil(peripheral);
|
||||
[expectation fulfill];
|
||||
[self.requestDisconnectionHandlerExpectation fulfill];
|
||||
}];
|
||||
}
|
||||
|
||||
[l2capClient disconnect];
|
||||
- (void)tearDown {
|
||||
self.fakePeripheral = nil;
|
||||
self.l2capClient = nil;
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
- (void)testSuccessfulOpenL2CAPChannel {
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"L2CAP channel opened successfully"];
|
||||
uint16_t psm = 123;
|
||||
|
||||
[self.l2capClient openL2CAPChannelWithPSM:psm
|
||||
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
|
||||
XCTAssertNil(error, @"Error should be nil");
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
}
|
||||
|
||||
- (void)testOpenL2CAPChannelWithError {
|
||||
XCTestExpectation *expectation =
|
||||
[self expectationWithDescription:@"L2CAP channel open failed with error"];
|
||||
uint16_t psm = 123;
|
||||
NSError *expectedError = [NSError errorWithDomain:@"TestErrorDomain" code:1 userInfo:nil];
|
||||
self.fakePeripheral.openL2CAPChannelError = expectedError;
|
||||
|
||||
[self.l2capClient openL2CAPChannelWithPSM:psm
|
||||
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
|
||||
XCTAssertNotNil(error, @"Error should not be nil");
|
||||
XCTAssertEqualObjects(error, expectedError);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:1.0];
|
||||
}
|
||||
|
||||
- (void)testDisconnect {
|
||||
self.requestDisconnectionHandlerExpectation =
|
||||
[self expectationWithDescription:@"Request disconnection handler should be called"];
|
||||
|
||||
[self.l2capClient disconnect];
|
||||
|
||||
[self waitForExpectations:@[ self.requestDisconnectionHandlerExpectation ] timeout:1.0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -108,6 +108,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)openL2CAPChannel:(CBL2CAPPSM)PSM {
|
||||
[self delayDelegateUsingBlock:^() {
|
||||
CBL2CAPChannel *channel = [[CBL2CAPChannel alloc] init];
|
||||
if (_openL2CAPChannelError) {
|
||||
[peripheralDelegate gnc_peripheral:self
|
||||
didOpenL2CAPChannel:channel
|
||||
error:_openL2CAPChannelError];
|
||||
} else {
|
||||
[peripheralDelegate gnc_peripheral:self didOpenL2CAPChannel:channel error:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)delayDelegateUsingBlock:(void (^)())block {
|
||||
if (_delegateDelay <= 0) {
|
||||
block();
|
||||
@@ -119,18 +132,6 @@ 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
|
||||
|
||||
@@ -491,11 +491,30 @@ std::unique_ptr<api::ble_v2::BleL2capSocket> BleMedium::ConnectOverL2cap(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: b/399815436 - Continue to add implementation for this method when BleL2capSocket is
|
||||
// ready.
|
||||
[medium_ openL2CAPChannelWithPSM:psm peripheral:non_empty_peripheral->GetPeripheral()];
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block std::unique_ptr<BleL2capSocket> socket;
|
||||
std::string service_id_str = service_id;
|
||||
[medium_ openL2CAPChannelWithPSM:psm
|
||||
peripheral:non_empty_peripheral->GetPeripheral()
|
||||
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
|
||||
if (error) {
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
return;
|
||||
}
|
||||
GNCBLEL2CAPConnection *connection =
|
||||
[GNCBLEL2CAPConnection connectionWithStream:stream
|
||||
serviceID:@(service_id_str.c_str())
|
||||
incomingConnection:NO
|
||||
callbackQueue:dispatch_get_main_queue()];
|
||||
socket = std::make_unique<BleL2capSocket>(connection, non_empty_peripheral);
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
if (socket == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return std::move(socket);
|
||||
}
|
||||
|
||||
bool BleMedium::IsExtendedAdvertisementsAvailable() {
|
||||
|
||||
Reference in New Issue
Block a user