mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Refactor start listening channel to provide PSM and channel opened callback separately.
PiperOrigin-RevId: 750065027
This commit is contained in:
committed by
Copybara-Service
parent
15a59a7969
commit
d2144cac1b
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user