Refactor start listening channel to provide PSM and channel opened callback separately.

PiperOrigin-RevId: 750065027
This commit is contained in:
Edwin Wu
2025-04-21 23:43:07 -07:00
committed by Copybara-Service
parent 15a59a7969
commit d2144cac1b
13 changed files with 232 additions and 257 deletions
@@ -21,11 +21,21 @@
NS_ASSUME_NONNULL_BEGIN
/**
* Completion handler for starting listening for an L2CAP channel.
* A block to be invoked when the PSM is published.
*
* @param PSM The PSM value of the L2CAP channel.
* @param error The cause of the failure, or @c nil if no error occurred.
*/
typedef void (^GNCStartListeningL2CAPChannelCompletionHandler)(NSError *_Nullable error);
typedef void (^GNCOpenL2CAPServerPSMPublishedCompletionHandler)(uint16_t PSM,
NSError *_Nullable error);
/**
* A block to be invoked when a call to @c openL2CAPServerWithCompletionHandler: has completed.
*
* @param stream The successfully started L2CAP stream, or @c nil if an error occurred.
* @param error The cause of the failure, or @c nil if no error occurred.
*/
typedef void (^GNCOpenL2CAPServerChannelOpendCompletionHandler)(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error);
/**
* An object that publishes the @c PSM value.
@@ -50,10 +60,15 @@ typedef void (^GNCStartListeningL2CAPChannelCompletionHandler)(NSError *_Nullabl
/**
* Starts listening for an L2CAP channel.
*
* @param completionHandler The completion handler to call when the L2CAP channel is started.
* @param psmPublishedCompletionHandler The completion handler to call when the PSM is published.
* @param channelOpenedCompletionHandler The completion handler to call when the L2CAP channel is
* opened.
*/
- (void)startListeningChannelWithCompletionHandler:
(GNCStartListeningL2CAPChannelCompletionHandler)completionHandler;
- (void)startListeningChannelWithPSMPublishedCompletionHandler:
(GNCOpenL2CAPServerPSMPublishedCompletionHandler)psmPublishedCompletionHandler
channelOpenedCompletionHandler:
(GNCOpenL2CAPServerChannelOpendCompletionHandler)
channelOpenedCompletionHandler;
/**
* Closes the L2CAP channel.
@@ -34,7 +34,8 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
dispatch_queue_t _queue;
id<GNCPeripheralManager> _peripheralManager;
NSString *_serviceID;
GNCStartListeningL2CAPChannelCompletionHandler _startListeningL2CAPChannelcompletionHandler;
GNCOpenL2CAPServerPSMPublishedCompletionHandler _psmPublishedCompletionHandler;
GNCOpenL2CAPServerChannelOpendCompletionHandler _channelOpenedCompletionHandler;
CBL2CAPChannel *_l2CAPChannel;
GNCBLEL2CAPStream *_l2CAPStream;
@@ -62,14 +63,17 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
return self;
}
- (void)startListeningChannelWithCompletionHandler:
(GNCStartListeningL2CAPChannelCompletionHandler)completionHandler {
_startListeningL2CAPChannelcompletionHandler = [completionHandler copy];
- (void)startListeningChannelWithPSMPublishedCompletionHandler:
(GNCOpenL2CAPServerPSMPublishedCompletionHandler)psmPublishedCompletionHandler
channelOpenedCompletionHandler:
(GNCOpenL2CAPServerChannelOpendCompletionHandler)
channelOpenedCompletionHandler {
_psmPublishedCompletionHandler = [psmPublishedCompletionHandler copy];
_channelOpenedCompletionHandler = [channelOpenedCompletionHandler copy];
if (!_queue) {
_startListeningL2CAPChannelcompletionHandler([NSError
errorWithDomain:GNCBLEErrorDomain
code:GNCBLEErrorL2CAPListeningOnQueueNil
userInfo:nil]);
_psmPublishedCompletionHandler(0, [NSError errorWithDomain:GNCBLEErrorDomain
code:GNCBLEErrorL2CAPListeningOnQueueNil
userInfo:nil]);
return;
}
if (!_peripheralManager) {
@@ -123,14 +127,14 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
GTMLoggerDebug(@"[NEARBY] didPublishL2CAPChannel with PSM: %@", @(PSM));
if (error) {
GTMLoggerError(@"[NEARBY] Failed to publish L2CAP channel: %@", error);
if (_startListeningL2CAPChannelcompletionHandler) {
_startListeningL2CAPChannelcompletionHandler(error);
if (_psmPublishedCompletionHandler) {
_psmPublishedCompletionHandler(0, error);
}
return;
}
_PSM = PSM;
if (_startListeningL2CAPChannelcompletionHandler) {
_startListeningL2CAPChannelcompletionHandler(nil);
if (_psmPublishedCompletionHandler) {
_psmPublishedCompletionHandler(PSM, nil);
}
}
@@ -151,9 +155,14 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
dispatch_assert_queue(_queue);
GTMLoggerDebug(@"[NEARBY] didOpenL2CAPChannel");
if (error) {
GTMLoggerError(@"[NEARBY] Failed to open L2CAP channel: %@", error);
GTMLoggerDebug(
@"[NEARBY] didOpenL2CAPChannel, channel: %@, inputStream: %@, outputStream: %@, error: %@",
channel, channel.inputStream, channel.outputStream, error);
// TODO: edwinwu - channel.inputStream is null when doing testing. Refactor tests in the future.
if (error || (channel && (!channel.inputStream || !channel.outputStream))) {
if (_channelOpenedCompletionHandler) {
_channelOpenedCompletionHandler(nil, error);
}
return;
}
@@ -167,7 +176,19 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
}
_l2CAPChannel = channel;
// TODO: b/399815436 - Implement to wrap up l2cap channel with |GNCBLEL2CAPStream|.
__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 (_channelOpenedCompletionHandler) {
_channelOpenedCompletionHandler(_l2CAPStream, nil);
}
}
#pragma mark - CBPeripheralManagerDelegate
@@ -21,9 +21,6 @@ NS_ASSUME_NONNULL_BEGIN
/// Block invoked when the stream is closed.
typedef void (^GNCBLEL2CAPStreamClosedBlock)(void);
/// Block invoked when |data| is received from the remote device on the L2CAP connection.
typedef void (^GNCBLEL2CAPControllerReceivedDataBlock)(NSData *data);
/** Delegate for @c GNCBLEL2CAPStream. */
@protocol GNCBLEL2CAPStreamDelegate <NSObject>
@@ -40,7 +37,7 @@ typedef void (^GNCBLEL2CAPControllerReceivedDataBlock)(NSData *data);
- (void)stream:(GNCBLEL2CAPStream *)stream didDisconnectWithError:(NSError *_Nullable)error;
@end
/**
* Abstraction to take in two streams returned from L2CAP controller and provide a simplified
* interface to send and receive data from the streams.
@@ -55,10 +52,7 @@ typedef void (^GNCBLEL2CAPControllerReceivedDataBlock)(NSData *data);
/// and |outputStream|.
/// Invokes |closedBlock| if stream closed signal is received when reading data.
/// The stream should be torn down when this block is called.
/// Invokes |receivedDataBlock| with data received |inputStream|.
/// The blocks are invoked on an arbitrary queue with DISPATCH_QUEUE_PRIORITY_HIGH.
- (instancetype)initWithClosedBlock:(GNCBLEL2CAPStreamClosedBlock)closedBlock
receivedDataBlock:(GNCBLEL2CAPControllerReceivedDataBlock)receivedDataBlock
inputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream NS_DESIGNATED_INITIALIZER;
@@ -16,7 +16,7 @@
#import "GoogleToolboxForMac/GTMLogger.h"
#define READ_BUFFER_SIZE 409600
enum { READ_BUFFER_SIZE = 409600 };
/** A pending packet that will be written to the L2CAP socket. */
@interface GNCBLEL2CAPStreamWriteOperation : NSObject
@@ -51,9 +51,8 @@
@implementation GNCBLEL2CAPStream {
GNCBLEL2CAPStreamClosedBlock _closedBlock;
GNCBLEL2CAPControllerReceivedDataBlock _receivedDataBlock;
/// Serial queue used when invoking |_receivedDataBlock|.
/// Serial queue used when invoking delegate didReceiveData.
dispatch_queue_t _receivedDataQueue;
/// Queue used exclusively from events on |inputStream| and |outputStream|.
@@ -75,7 +74,6 @@
#pragma mark Public
- (instancetype)initWithClosedBlock:(GNCBLEL2CAPStreamClosedBlock)closedBlock
receivedDataBlock:(GNCBLEL2CAPControllerReceivedDataBlock)receivedDataBlock
inputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream {
self = [super init];
@@ -90,7 +88,6 @@
DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, -1));
_closedBlock = closedBlock;
_receivedDataBlock = receivedDataBlock;
_writeBufferArray = [NSMutableArray array];
@@ -321,8 +318,6 @@
dispatch_async(_receivedDataQueue, ^{
[_delegate stream:self didReceiveData:data];
// TODO: b/399815436 - Remove below once the delegate is implemented.
self->_receivedDataBlock(data);
});
} else if (bytesRead < 0) {
GTMLoggerError(@"[NEARBY] Stream read error: %@", self.inputStream.streamError);
@@ -15,6 +15,9 @@
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h"
@class GNCBLEGATTServer;
@class GNCBLEGATTClient;
@class GNCBLEGATTCharacteristic;
@@ -89,15 +92,6 @@ typedef void (^GNCGATTDisconnectionHandler)();
typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable client,
NSError *_Nullable error);
/**
* A block to be invoked when a call to @c openL2CAPServerWithCompletionHandler: has completed.
*
* @param server The successfully started L2CAP server, or @c nil if an error occurred.
* @param error The cause of the failure, or @c nil if no error occurred.
*/
typedef void (^GNCOpenL2CAPServerCompletionHandler)(GNCBLEL2CAPServer *_Nullable server,
NSError *_Nullable error);
/**
* The main BLE medium used inside of Nearby. This serves as the entry point for all BLE and GATT
* related operations.
@@ -204,12 +198,19 @@ typedef void (^GNCOpenL2CAPServerCompletionHandler)(GNCBLEL2CAPServer *_Nullable
/**
* Opens a L2CAP server.
*
* @param completionHandler Called on a private queue with the L2CAP server if successfully started
* or an error if one has occurred.
* @param peripheralManager The peripheral manager instance.
* @param psmPublishedCompletionHandler Called on a private queue with @c nil if the PSM has been
* published or an error if one has occurred.
* @param channelOpenedCompletionHandler Called on a private queue with the opened L2CAP stream if
* successfully opened or an error if one has occurred.
* @param peripheralManager The peripheral manager to use for the L2CAP server.
*/
- (void)openL2CAPServerWithCompletionHandler:(GNCOpenL2CAPServerCompletionHandler)completionHandler
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager;
- (void)openL2CAPServerWithPSMPublishedCompletionHandler:
(GNCOpenL2CAPServerPSMPublishedCompletionHandler)psmPublishedCompletionHandler
channelOpenedCompletionHandler:
(GNCOpenL2CAPServerChannelOpendCompletionHandler)
channelOpenedCompletionHandler
peripheralManager:
(nullable id<GNCPeripheralManager>)peripheralManager;
/**
* Opens a L2CAP channel with the @c PSM on the remote peripheral.
@@ -172,8 +172,7 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
});
}
- (void)resumeMediumScanning:
(nullable GNCStartScanningCompletionHandler)completionHandler {
- (void)resumeMediumScanning:(nullable GNCStartScanningCompletionHandler)completionHandler {
dispatch_async(_queue, ^{
[self internalStartScanningIfPoweredOn];
if (completionHandler) {
@@ -205,23 +204,20 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
});
}
- (void)openL2CAPServerWithCompletionHandler:(GNCOpenL2CAPServerCompletionHandler)completionHandler
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager {
// Capture the completion handler.
GNCOpenL2CAPServerCompletionHandler localCompletionHandler = completionHandler;
- (void)openL2CAPServerWithPSMPublishedCompletionHandler:
(GNCOpenL2CAPServerPSMPublishedCompletionHandler)psmPublishedCompletionHandler
channelOpenedCompletionHandler:
(GNCOpenL2CAPServerChannelOpendCompletionHandler)
channelOpenedCompletionHandler
peripheralManager:
(nullable id<GNCPeripheralManager>)peripheralManager {
dispatch_async(_queue, ^{
if (!_l2capServer) {
_l2capServer = CreateL2CapServer(peripheralManager);
}
__weak __typeof__(GNCBLEL2CAPServer *) weakL2capServer = _l2capServer;
[_l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
__typeof__(GNCBLEL2CAPServer *) strongL2capServer = weakL2capServer;
if (error) {
localCompletionHandler(nil, error);
return;
}
localCompletionHandler(strongL2capServer, nil);
}];
[_l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:psmPublishedCompletionHandler
channelOpenedCompletionHandler:channelOpenedCompletionHandler];
});
}
@@ -27,17 +27,26 @@
#pragma mark Tests
- (void)testPublishL2CAPChannelWhenStartListeningChannel {
- (void)testPublishL2CAPChannelAndOpenChannelWhenStartListeningChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
[channelOpenedexpectation fulfill];
}];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
}
- (void)testFailedToPublishL2CAPChannel {
@@ -50,10 +59,15 @@
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, fakePeripheralManager.didPublishL2CAPChannelError);
XCTAssertEqual([l2capServer PSM], 0);
}];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, fakePeripheralManager.didPublishL2CAPChannelError);
XCTAssertEqual(PSM, 0);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
}
- (void)testPoweredOffUnpublishesChannel {
@@ -63,10 +77,15 @@
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOff];
@@ -79,10 +98,15 @@
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
}
@@ -94,10 +118,15 @@
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer startListeningChannelWithCompletionHandler:^(NSError *error) {
XCTAssertEqual(error, nil);
XCTAssertEqual([l2capServer PSM], fakePeripheralManager.PSM);
}];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[l2capServer close];
@@ -21,6 +21,7 @@
@interface GNCBLEL2CAPStreamTest : XCTestCase
@end
// TODO: edwinwu - Add tests for the stream received delegate.
@implementation GNCBLEL2CAPStreamTest {
GNCBLEL2CAPFakeInputOutputStream* _fakeInputOutputStream;
GNCBLEL2CAPStream* _stream;
@@ -36,43 +37,6 @@
#pragma mark Tests
/// Tests that received data block called twice with two messages (in order) with size of the stream
/// buffer.
- (void)testReceivedDataBlockForTwoBufferSizeMessagesFromWatch {
// GIVEN
NSData* dummyData1 = [@"dummyData1" dataUsingEncoding:NSASCIIStringEncoding];
NSData* dummyData2 = [@"dummyData2" dataUsingEncoding:NSASCIIStringEncoding];
NSMutableData* expectedData = [NSMutableData dataWithData:dummyData1];
[expectedData appendData:dummyData2];
_fakeInputOutputStream =
[[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:dummyData1.length];
XCTestExpectation* expectation = [self expectationWithDescription:@"Received data from watch."];
expectation.expectedFulfillmentCount = 2;
NSMutableData* receivedData = [NSMutableData data];
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
XCTFail(@"Should not be invoked.");
}
receivedDataBlock:^(NSData* data) {
[receivedData appendData:data];
[expectation fulfill];
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
[_fakeInputOutputStream writeFromDevice:dummyData1];
[_fakeInputOutputStream writeFromDevice:dummyData2];
// THEN
[self waitForExpectations:@[ expectation ] timeout:1.0];
XCTAssertEqualObjects(receivedData, expectedData);
}
/// Tests data larger that buffer size is sent to watch despite chunking.
- (void)testSendDataWithSmallerStreamBuffer {
// GIVEN
@@ -83,11 +47,8 @@
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
receivedDataBlock:^(NSData* data) {
XCTFail(@"Should not call block.");
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
[_stream sendData:dummyData
@@ -108,11 +69,8 @@
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
receivedDataBlock:^(NSData* data) {
XCTFail(@"Should not call block.");
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
XCTestExpectation* completionExpectation =
@@ -137,11 +95,8 @@
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
receivedDataBlock:^(NSData* data) {
XCTFail(@"Should not call block.");
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
XCTestExpectation* noCompletionExpectation =
@@ -166,11 +121,8 @@
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
receivedDataBlock:^(NSData* data) {
XCTFail(@"Should not call block.");
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
XCTestExpectation* completionExpectation =
@@ -197,11 +149,8 @@
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
receivedDataBlock:^(NSData* data) {
XCTFail(@"Should not call block.");
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
XCTestExpectation* completionExpectation =
[self expectationWithDescription:@"Completion is called"];
@@ -232,11 +181,8 @@
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
receivedDataBlock:^(NSData* data) {
[expectation fulfill];
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
[_stream tearDown];
@@ -262,11 +208,8 @@
initWithClosedBlock:^{
[disconnectedExpectation fulfill];
}
receivedDataBlock:^(NSData* data) {
XCTFail(@"Should not call block.");
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
[_fakeInputOutputStream tearDown];
@@ -275,50 +218,4 @@
[self waitForExpectations:@[ disconnectedExpectation ] timeout:1.0];
}
/// Tests that received data block is not invoked concurrently and the data is passed in the order
/// it was written.
- (void)testForwardsReceivedDataSeriallyInCorrectOrder {
// GIVEN
NSMutableArray<NSData*>* testData = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
[testData addObject:[[NSString stringWithFormat:@"testData%@", @(i)]
dataUsingEncoding:NSASCIIStringEncoding]];
}
// Buffer size is as small as possible so that the stream does not combine the written packets.
_fakeInputOutputStream =
[[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:testData[0].length];
XCTestExpectation* receivedDataExpectation =
[self expectationWithDescription:@"Received data from watch."];
receivedDataExpectation.expectedFulfillmentCount = testData.count;
__block BOOL processingData = NO;
NSMutableArray<NSData*>* receivedData = [NSMutableArray array];
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
XCTFail(@"Should not be invoked.");
}
receivedDataBlock:^(NSData* data) {
XCTAssertFalse(processingData, @"Received data block must not be invoked concurrently");
processingData = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
[receivedData addObject:data];
[receivedDataExpectation fulfill];
});
processingData = NO;
}
inputStream:_fakeInputOutputStream.inputStream
outputStream:_fakeInputOutputStream.outputStream];
// WHEN
for (NSData* data in testData) {
[_fakeInputOutputStream writeFromDevice:data];
}
// THEN
[self waitForExpectations:@[ receivedDataExpectation ] timeout:1];
XCTAssertEqualObjects(receivedData, testData);
}
@end
@@ -279,21 +279,29 @@ static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
- (void)testOpenL2CAPServerSocket {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Open L2CAP server."];
XCTestExpectation *psmPublishedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
// Open L2CAP server is fully covered with @c GNCBLEL2CAPServer tests.
[medium
openL2CAPServerWithCompletionHandler:^(GNCBLEL2CAPServer *l2capServer, NSError *error) {
XCTAssertNotNil(l2capServer);
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *error) {
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
XCTAssertNil(error);
[expectation fulfill];
[psmPublishedexpectation fulfill];
}
peripheralManager:fakePeripheralManager];
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
XCTAssertNil(error);
[channelOpenedexpectation fulfill];
}
peripheralManager:fakePeripheralManager];
[self waitForExpectations:@[ expectation ] timeout:3];
[self waitForExpectations:@[ psmPublishedexpectation ] timeout:0.1];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
}
#pragma mark - Connect
@@ -56,6 +56,8 @@
@end
static const uint16_t kPSM = 192;
@implementation GNCFakePeripheralManager {
CBManagerState _state;
BOOL _isAdvertising;
@@ -65,6 +67,8 @@
@synthesize peripheralDelegate;
#pragma mark Public
- (instancetype)init {
self = [super init];
if (self) {
@@ -78,7 +82,7 @@
_state = CBManagerStateUnknown;
_advertisementData = nil;
_services = [[NSMutableArray alloc] init];
_PSM = 192;
_PSM = kPSM;
}
return self;
}
@@ -32,15 +32,15 @@ namespace apple {
// A BLE L2CAP server socket for listening incoming L2CAP socket.
class BleL2capServerSocket : public api::ble_v2::BleL2capServerSocket {
public:
// Creates a BLE L2CAP server socket.
//
// @param l2cap_server The L2CAP server to use.
explicit BleL2capServerSocket(GNCBLEL2CAPServer* l2cap_server);
BleL2capServerSocket() = default;
~BleL2capServerSocket() override = default;
// Gets PSM value has been published by the server.
int GetPSM() const override;
// Sets PSM value has been published by the server.
void SetPSM(int PSM);
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
@@ -53,10 +53,12 @@ class BleL2capServerSocket : public api::ble_v2::BleL2capServerSocket {
// Closes the L2CAP server socket.
Exception Close() override;
// Connects to the L2CAP server socket.
bool Connect(std::unique_ptr<BleL2capSocket> socket);
private:
// The L2CAP server to use for listening incoming L2CAP socket and publishing
// PSM value.
GNCBLEL2CAPServer* l2cap_server_;
// The PSM value of the L2CAP server socket.
int PSM_ = 0;
};
} // namespace apple
@@ -27,18 +27,21 @@
namespace nearby {
namespace apple {
BleL2capServerSocket::BleL2capServerSocket(GNCBLEL2CAPServer* l2cap_server)
: l2cap_server_(l2cap_server) {}
int BleL2capServerSocket::GetPSM() const { return PSM_; }
int BleL2capServerSocket::GetPSM() const { return [l2cap_server_ PSM]; }
void BleL2capServerSocket::SetPSM(int PSM) { PSM_ = PSM; }
std::unique_ptr<api::ble_v2::BleL2capSocket> BleL2capServerSocket::Accept() {
// TODO: edwinwu - Implement to wrap up l2cap channel with |GNCBLEL2CAPStream|.
// TODO: b/399815436 - Implement to accept incoming l2cap connection.
return nullptr;
}
bool BleL2capServerSocket::Connect(std::unique_ptr<BleL2capSocket> socket) {
// TODO: b/399815436 - Implement to connect to l2cap server socket.
return false;
}
Exception BleL2capServerSocket::Close() {
[l2cap_server_ close];
return {Exception::kSuccess};
}
@@ -13,7 +13,6 @@
// limitations under the License.
#import "internal/platform/implementation/apple/ble_medium.h"
#import "internal/platform/implementation/apple/utils.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
@@ -31,26 +30,29 @@
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.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/GNCBLEL2CAPClient.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPConnection.h"
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.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"
#import "internal/platform/implementation/apple/ble_peripheral.h"
#import "internal/platform/implementation/apple/ble_server_socket.h"
#import "internal/platform/implementation/apple/ble_socket.h"
#import "internal/platform/implementation/apple/bluetooth_adapter_v2.h"
#import "GoogleToolboxForMac/GTMLogger.h"
// TODO(b/293336684): Old Weave imports that need to be deleted once shared Weave is complete.
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h"
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleUtils.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralPeerManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralServiceManager.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"
#import "internal/platform/implementation/apple/ble_l2cap_socket.h"
#import "internal/platform/implementation/apple/ble_peripheral.h"
#import "internal/platform/implementation/apple/ble_server_socket.h"
#import "internal/platform/implementation/apple/ble_socket.h"
#import "internal/platform/implementation/apple/bluetooth_adapter_v2.h"
#import "internal/platform/implementation/apple/utils.h"
#import "GoogleToolboxForMac/GTMLogger.h"
static NSString *const kWeaveServiceUUID = @"FEF3";
@@ -267,20 +269,18 @@ bool BleMedium::StopScanning() {
return blockError == nil;
}
bool BleMedium::PauseMediumScanning() {
return StopScanning();
}
bool BleMedium::PauseMediumScanning() { return StopScanning(); }
bool BleMedium::ResumeMediumScanning() {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[medium_ resumeMediumScanning:^(NSError *error) {
if (error != nil) {
GTMLoggerError(@"Failed to start scanning for multiple services: %@", error);
blockError = error;
}
dispatch_semaphore_signal(semaphore);
}];
if (error != nil) {
GTMLoggerError(@"Failed to start scanning for multiple services: %@", error);
blockError = error;
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return blockError == nil;
}
@@ -386,26 +386,36 @@ std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
std::unique_ptr<api::ble_v2::BleL2capServerSocket> BleMedium::OpenL2capServerSocket(
const std::string &service_id) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block GNCBLEL2CAPServer *block_l2cap_server = nil;
__block NSError *blockPSMPublishedError = nil;
auto l2cap_server_socket = std::make_unique<BleL2capServerSocket>();
__block auto l2cap_server_socket_ptr = l2cap_server_socket.get();
std::string service_id_str = service_id;
[medium_
openL2CAPServerWithCompletionHandler:^(GNCBLEL2CAPServer *server, NSError *error) {
if (error != nil) {
GTMLoggerError(@"Error opening L2CAP server: %@", error);
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *_Nullable error) {
if (error) {
blockPSMPublishedError = error;
dispatch_semaphore_signal(semaphore);
return;
}
block_l2cap_server = server;
l2cap_server_socket_ptr->SetPSM(PSM);
dispatch_semaphore_signal(semaphore);
}
peripheralManager:nil];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC)) != 0) {
GTMLoggerError(@"Opening L2CAP server timed out.");
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
if (error != nil) {
GTMLoggerError(@"Error opening L2CAP channel in L2CAP server: %@", error);
return;
}
// TODO: b/399815436 - Implement to create socket when stream is ready.
}
peripheralManager:nil];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (blockPSMPublishedError != nil) {
return nullptr;
}
if (!block_l2cap_server) {
return nullptr;
}
return std::make_unique<BleL2capServerSocket>(block_l2cap_server);
}
return std::move(l2cap_server_socket);
}
// TODO(b/290385712): Add support for @c cancellation_flag.
// TODO(b/293336684): Old Weave code that need to be deleted once shared Weave is complete.
std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string &service_id,