mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
[BLE Refactor] Implement iOS BLE incoming connection.
PiperOrigin-RevId: 457110588
This commit is contained in:
committed by
Copybara-Service
parent
75aa59d57e
commit
9ce81d8534
@@ -395,8 +395,9 @@ bool BleV2::StartAcceptingConnections(const std::string& service_id,
|
||||
// listening for new incoming connections until StopAcceptingConnections() is
|
||||
// invoked.
|
||||
accept_loops_runner_.Execute(
|
||||
"ble-accept", [this, &service_id, callback = std::move(callback),
|
||||
server_socket = std::move(owned_server_socket)]() mutable {
|
||||
"ble-accept",
|
||||
[this, service_id = service_id, callback = std::move(callback),
|
||||
server_socket = std::move(owned_server_socket)]() mutable {
|
||||
while (true) {
|
||||
BleV2Socket client_socket = server_socket.Accept();
|
||||
if (!client_socket.IsValid()) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
load("//tools/build_defs/apple:objc.bzl", "objc_proto_library")
|
||||
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,7 +21,7 @@ objc_library(
|
||||
"Ble/GNCMBleCentral.m",
|
||||
"Ble/GNCMBleConnection.m",
|
||||
"Ble/GNCMBlePeripheral.m",
|
||||
"Ble/GNCMBleUtils.m",
|
||||
"Ble/GNCMBleUtils.mm",
|
||||
"GNCLeaks.m",
|
||||
"GNCMConnection.m",
|
||||
"WifiLan/GNCMBonjourBrowser.m",
|
||||
@@ -44,24 +42,12 @@ objc_library(
|
||||
"WifiLan/GNCMBonjourUtils.h",
|
||||
],
|
||||
deps = [
|
||||
":ObjCProtos",
|
||||
"//internal/platform/implementation/ios:Shared",
|
||||
"//internal/platform/implementation/ios/Mediums/Ble/Sockets:Central",
|
||||
"//internal/platform/implementation/ios/Mediums/Ble/Sockets:Peripheral",
|
||||
"//internal/platform/implementation/ios/Mediums/Ble/Sockets:Shared",
|
||||
"//proto/mediums:ble_frames_cc_proto",
|
||||
"//third_party/objective_c/google_toolbox_for_mac:GTM_Logger",
|
||||
"@com_google_absl//absl/numeric:int128",
|
||||
],
|
||||
)
|
||||
|
||||
objc_proto_library(
|
||||
name = "ObjCProtos",
|
||||
deps = [":Protos"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "Protos",
|
||||
deps = [
|
||||
"//connections/implementation/proto:offline_wire_formats_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -25,8 +25,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface GNCMBleConnection : NSObject <GNCMConnection>
|
||||
@property(nonatomic) GNCMConnectionHandlers *connectionHandlers;
|
||||
|
||||
/**
|
||||
* Creates a |GNCMBleConnectiom|.
|
||||
*
|
||||
* @param socket A |GNSSocket| instance.
|
||||
* @param serviceID A string that uniquely identifies the service.
|
||||
* @param expectedIntroPacket A flag to indicate the connection is expecting the
|
||||
* introduction packet.
|
||||
* @param callbackQueue The queue on which all callbacks are made.
|
||||
*/
|
||||
+ (instancetype)connectionWithSocket:(GNSSocket *)socket
|
||||
serviceId:(NSString *)serviceId
|
||||
serviceID:(nullable NSString *)serviceID
|
||||
expectedIntroPacket:(BOOL)expectedIntroPacket
|
||||
callbackQueue:(dispatch_queue_t)callbackQueue;
|
||||
@end
|
||||
|
||||
|
||||
@@ -25,21 +25,25 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface GNCMBleConnection () <GNSSocketDelegate>
|
||||
@property(nonatomic) dispatch_queue_t selfQueue;
|
||||
@property(nonatomic) GNSSocket *socket;
|
||||
@property(nonatomic) NSData *serviceIdHash;
|
||||
@property(nonatomic) NSData *serviceIDHash;
|
||||
@property(nonatomic) dispatch_queue_t callbackQueue;
|
||||
@property(nonatomic) BOOL expectedIntroPacket;
|
||||
@property(nonatomic) BOOL receivedIntroPacket;
|
||||
@end
|
||||
|
||||
@implementation GNCMBleConnection
|
||||
|
||||
+ (instancetype)connectionWithSocket:(GNSSocket *)socket
|
||||
serviceId:(NSString *)serviceId
|
||||
serviceID:(nullable NSString *)serviceID
|
||||
expectedIntroPacket:(BOOL)expectedIntroPacket
|
||||
callbackQueue:(dispatch_queue_t)callbackQueue {
|
||||
GNCMBleConnection *connection = [[GNCMBleConnection alloc] init];
|
||||
connection.socket = socket;
|
||||
socket.delegate = connection;
|
||||
connection.serviceIdHash = GNCMServiceIdHash(serviceId);
|
||||
connection.serviceIDHash = serviceID ? GNCMServiceIDHash(serviceID) : nil;
|
||||
connection.callbackQueue = callbackQueue;
|
||||
connection.selfQueue = dispatch_queue_create("GNCMBleConnectionQueue", DISPATCH_QUEUE_SERIAL);
|
||||
connection.expectedIntroPacket = expectedIntroPacket;
|
||||
return connection;
|
||||
}
|
||||
|
||||
@@ -49,16 +53,26 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
#pragma mark GNCMConnection
|
||||
|
||||
- (void)sendData:(NSData *)data
|
||||
progressHandler:(GNCMProgressHandler)progressHandler
|
||||
completion:(GNCMPayloadResultHandler)completion {
|
||||
dispatch_async(_selfQueue, ^{
|
||||
[_socket sendData:data
|
||||
NSMutableData *packet;
|
||||
if (data.length == 0) {
|
||||
// Get the Control introduction packet if data length is 0.
|
||||
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
|
||||
packet = [NSMutableData dataWithData:introData];
|
||||
} else {
|
||||
// Prefix the service ID hash.
|
||||
packet = [NSMutableData dataWithData:_serviceIDHash];
|
||||
[packet appendData:data];
|
||||
}
|
||||
|
||||
[_socket sendData:packet
|
||||
progressHandler:^(float progress) {
|
||||
// Convert normalized progress value to number of bytes.
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
progressHandler((size_t)(progress * data.length));
|
||||
progressHandler((size_t)(progress * packet.length));
|
||||
});
|
||||
}
|
||||
completion:^(NSError *error) {
|
||||
@@ -86,10 +100,43 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
- (void)socket:(GNSSocket *)socket didReceiveData:(NSData *)data {
|
||||
// Extract the service ID prefix from each data packet.
|
||||
NSMutableData *packet;
|
||||
NSUInteger prefixLength = _serviceIDHash.length;
|
||||
if (_expectedIntroPacket && !_receivedIntroPacket) {
|
||||
// Check if the first packet is intro packet.
|
||||
if (!_serviceIDHash) {
|
||||
// If _serviceIdHash is nil, then we need to parse the first incoming packet if it conforms to
|
||||
// introducion packet and extract the serviceIdHash for coming packets.
|
||||
NSData *serviceIDHash = GNCMParseBLEFramesIntroductionPacket(data);
|
||||
if (serviceIDHash) {
|
||||
_serviceIDHash = serviceIDHash;
|
||||
_receivedIntroPacket = YES;
|
||||
} else {
|
||||
GTMLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
|
||||
}
|
||||
} else {
|
||||
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
|
||||
if ([data isEqual:introData]) {
|
||||
_receivedIntroPacket = YES;
|
||||
} else {
|
||||
GTMLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
|
||||
GTMLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded");
|
||||
return;
|
||||
}
|
||||
packet = [NSMutableData
|
||||
dataWithData:[data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)]];
|
||||
|
||||
dispatch_async(_selfQueue, ^{
|
||||
if (_connectionHandlers.payloadHandler) {
|
||||
dispatch_async(_callbackQueue, ^{
|
||||
_connectionHandlers.payloadHandler(data);
|
||||
_connectionHandlers.payloadHandler(packet);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/ios/Mediums/GNCMConnection.h"
|
||||
|
||||
@class CBCharacteristic;
|
||||
@class CBUUID;
|
||||
|
||||
@@ -63,9 +65,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @param serviceUUID A string that uniquely identifies the advertised service to search for.
|
||||
* @param advertisementData The data to advertise.
|
||||
* @param endpointconnectedHandler The handler that is called when a discoverer connects.
|
||||
* @param callbackQueue The queue on which all callbacks are made. If |callbackQueue| is not
|
||||
* provided, then the main queue is used in the function.
|
||||
*/
|
||||
- (BOOL)startAdvertisingWithServiceUUID:(NSString *)serviceUUID
|
||||
advertisementData:(NSData *)advertisementData;
|
||||
advertisementData:(NSData *)advertisementData
|
||||
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler
|
||||
callbackQueue:(nullable dispatch_queue_t)callbackQueue;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
#import "internal/platform/implementation/ios/GNCUtils.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/GNCMBleConnection.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/GNCMBleUtils.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralManager.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/GNCMConnection.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -45,6 +50,16 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
dispatch_queue_t _selfQueue;
|
||||
/** Peripheral state for stop or advertising. */
|
||||
GNCMPeripheralState _state;
|
||||
/** Peripheral manager used for socket connection based on weave protocol. */
|
||||
GNSPeripheralManager *_socketPeripheralManager;
|
||||
/** Peripheral service manager used to manage one BLE service. */
|
||||
GNSPeripheralServiceManager *_socketPeripheralServiceManager;
|
||||
/** Client callback queue. If client doesn't assign it, then use main queue. */
|
||||
dispatch_queue_t _clientCallbackQueue;
|
||||
/** Internal async priority queue. */
|
||||
dispatch_queue_t _internalCallbackQueue;
|
||||
/** Flag to disable callback for dealloc. */
|
||||
BOOL _callbacksEnabled;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
@@ -53,11 +68,6 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
// Bluetooth also use this queue.
|
||||
_selfQueue = dispatch_queue_create("GNCPeripheralManagerQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// Set up the peripheral manager for the advertisement data.
|
||||
_peripheralManager = [[CBPeripheralManager alloc]
|
||||
initWithDelegate:self
|
||||
queue:_selfQueue
|
||||
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
|
||||
_state = GNCMPeripheralStateStopped;
|
||||
}
|
||||
return self;
|
||||
@@ -69,6 +79,8 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
// must never be captured by any escaping block used in this class.
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
[self stopAdvertisingInternal];
|
||||
|
||||
_callbacksEnabled = NO;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,12 +116,55 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
}
|
||||
|
||||
- (BOOL)startAdvertisingWithServiceUUID:(NSString *)serviceUUID
|
||||
advertisementData:(NSData *)advertisementData {
|
||||
advertisementData:(NSData *)advertisementData
|
||||
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler
|
||||
callbackQueue:(nullable dispatch_queue_t)callbackQueue {
|
||||
NSLog(@"[NEARBY] Client rquests startAdvertising");
|
||||
// The client may be using the callback queue for other purposes, so wrap it with a private
|
||||
// queue to know with certainty when all callbacks are done.
|
||||
_clientCallbackQueue = callbackQueue ?: dispatch_get_main_queue();
|
||||
_internalCallbackQueue =
|
||||
dispatch_queue_create("GNCMBlePeripheralCallbackQueue", DISPATCH_QUEUE_PRIORITY_DEFAULT);
|
||||
_callbacksEnabled = YES;
|
||||
|
||||
_advertisementService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:serviceUUID]
|
||||
primary:YES];
|
||||
_advertisementData = [advertisementData copy];
|
||||
|
||||
// To make this class thread-safe, use a serial queue for all state changes, and have Core
|
||||
// Bluetooth also use this queue.
|
||||
_selfQueue = dispatch_queue_create("GNCPeripheralManagerQueue", DISPATCH_QUEUE_SERIAL);
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
// Set up the peripheral manager for the socket. This must be done before creating the
|
||||
// peripheral manager for the advertisement data because it's started/stopped in the
|
||||
// -peripheralManagerDidUpdateState: callback.
|
||||
_socketPeripheralServiceManager = [[GNSPeripheralServiceManager alloc]
|
||||
initWithBleServiceUUID:_advertisementService.UUID
|
||||
addPairingCharacteristic:NO
|
||||
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
|
||||
// Call the connection handler when the socket has connected or fails to connect.
|
||||
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
|
||||
[weakSelf establishConnectionWithSocket:socket
|
||||
didConnect:didConnect
|
||||
endpointConnectedHandler:endpointConnectedHandler];
|
||||
});
|
||||
return YES;
|
||||
}
|
||||
queue:_selfQueue];
|
||||
_socketPeripheralManager = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
|
||||
restoreIdentifier:nil
|
||||
queue:_selfQueue];
|
||||
[_socketPeripheralManager addPeripheralServiceManager:_socketPeripheralServiceManager
|
||||
bleServiceAddedCompletion:^(NSError *error) {
|
||||
NSLog(@"Failed to add service");
|
||||
}];
|
||||
|
||||
// Set up the peripheral manager for the advertisement data.
|
||||
_peripheralManager = [[CBPeripheralManager alloc]
|
||||
initWithDelegate:self
|
||||
queue:_selfQueue
|
||||
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
|
||||
|
||||
if (_GATTService) {
|
||||
if (_gattCharacteristics && _gattCharacteristics.count > 0) {
|
||||
_GATTService.characteristics = _gattCharacteristics;
|
||||
@@ -126,6 +181,7 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
if (peripheral.state == CBManagerStatePoweredOn && !peripheral.isAdvertising &&
|
||||
_state == GNCMPeripheralStateAdvertising) {
|
||||
NSLog(@"[NEARBY] CBPeripheralManager powered on; starting advertising");
|
||||
[_socketPeripheralManager start];
|
||||
[self startAdvertisingInternal];
|
||||
} else {
|
||||
NSLog(@"[NEARBY] CBPeripheralManager not powered on; stopping advertising");
|
||||
@@ -149,7 +205,7 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
NSLog(@"[NEARBY] peripheralManager:didReceiveReadRequest");
|
||||
NSLog(@"[NEARBY] peripheralManager:didReceiveReadRequest");
|
||||
// This is called when a central asks to read a characteristic's value.
|
||||
CBATTError error = CBATTErrorAttributeNotFound;
|
||||
NSData *value = _gattCharacteristicValues[request.characteristic.UUID];
|
||||
@@ -196,6 +252,42 @@ typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects with socket and callback the |GNCMBleConnection| is established or nil if it is not.
|
||||
*/
|
||||
- (void)establishConnectionWithSocket:(GNSSocket *)socket
|
||||
didConnect:(BOOL)didConnect
|
||||
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler {
|
||||
if (!_callbacksEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self callbackAsync:^{
|
||||
if (!didConnect) {
|
||||
NSLog(@"[NEARBY] Peripheral failed to create BLE socket");
|
||||
endpointConnectedHandler(nil);
|
||||
} else {
|
||||
GNCMBleConnection *connection = [GNCMBleConnection connectionWithSocket:socket
|
||||
serviceID:nil
|
||||
expectedIntroPacket:YES
|
||||
callbackQueue:_clientCallbackQueue];
|
||||
connection.connectionHandlers = endpointConnectedHandler(connection);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the specified block on the callback queue, preventing it from being dispatched to the
|
||||
* client callback queue when callbacks are disabled. And without capturing |self|, since
|
||||
* callbacks are disabled in dealloc.
|
||||
*/
|
||||
- (void)callbackAsync:(dispatch_block_t)block {
|
||||
dispatch_queue_t clientCallbackQueue = _clientCallbackQueue; // don't capture |self|
|
||||
dispatch_async(_internalCallbackQueue, ^{
|
||||
dispatch_sync(clientCallbackQueue, block);
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -27,11 +27,23 @@ extern "C" {
|
||||
/** Required lengths of certain BLE advertisement fields. */
|
||||
typedef NS_ENUM(NSUInteger, GNCMBleAdvertisementLength) {
|
||||
/** Length of service ID hash data object, used in the BLE advertisement and the packet prefix. */
|
||||
GNCMBleAdvertisementLengthServiceIdHash = 3,
|
||||
GNCMBleAdvertisementLengthServiceIDHash = 3,
|
||||
};
|
||||
|
||||
/** Computes a hash from a service ID string. It is used in the NC BLE advertisement. */
|
||||
NSData *GNCMServiceIdHash(NSString *serviceId);
|
||||
/** Computes a hash from a service ID string. */
|
||||
NSData *GNCMServiceIDHash(NSString *serviceID);
|
||||
|
||||
/** Creates the introduction packet for Ble SocketControlFrame. */
|
||||
NSData *GNCMGenerateBLEFramesIntroductionPacket(NSData *serviceIDHash);
|
||||
|
||||
/**
|
||||
* Parses the packet for Ble SocketControlFrame introduction packet and returns
|
||||
* serviceIdHash if succeed.
|
||||
*/
|
||||
NSData *GNCMParseBLEFramesIntroductionPacket(NSData *data);
|
||||
|
||||
/** Creates the disconnection packet for Ble SocketControlFrame. */
|
||||
NSData *GNCMGenerateBLEFramesDisconnectionPacket(NSData *serviceIDHash);
|
||||
|
||||
/**
|
||||
* Calls the completion handler with (a) YES if the GNSSocket connected, or (b) NO if it failed to
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright 2022 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/ios/Mediums/Ble/GNCMBleUtils.h"
|
||||
|
||||
#import "internal/platform/implementation/ios/GNCUtils.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/Sockets/Source/Shared/GNSSocket.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static const NSTimeInterval kBleSocketConnectionTimeout = 5.0;
|
||||
|
||||
NSData *GNCMServiceIdHash(NSString *serviceId) {
|
||||
return [GNCSha256String(serviceId)
|
||||
subdataWithRange:NSMakeRange(0, GNCMBleAdvertisementLengthServiceIdHash)];
|
||||
}
|
||||
|
||||
@interface GNCMBleSocketDelegate : NSObject <GNSSocketDelegate>
|
||||
@property(nonatomic) GNCMBoolHandler connectedHandler;
|
||||
@end
|
||||
|
||||
@implementation GNCMBleSocketDelegate
|
||||
|
||||
+ (instancetype)delegateWithConnectedHandler:(GNCMBoolHandler)connectedHandler {
|
||||
GNCMBleSocketDelegate *connection = [[GNCMBleSocketDelegate alloc] init];
|
||||
connection.connectedHandler = connectedHandler;
|
||||
return connection;
|
||||
}
|
||||
|
||||
#pragma mark - GNSSocketDelegate
|
||||
|
||||
- (void)socketDidConnect:(GNSSocket *)socket {
|
||||
_connectedHandler(YES);
|
||||
}
|
||||
|
||||
- (void)socket:(GNSSocket *)socket didDisconnectWithError:(NSError *)error {
|
||||
_connectedHandler(NO);
|
||||
}
|
||||
|
||||
- (void)socket:(GNSSocket *)socket didReceiveData:(NSData *)data {
|
||||
GTMLoggerError(@"Unexpected -didReceiveData: call");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion) {
|
||||
// This function passes YES to the completion when the socket has successfully connected, and
|
||||
// otherwise passes NO to the completion after a timeout of several seconds. We shouldn't retain
|
||||
// the completion after it's been called, so store it in a __block variable and nil it out once
|
||||
// the socket has connected.
|
||||
__block GNCMBoolHandler completionRef = completion;
|
||||
|
||||
// The delegate listens for the socket connection callbacks. It's retained by the block passed to
|
||||
// dispatch_after below, so it will live long enough to do its job.
|
||||
GNCMBleSocketDelegate *delegate =
|
||||
[GNCMBleSocketDelegate delegateWithConnectedHandler:^(BOOL didConnect) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (completionRef) completionRef(didConnect);
|
||||
completionRef = nil;
|
||||
});
|
||||
}];
|
||||
socket.delegate = delegate;
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBleSocketConnectionTimeout * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
(void)delegate; // make sure it's retained until the timeout
|
||||
if (completionRef) completionRef(NO);
|
||||
});
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2022 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/ios/Mediums/Ble/GNCMBleUtils.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#import "internal/platform/implementation/ios/GNCUtils.h"
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/Sockets/Source/Shared/GNSSocket.h"
|
||||
#include "proto/mediums/ble_frames.pb.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static const uint8_t kGNCMControlPacketServiceIDHash[] = {0x00, 0x00, 0x00};
|
||||
static const NSTimeInterval kBleSocketConnectionTimeout = 5.0;
|
||||
|
||||
NSData *GNCMServiceIDHash(NSString *serviceID) {
|
||||
return [GNCSha256String(serviceID)
|
||||
subdataWithRange:NSMakeRange(0, GNCMBleAdvertisementLengthServiceIDHash)];
|
||||
}
|
||||
|
||||
NSData *GNCMGenerateBLEFramesIntroductionPacket(NSData *serviceIDHash) {
|
||||
::location::nearby::mediums::SocketControlFrame socket_control_frame;
|
||||
|
||||
socket_control_frame.set_type(::location::nearby::mediums::SocketControlFrame::INTRODUCTION);
|
||||
auto *introduction_frame = socket_control_frame.mutable_introduction();
|
||||
introduction_frame->set_socket_version(::location::nearby::mediums::SocketVersion::V2);
|
||||
std::string service_id_hash((char *)serviceIDHash.bytes, (size_t)serviceIDHash.length);
|
||||
introduction_frame->set_service_id_hash(service_id_hash);
|
||||
|
||||
NSMutableData *packet = [NSMutableData dataWithBytes:kGNCMControlPacketServiceIDHash
|
||||
length:sizeof(kGNCMControlPacketServiceIDHash)];
|
||||
std::ostringstream stream;
|
||||
socket_control_frame.SerializeToOstream(&stream);
|
||||
NSData *frameData = [NSData dataWithBytes:stream.str().data() length:stream.str().length()];
|
||||
[packet appendData:frameData];
|
||||
return packet;
|
||||
}
|
||||
|
||||
NSData *GNCMParseBLEFramesIntroductionPacket(NSData *data) {
|
||||
::location::nearby::mediums::SocketControlFrame socket_control_frame;
|
||||
NSUInteger prefixLength = sizeof(kGNCMControlPacketServiceIDHash);
|
||||
NSData *packet = [data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)];
|
||||
if (socket_control_frame.ParseFromArray(packet.bytes, (int)packet.length)) {
|
||||
if (socket_control_frame.type() ==
|
||||
::location::nearby::mediums::SocketControlFrame::INTRODUCTION &&
|
||||
socket_control_frame.has_introduction() &&
|
||||
socket_control_frame.introduction().has_socket_version() &&
|
||||
socket_control_frame.introduction().socket_version() ==
|
||||
::location::nearby::mediums::SocketVersion::V2 &&
|
||||
socket_control_frame.introduction().has_service_id_hash()) {
|
||||
std::string service_id_hash = socket_control_frame.introduction().service_id_hash();
|
||||
return [NSData dataWithBytes:service_id_hash.data() length:service_id_hash.length()];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *GNCMGenerateBLEFramesDisconnectionPacket(NSData *serviceIDHash) {
|
||||
::location::nearby::mediums::SocketControlFrame socket_control_frame;
|
||||
|
||||
socket_control_frame.set_type(::location::nearby::mediums::SocketControlFrame::DISCONNECTION);
|
||||
auto *disconnection_frame = socket_control_frame.mutable_disconnection();
|
||||
std::string service_id_hash((char *)serviceIDHash.bytes, (size_t)serviceIDHash.length);
|
||||
disconnection_frame->set_service_id_hash(service_id_hash);
|
||||
|
||||
NSMutableData *packet = [NSMutableData dataWithBytes:kGNCMControlPacketServiceIDHash
|
||||
length:sizeof(kGNCMControlPacketServiceIDHash)];
|
||||
std::ostringstream stream;
|
||||
socket_control_frame.SerializeToOstream(&stream);
|
||||
NSData *frameData = [NSData dataWithBytes:stream.str().data() length:stream.str().length()];
|
||||
[packet appendData:frameData];
|
||||
return packet;
|
||||
}
|
||||
|
||||
@interface GNCMBleSocketDelegate : NSObject <GNSSocketDelegate>
|
||||
@property(nonatomic) GNCMBoolHandler connectedHandler;
|
||||
@end
|
||||
|
||||
@implementation GNCMBleSocketDelegate
|
||||
|
||||
+ (instancetype)delegateWithConnectedHandler:(GNCMBoolHandler)connectedHandler {
|
||||
GNCMBleSocketDelegate *connection = [[GNCMBleSocketDelegate alloc] init];
|
||||
connection.connectedHandler = connectedHandler;
|
||||
return connection;
|
||||
}
|
||||
|
||||
#pragma mark - GNSSocketDelegate
|
||||
|
||||
- (void)socketDidConnect:(GNSSocket *)socket {
|
||||
_connectedHandler(YES);
|
||||
}
|
||||
|
||||
- (void)socket:(GNSSocket *)socket didDisconnectWithError:(NSError *)error {
|
||||
_connectedHandler(NO);
|
||||
}
|
||||
|
||||
- (void)socket:(GNSSocket *)socket didReceiveData:(NSData *)data {
|
||||
GTMLoggerError(@"Unexpected -didReceiveData: call");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion) {
|
||||
// This function passes YES to the completion when the socket has successfully connected, and
|
||||
// otherwise passes NO to the completion after a timeout of several seconds. We shouldn't retain
|
||||
// the completion after it's been called, so store it in a __block variable and nil it out once
|
||||
// the socket has connected.
|
||||
__block GNCMBoolHandler completionRef = completion;
|
||||
|
||||
// The delegate listens for the socket connection callbacks. It's retained by the block passed to
|
||||
// dispatch_after below, so it will live long enough to do its job.
|
||||
GNCMBleSocketDelegate *delegate =
|
||||
[GNCMBleSocketDelegate delegateWithConnectedHandler:^(BOOL didConnect) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (completionRef) completionRef(didConnect);
|
||||
completionRef = nil;
|
||||
});
|
||||
}];
|
||||
socket.delegate = delegate;
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBleSocketConnectionTimeout * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
(void)delegate; // make sure it's retained until the timeout
|
||||
if (completionRef) completionRef(NO);
|
||||
});
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -70,14 +70,13 @@ class BleOutputStream : public OutputStream {
|
||||
/** Concrete BleSocket implementation. */
|
||||
class BleSocket : public api::ble_v2::BleSocket {
|
||||
public:
|
||||
BleSocket() = default;
|
||||
explicit BleSocket(id<GNCMConnection> connection);
|
||||
BleSocket(id<GNCMConnection> connection, BlePeripheral *peripheral);
|
||||
~BleSocket() override;
|
||||
|
||||
InputStream &GetInputStream() override { return *input_stream_; }
|
||||
OutputStream &GetOutputStream() override { return *output_stream_; }
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
BlePeripheral *GetRemotePeripheral() override { return nullptr; }
|
||||
BlePeripheral *GetRemotePeripheral() override { return peripheral_; }
|
||||
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
@@ -88,6 +87,7 @@ class BleSocket : public api::ble_v2::BleSocket {
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
std::unique_ptr<BleInputStream> input_stream_;
|
||||
std::unique_ptr<BleOutputStream> output_stream_;
|
||||
BlePeripheral *peripheral_;
|
||||
};
|
||||
|
||||
/** Concrete BleServerSocket implementation. */
|
||||
@@ -192,6 +192,7 @@ class BleMedium : public api::ble_v2::BleMedium {
|
||||
absl::flat_hash_map<std::string, BleServerSocket *> server_sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::flat_hash_map<std::string, GNCMConnectionRequester> connection_requesters_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
dispatch_queue_t callback_queue_;
|
||||
};
|
||||
|
||||
} // namespace ios
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/GNCMBlePeripheral.h"
|
||||
#include "internal/platform/implementation/ios/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/ios/utils.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -143,8 +142,7 @@ ExceptionOr<ByteArray> BleInputStream::Read(std::int64_t size) {
|
||||
[condition_ unlock];
|
||||
|
||||
if (dataToReturn) {
|
||||
GTMLoggerInfo(@"[NEARBY] Input stream: Received data of size: %lu",
|
||||
(unsigned long)dataToReturn.length);
|
||||
NSLog(@"[NEARBY] Input stream: Received data of size: %lu", (unsigned long)dataToReturn.length);
|
||||
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
|
||||
} else {
|
||||
return ExceptionOr<ByteArray>{Exception::kIo};
|
||||
@@ -167,8 +165,7 @@ BleOutputStream::~BleOutputStream() {
|
||||
|
||||
Exception BleOutputStream::Write(const ByteArray& data) {
|
||||
[condition_ lock];
|
||||
GTMLoggerInfo(@"[NEARBY] Sending data of size: %lu",
|
||||
(unsigned long)NSDataFromByteArray(data).length);
|
||||
NSLog(@"[NEARBY] Sending data of size: %lu", (unsigned long)NSDataFromByteArray(data).length);
|
||||
|
||||
NSMutableData* packet = [NSMutableData dataWithData:NSDataFromByteArray(data)];
|
||||
|
||||
@@ -225,8 +222,10 @@ Exception BleOutputStream::Close() {
|
||||
}
|
||||
|
||||
/** BleSocket implementation.*/
|
||||
BleSocket::BleSocket(id<GNCMConnection> connection)
|
||||
: input_stream_(new BleInputStream()), output_stream_(new BleOutputStream(connection)) {}
|
||||
BleSocket::BleSocket(id<GNCMConnection> connection, BlePeripheral* peripheral)
|
||||
: input_stream_(new BleInputStream()),
|
||||
output_stream_(new BleOutputStream(connection)),
|
||||
peripheral_(peripheral) {}
|
||||
|
||||
BleSocket::~BleSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
@@ -328,8 +327,29 @@ bool BleMedium::StartAdvertising(
|
||||
peripheral_ = [[GNCMBlePeripheral alloc] init];
|
||||
}
|
||||
|
||||
[peripheral_ startAdvertisingWithServiceUUID:ObjCStringFromCppString(service_uuid)
|
||||
advertisementData:NSDataFromByteArray(service_data_bytes)];
|
||||
auto& peripheral = adapter_->GetPeripheral();
|
||||
[peripheral_
|
||||
startAdvertisingWithServiceUUID:ObjCStringFromCppString(service_uuid)
|
||||
advertisementData:NSDataFromByteArray(service_data_bytes)
|
||||
endpointConnectedHandler:^GNCMConnectionHandlers*(id<GNCMConnection> connection) {
|
||||
// TODO(edwinwu): This server_socket is supposed to be gotten from the map by key of
|
||||
// servcie_id. We now always get the first iteration since we don't know the key now.
|
||||
// Try the way to move the Ble socket frame verification up to one layer.
|
||||
std::string service_id;
|
||||
BleServerSocket* server_socket;
|
||||
if (!server_sockets_.empty()) {
|
||||
service_id = server_sockets_.begin()->first;
|
||||
server_socket = server_sockets_.begin()->second;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
auto socket = std::make_unique<BleSocket>(connection, &peripheral);
|
||||
GNCMConnectionHandlers* connectionHandlers =
|
||||
static_cast<BleInputStream&>(socket->GetInputStream()).GetConnectionHandlers();
|
||||
server_socket->Connect(std::move(socket));
|
||||
return connectionHandlers;
|
||||
}
|
||||
callbackQueue:callback_queue_];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -419,10 +439,11 @@ std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string& se
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
__block std::unique_ptr<BleSocket> socket;
|
||||
__block BlePeripheral ios_peripheral = static_cast<BlePeripheral&>(peripheral);
|
||||
if (connection_requester != nil) {
|
||||
if (cancellation_flag->Cancelled()) {
|
||||
GTMLoggerError(@"[NEARBY] BLE Connect: Has been cancelled: service_id=%@",
|
||||
ObjCStringFromCppString(service_id));
|
||||
NSLog(@"[NEARBY] BLE Connect: Has been cancelled: service_id=%@",
|
||||
ObjCStringFromCppString(service_id));
|
||||
dispatch_group_leave(group); // unblock
|
||||
return {};
|
||||
}
|
||||
@@ -430,7 +451,7 @@ std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string& se
|
||||
connection_requester(^(id<GNCMConnection> connection) {
|
||||
// If the connection wasn't successfully established, return a NULL socket.
|
||||
if (connection) {
|
||||
socket = std::make_unique<BleSocket>(connection);
|
||||
socket = std::make_unique<BleSocket>(connection, &ios_peripheral);
|
||||
}
|
||||
|
||||
dispatch_group_leave(group); // unblock
|
||||
|
||||
Reference in New Issue
Block a user