[BLE Refactor] Implement iOS BLE incoming connection.

PiperOrigin-RevId: 457110588
This commit is contained in:
edwinwu
2022-06-24 15:58:47 -07:00
committed by Copybara-Service
parent 75aa59d57e
commit 9ce81d8534
11 changed files with 373 additions and 136 deletions
+3 -2
View File
@@ -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
+4 -3
View File
@@ -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
+33 -12
View File
@@ -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