mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -101,7 +101,6 @@ cc_library(
|
||||
# TODO: Support WebRTC
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
|
||||
@@ -23,9 +23,6 @@ package(default_visibility = [
|
||||
objc_library(
|
||||
name = "apple",
|
||||
srcs = [
|
||||
"ble.mm",
|
||||
"ble_utils.mm",
|
||||
"bluetooth_adapter.mm",
|
||||
"crypto.mm",
|
||||
"device_info.mm",
|
||||
"log_message.mm",
|
||||
@@ -34,13 +31,9 @@ objc_library(
|
||||
"preferences_manager.mm",
|
||||
"scheduled_executor.mm",
|
||||
"timer.mm",
|
||||
"utils.mm",
|
||||
"wifi_lan.mm",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
"ble_utils.h",
|
||||
"bluetooth_adapter.h",
|
||||
"device_info.h",
|
||||
"log_message.h",
|
||||
"multi_thread_executor.h",
|
||||
@@ -48,7 +41,6 @@ objc_library(
|
||||
"scheduled_executor.h",
|
||||
"single_thread_executor.h",
|
||||
"timer.h",
|
||||
"utils.h",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
# Prevent Objective-C++ headers from being pulled into swift.
|
||||
@@ -57,8 +49,8 @@ objc_library(
|
||||
deps = [
|
||||
":Platform_cc",
|
||||
":Shared",
|
||||
":ble_v2",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:cancellation_flag",
|
||||
"//internal/platform/implementation:comm",
|
||||
"//internal/platform/implementation:platform",
|
||||
"//internal/platform/implementation:types",
|
||||
@@ -73,7 +65,6 @@ objc_library(
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
"@com_google_absl//absl/types:span",
|
||||
"@nlohmann_json//:json",
|
||||
] + select({
|
||||
@@ -84,6 +75,50 @@ objc_library(
|
||||
}),
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "ble_v2",
|
||||
srcs = [
|
||||
"ble_gatt_client.h",
|
||||
"ble_gatt_client.mm",
|
||||
"ble_gatt_server.h",
|
||||
"ble_gatt_server.mm",
|
||||
"ble_medium.h",
|
||||
"ble_medium.mm",
|
||||
"ble_peripheral.h",
|
||||
"ble_peripheral.mm",
|
||||
"ble_server_socket.h",
|
||||
"ble_server_socket.mm",
|
||||
"ble_socket.h",
|
||||
"ble_socket.mm",
|
||||
"ble_utils.mm",
|
||||
"bluetooth_adapter_v2.h",
|
||||
"bluetooth_adapter_v2.mm",
|
||||
"utils.mm",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
"ble_utils.h",
|
||||
"utils.h",
|
||||
],
|
||||
# Prevent Objective-C++ headers from being pulled into swift.
|
||||
aspect_hints = ["//tools/build_defs/swift:no_module"],
|
||||
deps = [
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:cancellation_flag",
|
||||
"//internal/platform:uuid",
|
||||
"//internal/platform/implementation:comm",
|
||||
"//internal/platform/implementation/apple/Mediums",
|
||||
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Central",
|
||||
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Peripheral",
|
||||
"//third_party/apple_frameworks:CoreBluetooth",
|
||||
"//third_party/apple_frameworks:Foundation",
|
||||
"//third_party/objective_c/google_toolbox_for_mac:GTM_Logger",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "Shared",
|
||||
srcs = [
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 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 <Foundation/Foundation.h>
|
||||
|
||||
/** The domain for @c NSErrors raised by the BLE medium. */
|
||||
extern NSErrorDomain const GNCBLEErrorDomain;
|
||||
|
||||
/** BLE medium error codes. */
|
||||
typedef NS_ERROR_ENUM(GNCBLEErrorDomain, GNCBLEError){
|
||||
GNCBLEErrorUnknown,
|
||||
GNCBLEErrorDuplicateCharacteristic,
|
||||
GNCBLEErrorInvalidServiceData,
|
||||
GNCBLEErrorAlreadyAdvertising,
|
||||
GNCBLEErrorInvalidCharacteristic,
|
||||
GNCBLEErrorAlreadyDiscoveringSpecifiedCharacteristics,
|
||||
GNCBLEErrorAlreadyReadingCharacteristic,
|
||||
GNCBLEErrorAlreadyScanning,
|
||||
};
|
||||
+4
-13
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Google LLC
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,17 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
|
||||
#include <string>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
std::string BlePeripheral::GetAddress() const { return adapter_.GetMacAddress(); }
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
NSErrorDomain const GNCBLEErrorDomain = @"com.google.nearby.ble.error";
|
||||
@@ -15,20 +15,24 @@
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** A container for GATT characteristic information. */
|
||||
@interface GNCBLEGATTCharacteristic : NSObject
|
||||
|
||||
/** @remark init is not an available initializer. */
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Creates a container for GATT characteristic information.
|
||||
* Creates a container for GATT characteristic information with properties.
|
||||
*
|
||||
* @param characteristicUUID The UUID of the characteristic.
|
||||
* @param serviceUUID The UUID of the service.
|
||||
* @param properties The properties of the characteristic.
|
||||
*/
|
||||
- (instancetype)initWithUUID:(nonnull CBUUID *)characteristicUUID
|
||||
serviceUUID:(nonnull CBUUID *)serviceUUID;
|
||||
- (instancetype)initWithUUID:(CBUUID *)characteristicUUID
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
properties:(CBCharacteristicProperties)properties;
|
||||
|
||||
/**
|
||||
* Creates a container for GATT characteristic information with permissions and properties.
|
||||
@@ -38,17 +42,16 @@
|
||||
* @param permissions The permissions of the characteristic value.
|
||||
* @param properties The properties of the characteristic.
|
||||
*/
|
||||
- (nonnull instancetype)initWithUUID:(nonnull CBUUID *)characteristicUUID
|
||||
serviceUUID:(nonnull CBUUID *)serviceUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithUUID:(CBUUID *)characteristicUUID
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/** The 128-bit UUID that identifies the characteristic. */
|
||||
@property(nonatomic, readonly, nonnull) CBUUID *characteristicUUID;
|
||||
@property(nonatomic, readonly) CBUUID *characteristicUUID;
|
||||
|
||||
/** The 128-bit UUID that identifies the service that the characteristic belongs to. */
|
||||
@property(nonatomic, readonly, nonnull) CBUUID *serviceUUID;
|
||||
@property(nonatomic, readonly) CBUUID *serviceUUID;
|
||||
|
||||
/**
|
||||
* The permissions of the characteristic value.
|
||||
@@ -67,3 +70,5 @@
|
||||
@property(nonatomic, readonly) CBCharacteristicProperties properties;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -17,10 +17,17 @@
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation GNCBLEGATTCharacteristic
|
||||
|
||||
- (instancetype)initWithUUID:(CBUUID *)characteristicUUID serviceUUID:(CBUUID *)serviceUUID {
|
||||
return [self initWithUUID:characteristicUUID serviceUUID:serviceUUID permissions:0 properties:0];
|
||||
- (instancetype)initWithUUID:(CBUUID *)characteristicUUID
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
properties:(CBCharacteristicProperties)properties {
|
||||
return [self initWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
permissions:0
|
||||
properties:properties];
|
||||
}
|
||||
|
||||
- (instancetype)initWithUUID:(CBUUID *)characteristicUUID
|
||||
@@ -38,3 +45,5 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2023 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class GNCBLEGATTCharacteristic;
|
||||
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to
|
||||
* @c discoverCharacteristicsWithUUIDs:serviceUUID:completionHandler: has completed.
|
||||
*
|
||||
* @param error On success, error will be @c nil. Otherwise, the error that occurred.
|
||||
*/
|
||||
typedef void (^GNCDiscoverCharacteristicsCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c characteristicWithUUID:serviceUUID:completionHandler: has
|
||||
* completed.
|
||||
*
|
||||
* @param characteristic The characteristic or @c nil if an error occurred.
|
||||
* @param error On success, error will be @c nil. Otherwise, the error that occurred.
|
||||
*/
|
||||
typedef void (^GNCGetCharacteristicCompletionHandler)(
|
||||
GNCBLEGATTCharacteristic *_Nullable characteristic, NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c readValueForCharacteristic:completionHandler: has
|
||||
* completed.
|
||||
*
|
||||
* @param value The characteristic's value or @c nil if an error occurred.
|
||||
* @param error On success, error will be @c nil. Otherwise, the error that occurred.
|
||||
*/
|
||||
typedef void (^GNCReadCharacteristicValueCompletionHandler)(NSData *_Nullable value,
|
||||
NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* An object that can be used to discover, explore, and interact with GATT services and
|
||||
* characteristics available on a remote peripheral.
|
||||
*
|
||||
* @note The public APIs of this class are thread safe.
|
||||
*/
|
||||
@interface GNCBLEGATTClient : NSObject
|
||||
|
||||
/** @remark init is not an available initializer. */
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Initializes the GATT client with a specified peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral instance.
|
||||
*/
|
||||
- (instancetype)initWithPeripheral:(id<GNCPeripheral>)peripheral;
|
||||
|
||||
/**
|
||||
* Discovers the specified characteristics of a service.
|
||||
*
|
||||
* A successful call to this method means the characteristics can be retreived with
|
||||
* @c characteristicWithUUID:serviceUUID:completionHandler:.
|
||||
*
|
||||
* @param characteristics An array of 128-bit UUID objects of the characteristics that you are
|
||||
* interested in.
|
||||
* @param serviceUUID A 128-bit UUID that identifies the service that the characteristics belongs
|
||||
* to.
|
||||
* @param completionHandler Called on a private queue with @c nil if all characteristics have
|
||||
* successfully been discovered or else an error.
|
||||
*/
|
||||
- (void)discoverCharacteristicsWithUUIDs:(NSArray<CBUUID *> *)characteristicUUIDs
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
completionHandler:
|
||||
(nullable GNCDiscoverCharacteristicsCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Retrieves a GATT characteristic.
|
||||
*
|
||||
* If you haven’t yet called the @c discoverCharacteristicsWithUUIDs:serviceUUID:completionHandler:
|
||||
* method to discover the characteristic, or if there was an error in doing so, this method will
|
||||
* complete with an error.
|
||||
*
|
||||
* @param characteristicUUID A 128-bit UUID that identifies the characteristic.
|
||||
* @param serviceUUID A 128-bit UUID that identifies the service that the characteristic belongs to.
|
||||
* @param completionHandler Called on a private queue with the characteristic if successful or an
|
||||
* error if one has occured.
|
||||
*/
|
||||
- (void)characteristicWithUUID:(CBUUID *)characteristicUUID
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
completionHandler:(nullable GNCGetCharacteristicCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Reads the requested characteristic from the associated remote device.
|
||||
*
|
||||
* @param characteristic The characteristic whose value you want to read.
|
||||
* @param completionHandler Called on a private queue with the characteristics value if successful
|
||||
* or an error if one has occured.
|
||||
*/
|
||||
- (void)readValueForCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
completionHandler:
|
||||
(nullable GNCReadCharacteristicValueCompletionHandler)completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static char *const kGNCBLEGATTClientQueueLabel = "com.nearby.GNCBLEGATTClient";
|
||||
|
||||
static NSError *InvalidCharacteristicError() {
|
||||
return [NSError errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorInvalidCharacteristic
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
static NSError *AlreadyDiscoveringSpecifiedCharacteristicsError() {
|
||||
return [NSError errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorAlreadyDiscoveringSpecifiedCharacteristics
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
static NSError *AlreadyReadingCharacteristicError() {
|
||||
return [NSError errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorAlreadyReadingCharacteristic
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
@interface GNCBLEGATTClient () <GNCPeripheralDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCBLEGATTClient {
|
||||
dispatch_queue_t _queue;
|
||||
id<GNCPeripheral> _peripheral;
|
||||
|
||||
/**
|
||||
* A map of service UUIDs with each service holding a map of a list of characterisitcs to a
|
||||
* completion handler. This is used to track the groupings of characteristic discovery requests.
|
||||
* When all characteristics of a request are discovered, the completion handler is called and
|
||||
* removed from the map.
|
||||
*/
|
||||
NSMutableDictionary<CBUUID *, NSMutableDictionary<NSArray<CBUUID *> *,
|
||||
GNCDiscoverCharacteristicsCompletionHandler> *>
|
||||
*_discoverCharacteristicsCompletionHandlers;
|
||||
|
||||
/**
|
||||
* A service to characteristic to completion handler map. Used to track pending read requests.
|
||||
* When a characteristic's value has been updated, the completion handler is called and removed
|
||||
* from the map.
|
||||
*/
|
||||
NSMutableDictionary<CBUUID *,
|
||||
NSMutableDictionary<CBUUID *, GNCReadCharacteristicValueCompletionHandler> *>
|
||||
*_readCharacteristicValueCompletionHandlers;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPeripheral:(id<GNCPeripheral>)peripheral {
|
||||
return [self
|
||||
initWithPeripheral:peripheral
|
||||
queue:dispatch_queue_create(kGNCBLEGATTClientQueueLabel, DISPATCH_QUEUE_SERIAL)];
|
||||
};
|
||||
|
||||
// Private.
|
||||
- (instancetype)initWithPeripheral:(id<GNCPeripheral>)peripheral
|
||||
queue:(nullable dispatch_queue_t)queue {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = queue ?: dispatch_get_main_queue();
|
||||
_peripheral = peripheral;
|
||||
_peripheral.peripheralDelegate = self;
|
||||
_discoverCharacteristicsCompletionHandlers = [[NSMutableDictionary alloc] init];
|
||||
_readCharacteristicValueCompletionHandlers = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
};
|
||||
|
||||
- (void)discoverCharacteristicsWithUUIDs:(NSArray<CBUUID *> *)characteristicUUIDs
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
completionHandler:
|
||||
(nullable GNCDiscoverCharacteristicsCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_discoverCharacteristicsCompletionHandlers[serviceUUID]) {
|
||||
_discoverCharacteristicsCompletionHandlers[serviceUUID] = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
// Return an error if we are already discovering the provided characteristics.
|
||||
if (_discoverCharacteristicsCompletionHandlers[serviceUUID][characteristicUUIDs]) {
|
||||
if (completionHandler) {
|
||||
completionHandler(AlreadyDiscoveringSpecifiedCharacteristicsError());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Use the list of characteristic UUIDs as the key. This makes it easy to associate the
|
||||
// completion handler with the list of characteristics we are waiting for, as well as retrieving
|
||||
// the complete list of characteristics we need to query for a single service.
|
||||
_discoverCharacteristicsCompletionHandlers[serviceUUID][characteristicUUIDs] =
|
||||
completionHandler;
|
||||
|
||||
// Note: A call to @c discoverServices: will always be paired with a delegate call even if the
|
||||
// service has already been discovered.
|
||||
[_peripheral discoverServices:@[ serviceUUID ]];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)characteristicWithUUID:(CBUUID *)characteristicUUID
|
||||
serviceUUID:(CBUUID *)serviceUUID
|
||||
completionHandler:(nullable GNCGetCharacteristicCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
CBCharacteristic *characteristic = [self synchronousCharacteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID];
|
||||
if (!characteristic) {
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, InvalidCharacteristicError());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (completionHandler) {
|
||||
completionHandler([[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristic.UUID
|
||||
serviceUUID:characteristic.service.UUID
|
||||
properties:characteristic.properties],
|
||||
nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)readValueForCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
completionHandler:
|
||||
(nullable GNCReadCharacteristicValueCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
CBCharacteristic *cbCharacteristic =
|
||||
[self synchronousCharacteristicWithUUID:characteristic.characteristicUUID
|
||||
serviceUUID:characteristic.serviceUUID];
|
||||
|
||||
if (!cbCharacteristic) {
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, InvalidCharacteristicError());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_readCharacteristicValueCompletionHandlers[characteristic.serviceUUID]) {
|
||||
_readCharacteristicValueCompletionHandlers[characteristic.serviceUUID] =
|
||||
[[NSMutableDictionary alloc] init];
|
||||
}
|
||||
// Return an error if we are already discovering the provided characteristics.
|
||||
if (_readCharacteristicValueCompletionHandlers[characteristic.serviceUUID]
|
||||
[characteristic.characteristicUUID]) {
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, AlreadyReadingCharacteristicError());
|
||||
}
|
||||
return;
|
||||
}
|
||||
_readCharacteristicValueCompletionHandlers[characteristic.serviceUUID]
|
||||
[characteristic.characteristicUUID] =
|
||||
completionHandler;
|
||||
|
||||
[_peripheral readValueForCharacteristic:cbCharacteristic];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
- (CBCharacteristic *)synchronousCharacteristicWithUUID:(CBUUID *)characteristicUUID
|
||||
serviceUUID:(CBUUID *)serviceUUID {
|
||||
dispatch_assert_queue(_queue);
|
||||
for (CBService *service in _peripheral.services) {
|
||||
if ([service.UUID isEqual:serviceUUID]) {
|
||||
for (CBCharacteristic *characteristic in service.characteristics) {
|
||||
if ([characteristic.UUID isEqual:characteristicUUID]) {
|
||||
return characteristic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - GNCPeripheralDelegate
|
||||
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral didDiscoverServices:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
// TODO(b/295911088): Queue incoming requests by service would allow us to not attempt
|
||||
// characteristc discovery on all services and to short circuit and call the completion handler if
|
||||
// there was an error.
|
||||
for (CBService *service in peripheral.services) {
|
||||
// Flatten lists of characteristics for a given service into a single list for discovery.
|
||||
NSArray<NSArray<CBUUID *> *> *groupedCharacteristics =
|
||||
_discoverCharacteristicsCompletionHandlers[service.UUID].allKeys;
|
||||
if (!groupedCharacteristics) {
|
||||
continue;
|
||||
}
|
||||
NSMutableSet<CBUUID *> *flattenedCharacteristics = [[NSMutableSet alloc] init];
|
||||
for (NSArray<CBUUID *> *characteristics in groupedCharacteristics) {
|
||||
[flattenedCharacteristics addObjectsFromArray:characteristics];
|
||||
}
|
||||
|
||||
// Note: Since we don't clear @c _discoverCharacteristicsCompletionHandlers until the
|
||||
// characterististics have been discovered, multiple calls to
|
||||
// @c discoverService:characteristics:completionHandler: will cause duplicate characteristic
|
||||
// discovery calls on the same service. We CANNOT clear the pending list until after we actually
|
||||
// discover the characteristics, because there can be more than 1 service with the same UUID.
|
||||
// This is a common occurence for Nearby services, so it should not be treated as an edge case.
|
||||
[_peripheral discoverCharacteristics:[flattenedCharacteristics allObjects] forService:service];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral
|
||||
didDiscoverCharacteristicsForService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
|
||||
// Check if each group of characteristics is a subset of the discovered characteristics. If all
|
||||
// characteristics of the group have been discovered, call the completion handler with success,
|
||||
// otherwise continue waiting. If the characteristic discovery returns an error, call all pending
|
||||
// discovery request completion handlers with the error.
|
||||
NSMutableSet<CBUUID *> *characteristics = [[NSMutableSet alloc] init];
|
||||
for (CBCharacteristic *characteristic in service.characteristics) {
|
||||
[characteristics addObject:characteristic.UUID];
|
||||
}
|
||||
[_discoverCharacteristicsCompletionHandlers[service.UUID].copy
|
||||
enumerateKeysAndObjectsUsingBlock:^(NSArray<CBUUID *> *pendingCharacteristics,
|
||||
GNCDiscoverCharacteristicsCompletionHandler handler,
|
||||
BOOL *stop) {
|
||||
if ([[NSSet setWithArray:pendingCharacteristics] isSubsetOfSet:characteristics]) {
|
||||
_discoverCharacteristicsCompletionHandlers[service.UUID][pendingCharacteristics] = nil;
|
||||
handler(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/295911088): Queue incoming requests by service to avoid this issue.
|
||||
// This could be a race, where @c discoverService:characteristics:completionHandler: is
|
||||
// called multiple times quickly for the same service. If a request fails, all pending
|
||||
// requests for the same service are also failed.
|
||||
if (error) {
|
||||
_discoverCharacteristicsCompletionHandlers[service.UUID][pendingCharacteristics] = nil;
|
||||
handler(error);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral
|
||||
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
NSMutableDictionary<CBUUID *, GNCReadCharacteristicValueCompletionHandler> *handlers =
|
||||
_readCharacteristicValueCompletionHandlers[characteristic.service.UUID];
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
GNCReadCharacteristicValueCompletionHandler handler = handlers[characteristic.UUID];
|
||||
handlers[characteristic.UUID] = nil;
|
||||
if (handler) {
|
||||
handler(characteristic.value, error);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - CBPeripheralDelegate
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheral:peripheral didDiscoverServices:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didDiscoverCharacteristicsForService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheral:peripheral didDiscoverCharacteristicsForService:service error:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheral:peripheral didUpdateValueForCharacteristic:characteristic error:error];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -19,11 +19,43 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to
|
||||
* @c createCharacteristicWithServiceID:characteristicUUID:permissions:properties:completionHandler:
|
||||
* has completed.
|
||||
*
|
||||
* @param characteristic The created characteristic, or @c nil if an error occurred.
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCCreateCharacteristicCompletionHandler)(
|
||||
GNCBLEGATTCharacteristic *_Nullable characteristic, NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c updateCharacteristic:value:completionHandler: has
|
||||
* completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCUpdateCharacteristicCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c startAdvertisingData:completionHandler: has completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c stopAdvertisingWithcompletionHandler: has completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCStopAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* An object that manages and advertises GATT characteritics.
|
||||
*
|
||||
* @note The public APIs of this class are NOT thread safe. All methods in this class should be
|
||||
* invoked from the same thread or serially.
|
||||
* @note The public APIs of this class are thread safe.
|
||||
*/
|
||||
@interface GNCBLEGATTServer : NSObject
|
||||
|
||||
@@ -33,30 +65,31 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* Characteristics of the same service UUID will cause the service to be unpublished and republished
|
||||
* with the new characteristic appended.
|
||||
*
|
||||
* This method blocks until the characteristic has successfully been added to the GATT server or an
|
||||
* error occurs.
|
||||
*
|
||||
* @param serviceUUID A 128-bit UUID that identifies the service that the characteristic belongs to.
|
||||
* @param characteristicUUID A 128-bit UUID that identifies the characteristic.
|
||||
* @param permissions The permissions of the characteristic value.
|
||||
* @param properties The properties of the characteristic.
|
||||
* @return Returns the characteristic or nil if an error has occured.
|
||||
* @param completionHandler Called on a private queue with the characteristic if successfully
|
||||
* created or an error if one has occured.
|
||||
*/
|
||||
- (nullable GNCBLEGATTCharacteristic *)
|
||||
createCharacteristicWithServiceID:(CBUUID *)serviceUUID
|
||||
characteristicUUID:(CBUUID *)characteristicUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties;
|
||||
- (void)createCharacteristicWithServiceID:(CBUUID *)serviceUUID
|
||||
characteristicUUID:(CBUUID *)characteristicUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties
|
||||
completionHandler:
|
||||
(nullable GNCCreateCharacteristicCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Updates a local characteristic with the provided value.
|
||||
*
|
||||
* @param characteristic The characteristic to update.
|
||||
* @param value The new value for the characteristic.
|
||||
* @return Returns @c YES if successfully updated or @c NO if an error has occured.
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully updated or an
|
||||
* error if one has occured.
|
||||
*/
|
||||
- (BOOL)updateCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
value:(nullable NSData *)value;
|
||||
- (void)updateCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
value:(nullable NSData *)value
|
||||
completionHandler:(nullable GNCUpdateCharacteristicCompletionHandler)completionHandler;
|
||||
|
||||
/** Removes all published services from the local GATT database. */
|
||||
- (void)stop;
|
||||
@@ -68,14 +101,23 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* service list is advertised using @c CBAdvertisementDataServiceUUIDsKey and the associated data is
|
||||
* advertised using @c CBAdvertisementDataLocalNameKey. Since @c CBAdvertisementDataLocalNameKey
|
||||
* does not support binary data, the value is base64 encoded and truncated if the resulting value is
|
||||
* longer than 22 bytes.
|
||||
*
|
||||
* This method blocks until the service data is being advertised or an error occurs.
|
||||
* longer than 22 bytes. This also means we can only support advertising a single service.
|
||||
*
|
||||
* @param serviceData A dictionary that contains service-specific advertisement data.
|
||||
* @return Returns @c YES if successfully updated or @c NO if an error has occured.
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully started
|
||||
* advertising or an error if one has occured.
|
||||
*/
|
||||
- (BOOL)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData;
|
||||
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
|
||||
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Stops advertising all service data.
|
||||
*
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully stopped
|
||||
* advertising or an error if one has occured.
|
||||
*/
|
||||
- (void)stopAdvertisingWithCompletionHandler:
|
||||
(nullable GNCStopAdvertisingCompletionHandler)completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -17,174 +17,193 @@
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/NSData+GNCWebSafeBase64.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// An arbitrary timeout that should be pretty lenient.
|
||||
static NSTimeInterval const GNCBLEGATTServerTimeoutInSeconds = 2;
|
||||
static char *const kGNCBLEGATTServerQueueLabel = "com.nearby.GNCBLEGATTServer";
|
||||
|
||||
@interface GNCBLEGATTServer () <GNCPeripheralManagerDelegate, CBPeripheralManagerDelegate>
|
||||
@interface GNCBLEGATTServer () <GNCPeripheralManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCBLEGATTServer {
|
||||
dispatch_queue_t _queue;
|
||||
id<GNCPeripheralManager> _peripheralManager;
|
||||
|
||||
// A map of service UUIDs to the service. This is used to keep track of all services and
|
||||
// characteristics that should be in the GATT database. This also keeps track of a characteristics
|
||||
// dynamic read value. This is the value returned when a remote device attempts to read the
|
||||
// characteristic.
|
||||
// characteristics that have been requested to be added to the GATT database. This may not match
|
||||
// the "real" services that remote devices can see.
|
||||
NSMutableDictionary<CBUUID *, CBMutableService *> *_services;
|
||||
|
||||
// A list of services that have been requested to be added to the GATT database, but have not yet
|
||||
// completed. This is used by the create characteristic method to block until the characteristic
|
||||
// has been added to the database.
|
||||
NSMutableArray<CBUUID *> *_pendingServiceAdditions;
|
||||
// A list of characteristics that will be added to the GATT database once the peripheral
|
||||
// transitions into a valid state. If a characteristic is in this list it means that a
|
||||
// CoreBluetooth request to add to the characteristic to the GATT database has not yet been made.
|
||||
NSMutableDictionary<CBUUID *, NSMutableArray<CBMutableCharacteristic *> *>
|
||||
*_pendingCharacteristics;
|
||||
|
||||
// A map of service UUIDs to its associated error if the service had failed to be added to the
|
||||
// GATT database. This is used by the create characteristic method to determine if the service was
|
||||
// successfully added.
|
||||
NSMutableDictionary<CBUUID *, NSError *> *_serviceErrors;
|
||||
// This keeps track of a characteristic's dynamic read value. This is the value returned when a
|
||||
// remote device attempts to read the characteristic.
|
||||
NSMutableDictionary<CBUUID *, NSMutableDictionary<CBUUID *, NSData *> *> *_characteristicValues;
|
||||
|
||||
// Guards access to @c _services, @c _pendingServiceAdditions and @c _serviceErrors. The condition
|
||||
// is also used to block method execution until its async action completes.
|
||||
NSCondition *_condition;
|
||||
// The data that should be advertised. This value is cached until the caller explicitly stops
|
||||
// advertising, because we will do our best to restart the advertisement if BT turns off then back
|
||||
// on.
|
||||
NSDictionary<NSString *, id> *_advertisementData;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
dispatch_queue_t queue =
|
||||
dispatch_queue_create("com.nearby.GNCBLEGATTServer", DISPATCH_QUEUE_SERIAL);
|
||||
return [self initWithPeripheralManager:[[CBPeripheralManager alloc] initWithDelegate:nil
|
||||
queue:queue]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPeripheralManager:(id<GNCPeripheralManager>)peripheralManager {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = dispatch_queue_create(kGNCBLEGATTServerQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil queue:_queue];
|
||||
// Set for @c GNCPeripheralManager to be able to forward callbacks.
|
||||
_peripheralManager.peripheralDelegate = self;
|
||||
_services = [[NSMutableDictionary alloc] init];
|
||||
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
|
||||
_characteristicValues = [[NSMutableDictionary alloc] init];
|
||||
_advertisementData = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// This is private and should only be used for tests. The provided peripheral manager must call
|
||||
// delegate methods on the main queue.
|
||||
- (instancetype)initWithPeripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = dispatch_get_main_queue();
|
||||
_peripheralManager = peripheralManager;
|
||||
// Set for @c GNCPeripheralManager to be able to forward callbacks.
|
||||
_peripheralManager.peripheralDelegate = self;
|
||||
_services = [[NSMutableDictionary alloc] init];
|
||||
_pendingServiceAdditions = [[NSMutableArray alloc] init];
|
||||
_serviceErrors = [[NSMutableDictionary alloc] init];
|
||||
_condition = [[NSCondition alloc] init];
|
||||
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
|
||||
_characteristicValues = [[NSMutableDictionary alloc] init];
|
||||
_advertisementData = nil;
|
||||
}
|
||||
return self;
|
||||
};
|
||||
|
||||
- (nullable GNCBLEGATTCharacteristic *)
|
||||
createCharacteristicWithServiceID:(CBUUID *)serviceUUID
|
||||
characteristicUUID:(CBUUID *)characteristicUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties {
|
||||
// Ensure we are in a powered on state.
|
||||
if (![self waitUntilPoweredOn]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDate *timeLimit = [NSDate dateWithTimeIntervalSinceNow:GNCBLEGATTServerTimeoutInSeconds];
|
||||
BOOL wasSignaled = YES;
|
||||
[_condition lock];
|
||||
|
||||
// If a service with the specified UUID exists, we are modifying it, which requires us to remove
|
||||
// the service and then re-add it after the new characteristic has been added. Otherwise, create
|
||||
// the service for the specified UUID and keep track of it.
|
||||
CBMutableService *service = _services[serviceUUID];
|
||||
if (service != nil) {
|
||||
[_peripheralManager removeService:service];
|
||||
} else {
|
||||
service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
|
||||
_services[serviceUUID] = service;
|
||||
}
|
||||
|
||||
// Create a characteristic with the specified permissions and properties.
|
||||
CBMutableCharacteristic *characteristic =
|
||||
[[CBMutableCharacteristic alloc] initWithType:characteristicUUID
|
||||
properties:properties
|
||||
value:nil
|
||||
permissions:permissions];
|
||||
|
||||
// Add the characteristic to the service's list of characteristics
|
||||
NSMutableArray<CBMutableCharacteristic *> *characteristics =
|
||||
[service.characteristics mutableCopy];
|
||||
if (characteristics == nil) {
|
||||
characteristics = [[NSMutableArray alloc] init];
|
||||
}
|
||||
[characteristics addObject:characteristic];
|
||||
service.characteristics = characteristics;
|
||||
|
||||
// Publish the service and wait until complete.
|
||||
[_pendingServiceAdditions addObject:service.UUID];
|
||||
[_peripheralManager addService:service];
|
||||
while ([_pendingServiceAdditions containsObject:service.UUID] && wasSignaled) {
|
||||
wasSignaled = [_condition waitUntilDate:timeLimit];
|
||||
}
|
||||
NSError *serviceError = [_serviceErrors objectForKey:service.UUID];
|
||||
[_serviceErrors removeObjectForKey:service.UUID];
|
||||
[_condition unlock];
|
||||
if (serviceError) {
|
||||
return nil;
|
||||
}
|
||||
return [[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
permissions:permissions
|
||||
properties:properties];
|
||||
}
|
||||
|
||||
- (BOOL)updateCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
value:(nullable NSData *)value {
|
||||
// Ensure we are in a powered on state.
|
||||
if (![self waitUntilPoweredOn]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
[_condition lock];
|
||||
// Find and update the specified characteristic's value.
|
||||
CBMutableService *service = _services[characteristic.serviceUUID];
|
||||
if (!service) {
|
||||
[_condition unlock];
|
||||
return NO;
|
||||
}
|
||||
for (CBMutableCharacteristic *c in service.characteristics) {
|
||||
if ([c.UUID isEqual:characteristic.characteristicUUID]) {
|
||||
c.value = value;
|
||||
[_condition unlock];
|
||||
return YES;
|
||||
- (void)createCharacteristicWithServiceID:(CBUUID *)serviceUUID
|
||||
characteristicUUID:(CBUUID *)characteristicUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties
|
||||
completionHandler:
|
||||
(nullable GNCCreateCharacteristicCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
// Check if the characteristic exists in the pending additions or existing services.
|
||||
for (CBCharacteristic *c in _pendingCharacteristics[serviceUUID]) {
|
||||
if ([c.UUID isEqual:characteristicUUID]) {
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, [NSError errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorDuplicateCharacteristic
|
||||
userInfo:nil]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
[_condition unlock];
|
||||
return NO;
|
||||
for (CBCharacteristic *c in _services[serviceUUID].characteristics) {
|
||||
if ([c.UUID isEqual:characteristicUUID]) {
|
||||
if (completionHandler) {
|
||||
completionHandler(nil, [NSError errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorDuplicateCharacteristic
|
||||
userInfo:nil]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a characteristic with the specified permissions and properties.
|
||||
CBMutableCharacteristic *characteristic =
|
||||
[[CBMutableCharacteristic alloc] initWithType:characteristicUUID
|
||||
properties:properties
|
||||
value:nil
|
||||
permissions:permissions];
|
||||
// Track characteristics that need to be added to the GATT database. These will either be added
|
||||
// immediately or as soon as the peripheral manager is in a valid state.
|
||||
if (!_pendingCharacteristics[serviceUUID]) {
|
||||
_pendingCharacteristics[serviceUUID] = [[NSMutableArray alloc] init];
|
||||
}
|
||||
[_pendingCharacteristics[serviceUUID] addObject:characteristic];
|
||||
|
||||
GNCBLEGATTCharacteristic *gncCharacteristic =
|
||||
[[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
permissions:permissions
|
||||
properties:properties];
|
||||
[self internalAddPendingServicesIfPoweredOn];
|
||||
if (completionHandler) {
|
||||
completionHandler(gncCharacteristic, nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)updateCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
value:(nullable NSData *)value
|
||||
completionHandler:(nullable GNCUpdateCharacteristicCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
// Keep track of the characteristics value, because we will need to retreive it during a read
|
||||
// request.
|
||||
if (_characteristicValues[characteristic.serviceUUID] == nil) {
|
||||
_characteristicValues[characteristic.serviceUUID] = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
_characteristicValues[characteristic.serviceUUID][characteristic.characteristicUUID] = value;
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[_condition lock];
|
||||
[_services removeAllObjects];
|
||||
[_condition unlock];
|
||||
[_peripheralManager removeAllServices];
|
||||
dispatch_async(_queue, ^{
|
||||
// Note: Do not clear/stop advertisements here, since there is a separate method for that.
|
||||
[_peripheralManager removeAllServices];
|
||||
[_services removeAllObjects];
|
||||
[_pendingCharacteristics removeAllObjects];
|
||||
[_characteristicValues removeAllObjects];
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData {
|
||||
// We can only handle advertising a single service data item, so return early if there is more
|
||||
// than one service incuded.
|
||||
if (serviceData.count > 1) {
|
||||
return NO;
|
||||
}
|
||||
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
|
||||
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
// We can only handle advertising a single service data item, so return early if there is more
|
||||
// than one service incuded.
|
||||
if (serviceData.count != 1) {
|
||||
if (completionHandler) {
|
||||
completionHandler([NSError
|
||||
errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorInvalidServiceData
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"Failed to start advertising, because Nearby for Apple platforms only "
|
||||
@"supports a single service data item."
|
||||
}]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we are in a powered on state.
|
||||
if (![self waitUntilPoweredOn]) {
|
||||
return NO;
|
||||
}
|
||||
// If we have advertisement data set, that means we are already advertising and should return
|
||||
// early. Advertising must be stopped before being started again.
|
||||
if (_advertisementData) {
|
||||
if (completionHandler) {
|
||||
completionHandler([NSError errorWithDomain:GNCBLEErrorDomain
|
||||
code:GNCBLEErrorAlreadyAdvertising
|
||||
userInfo:nil]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (serviceData.count == 1) {
|
||||
// Apple doesn't support setting service data, so we must convert it to a "local name". We do
|
||||
// this by assuming there will only ever be one service and then base64 encoding its associated
|
||||
// data. Other platforms are aware of this behavior and always check the local name if service
|
||||
// data is unavailable.
|
||||
CBUUID *serviceUUID = [serviceData.allKeys objectAtIndex:0];
|
||||
NSData *value = [serviceData objectForKey:serviceUUID];
|
||||
NSString *encoded = [value webSafebase64EncodedString];
|
||||
NSString *encoded = [value webSafeBase64EncodedString];
|
||||
|
||||
// Base64 encoding increases the size of the data so we must truncate it to 22 bytes to ensure
|
||||
// it fits in the advertisement alongside an assumed 16-bit serviceUUID.
|
||||
@@ -192,107 +211,151 @@ static NSTimeInterval const GNCBLEGATTServerTimeoutInSeconds = 2;
|
||||
encoded = [encoded substringToIndex:22];
|
||||
}
|
||||
|
||||
[_peripheralManager startAdvertising:@{
|
||||
_advertisementData = @{
|
||||
CBAdvertisementDataLocalNameKey : encoded,
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ]
|
||||
}];
|
||||
} else {
|
||||
[_peripheralManager startAdvertising:nil];
|
||||
}
|
||||
};
|
||||
|
||||
// Wait until advertisement has started.
|
||||
NSDate *timeLimit = [NSDate dateWithTimeIntervalSinceNow:GNCBLEGATTServerTimeoutInSeconds];
|
||||
BOOL wasSignaled = YES;
|
||||
[_condition lock];
|
||||
while (!_peripheralManager.isAdvertising && wasSignaled) {
|
||||
wasSignaled = [_condition waitUntilDate:timeLimit];
|
||||
}
|
||||
[_condition unlock];
|
||||
return _peripheralManager.isAdvertising;
|
||||
[self internalStartAdvertisingIfPoweredOn];
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
- (void)stopAdvertisingWithCompletionHandler:
|
||||
(nullable GNCStopAdvertisingCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
_advertisementData = nil;
|
||||
[_peripheralManager stopAdvertising];
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)waitUntilPoweredOn {
|
||||
NSDate *timeLimit = [NSDate dateWithTimeIntervalSinceNow:GNCBLEGATTServerTimeoutInSeconds];
|
||||
BOOL timedOut = NO;
|
||||
[_condition lock];
|
||||
while (_peripheralManager.state != CBManagerStatePoweredOn && !timedOut) {
|
||||
timedOut = ![_condition waitUntilDate:timeLimit];
|
||||
#pragma mark - Internal
|
||||
|
||||
- (void)internalAddPendingServicesIfPoweredOn {
|
||||
dispatch_assert_queue(_queue);
|
||||
// Services can only be added while bluetooth is on or off. Since this method will be called
|
||||
// anytime the peripheral manager's state changes and turning bluetooth on/off does not reset
|
||||
// the GATT database, we can add all pending services when we transition to the on or off state.
|
||||
// However, other state transitions may behave differently, so we may need to modify this
|
||||
// functionality in this future to re-add services if they disapear.
|
||||
if (_peripheralManager.state == CBManagerStatePoweredOn ||
|
||||
_peripheralManager.state == CBManagerStatePoweredOff) {
|
||||
for (CBUUID *serviceUUID in _pendingCharacteristics.allKeys) {
|
||||
// If a service with the specified UUID exists, we are modifying it, which requires us to
|
||||
// remove the service and then re-add it after the new characteristic has been added.
|
||||
// Otherwise, create the service for the specified UUID and keep track of it.
|
||||
CBMutableService *service = _services[serviceUUID];
|
||||
if (service != nil) {
|
||||
[_peripheralManager removeService:service];
|
||||
} else {
|
||||
service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
|
||||
_services[serviceUUID] = service;
|
||||
}
|
||||
|
||||
// Add the pending characteristics to the current service's list of characteristics.
|
||||
NSMutableArray<CBMutableCharacteristic *> *characteristics =
|
||||
[service.characteristics mutableCopy];
|
||||
if (characteristics == nil) {
|
||||
characteristics = [[NSMutableArray alloc] init];
|
||||
}
|
||||
// This makes the assumption that _pendingCharacteristics does not contain any duplicate
|
||||
// characteristics within itself or the current service characteristics. This is only
|
||||
// enforced on characteristic creation.
|
||||
[characteristics addObjectsFromArray:_pendingCharacteristics[serviceUUID]];
|
||||
service.characteristics = characteristics;
|
||||
|
||||
// Publish the service.
|
||||
[_peripheralManager addService:service];
|
||||
}
|
||||
// Clean up pending characteristics so we don't try to re-add them any time the power state
|
||||
// changes.
|
||||
[_pendingCharacteristics removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)internalStartAdvertisingIfPoweredOn {
|
||||
dispatch_assert_queue(_queue);
|
||||
// Advertising can only be done when powered on and must be restarted if bluetooth is turned off
|
||||
// then back on. This will be called anytime the peripheral manager's state changes, so
|
||||
// @c startAdvertising: will be called anytime state transitions back to powered on.
|
||||
if (_peripheralManager.state == CBManagerStatePoweredOn && _advertisementData != nil) {
|
||||
// Stop advertising just in case something outside of this class is advertising (like weave).
|
||||
[_peripheralManager stopAdvertising];
|
||||
[_peripheralManager startAdvertising:_advertisementData];
|
||||
}
|
||||
[_condition unlock];
|
||||
return _peripheralManager.state == CBManagerStatePoweredOn;
|
||||
}
|
||||
|
||||
#pragma mark - GNCPeripheralManagerDelegate
|
||||
|
||||
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
|
||||
[_condition lock];
|
||||
[_condition signal];
|
||||
[_condition unlock];
|
||||
dispatch_assert_queue(_queue);
|
||||
[self internalAddPendingServicesIfPoweredOn];
|
||||
[self internalStartAdvertisingIfPoweredOn];
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
[_condition lock];
|
||||
[_condition signal];
|
||||
[_condition unlock];
|
||||
dispatch_assert_queue(_queue);
|
||||
if (error) {
|
||||
GTMLoggerError(@"Failed to start advertising: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
[_condition lock];
|
||||
[_pendingServiceAdditions removeObject:service.UUID];
|
||||
dispatch_assert_queue(_queue);
|
||||
if (error) {
|
||||
[_serviceErrors setObject:error forKey:service.UUID];
|
||||
GTMLoggerError(@"Failed to add service %@: %@", service, error);
|
||||
}
|
||||
[_condition signal];
|
||||
[_condition unlock];
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
[_condition lock];
|
||||
// Find the requested charactertic and respond with its value if it exists.
|
||||
CBMutableService *service = _services[request.characteristic.service.UUID];
|
||||
if (!service) {
|
||||
[_condition unlock];
|
||||
dispatch_assert_queue(_queue);
|
||||
NSData *value =
|
||||
_characteristicValues[request.characteristic.service.UUID][request.characteristic.UUID];
|
||||
if (!value) {
|
||||
[_peripheralManager respondToRequest:request withResult:CBATTErrorAttributeNotFound];
|
||||
return;
|
||||
}
|
||||
for (CBMutableCharacteristic *c in service.characteristics) {
|
||||
if ([c.UUID isEqual:request.characteristic.UUID]) {
|
||||
request.value = c.value;
|
||||
[_condition unlock];
|
||||
[_peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
|
||||
return;
|
||||
}
|
||||
}
|
||||
[_condition unlock];
|
||||
[_peripheralManager respondToRequest:request withResult:CBATTErrorAttributeNotFound];
|
||||
request.value = value;
|
||||
[_peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
|
||||
}
|
||||
|
||||
#pragma mark - CBPeripheralManagerDelegate
|
||||
|
||||
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
|
||||
[self gnc_peripheralManagerDidUpdateState:peripheral];
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManagerDidUpdateState:peripheral];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManagerDidStartAdvertising:peripheral error:error];
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManagerDidStartAdvertising:peripheral error:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
[self gnc_peripheralManager:peripheral didAddService:service error:error];
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManager:peripheral didAddService:service error:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
[self gnc_peripheralManager:peripheral didReceiveReadRequest:request];
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_peripheralManager:peripheral didReceiveReadRequest:request];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright 2023 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class GNCBLEGATTServer;
|
||||
@class GNCBLEGATTClient;
|
||||
@class GNCBLEGATTCharacteristic;
|
||||
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c startAdvertisingData:completionHandler: has completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c stopAdvertisingWithcompletionHandler: has completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCStopAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a peripheral’s advertisement has been discovered.
|
||||
*
|
||||
* @note This block can be called numerous times.
|
||||
*
|
||||
* @param peripheral The discovered peripheral.
|
||||
* @param serviceData A dictionary that contains service-specific advertisement data. The keys
|
||||
* represent services and the values represent the service-specific data.
|
||||
*/
|
||||
typedef void (^GNCAdvertisementFoundHandler)(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to
|
||||
* @c startScanningForService:advertisementFoundHandler:completionHandler: has completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCStartScanningCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c stopScanningWithCompletionHandler: has completed.
|
||||
*
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCStopScanningCompletionHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to @c startGATTServerWithCompletionHandler: has completed.
|
||||
*
|
||||
* @param server The successfully started GATT server, or @c nil if an error occurred.
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCGATTServerCompletionHandler)(GNCBLEGATTServer *_Nullable server,
|
||||
NSError *_Nullable error);
|
||||
|
||||
/** A block to be invoked when a peripheral has disconnected. */
|
||||
typedef void (^GNCGATTDisconnectionHandler)();
|
||||
|
||||
/**
|
||||
* A block to be invoked when a call to
|
||||
* @c connectToGATTServerForPeripheral:disconnectionHandler:completionHandler: has completed.
|
||||
*
|
||||
* @param client The interface to the remote peripheral’s GATT server, or @c nil if an error
|
||||
* occurred.
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable client,
|
||||
NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* The main BLE medium used inside of Nearby. This serves as the entry point for all BLE and GATT
|
||||
* related operations.
|
||||
*
|
||||
* @note The public APIs of this class are thread safe.
|
||||
*/
|
||||
@interface GNCBLEMedium : NSObject
|
||||
|
||||
/** The hardware supports BOTH advertising extensions and extended scans. */
|
||||
@property(nonatomic, readonly) BOOL supportsExtendedAdvertisements;
|
||||
|
||||
/**
|
||||
* Starts advertising service data in a way that is supported by CoreBluetooth.
|
||||
*
|
||||
* Since CoreBluetooth doesn't support setting the @c CBAdvertisementDataServiceDataKey key, the
|
||||
* service list is advertised using @c CBAdvertisementDataServiceUUIDsKey and the associated data is
|
||||
* advertised using @c CBAdvertisementDataLocalNameKey. Since @c CBAdvertisementDataLocalNameKey
|
||||
* does not support binary data, the value is base64 encoded and truncated if the resulting value is
|
||||
* longer than 22 bytes. This also means we can only support advertising a single service.
|
||||
*
|
||||
* @param serviceData A dictionary that contains service-specific advertisement data.
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully started
|
||||
* advertising or an error if one has occured.
|
||||
*/
|
||||
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
|
||||
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Stops advertising all service data.
|
||||
*
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully stopped
|
||||
* advertising or an error if one has occured.
|
||||
*/
|
||||
- (void)stopAdvertisingWithCompletionHandler:
|
||||
(nullable GNCStopAdvertisingCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Scans for peripherals that are advertising the specified service.
|
||||
*
|
||||
* @param serviceUUID The service UUID to scan for.
|
||||
* @param advertisementFoundHandler Called on a private queue when a peripheral has been discovered.
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully started scanning
|
||||
* or an error if one has occured.
|
||||
*/
|
||||
- (void)startScanningForService:(CBUUID *)serviceUUID
|
||||
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
|
||||
completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Stops scanning for peripherals.
|
||||
*
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully stopped
|
||||
* scanning or an error if one has occured.
|
||||
*/
|
||||
- (void)stopScanningWithCompletionHandler:
|
||||
(nullable GNCStopScanningCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Starts a GATT server.
|
||||
*
|
||||
* @param completionHandler Called on a private queue with the GATT server if successfully started
|
||||
* or an error if one has occured.
|
||||
*/
|
||||
- (void)startGATTServerWithCompletionHandler:
|
||||
(nullable GNCGATTServerCompletionHandler)completionHandler;
|
||||
|
||||
/**
|
||||
* Connects to a peripheral’s GATT server.
|
||||
*
|
||||
* @param remotePeripheral The peripheral to which the central is attempting to connect.
|
||||
* @param disconnectionHandler Called on a private queue when the peripheral has been disconnected.
|
||||
* @param completionHandler Called on a private queue with a GATT client if successfully connected
|
||||
* or an error if one has occured.
|
||||
*/
|
||||
- (void)connectToGATTServerForPeripheral:(id<GNCPeripheral>)remotePeripheral
|
||||
disconnectionHandler:(nullable GNCGATTDisconnectionHandler)disconnectionHandler
|
||||
completionHandler:
|
||||
(nullable GNCGATTConnectionCompletionHandler)completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,319 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCCentralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/NSData+GNCWebSafeBase64.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static char *const kBLEMediumQueueLabel = "com.nearby.GNCBLEMedium";
|
||||
|
||||
static NSError *AlreadyScanningError() {
|
||||
return [NSError errorWithDomain:GNCBLEErrorDomain code:GNCBLEErrorAlreadyScanning userInfo:nil];
|
||||
}
|
||||
|
||||
@interface GNCBLEMedium () <GNCCentralManagerDelegate, CBCentralManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCBLEMedium {
|
||||
dispatch_queue_t _queue;
|
||||
id<GNCCentralManager> _centralManager;
|
||||
|
||||
// The active GATT server, or @nil if one hasn't been started yet.
|
||||
GNCBLEGATTServer *_server;
|
||||
|
||||
// The service that is being actively scanned for, or @c nil if not currently scanning.
|
||||
CBUUID *_serviceUUID;
|
||||
|
||||
// The handler called when an advertisement for the service represented by @c _serviceUUID has
|
||||
// been discovered. This will be called continuously, until the peripheral disappears.
|
||||
GNCAdvertisementFoundHandler _advertisementFoundHandler;
|
||||
|
||||
// A peripheral to connection completion handler map. Used to track connection attempts. When a
|
||||
// connection attempt has succeeded or failed, the completion handler is called and removed from
|
||||
// the map.
|
||||
NSMutableDictionary<NSUUID *, GNCGATTConnectionCompletionHandler> *_connectionCompletionHandlers;
|
||||
|
||||
// A peripheral to disconnection handler map. Used to track when a peripheral becomes
|
||||
// disconnected. Once disconnected, the completion handler is called and removed from the map.
|
||||
NSMutableDictionary<NSUUID *, GNCGATTDisconnectionHandler> *_disconnectionHandlers;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
CBCentralManager *centralManager =
|
||||
[[CBCentralManager alloc] initWithDelegate:self
|
||||
queue:_queue
|
||||
options:@{CBCentralManagerOptionShowPowerAlertKey : @NO}];
|
||||
dispatch_queue_t queue = dispatch_queue_create(kBLEMediumQueueLabel, DISPATCH_QUEUE_SERIAL);
|
||||
return [self initWithCentralManager:centralManager queue:queue];
|
||||
}
|
||||
|
||||
// This is private and should only be used for tests. The provided central manager must call
|
||||
// delegate methods on the main queue.
|
||||
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
|
||||
queue:(nullable dispatch_queue_t)queue {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_queue = queue ?: dispatch_get_main_queue();
|
||||
_centralManager = centralManager;
|
||||
_centralManager.centralDelegate = self;
|
||||
_connectionCompletionHandlers = [NSMutableDictionary dictionary];
|
||||
_disconnectionHandlers = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)supportsExtendedAdvertisements {
|
||||
// TODO(b/294736083): CoreBluetooth doesn't support actually advertising any extensions, however
|
||||
// some devices can scan for them if the feature is available. If we return @c YES from this
|
||||
// method, we would be enabling advertising extensions (which won't work), so we must return @c NO
|
||||
// until we add support for a new method to check only if extended scans are supported.
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
|
||||
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_server) {
|
||||
_server = [[GNCBLEGATTServer alloc] init];
|
||||
}
|
||||
[_server startAdvertisingData:serviceData completionHandler:completionHandler];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopAdvertisingWithCompletionHandler:
|
||||
(nullable GNCStopAdvertisingCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_server) {
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
[_server stopAdvertisingWithCompletionHandler:completionHandler];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)startScanningForService:(CBUUID *)serviceUUID
|
||||
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
|
||||
completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (_serviceUUID) {
|
||||
if (completionHandler) {
|
||||
completionHandler(AlreadyScanningError());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_serviceUUID = serviceUUID;
|
||||
_advertisementFoundHandler = advertisementFoundHandler;
|
||||
|
||||
[self internalStartScanningIfPoweredOn];
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopScanningWithCompletionHandler:
|
||||
(nullable GNCStopScanningCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
_serviceUUID = nil;
|
||||
_advertisementFoundHandler = nil;
|
||||
[_centralManager stopScan];
|
||||
if (completionHandler) {
|
||||
completionHandler(nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)startGATTServerWithCompletionHandler:
|
||||
(nullable GNCGATTServerCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
if (!_server) {
|
||||
_server = [[GNCBLEGATTServer alloc] init];
|
||||
}
|
||||
if (completionHandler) {
|
||||
completionHandler(_server, nil);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)connectToGATTServerForPeripheral:(id<GNCPeripheral>)remotePeripheral
|
||||
disconnectionHandler:(nullable GNCGATTDisconnectionHandler)disconnectionHandler
|
||||
completionHandler:
|
||||
(nullable GNCGATTConnectionCompletionHandler)completionHandler {
|
||||
dispatch_async(_queue, ^{
|
||||
_disconnectionHandlers[remotePeripheral.identifier] = disconnectionHandler;
|
||||
_connectionCompletionHandlers[remotePeripheral.identifier] = completionHandler;
|
||||
[_centralManager connectPeripheral:remotePeripheral options:@{}];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
- (void)internalStartScanningIfPoweredOn {
|
||||
dispatch_assert_queue(_queue);
|
||||
// Scanning can only be done when powered on and must be restarted if bluetooth is turned off
|
||||
// then back on. This will be called anytime the central manager's state changes, so
|
||||
// @c scanForPeripheralsWithServices:options: will be called anytime state transitions back to
|
||||
// powered on.
|
||||
if (_centralManager.state == CBManagerStatePoweredOn && _serviceUUID != nil) {
|
||||
// Stop scanning just in case something outside of this class is already scanning.
|
||||
[_centralManager stopScan];
|
||||
[_centralManager
|
||||
scanForPeripheralsWithServices:@[ _serviceUUID ]
|
||||
// Nearby relies on the existence of an advertisement for endpoint
|
||||
// discovery/lost events, so we must set this key to keep the stream
|
||||
// of duplicate delegate events flowing. This has adverse effect on
|
||||
// battery life, but currently necessary.
|
||||
options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary<CBUUID *, NSData *> *)decodeAdvertisementData:
|
||||
(NSDictionary<NSString *, id> *)advertisementData {
|
||||
dispatch_assert_queue(_queue);
|
||||
// If service data is available, return it directly.
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData =
|
||||
advertisementData[CBAdvertisementDataServiceDataKey];
|
||||
if (serviceData) {
|
||||
return serviceData;
|
||||
}
|
||||
|
||||
// Apple devices don't support advertising service data, so Apple devices advertise a base64
|
||||
// encoded local name, while other devices advertise service data. Here we attempt to reconstruct
|
||||
// service data by decoding the local name. If successful, this is possibly a Nearby advertisement
|
||||
// on an Apple device.
|
||||
NSString *localName = advertisementData[CBAdvertisementDataLocalNameKey];
|
||||
if (!localName) {
|
||||
return @{};
|
||||
}
|
||||
NSData *data = [[NSData alloc] initWithWebSafeBase64EncodedString:localName];
|
||||
|
||||
// A Nearby Apple advertisement should only have a single service, so simply grab the first one if
|
||||
// it exists.
|
||||
NSArray<CBUUID *> *serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey];
|
||||
CBUUID *serviceUUID = serviceUUIDs.firstObject;
|
||||
if (data && serviceUUID) {
|
||||
return @{serviceUUID : data};
|
||||
}
|
||||
|
||||
return @{};
|
||||
}
|
||||
|
||||
#pragma mark - GNCCentralManagerDelegate
|
||||
|
||||
- (void)gnc_centralManagerDidUpdateState:(id<GNCCentralManager>)central {
|
||||
dispatch_assert_queue(_queue);
|
||||
[self internalStartScanningIfPoweredOn];
|
||||
}
|
||||
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didDiscoverPeripheral:(id<GNCPeripheral>)peripheral
|
||||
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
|
||||
RSSI:(NSNumber *)RSSI {
|
||||
dispatch_assert_queue(_queue);
|
||||
if (_advertisementFoundHandler) {
|
||||
_advertisementFoundHandler(peripheral, [self decodeAdvertisementData:advertisementData]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didConnectPeripheral:(id<GNCPeripheral>)peripheral {
|
||||
dispatch_assert_queue(_queue);
|
||||
GNCGATTConnectionCompletionHandler handler = _connectionCompletionHandlers[peripheral.identifier];
|
||||
_connectionCompletionHandlers[peripheral.identifier] = nil;
|
||||
if (handler) {
|
||||
GNCBLEGATTClient *client = [[GNCBLEGATTClient alloc] initWithPeripheral:peripheral];
|
||||
handler(client, nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didFailToConnectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
GNCGATTConnectionCompletionHandler handler = _connectionCompletionHandlers[peripheral.identifier];
|
||||
_connectionCompletionHandlers[peripheral.identifier] = nil;
|
||||
if (handler) {
|
||||
handler(nil, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didDisconnectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_assert_queue(_queue);
|
||||
GNCGATTDisconnectionHandler handler = _disconnectionHandlers[peripheral.identifier];
|
||||
_disconnectionHandlers[peripheral.identifier] = nil;
|
||||
if (handler) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - CBCentralManagerDelegate
|
||||
|
||||
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_centralManagerDidUpdateState:central];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didDiscoverPeripheral:(CBPeripheral *)peripheral
|
||||
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
|
||||
RSSI:(NSNumber *)RSSI {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_centralManager:central
|
||||
didDiscoverPeripheral:peripheral
|
||||
advertisementData:advertisementData
|
||||
RSSI:RSSI];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_centralManager:central didConnectPeripheral:peripheral];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didFailToConnectPeripheral:(CBPeripheral *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_centralManager:central didFailToConnectPeripheral:peripheral error:error];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didDisconnectPeripheral:(CBPeripheral *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
dispatch_async(_queue, ^{
|
||||
[self gnc_centralManager:central didDisconnectPeripheral:peripheral error:error];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2023 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCCentralManagerDelegate;
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** Protocol which helps create a fake of a @c CBCentralManager to inject for testing. */
|
||||
@protocol GNCCentralManager
|
||||
|
||||
/** Shadow property of a @c CBCentralManagerDelegate. */
|
||||
@property(weak, nonatomic, nullable) id<GNCCentralManagerDelegate> centralDelegate;
|
||||
|
||||
/**
|
||||
* The current state of the manager.
|
||||
*
|
||||
* This state is initially set to @c CBManagerStateUnknown. When the state updates, the manager
|
||||
* calls its delegate’s @c gnc_centralManagerDidUpdateState: method.
|
||||
*/
|
||||
@property(nonatomic, assign, readonly) CBManagerState state;
|
||||
|
||||
/**
|
||||
* Scans for peripherals that are advertising services.
|
||||
*
|
||||
* You can provide an array of @c CBUUID objects, representing service UUIDs, in the @c serviceUUIDs
|
||||
* parameter. When you do, the central manager returns only peripherals that advertise the services
|
||||
* you specify. If the @c serviceUUIDs parameter is @c nil, this method returns all discovered
|
||||
* peripherals, regardless of their supported services.
|
||||
*
|
||||
* @note The recommended practice is to populate the @c serviceUUIDs parameter rather than leaving
|
||||
* it @c nil.
|
||||
*
|
||||
* If the central manager is actively scanning with one set of parameters and it receives another
|
||||
* set to scan, the new parameters override the previous set. When the central manager discovers a
|
||||
* peripheral, it calls the @c gnc_centralManager:didDiscoverPeripheral:advertisementData:RSSI:
|
||||
* method of its delegate object.
|
||||
*
|
||||
* Your app can scan for Bluetooth devices in the background by specifying the @c bluetooth-central
|
||||
* background mode. To do this, your app must explicitly scan for one or more services by specifying
|
||||
* them in the @c serviceUUIDs parameter. The CBCentralManager scan option has no effect while
|
||||
* scanning in the background.
|
||||
*
|
||||
* @param serviceUUIDs An array of @c CBUUID objects that the app is interested in. Each @c CBUUID
|
||||
* object represents the UUID of a service that a peripheral advertises.
|
||||
* @param options A dictionary of options for customizing the scan.
|
||||
*/
|
||||
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs
|
||||
options:(nullable NSDictionary<NSString *, id> *)options;
|
||||
|
||||
/**
|
||||
* Establishes a local connection to a peripheral.
|
||||
*
|
||||
* After successfully establishing a local connection to a peripheral, the central manager object
|
||||
* calls the @c gnc_centralManager:didConnectPeripheral: method of its delegate object. If the
|
||||
* connection attempt fails, the central manager object calls the
|
||||
* @c gnc_centralManager:didFailToConnectPeripheral:error: method of its delegate object instead.
|
||||
* Attempts to connect to a peripheral don’t time out. To explicitly cancel a pending connection to
|
||||
* a peripheral, call the @c cancelPeripheralConnection: method. Deallocating @c peripheral also
|
||||
* implicitly calls @c cancelPeripheralConnection:.
|
||||
*
|
||||
* @param peripheral The peripheral to which the central is attempting to connect.
|
||||
* @param options A dictionary to customize the behavior of the connection.
|
||||
*/
|
||||
- (void)connectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
options:(nullable NSDictionary<NSString *, id> *)options;
|
||||
|
||||
/** Asks the central manager to stop scanning for peripherals. */
|
||||
- (void)stopScan;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Protocol which helps the @c GNCCentralManager wrap a @c CBCentralManagerDelegate for
|
||||
* testing.
|
||||
*/
|
||||
@protocol GNCCentralManagerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Tells the delegate the central manager’s state updated.
|
||||
*
|
||||
* You implement this required method to ensure that the central device supports Bluetooth low
|
||||
* energy and that it’s available to use. You should issue commands to the central manager only when
|
||||
* the central manager’s @c state indicates it’s powered on. A state with a value lower than
|
||||
* @c CBManagerStatePoweredOn implies that scanning has stopped, which in turn disconnects any
|
||||
* previously-connected peripherals. If the state moves below @c CBManagerStatePoweredOff, all
|
||||
* @c CBPeripheral objects obtained from this central manager become invalid; you must retrieve or
|
||||
* discover these peripherals again.
|
||||
*
|
||||
* @param central The central manager whose state has changed.
|
||||
*/
|
||||
- (void)gnc_centralManagerDidUpdateState:(id<GNCCentralManager>)central;
|
||||
|
||||
/**
|
||||
* Tells the delegate the central manager discovered a peripheral while scanning for devices.
|
||||
*
|
||||
* You must retain a local copy of the peripheral if you want to perform commands on it. Use the
|
||||
* RSSI data to determine the proximity of a discoverable peripheral device, and whether you want to
|
||||
* connect to it automatically.
|
||||
*
|
||||
* @param central The central manager that provides the update.
|
||||
* @param peripheral The discovered peripheral.
|
||||
* @param advertisementData A dictionary containing any advertisement data.
|
||||
* @param RSSI The current received signal strength indicator (RSSI) of the peripheral, in decibels.
|
||||
*/
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didDiscoverPeripheral:(id<GNCPeripheral>)peripheral
|
||||
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
|
||||
RSSI:(NSNumber *)RSSI;
|
||||
|
||||
/**
|
||||
* Tells the delegate that the central manager connected to a peripheral.
|
||||
*
|
||||
* The manager invokes this method when a call to @c connectPeripheral:options: succeeds. You
|
||||
* typically implement this method to set the peripheral’s delegate and discover its services.
|
||||
*
|
||||
* @param central The central manager that provides this information.
|
||||
* @param peripheral The now-connected peripheral.
|
||||
*/
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didConnectPeripheral:(id<GNCPeripheral>)peripheral;
|
||||
|
||||
/**
|
||||
* Tells the delegate the central manager failed to create a connection with a peripheral.
|
||||
*
|
||||
* The manager invokes this method when a connection initiated with the
|
||||
* @c connectPeripheral:options: method fails to complete. Because connection attempts don’t time
|
||||
* out, a failed connection usually indicates a transient issue, in which case you may attempt
|
||||
* connecting to the peripheral again.
|
||||
*
|
||||
* @param central The central manager that provides this information.
|
||||
* @param peripheral The peripheral that failed to connect.
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didFailToConnectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
/**
|
||||
* Tells the delegate that the central manager disconnected from a peripheral.
|
||||
*
|
||||
* The manager invokes this method when disconnecting a peripheral previously connected with the
|
||||
* @c connectPeripheral:options: method. The error parameter contains the reason for the
|
||||
* disconnection, unless the disconnect resulted from a call to @c cancelPeripheralConnection:.
|
||||
*
|
||||
* All services, characteristics, and characteristic descriptors of a peripheral become invalidated
|
||||
* after it disconnects.
|
||||
*
|
||||
* @param central The central manager that provides this information.
|
||||
* @param peripheral The now-disconnected peripheral.
|
||||
* @param error The cause of the failure, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_centralManager:(id<GNCCentralManager>)central
|
||||
didDisconnectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Declares that @c CBCentralManager implements the @c GNCCentralManager protocol.
|
||||
*
|
||||
* This allows us to directly use a @c CBCentralManager as a @c GNCCentralManager.
|
||||
*/
|
||||
@interface CBCentralManager () <GNCCentralManager>
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCCentralManager.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation CBCentralManager (GNCCentralManagerAdditions)
|
||||
|
||||
- (void)setCentralDelegate:(nullable id<GNCCentralManagerDelegate>)centralDelegate {
|
||||
NSAssert([centralDelegate conformsToProtocol:@protocol(CBCentralManagerDelegate)],
|
||||
@"centralDelegate must conform to protocol CBCentralManagerDelegate");
|
||||
self.delegate = (id<CBCentralManagerDelegate>)centralDelegate;
|
||||
}
|
||||
|
||||
- (nullable id<GNCCentralManagerDelegate>)centralDelegate {
|
||||
return (id<GNCCentralManagerDelegate>)self.delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2023 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCPeripheralDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** Protocol which helps create a fake of a @c CBPeripheral to inject for testing. */
|
||||
@protocol GNCPeripheral
|
||||
|
||||
// This can't be @c delegate, because it would shadow @c CBPeripheral's delegate.
|
||||
/**
|
||||
* The peripheral's delegate.
|
||||
*
|
||||
* This delegate is only sent CBPeripheralDelegate messages, and the delegate is responsible for
|
||||
* forwarding those messages to its GNCPeripheralDelegate method implementations.
|
||||
*
|
||||
* See: https://developer.apple.com/videos/play/wwdc2018/417/
|
||||
*/
|
||||
@property(weak, nonatomic, nullable) id<GNCPeripheralDelegate> peripheralDelegate;
|
||||
|
||||
/**
|
||||
* A list of a peripheral’s discovered services.
|
||||
*
|
||||
* Returns an array of services (represented by CBService objects) that successful a call to the
|
||||
* @c discoverServices: method discovered. If you haven’t yet called the @c discoverServices: method
|
||||
* to discover the services of the peripheral, or if there was an error in doing so, the value of
|
||||
* this property is @c nil.
|
||||
*/
|
||||
@property(retain, readonly, nullable) NSArray<CBService *> *services;
|
||||
|
||||
/**
|
||||
* The UUID associated with the peer.
|
||||
*
|
||||
* The value of this property represents the unique identifier of the peer. The first time a local
|
||||
* manager encounters a peer, the system assigns the peer a UUID, represented by a new @c NSUUID
|
||||
* object. Peers use @c NSUUID instances to identify themselves, instead of by the @c CBUUID objects
|
||||
* that identify a peripheral’s services, characteristics, and descriptors.
|
||||
*/
|
||||
@property(readonly, nonatomic) NSUUID *identifier;
|
||||
|
||||
/**
|
||||
* Discovers the specified services of the peripheral.
|
||||
*
|
||||
* You can provide an array of CBUUID objects, representing service UUIDs, in the @c serviceUUIDs
|
||||
* parameter. When you do, the peripheral returns only the services of the peripheral that match the
|
||||
* provided UUIDs.
|
||||
*
|
||||
* @note If the @c serviceUUIDs parameter is @c nil, this method returns all of the peripheral’s
|
||||
* available services. This is much slower than providing an array of service UUIDs to search for.
|
||||
*
|
||||
* When the peripheral discovers one or more services, it calls the
|
||||
* @c peripheral:didDiscoverServices: method of its delegate object. After a peripheral discovers
|
||||
* services, you can access them through the peripheral’s @c services property.
|
||||
*
|
||||
* @param serviceUUIDs An array of CBUUID objects that you are interested in. Each CBUUID object
|
||||
* represents a UUID that identifies the type of service you want to discover.
|
||||
*/
|
||||
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
|
||||
|
||||
/**
|
||||
* Discovers the specified characteristics of a service.
|
||||
*
|
||||
* You can provide an array of CBUUID objects, representing characteristic UUIDs, in the
|
||||
* @c characteristicUUIDs parameter. When you do, the peripheral returns only the characteristics of
|
||||
* the service that match the provided UUIDs. If the @c characteristicUUIDs parameter is @c nil,
|
||||
* this method returns all characteristics of the service.
|
||||
*
|
||||
* @note If the @c characteristicUUIDs parameter is @c nil, this method returns all of the service’s
|
||||
* characteristics. This is much slower than providing an array of characteristic UUIDs to search
|
||||
* for.
|
||||
*
|
||||
* When the peripheral discovers one or more characteristics of the specified service, it calls the
|
||||
* @c peripheral:didDiscoverCharacteristicsForService:error: method of its delegate object. After
|
||||
* the peripheral discovers the service’s characteristics, you can access them through the service’s
|
||||
* @c characteristics property.
|
||||
*
|
||||
* @param characteristicUUIDs An array of CBUUID objects that you are interested in. Each CBUUID
|
||||
* object represents a UUID that identifies the type of a characteristic
|
||||
* you want to discover.
|
||||
* @param service The service whose characteristics you want to discover.
|
||||
*/
|
||||
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs
|
||||
forService:(CBService *)service;
|
||||
|
||||
/**
|
||||
* Retrieves the value of a specified characteristic.
|
||||
*
|
||||
* When you call this method to read the value of a characteristic, the peripheral calls the
|
||||
* @c peripheral:didUpdateValueForCharacteristic:error: method of its delegate object. If the
|
||||
* peripheral successfully reads the value of the characteristic, you can access it through the
|
||||
* characteristic’s @c value property.
|
||||
*
|
||||
* Not all characteristics have a readable value. You can determine whether a characteristic’s value
|
||||
* is readable by accessing the relevant properties of the CBCharacteristicProperties enumeration.
|
||||
*
|
||||
* @param characteristic The characteristic whose value you want to read.
|
||||
*/
|
||||
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Protocol which helps the @c GNCPeripheral wrap a @c CBPeripheralDelegate for
|
||||
* testing.
|
||||
*/
|
||||
@protocol GNCPeripheralDelegate <CBPeripheralDelegate>
|
||||
|
||||
/**
|
||||
* Tells the delegate that peripheral service discovery succeeded.
|
||||
*
|
||||
* Called when your app calls the @c discoverServices: method. If the peripheral successfully
|
||||
* discovers services, you can access them through the peripheral’s @c services property. If
|
||||
* successful, the @c error parameter is @c nil. If unsuccessful, the @c error parameter returns the
|
||||
* cause of the failure.
|
||||
*
|
||||
* @param peripheral The peripheral to which the services belong.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral didDiscoverServices:(nullable NSError *)error;
|
||||
|
||||
/**
|
||||
* Tells the delegate that the peripheral found characteristics for a service.
|
||||
*
|
||||
* Called when your app calls the @c discoverCharacteristics:forService: method. If the peripheral
|
||||
* successfully discovers the characteristics of the specified service, you can access them through
|
||||
* the service’s @c characteristics property. If successful, the @c error parameter is @c nil. If
|
||||
* unsuccessful, the @c error parameter returns the cause of the failure.
|
||||
*
|
||||
* @param peripheral The peripheral providing this information.
|
||||
* @param service The service to which the characteristics belong.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral
|
||||
didDiscoverCharacteristicsForService:(CBService *)service
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
/**
|
||||
* Tells the delegate that retrieving the specified characteristic’s value succeeded, or that the
|
||||
* characteristic’s value changed.
|
||||
*
|
||||
* Called when your app calls the @c readValueForCharacteristic: method. If successful, the @c error
|
||||
* parameter is @c nil. If unsuccessful, the @c error parameter returns the cause of the failure.
|
||||
*
|
||||
* @param peripheral The peripheral providing this information.
|
||||
* @param characteristic The characteristic containing the value.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_peripheral:(id<GNCPeripheral>)peripheral
|
||||
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Declares that @c CBPeripheral implements the @c GNCPeripheral protocol.
|
||||
*
|
||||
* This allows us to directly use a @c CBPeripheral as a @c GNCPeripheral.
|
||||
*/
|
||||
@interface CBPeripheral () <GNCPeripheral>
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation CBPeripheral (GNCPeripheralAdditions)
|
||||
|
||||
- (void)setPeripheralDelegate:(nullable id<GNCPeripheralDelegate>)peripheralDelegate {
|
||||
self.delegate = peripheralDelegate;
|
||||
}
|
||||
|
||||
- (nullable id<GNCPeripheralDelegate>)peripheralDelegate {
|
||||
return (id<GNCPeripheralDelegate>)self.delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol GNCPeripheralManager
|
||||
|
||||
/** Shadow property of a @c CBPeripheralManagerDelegate. */
|
||||
@property(nonatomic, nullable) id<GNCPeripheralManagerDelegate> peripheralDelegate;
|
||||
@property(weak, nonatomic, nullable) id<GNCPeripheralManagerDelegate> peripheralDelegate;
|
||||
|
||||
@property(nonatomic, assign, readonly) CBManagerState state;
|
||||
|
||||
@@ -111,13 +111,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result;
|
||||
|
||||
/**
|
||||
* Stops advertising peripheral manager data.
|
||||
*
|
||||
* Call this method when you no longer want to advertise peripheral manager data.
|
||||
*/
|
||||
- (void)stopAdvertising;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Protocol which helps the @c GNCPeripheralManager wrap a @c CBPeripheralManagerDelegate for
|
||||
* testing.
|
||||
*/
|
||||
@protocol GNCPeripheralManagerDelegate <NSObject>
|
||||
@protocol GNCPeripheralManagerDelegate <CBPeripheralManagerDelegate>
|
||||
|
||||
/**
|
||||
* Tells the delegate the peripheral manager’s state updated.
|
||||
|
||||
@@ -23,9 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@implementation CBPeripheralManager (GNCPeripheralManagerAdditions)
|
||||
|
||||
- (void)setPeripheralDelegate:(nullable id<GNCPeripheralManagerDelegate>)peripheralDelegate {
|
||||
NSAssert([peripheralDelegate conformsToProtocol:@protocol(CBPeripheralManagerDelegate)],
|
||||
@"peripheralDelegate must conform to protocol CBPeripheralManagerDelegate");
|
||||
self.delegate = (id<CBPeripheralManagerDelegate>)peripheralDelegate;
|
||||
self.delegate = peripheralDelegate;
|
||||
}
|
||||
|
||||
- (nullable id<GNCPeripheralManagerDelegate>)peripheralDelegate {
|
||||
|
||||
@@ -19,7 +19,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface NSData (GNCWebSafeBase64)
|
||||
|
||||
/** Creates a Base64 encoded string from the data using websafe characters and no padding. */
|
||||
- (NSString *)webSafebase64EncodedString;
|
||||
- (NSString *)webSafeBase64EncodedString;
|
||||
|
||||
/**
|
||||
* Initializes a data object with the given Base64 encoded string.
|
||||
*
|
||||
* @param base64String A Base64 encoded string.
|
||||
* @return A data object built by Base64 decoding the provided string. Returns @c nil if the data
|
||||
* object could not be decoded.
|
||||
*/
|
||||
- (nullable instancetype)initWithWebSafeBase64EncodedString:(NSString *)base64String;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/NSData+GNCWebSafeBase64.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation NSData (GNCWebSafeBase64)
|
||||
|
||||
- (NSString *)webSafebase64EncodedString {
|
||||
- (NSString *)webSafeBase64EncodedString {
|
||||
NSString *encoded = [self base64EncodedStringWithOptions:0];
|
||||
|
||||
// Convert the standard base64 characters to URL safe variants.
|
||||
@@ -29,6 +31,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return encoded;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithWebSafeBase64EncodedString:(NSString *)base64String {
|
||||
// Convert the URL safe base64 characters to the standard variants.
|
||||
base64String = [base64String stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
|
||||
base64String = [base64String stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
|
||||
|
||||
// @c initWithBase64EncodedString:options: requires a padded base64 string. Append enough "="
|
||||
// characters to make the string a multiple of 4.
|
||||
NSUInteger paddedLength = base64String.length + ((4 - (base64String.length % 4)) % 4);
|
||||
base64String = [base64String stringByPaddingToLength:paddedLength
|
||||
withString:@"="
|
||||
startingAtIndex:0];
|
||||
return [self initWithBase64EncodedString:base64String options:0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -18,13 +18,16 @@ package(default_visibility = ["//internal/platform/implementation/apple:__subpac
|
||||
objc_library(
|
||||
name = "Mediums",
|
||||
srcs = [
|
||||
"BLEv2/GNCBLEError.m",
|
||||
"BLEv2/GNCBLEGATTCharacteristic.m",
|
||||
"BLEv2/GNCBLEGATTClient.m",
|
||||
"BLEv2/GNCBLEGATTServer.m",
|
||||
"BLEv2/GNCBLEMedium.m",
|
||||
"BLEv2/GNCCentralManager.m",
|
||||
"BLEv2/GNCPeripheral.m",
|
||||
"BLEv2/GNCPeripheralManager.m",
|
||||
"BLEv2/NSData+GNCWebSafeBase64.m",
|
||||
"Ble/GNCMBleCentral.m",
|
||||
"Ble/GNCMBleConnection.m",
|
||||
"Ble/GNCMBlePeripheral.m",
|
||||
"Ble/GNCMBleUtils.mm",
|
||||
"GNCLeaks.h",
|
||||
"GNCLeaks.m",
|
||||
@@ -37,13 +40,16 @@ objc_library(
|
||||
"WiFiLAN/GNCWiFiLANSocket.m",
|
||||
],
|
||||
hdrs = [
|
||||
"BLEv2/GNCBLEError.h",
|
||||
"BLEv2/GNCBLEGATTCharacteristic.h",
|
||||
"BLEv2/GNCBLEGATTClient.h",
|
||||
"BLEv2/GNCBLEGATTServer.h",
|
||||
"BLEv2/GNCBLEMedium.h",
|
||||
"BLEv2/GNCCentralManager.h",
|
||||
"BLEv2/GNCPeripheral.h",
|
||||
"BLEv2/GNCPeripheralManager.h",
|
||||
"BLEv2/NSData+GNCWebSafeBase64.h",
|
||||
"Ble/GNCMBleCentral.h",
|
||||
"Ble/GNCMBleConnection.h",
|
||||
"Ble/GNCMBlePeripheral.h",
|
||||
"Ble/GNCMBleUtils.h",
|
||||
"GNCMConnection.h",
|
||||
"WiFiLAN/GNCIPv4Address.h",
|
||||
@@ -54,8 +60,6 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
"//internal/platform/implementation/apple:Shared",
|
||||
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Central",
|
||||
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Peripheral",
|
||||
"//internal/platform/implementation/apple/Mediums/Ble/Sockets:Shared",
|
||||
"//proto/mediums:ble_frames_cc_proto",
|
||||
"//third_party/apple_frameworks:CoreBluetooth",
|
||||
|
||||
@@ -1,114 +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 <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
|
||||
|
||||
@class CBUUID;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* This handler is called on a discover when a nearby advertising endpoint is discovered.
|
||||
*/
|
||||
typedef void (^GNCMScanResultHandler)(NSString *peripheralID, NSData *serviceData);
|
||||
|
||||
/**
|
||||
* This handler is called on a discovery when a nearby advertising endpoint is connected.
|
||||
*/
|
||||
typedef void (^GNCMGATTConnectionResultHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* This handler is called on a discovery when a nearby advertising endpoint is connected. The input
|
||||
* is a discovered set of characteristics.
|
||||
*/
|
||||
typedef void (^GNCMGATTDiscoverResultHandler)(
|
||||
NSOrderedSet<CBCharacteristic *> *_Nullable characteristicValues);
|
||||
|
||||
/**
|
||||
* This handler is called by a discovering endpoint to request a connection with an an advertising
|
||||
* endpoint.
|
||||
*/
|
||||
typedef void (^GNCMBleConnectionRequester)(NSString *serviceID,
|
||||
GNCMConnectionHandler connectionHandler);
|
||||
|
||||
/**
|
||||
* This handler is called on a discoverer when a nearby advertising endpoint is discovered.
|
||||
* Call |requestConnection| to request a connection with the advertiser.
|
||||
*/
|
||||
typedef void (^GNCMBleRequestConnectionHandler)(GNCMBleConnectionRequester requestConnection);
|
||||
|
||||
/**
|
||||
* GNCMBleCentral discovers devices advertising the specified service UUID via BLE (using the
|
||||
* GNCMBlePeripheral class) and calls the specififed scanning result handler when one is found.
|
||||
*
|
||||
* This class is thread-safe. Any calls made to it (and to the objects/closures it passes back via
|
||||
* callbacks) can be made from any thread/queue. Callbacks made from this class are called on the
|
||||
* specified queue.
|
||||
*/
|
||||
@interface GNCMBleCentral : NSObject
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Starts scanning with service UUID.
|
||||
*
|
||||
* @param serviceUUID A string that uniquely identifies the scanning services to search for.
|
||||
* @param scanResultHandler The handler that is called when an endpoint advertising the service
|
||||
* UUID is discovered.
|
||||
* @param requestConnectionHandler The handler that is called when an endpoint is discovered.
|
||||
* @param callbackQueue The queue on which all callbacks are made.
|
||||
*/
|
||||
- (BOOL)startScanningWithServiceUUID:(NSString *)serviceUUID
|
||||
scanResultHandler:(GNCMScanResultHandler)scanResultHandler
|
||||
requestConnectionHandler:(GNCMBleRequestConnectionHandler)requestConnectionHandler
|
||||
callbackQueue:(nullable dispatch_queue_t)callbackQueue;
|
||||
|
||||
/**
|
||||
* Sets up a GATT connection.
|
||||
*
|
||||
* @param peripheralID A string that uniquely identifies the peripheral.
|
||||
* @param gattConnectionResultHandler The handler that is called when an endpoint is
|
||||
* connected.
|
||||
*/
|
||||
- (void)connectGattServerWithPeripheralID:(NSString *)peripheralID
|
||||
gattConnectionResultHandler:
|
||||
(GNCMGATTConnectionResultHandler)gattConnectionResultHandler;
|
||||
|
||||
/**
|
||||
* Discovers GATT service and its associated characteristics with values.
|
||||
*
|
||||
* @param serviceUUID A CBUUID for service to discover.
|
||||
* @param gattCharacteristics Array of CBUUID for characteristic to discover.
|
||||
* @param peripheralID A string that uniquely identifies the peripheral.
|
||||
* @param gattDiscoverResultHandler This handler is called on a discovery for a discovered map of
|
||||
* characteristic values when a nearby advertising endpoint is
|
||||
* connected.
|
||||
*/
|
||||
- (void)discoverGattService:(CBUUID *)serviceUUID
|
||||
gattCharacteristics:(NSArray<CBUUID *> *)characteristicUUIDs
|
||||
peripheralID:(NSString *)peripheralID
|
||||
gattDiscoverResultHandler:(GNCMGATTDiscoverResultHandler)gattDiscoverResultHandler;
|
||||
|
||||
/**
|
||||
* Disconnects GATT connection.
|
||||
*
|
||||
* @param peripheralID A string that uniquely identifies the peripheral.
|
||||
*/
|
||||
- (void)disconnectGattServiceWithPeripheralID:(NSString *)peripheralID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,510 +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/apple/Mediums/Ble/GNCMBleCentral.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/GNCMConnection.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GNCMCentralState) {
|
||||
GNCMCentralStateStopped,
|
||||
GNCMCentralStateScanning,
|
||||
};
|
||||
|
||||
typedef void (^GNCMBleCharacteristicsHandler)(NSArray<CBCharacteristic *> *characteristics,
|
||||
NSError *error);
|
||||
typedef void (^GNCMBleCharacteristicValueHandler)(CBCharacteristic *characteristic, NSError *error);
|
||||
typedef void (^GNCIntHandler)(int);
|
||||
|
||||
/** This lets a GNCIntHandler call itself. */
|
||||
GNCIntHandler GNCRecursiveIntHandler(void (^block)(GNCIntHandler blockSelf, int i)) {
|
||||
return ^(int i) {
|
||||
return block(GNCRecursiveIntHandler(block), i);
|
||||
};
|
||||
}
|
||||
|
||||
/** This represents a discovered peripheral. */
|
||||
@interface GNCMPeripheralInfo : NSObject
|
||||
@property(nonatomic) CBPeripheral *peripheral;
|
||||
@property(nonatomic, copy) NSDictionary<NSString *, id> *advertisementData;
|
||||
|
||||
/** Called when characteristics are discovered. */
|
||||
@property(nonatomic, nullable) GNCMBleCharacteristicsHandler charsHandler;
|
||||
|
||||
/** Called when a characteristic value is read. */
|
||||
@property(nonatomic, nullable) GNCMBleCharacteristicValueHandler charValueHandler;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GNCMPeripheralInfo
|
||||
|
||||
- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral
|
||||
advertisementData:(NSDictionary<NSString *, id> *)advertisementData {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_peripheral = peripheral;
|
||||
_advertisementData = [advertisementData copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
// There is always exactly one info object per peripheral, so compare by identity. This is needed
|
||||
// for maintenance of the peripherals stored in multiple maps.
|
||||
return self == object;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return (NSUInteger)self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface GNCMBleCentral () <CBCentralManagerDelegate,
|
||||
CBPeripheralDelegate,
|
||||
GNSCentralManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCMBleCentral {
|
||||
/** Service UUID the central is scanning for. */
|
||||
CBUUID *_serviceUUID;
|
||||
/** The scan result callback handler. */
|
||||
GNCMScanResultHandler _scanResultHandler;
|
||||
/** Central manager used to scan or connect to peripherals. */
|
||||
CBCentralManager *_centralManager;
|
||||
/** Serial background queue for |centralManager|. */
|
||||
dispatch_queue_t _selfQueue;
|
||||
/** Central state for stop or scanning. */
|
||||
GNCMCentralState _centralState;
|
||||
/** The dictionary keyed by CBPeripheral identifier to value GNCMPeripheralInfo. */
|
||||
NSMutableDictionary<NSUUID *, GNCMPeripheralInfo *> *_nearbyPeripheralsByID;
|
||||
/** Array of characteristic UUID used for discovering. */
|
||||
NSArray<CBUUID *> *_characteristicUUIDs;
|
||||
/** GATT connection result handler. */
|
||||
GNCMGATTConnectionResultHandler _gattConnectionResultHandler;
|
||||
/** GATT service and characteristic discovery result hanadler. */
|
||||
GNCMGATTDiscoverResultHandler _gattDiscoverResultHandler;
|
||||
/** The discovered characteristic values map used to callback for `_gattDiscoverResultHandler`. */
|
||||
NSMutableOrderedSet<CBCharacteristic *> *_gattCharacteristicValues;
|
||||
/** Central manager used for socket connection based on weave protocol. */
|
||||
GNSCentralManager *_socketCentralManager;
|
||||
/** A callback handler to reuqest connection on the discovered advertiser. */
|
||||
GNCMBleRequestConnectionHandler _requestConnectionHandler;
|
||||
/** 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 {
|
||||
if (self = [super init]) {
|
||||
// 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("GNCCentralManagerQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_nearbyPeripheralsByID = [NSMutableDictionary dictionary];
|
||||
_gattCharacteristicValues = [[NSMutableOrderedSet alloc] init];
|
||||
|
||||
_centralState = GNCMCentralStateStopped;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// These calls must be made on |selfQueue|. Can't capture |self| in an async block, so must use
|
||||
// dispatch_sync. This means dealloc must be called from an external queue, which means |self|
|
||||
// must never be captured by any escaping block used in this class.
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
[self stopScanningInternal];
|
||||
[_socketCentralManager stopNoScanMode];
|
||||
|
||||
_callbacksEnabled = NO;
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)startScanningWithServiceUUID:(NSString *)serviceUUID
|
||||
scanResultHandler:(GNCMScanResultHandler)scanResultHandler
|
||||
requestConnectionHandler:(GNCMBleRequestConnectionHandler)requestConnectionHandler
|
||||
callbackQueue:(nullable dispatch_queue_t)callbackQueue {
|
||||
NSLog(@"[NEARBY] Client rquests startScanning");
|
||||
_serviceUUID = [CBUUID UUIDWithString:serviceUUID];
|
||||
_scanResultHandler = scanResultHandler;
|
||||
_requestConnectionHandler = requestConnectionHandler;
|
||||
|
||||
// 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("GNCMBleCentralCallbackQueue", DISPATCH_QUEUE_PRIORITY_DEFAULT);
|
||||
_callbacksEnabled = YES;
|
||||
|
||||
// Set up the central manager for scanning.
|
||||
_centralManager =
|
||||
[[CBCentralManager alloc] initWithDelegate:self
|
||||
queue:_selfQueue
|
||||
options:@{CBCentralManagerOptionShowPowerAlertKey : @NO}];
|
||||
|
||||
// Set up the central manager for the socket.
|
||||
_socketCentralManager = [[GNSCentralManager alloc] initWithSocketServiceUUID:_serviceUUID
|
||||
queue:_selfQueue];
|
||||
_socketCentralManager.delegate = self;
|
||||
[_socketCentralManager startNoScanModeWithAdvertisedServiceUUIDs:@[ _serviceUUID ]];
|
||||
|
||||
_centralState = GNCMCentralStateScanning;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)connectGattServerWithPeripheralID:(NSString *)peripheralID
|
||||
gattConnectionResultHandler:
|
||||
(GNCMGATTConnectionResultHandler)gattConnectionResultHandler {
|
||||
_gattConnectionResultHandler = gattConnectionResultHandler;
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
GNCMPeripheralInfo *peripheralInfo =
|
||||
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
|
||||
if (!peripheralInfo) return;
|
||||
[_centralManager connectPeripheral:peripheralInfo.peripheral options:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)discoverGattService:(CBUUID *)serviceUUID
|
||||
gattCharacteristics:(NSArray<CBUUID *> *)characteristicUUIDs
|
||||
peripheralID:(NSString *)peripheralID
|
||||
gattDiscoverResultHandler:(GNCMGATTDiscoverResultHandler)gattDiscoverResultHandler {
|
||||
_gattDiscoverResultHandler = gattDiscoverResultHandler;
|
||||
[_gattCharacteristicValues removeAllObjects];
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
GNCMPeripheralInfo *peripheralInfo =
|
||||
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
|
||||
if (!peripheralInfo) return;
|
||||
_characteristicUUIDs = [characteristicUUIDs copy];
|
||||
|
||||
// Start to discover service and the delegate will get its characteristics and read their values
|
||||
// recursively
|
||||
[peripheralInfo.peripheral discoverServices:@[ serviceUUID ]];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)disconnectGattServiceWithPeripheralID:(NSString *)peripheralID {
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
GNCMPeripheralInfo *peripheralInfo =
|
||||
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
|
||||
if (!peripheralInfo) return;
|
||||
_gattConnectionResultHandler = nil;
|
||||
_gattDiscoverResultHandler = nil;
|
||||
[_centralManager cancelPeripheralConnection:peripheralInfo.peripheral];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark CBCentralManagerDelegate
|
||||
|
||||
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
|
||||
if (central.state == CBManagerStatePoweredOn && !central.isScanning &&
|
||||
_centralState == GNCMCentralStateScanning) {
|
||||
NSLog(@"[NEARBY] CBCentralManager powered on; starting scan");
|
||||
[self startScanningInternal];
|
||||
} else {
|
||||
NSLog(@"[NEARBY] CBCentralManager not powered on; stopping scan");
|
||||
[self stopScanningInternal];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didDiscoverPeripheral:(CBPeripheral *)peripheral
|
||||
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
|
||||
RSSI:(NSNumber *)RSSI {
|
||||
NSNumber *connectable = advertisementData[CBAdvertisementDataIsConnectable];
|
||||
if (![connectable boolValue]) return;
|
||||
|
||||
// Look for the NC advertisement header in either the service data (from non-iOS) or the
|
||||
// advertised name (from iOS).
|
||||
NSData *serviceData = advertisementData[CBAdvertisementDataServiceDataKey][_serviceUUID]
|
||||
?: advertisementData[CBAdvertisementDataLocalNameKey];
|
||||
|
||||
// Try to look up the peripheral by ID.
|
||||
GNCMPeripheralInfo *info = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!info) {
|
||||
NSLog(@"[NEARBY] New peripheral: %@", peripheral);
|
||||
// This is a new peripheral, so create a new peripheral info object.
|
||||
info = [[GNCMPeripheralInfo alloc] initWithPeripheral:peripheral
|
||||
advertisementData:advertisementData];
|
||||
} else {
|
||||
info.peripheral = peripheral;
|
||||
}
|
||||
|
||||
_nearbyPeripheralsByID[peripheral.identifier] = info;
|
||||
_scanResultHandler(peripheral.identifier.UUIDString, serviceData);
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
|
||||
NSLog(@"[NEARBY] Connected to peripheral: %@", peripheral);
|
||||
peripheral.delegate = self;
|
||||
// Tell the caller the connection is done.
|
||||
_gattConnectionResultHandler(nil);
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didFailToConnectPeripheral:(CBPeripheral *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Failed to connect to peripheral: %@, error: %@", peripheral, error);
|
||||
// Tell the caller the connection failed.
|
||||
_gattConnectionResultHandler(error);
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didDisconnectPeripheral:(CBPeripheral *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Disconnected to peripheral: %@, error: %@", peripheral, error);
|
||||
}
|
||||
|
||||
#pragma mark CBPeripheralDelegate
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error {
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!peripheralInfo) return;
|
||||
if (error || (peripheral.services.count == 0)) {
|
||||
NSLog(@"[NEARBY] Error reading advertisement: unable to discover services.");
|
||||
_gattDiscoverResultHandler(nil);
|
||||
} else {
|
||||
NSLog(@"[NEARBY] Discovered services for %@: %@", peripheral.name, peripheral.services);
|
||||
|
||||
// Helper functions for discovering characteristics and reading their values.
|
||||
void (^discoverChars)(CBService *, GNCMBleCharacteristicsHandler) =
|
||||
^(CBService *service, GNCMBleCharacteristicsHandler handler) {
|
||||
NSAssert(!peripheralInfo.charsHandler, @"Unexpected characteristic handler");
|
||||
peripheralInfo.charsHandler = handler;
|
||||
|
||||
// Discover all characteristics that may contain the advertisement.
|
||||
[peripheral discoverCharacteristics:_characteristicUUIDs forService:service];
|
||||
};
|
||||
void (^readCharValue)(CBCharacteristic *, GNCMBleCharacteristicValueHandler) =
|
||||
^(CBCharacteristic *characteristic, GNCMBleCharacteristicValueHandler handler) {
|
||||
NSAssert(!peripheralInfo.charValueHandler, @"Unexpected characteristic value handler");
|
||||
peripheralInfo.charValueHandler = handler;
|
||||
[peripheral readValueForCharacteristic:characteristic];
|
||||
};
|
||||
|
||||
// Multiple services may have the same UUID, so find the right service by searching for the
|
||||
// characteristic containing the advertisement with a matching service ID hash.
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
void (^tryService)(int) = GNCRecursiveIntHandler(^(GNCIntHandler tryService, int serviceIndex) {
|
||||
__strong __typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) return;
|
||||
|
||||
// We've tried all the services, report the discovered characteristics and their values.
|
||||
if (serviceIndex == peripheral.services.count) {
|
||||
NSLog(@"[NEARBY] Done traversing all services or no services to traverse.");
|
||||
_gattDiscoverResultHandler(_gattCharacteristicValues);
|
||||
if (_gattCharacteristicValues.count > 0) {
|
||||
CBCharacteristic *characteristic = [_gattCharacteristicValues objectAtIndex:0];
|
||||
[strongSelf completeGATTReadWithData:characteristic.value
|
||||
forPeripheralInfo:peripheralInfo];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[NEARBY] Trying service: %@", peripheral.services[serviceIndex]);
|
||||
discoverChars(
|
||||
peripheral.services[serviceIndex], ^(NSArray<CBCharacteristic *> *chars, NSError *error) {
|
||||
void (^tryNextService)() = ^{
|
||||
tryService(serviceIndex + 1);
|
||||
};
|
||||
|
||||
// If there was an error or there are no characteristics on this service, try next one.
|
||||
if (error || (chars.count == 0)) {
|
||||
tryNextService();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read each characteristic.
|
||||
void (^tryChar)(int) = GNCRecursiveIntHandler(^(GNCIntHandler tryChar, int charIndex) {
|
||||
// We've tried all characteristics on this service without error, try next service.
|
||||
if (charIndex == chars.count) {
|
||||
NSLog(@"[NEARBY] No matching advertisement found");
|
||||
tryNextService();
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[NEARBY] Trying characteristic: %@", chars[charIndex]);
|
||||
readCharValue(chars[charIndex], ^(CBCharacteristic *characteristic, NSError *error) {
|
||||
if (error) {
|
||||
tryNextService();
|
||||
} else {
|
||||
// We've found the characteristic and its non-nil value. Store it.
|
||||
if (characteristic.value.length != 0) {
|
||||
[_gattCharacteristicValues addObject:characteristic];
|
||||
}
|
||||
tryChar(charIndex + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Start searching the characteristics for the current service.
|
||||
tryChar(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Start searching the services.
|
||||
tryService(0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didDiscoverCharacteristicsForService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Discovered characteristics: %@ error: %@", service.characteristics, error);
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!peripheralInfo) return;
|
||||
GNCMBleCharacteristicsHandler charsHandler = peripheralInfo.charsHandler;
|
||||
peripheralInfo.charsHandler = nil;
|
||||
charsHandler(service.characteristics, error);
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Read characteristic value: %@ error: %@", characteristic, error);
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!peripheralInfo) return;
|
||||
GNCMBleCharacteristicValueHandler valueHandler = peripheralInfo.charValueHandler;
|
||||
peripheralInfo.charValueHandler = nil;
|
||||
valueHandler(characteristic, error);
|
||||
}
|
||||
|
||||
#pragma mark GNSCentralManagerDelegate
|
||||
|
||||
- (void)centralManager:(GNSCentralManager *)centralManager
|
||||
didDiscoverPeer:(GNSCentralPeerManager *)centralPeerManager
|
||||
advertisementData:(nullable NSDictionary<NSString *, id> *)advertisementData {
|
||||
if (!_callbacksEnabled) return;
|
||||
|
||||
// Retrieve the cached peripheral info.
|
||||
NSUUID *peerId = centralPeerManager.identifier;
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peerId];
|
||||
if (!peripheralInfo) return;
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
[self callbackAsync:^{
|
||||
_requestConnectionHandler(^(NSString *serviceID, GNCMConnectionHandler connectionHandler) {
|
||||
__strong __typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) return;
|
||||
|
||||
void (^callConnectionHandler)(GNSSocket *__nullable) = ^(GNSSocket *__nullable socket) {
|
||||
__strong __typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf->_callbacksEnabled) return;
|
||||
[strongSelf callbackAsync:^{
|
||||
if (!socket) {
|
||||
NSLog(@"[NEARBY] Central failed to create BLE socket");
|
||||
connectionHandler(nil);
|
||||
} else {
|
||||
GNCMBleConnection *connection =
|
||||
[GNCMBleConnection connectionWithSocket:socket
|
||||
serviceID:serviceID
|
||||
expectedIntroPacket:NO
|
||||
callbackQueue:strongSelf->_clientCallbackQueue];
|
||||
connection.connectionHandlers = connectionHandler(connection);
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
dispatch_async(strongSelf->_selfQueue, ^{
|
||||
// A connection is being requested, so establish a BLE socket. Make sure to use the most
|
||||
// up-to-date GNSCentralPeerManager in case the MAC address rotated. The cached
|
||||
// peripheral info should be the single source of truth for which MAC address to use.
|
||||
GNSCentralPeerManager *updatedCentralPeerManager =
|
||||
[centralManager retrieveCentralPeerWithIdentifier:peripheralInfo.peripheral.identifier];
|
||||
if (!updatedCentralPeerManager) {
|
||||
callConnectionHandler(nil);
|
||||
}
|
||||
|
||||
// Make a socket connection.
|
||||
[updatedCentralPeerManager
|
||||
socketWithPairingCharacteristic:NO
|
||||
completion:^(GNSSocket *socket, NSError *error) {
|
||||
__strong __typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) return;
|
||||
dispatch_async(strongSelf->_selfQueue, ^{
|
||||
if (!error) {
|
||||
// Call the connection handler when the socket has
|
||||
// connected or fails to connect.
|
||||
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
|
||||
callConnectionHandler(didConnect ? socket : nil);
|
||||
});
|
||||
} else {
|
||||
callConnectionHandler(nil);
|
||||
}
|
||||
});
|
||||
}];
|
||||
});
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)centralManagerDidUpdateBleState:(GNSCentralManager *)centralManager {
|
||||
// No op.
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
/** This method assumes it's being called on selfQueue. */
|
||||
- (void)completeGATTReadWithData:(NSData *)advertisement
|
||||
forPeripheralInfo:(GNCMPeripheralInfo *)peripheralInfo {
|
||||
NSAssert(peripheralInfo, @"Nil peripheralInfo");
|
||||
if (peripheralInfo) {
|
||||
CBPeripheral *peripheral = peripheralInfo.peripheral;
|
||||
NSUUID *peripheralID = peripheral.identifier;
|
||||
// Store the data for the socket callback, and report the peripheral to the socket library.
|
||||
[_socketCentralManager retrievePeripheralWithIdentifier:peripheralID
|
||||
advertisementData:peripheralInfo.advertisementData];
|
||||
}
|
||||
}
|
||||
|
||||
/** Signals the central manager to start scanning. Must be called on _selfQueue. */
|
||||
- (void)startScanningInternal {
|
||||
if (![_centralManager isScanning]) {
|
||||
NSLog(@"[NEARBY] startScanningInternal");
|
||||
|
||||
[_centralManager
|
||||
scanForPeripheralsWithServices:@[ _serviceUUID ]
|
||||
options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}];
|
||||
}
|
||||
}
|
||||
|
||||
/** Signals the central manager to stop scanning. Must be called on _selfQueue */
|
||||
- (void)stopScanningInternal {
|
||||
if ([_centralManager isScanning]) {
|
||||
NSLog(@"[NEARBY] stopScanningInternal");
|
||||
_centralState = GNCMCentralStateStopped;
|
||||
|
||||
[_centralManager stopScan];
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls the specified block on the callback queue. */
|
||||
- (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
|
||||
@@ -1,79 +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 <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
|
||||
|
||||
@class CBCharacteristic;
|
||||
@class CBUUID;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* GNCMBlePeripheral advertises the specified service UUID via Ble for the purpose of being
|
||||
* discovered by a central using the GNCMBleCentral class.
|
||||
*
|
||||
* This class is thread-safe. Any calls made to it (and to the objects/closures it passes back via
|
||||
* callbacks) can be made from any thread/queue. Callbacks made from this class are called on the
|
||||
* specified queue.
|
||||
*/
|
||||
@interface GNCMBlePeripheral : NSObject
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Adds GATT CBService.
|
||||
*
|
||||
* @param serviceUUID A GATT service ID to advertise for.
|
||||
*/
|
||||
- (void)addCBServiceWithUUID:(CBUUID *)serviceUUID;
|
||||
|
||||
/**
|
||||
* Adds GATT CBCharacteristic.
|
||||
*
|
||||
* @param characteristic A characteristic CBUUID.
|
||||
*/
|
||||
- (void)addCharacteristic:(CBCharacteristic *)characteristic;
|
||||
|
||||
/**
|
||||
* Updates GATT CBCharacteristic with value.
|
||||
*
|
||||
* @param value The NSData to advertise.
|
||||
* @param characteristicUUID A characteristic CBUUID.
|
||||
*/
|
||||
- (void)updateValue:(NSData *)value forCharacteristic:(CBUUID *)characteristicUUID;
|
||||
|
||||
/**
|
||||
* Stops GATT server service.
|
||||
*/
|
||||
- (void)stopGATTService;
|
||||
|
||||
/**
|
||||
* Starts advertising with service UUID and advertisement data.
|
||||
*
|
||||
* @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
|
||||
endpointConnectedHandler:(GNCMConnectionHandler)endpointConnectedHandler
|
||||
callbackQueue:(nullable dispatch_queue_t)callbackQueue;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,293 +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/apple/Mediums/Ble/GNCMBlePeripheral.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/GNCUtils.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/Peripheral/GNSPeripheralManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GNCMPeripheralState) {
|
||||
GNCMPeripheralStateStopped,
|
||||
GNCMPeripheralStateAdvertising,
|
||||
};
|
||||
|
||||
@interface GNCMBlePeripheral () <CBPeripheralManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCMBlePeripheral {
|
||||
/** Service UUID for advertisement. */
|
||||
CBMutableService *_advertisementService;
|
||||
/** GATT service for GATT connection. */
|
||||
CBMutableService *_GATTService;
|
||||
/** GATT characteristics for GATT connection. */
|
||||
NSMutableArray<CBCharacteristic *> *_gattCharacteristics;
|
||||
/** CBUUID characteristic to NSData value dictionary for GATT connection. */
|
||||
NSMutableDictionary<CBUUID *, NSData *> *_gattCharacteristicValues;
|
||||
/** Data to be advertised. */
|
||||
NSData *_advertisementData;
|
||||
/** Peripheral manager used to advertise or connect to peripherals. */
|
||||
CBPeripheralManager *_peripheralManager;
|
||||
/** Serial background queue for |peripheralManager|. */
|
||||
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 {
|
||||
if (self = [super init]) {
|
||||
// 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);
|
||||
|
||||
_state = GNCMPeripheralStateStopped;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// These calls must be made on |selfQueue|. Can't capture |self| in an async block, so must use
|
||||
// dispatch_sync. This means delloc must be called from an external queue, which means |self|
|
||||
// must never be captured by any escaping block used in this class.
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
[self stopAdvertisingInternal];
|
||||
|
||||
_callbacksEnabled = NO;
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addCBServiceWithUUID:(CBUUID *)serviceUUID {
|
||||
if (!_GATTService) {
|
||||
// If it has been called, then don't do it again. Initialize one time.
|
||||
_GATTService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
|
||||
_gattCharacteristics = [[NSMutableArray alloc] init];
|
||||
_gattCharacteristicValues = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addCharacteristic:(CBCharacteristic *)characteristic {
|
||||
if (_gattCharacteristics) {
|
||||
[_gattCharacteristics addObject:characteristic];
|
||||
}
|
||||
if (_gattCharacteristicValues) {
|
||||
[_gattCharacteristicValues setObject:[[NSData alloc] init] forKey:characteristic.UUID];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateValue:(NSData *)value forCharacteristic:(CBUUID *)characteristicUUID {
|
||||
if ([_gattCharacteristicValues objectForKey:characteristicUUID]) {
|
||||
[_gattCharacteristicValues setObject:value forKey:characteristicUUID];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopGATTService {
|
||||
if (!_GATTService) return;
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
[_peripheralManager removeService:_GATTService];
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)startAdvertisingWithServiceUUID:(NSString *)serviceUUID
|
||||
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;
|
||||
}
|
||||
}
|
||||
_state = GNCMPeripheralStateAdvertising;
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark CBPeripheralManagerDelegate
|
||||
|
||||
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
|
||||
NSLog(@"[NEARBY] peripheralManagerDidUpdateState %li", (long)peripheral.state);
|
||||
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");
|
||||
[self stopAdvertisingInternal];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
if (error) {
|
||||
NSLog(@"[NEARBY] Error starting advertising: %@,", [error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
if (_peripheralManager.state != CBManagerStatePoweredOn) {
|
||||
NSLog(@"[NEARBY] Error starting advertising: peripheral manager not on!");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[NEARBY] Peripheral manager started advertising");
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
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];
|
||||
if (value != nil && value.length > 0) {
|
||||
if (request.offset > value.length) {
|
||||
error = CBATTErrorInvalidOffset;
|
||||
} else {
|
||||
// Reply with the advertisement data.
|
||||
NSRange rangeFromOffset = NSMakeRange(request.offset, value.length - request.offset);
|
||||
request.value = [value subdataWithRange:rangeFromOffset];
|
||||
error = CBATTErrorSuccess;
|
||||
}
|
||||
}
|
||||
[_peripheralManager respondToRequest:request withResult:error];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
/** Signals the peripheral manager to start advertising. Must be called on _selfQueue */
|
||||
- (void)startAdvertisingInternal {
|
||||
if (![_peripheralManager isAdvertising]) {
|
||||
NSLog(@"[NEARBY] startAdvertisingInternal");
|
||||
|
||||
if (_GATTService) {
|
||||
[_peripheralManager addService:_GATTService];
|
||||
}
|
||||
[_peripheralManager startAdvertising:@{
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ _advertisementService.UUID ],
|
||||
CBAdvertisementDataLocalNameKey : _advertisementData
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/** Signals the peripheral manager to stop advertising. Must be called on _selfQueue */
|
||||
- (void)stopAdvertisingInternal {
|
||||
if ([_peripheralManager isAdvertising]) {
|
||||
NSLog(@"[NEARBY] stopAdvertisingInternal");
|
||||
_state = GNCMPeripheralStateStopped;
|
||||
|
||||
if (_GATTService) {
|
||||
[_peripheralManager removeService:_GATTService];
|
||||
}
|
||||
[_peripheralManager stopAdvertising];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -24,16 +24,23 @@ objc_library(
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"GNCBLEGATTCharacteristicTest.mm",
|
||||
"GNCBLEGATTClient+Testing.h",
|
||||
"GNCBLEGATTClientTest.m",
|
||||
"GNCBLEGATTServer+Testing.h",
|
||||
"GNCBLEGATTServerTest.m",
|
||||
"GNCBLEMedium+Testing.h",
|
||||
"GNCBLEMediumTest.m",
|
||||
"GNCBLEUtilsTest.mm",
|
||||
"GNCBleTest.mm",
|
||||
"GNCBluetoothAdapterTest.mm",
|
||||
"GNCCryptoTest.mm",
|
||||
"GNCFakeCentralManager.h",
|
||||
"GNCFakeCentralManager.m",
|
||||
"GNCFakePeripheral.h",
|
||||
"GNCFakePeripheral.m",
|
||||
"GNCFakePeripheralManager.h",
|
||||
"GNCFakePeripheralManager.m",
|
||||
"GNCIPAddressTest.mm",
|
||||
"GNCMultiThreadExecutorTest.mm",
|
||||
"GNCPlatformTest.mm",
|
||||
"GNCScheduledExecutorTest.mm",
|
||||
"GNCSingleThreadExecutorTest.mm",
|
||||
"GNCUtilsTest.mm",
|
||||
@@ -44,7 +51,8 @@ objc_library(
|
||||
"//internal/platform/implementation:comm",
|
||||
"//internal/platform/implementation:platform",
|
||||
"//internal/platform/implementation:types",
|
||||
"//internal/platform/implementation/apple",
|
||||
"//internal/platform/implementation/apple", # buildcleaner: keep
|
||||
"//internal/platform/implementation/apple:ble_v2",
|
||||
"//internal/platform/implementation/apple/Mediums",
|
||||
"//third_party/apple_frameworks:CoreBluetooth",
|
||||
"//third_party/apple_frameworks:Foundation",
|
||||
|
||||
@@ -26,12 +26,15 @@
|
||||
- (void)testConvenienceInit {
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:@"00000000-0000-3000-8000-000000000000"];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:@"0000FEF3-0000-1000-8000-00805F9B34FB"];
|
||||
CBCharacteristicProperties properties = CBCharacteristicPropertyRead;
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID serviceUUID:serviceUUID];
|
||||
[[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
properties:properties];
|
||||
XCTAssertEqualObjects(characteristic.characteristicUUID, characteristicUUID);
|
||||
XCTAssertEqualObjects(characteristic.serviceUUID, serviceUUID);
|
||||
XCTAssertEqual(characteristic.permissions, 0);
|
||||
XCTAssertEqual(characteristic.properties, 0);
|
||||
XCTAssertEqual(characteristic.properties, properties);
|
||||
}
|
||||
|
||||
- (void)testInit {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_TESTS_GNCBLEGATTCLIENT_TESTING_H_
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCBLEGATTClient (Testing)
|
||||
|
||||
/**
|
||||
* Creates a GATT client with a provided peripheral.
|
||||
*
|
||||
* This is only exposed for testing and can be used to inject a fake peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral instance.
|
||||
* @param queue The queue to run on, this must match the queue that the peripheral's delegate is
|
||||
* running on. Defaults to the main queue when @c nil.
|
||||
*/
|
||||
- (instancetype)initWithPeripheral:(id<GNCPeripheral>)peripheral
|
||||
queue:(nullable dispatch_queue_t)queue;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,748 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCBLEGATTClient+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheral.h"
|
||||
|
||||
static NSString *const kServiceUUID1 = @"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
static NSString *const kServiceUUID2 = @"0000FEF4-0000-1000-8000-00805F9B34FB";
|
||||
static NSString *const kCharacteristicUUID1 = @"00000000-0000-3000-8000-000000000000";
|
||||
static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-000000000001";
|
||||
|
||||
@interface GNCBLEGATTClientTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEGATTClientTest
|
||||
|
||||
#pragma mark - Discover Characteristics
|
||||
|
||||
- (void)testDiscoverCharacteristics {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *discoverCharacteristicsExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics."];
|
||||
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[discoverCharacteristicsExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ discoverCharacteristicsExpectation ] timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].UUID, serviceUUID);
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].characteristics[0].UUID, characteristicUUID);
|
||||
}
|
||||
|
||||
- (void)testDiscoverCharacteristicsWithServiceDiscoveryError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.discoverServicesError = [NSError errorWithDomain:@"fake" code:0 userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *discoverCharacteristicsExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics."];
|
||||
|
||||
// TODO(b/295911088): Failed discovery of services won't trigger the completionHandler until we
|
||||
// implement a service queue.
|
||||
discoverCharacteristicsExpectation.inverted = YES;
|
||||
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
// TODO(b/295911088): Make assertions when service queue is
|
||||
// implemented.
|
||||
[discoverCharacteristicsExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ discoverCharacteristicsExpectation ] timeout:3];
|
||||
|
||||
XCTAssertEqual(fakePeripheral.services.count, 0);
|
||||
}
|
||||
|
||||
- (void)testDiscoverCharacteristicsWithCharacteristicDiscoveryError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.discoverCharacteristicsForServiceError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *discoverCharacteristicsExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics."];
|
||||
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[discoverCharacteristicsExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ discoverCharacteristicsExpectation ] timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].UUID, serviceUUID);
|
||||
XCTAssertEqual(fakePeripheral.services[0].characteristics.count, 0);
|
||||
}
|
||||
|
||||
// TODO(b/295911088): When service queue is implemented, this is expected to not be an error.
|
||||
- (void)testDuplicateDiscoverCharacteristics {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *discoverCharacteristics1Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics 1."];
|
||||
XCTestExpectation *discoverCharacteristics2Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics 2."];
|
||||
|
||||
fakePeripheral.delegateDelay = 1;
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[discoverCharacteristics1Expectation fulfill];
|
||||
}];
|
||||
|
||||
// Queue the delay change so it doesn't immediately overwrite the delay set for the previous
|
||||
// operation.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
fakePeripheral.delegateDelay = 0;
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[discoverCharacteristics2Expectation fulfill];
|
||||
}];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[
|
||||
discoverCharacteristics1Expectation, discoverCharacteristics2Expectation
|
||||
]
|
||||
timeout:3];
|
||||
}
|
||||
|
||||
- (void)testDiscoverCharacteristicsMultipleCallsWithDifferentServices {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID1 = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *serviceUUID2 = [CBUUID UUIDWithString:kServiceUUID2];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *discoverCharacteristics1Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics 1."];
|
||||
XCTestExpectation *discoverCharacteristics2Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics 2."];
|
||||
|
||||
fakePeripheral.delegateDelay = 1;
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID1
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[discoverCharacteristics1Expectation fulfill];
|
||||
}];
|
||||
|
||||
// Queue the delay change so it doesn't immediately overwrite the delay set for the previous
|
||||
// operation.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
fakePeripheral.delegateDelay = 0;
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID2
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[discoverCharacteristics2Expectation fulfill];
|
||||
}];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[
|
||||
discoverCharacteristics1Expectation, discoverCharacteristics2Expectation
|
||||
]
|
||||
timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].UUID, serviceUUID2);
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].characteristics[0].UUID, characteristicUUID);
|
||||
XCTAssertEqualObjects(fakePeripheral.services[1].UUID, serviceUUID1);
|
||||
XCTAssertEqualObjects(fakePeripheral.services[1].characteristics[0].UUID, characteristicUUID);
|
||||
}
|
||||
|
||||
- (void)testDiscoverCharacteristicsMultipleCallsWithDifferentCharacteristics {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:kCharacteristicUUID2];
|
||||
|
||||
XCTestExpectation *discoverCharacteristics1Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics 1."];
|
||||
XCTestExpectation *discoverCharacteristics2Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Discover characteristics 2."];
|
||||
|
||||
fakePeripheral.delegateDelay = 1;
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID1 ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[discoverCharacteristics1Expectation fulfill];
|
||||
}];
|
||||
|
||||
// Queue the delay change so it doesn't immediately overwrite the delay set for the previous
|
||||
// operation.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
fakePeripheral.delegateDelay = 0;
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID2 ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[discoverCharacteristics2Expectation fulfill];
|
||||
}];
|
||||
});
|
||||
|
||||
[self waitForExpectations:@[
|
||||
discoverCharacteristics1Expectation, discoverCharacteristics2Expectation
|
||||
]
|
||||
timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].UUID, serviceUUID);
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].characteristics[0].UUID, characteristicUUID2);
|
||||
XCTAssertEqualObjects(fakePeripheral.services[0].characteristics[1].UUID, characteristicUUID1);
|
||||
}
|
||||
|
||||
#pragma mark - Get Characteristic
|
||||
|
||||
- (void)testGetCharacteristic {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *characteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Get characteristic."];
|
||||
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(
|
||||
GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(characteristic);
|
||||
[characteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ characteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testGetCharacteristicWithServiceDiscoveryError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.discoverServicesError = [NSError errorWithDomain:@"fake" code:0 userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *characteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Get characteristic."];
|
||||
|
||||
// TODO(b/295911088): Failed discovery of services won't trigger the completionHandler until we
|
||||
// implement a service queue.
|
||||
characteristicExpectation.inverted = YES;
|
||||
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(
|
||||
GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
// TODO(b/295911088): Make assertions when service
|
||||
// queue is implemented.
|
||||
[characteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ characteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testGetCharacteristicWithCharacteristicDiscoveryError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.discoverCharacteristicsForServiceError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *characteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Get characteristic."];
|
||||
|
||||
[gattClient discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(
|
||||
GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertNil(characteristic);
|
||||
[characteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ characteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testDuplicateGetCharacteristic {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *characteristic1Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Get characteristic 1."];
|
||||
XCTestExpectation *characteristic2Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Get characteristic 2."];
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
fakePeripheral.delegateDelay = 1;
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(characteristic);
|
||||
[characteristic1Expectation fulfill];
|
||||
}];
|
||||
// Queue the delay change so it doesn't immediately overwrite the delay set
|
||||
// for the previous operation.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
fakePeripheral.delegateDelay = 0;
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(characteristic);
|
||||
[characteristic2Expectation fulfill];
|
||||
}];
|
||||
});
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ characteristic1Expectation, characteristic2Expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testGetNonExistentCharacteristic {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *characteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Get characteristic."];
|
||||
|
||||
[gattClient characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic, NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertNil(characteristic);
|
||||
[characteristicExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ characteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Read Value for Characteristic
|
||||
|
||||
- (void)testReadValueForCharacteristic {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *readValueForCharacteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic."];
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(value);
|
||||
[readValueForCharacteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ readValueForCharacteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testReadValueForCharacteristicWithServiceDiscoveryError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.discoverServicesError = [NSError errorWithDomain:@"fake" code:0 userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *readValueForCharacteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic."];
|
||||
|
||||
// TODO(b/295911088): Failed discovery of services won't trigger the completionHandler until we
|
||||
// implement a service queue.
|
||||
readValueForCharacteristicExpectation.inverted = YES;
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
// TODO(b/295911088): Make assertions when service
|
||||
// queue is implemented.
|
||||
[readValueForCharacteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ readValueForCharacteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testReadValueForCharacteristicWithCharacteristicDiscoveryError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.discoverCharacteristicsForServiceError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *readValueForCharacteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic."];
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertNil(value);
|
||||
[readValueForCharacteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ readValueForCharacteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testReadValueForCharacteristicWithReadValueForCharacteristicError {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
fakePeripheral.readValueForCharacteristicError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *readValueForCharacteristicExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic."];
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertNil(value);
|
||||
[readValueForCharacteristicExpectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ readValueForCharacteristicExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
// TODO(b/295911088): When service queue is implemented, this is expected to not be an error.
|
||||
- (void)testDuplicateReadValueForCharacteristic {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
XCTestExpectation *readValueForCharacteristic1Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic 1."];
|
||||
XCTestExpectation *readValueForCharacteristic2Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic 2."];
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
fakePeripheral.delegateDelay = 1;
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(value);
|
||||
[readValueForCharacteristic1Expectation fulfill];
|
||||
}];
|
||||
|
||||
// Queue the delay change so it doesn't immediately overwrite the
|
||||
// delay set for the previous operation.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
fakePeripheral.delegateDelay = 0;
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value,
|
||||
NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertNil(value);
|
||||
[readValueForCharacteristic2Expectation fulfill];
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[
|
||||
readValueForCharacteristic1Expectation, readValueForCharacteristic2Expectation
|
||||
]
|
||||
timeout:3];
|
||||
}
|
||||
|
||||
- (void)testReadValueForMultipleCharacteristics {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:kCharacteristicUUID2];
|
||||
|
||||
XCTestExpectation *readValueForCharacteristic1Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic 1."];
|
||||
XCTestExpectation *readValueForCharacteristic2Expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic 2."];
|
||||
|
||||
[gattClient
|
||||
discoverCharacteristicsWithUUIDs:@[ characteristicUUID1, characteristicUUID2 ]
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID1
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
fakePeripheral.delegateDelay = 1;
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(value);
|
||||
[readValueForCharacteristic1Expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[gattClient
|
||||
characteristicWithUUID:characteristicUUID2
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
// Queue the delay change so it doesn't immediately overwrite the
|
||||
// delay set for the previous operation.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
fakePeripheral.delegateDelay = 0;
|
||||
[gattClient
|
||||
readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value,
|
||||
NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertNotNil(value);
|
||||
[readValueForCharacteristic2Expectation fulfill];
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[
|
||||
readValueForCharacteristic1Expectation, readValueForCharacteristic2Expectation
|
||||
]
|
||||
timeout:3];
|
||||
}
|
||||
|
||||
- (void)testReadValueForUndiscoveredCharacteristic {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Read value for characteristic."];
|
||||
|
||||
[gattClient readValueForCharacteristic:characteristic
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertNil(value);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Delegate Calls
|
||||
|
||||
- (void)testUnexpectedDelegateCalls {
|
||||
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
GNCBLEGATTClient *gattClient = [[GNCBLEGATTClient alloc] initWithPeripheral:fakePeripheral
|
||||
queue:nil];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
|
||||
[fakePeripheral.peripheralDelegate gnc_peripheral:fakePeripheral didDiscoverServices:nil];
|
||||
|
||||
[fakePeripheral.peripheralDelegate
|
||||
gnc_peripheral:fakePeripheral
|
||||
didDiscoverCharacteristicsForService:[[CBMutableService alloc] initWithType:serviceUUID
|
||||
primary:YES]
|
||||
error:nil];
|
||||
|
||||
[fakePeripheral.peripheralDelegate gnc_peripheral:fakePeripheral
|
||||
didUpdateValueForCharacteristic:[[CBMutableCharacteristic alloc]
|
||||
initWithType:characteristicUUID
|
||||
properties:0
|
||||
value:nil
|
||||
permissions:0]
|
||||
error:nil];
|
||||
|
||||
// Test to make sure unexpected delegate calls don't cause any issues from missing handlers.
|
||||
XCTAssertNotNil(gattClient);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCBLEGATTServer+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.h"
|
||||
|
||||
@@ -42,17 +42,26 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
[gattServer
|
||||
createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertNil(error);
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1);
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testCreateMultipleCharacteristicsForOneService {
|
||||
@@ -63,41 +72,132 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation1 =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic 1."];
|
||||
XCTestExpectation *expectation2 =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic 2."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:kCharacteristicUUID2];
|
||||
GNCBLEGATTCharacteristic *characteristic1 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
GNCBLEGATTCharacteristic *characteristic2 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID2
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertNil(error);
|
||||
[expectation1 fulfill];
|
||||
}];
|
||||
|
||||
XCTAssertNotNil(characteristic1);
|
||||
XCTAssertNotNil(characteristic2);
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID2
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertNil(error);
|
||||
[expectation2 fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation1, expectation2 ] timeout:3];
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 2);
|
||||
}
|
||||
|
||||
- (void)testCreateDuplicateCharacteristics {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:nil];
|
||||
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNil(characteristic);
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1);
|
||||
}
|
||||
|
||||
- (void)testCreateDuplicatePendingCharacteristics {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:nil];
|
||||
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
XCTAssertNil(characteristic);
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testCreateCharacteristicNotPoweredOn {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
// The error occurs after completion, so its expected to fail
|
||||
// silently.
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
XCTAssertNil(characteristic);
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testCreateCharacteristicServiceFailure {
|
||||
@@ -109,101 +209,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
fakePeripheralManager.didAddServiceError = [NSError errorWithDomain:@"fake" code:0 userInfo:nil];
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
XCTAssertNil(characteristic);
|
||||
}
|
||||
|
||||
#pragma mark - Update Characteristic
|
||||
|
||||
- (void)testUpdateCharacteristic {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
BOOL success = [gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1);
|
||||
}
|
||||
|
||||
- (void)testUpdateCharacteristicInvalidService {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID serviceUUID:serviceUUID];
|
||||
|
||||
BOOL success = [gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
}
|
||||
|
||||
- (void)testUpdateCharacteristicInvalidCharacteristic {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID1 = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
CBUUID *characteristicUUID2 = [CBUUID UUIDWithString:kCharacteristicUUID2];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
// The error occurs after completion, so its expected to fail
|
||||
// silently.
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
GNCBLEGATTCharacteristic *characteristic2 =
|
||||
[[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID2 serviceUUID:serviceUUID];
|
||||
|
||||
BOOL success = [gattServer updateCharacteristic:characteristic2 value:[NSData data]];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
}
|
||||
|
||||
- (void)testUpdateCharacteristicNotPoweredOn {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
BOOL success = [gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Read Request
|
||||
@@ -216,15 +240,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattServer updateCharacteristic:characteristic
|
||||
value:[NSData data]
|
||||
completionHandler:^(NSError *error) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
|
||||
[fakePeripheralManager
|
||||
simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID
|
||||
@@ -242,15 +276,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattServer updateCharacteristic:characteristic
|
||||
value:[NSData data]
|
||||
completionHandler:^(NSError *error) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
|
||||
CBUUID *invalidServiceUUID = [CBUUID UUIDWithString:kServiceUUID2];
|
||||
|
||||
@@ -269,15 +313,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Create and update characteristic."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID1];
|
||||
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID1];
|
||||
GNCBLEGATTCharacteristic *characteristic =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[gattServer updateCharacteristic:characteristic
|
||||
value:[NSData data]
|
||||
completionHandler:^(NSError *error) {
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
|
||||
[gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
|
||||
CBUUID *invalidCharacteristicUUID =
|
||||
[CBUUID UUIDWithString:kCharacteristicUUID2];
|
||||
@@ -311,12 +365,18 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{}];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqual(data, nil);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingEmptyServiceData {
|
||||
@@ -326,16 +386,22 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [NSData data],
|
||||
}];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingShortServiceData {
|
||||
@@ -345,16 +411,24 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [@"0123" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMw");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [@"0123" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMw");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertising22ByteServiceData {
|
||||
@@ -364,16 +438,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [@"0123456789012345" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMzQ1Njc4OTAxMjM0NQ");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [@"0123456789012345" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey],
|
||||
@"MDEyMzQ1Njc4OTAxMjM0NQ");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingLongServiceData {
|
||||
@@ -383,17 +466,26 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] :
|
||||
[@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMzQ1Njc4OTAxMjM0NT");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer
|
||||
startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] :
|
||||
[@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"MDEyMzQ1Njc4OTAxMjM0NT");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingWithEmojiServiceData {
|
||||
@@ -403,16 +495,25 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [@"😁❤️🤡" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}];
|
||||
|
||||
XCTAssertTrue(success);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"8J-YgeKdpO-4j_CfpKE");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer
|
||||
startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] : [@"😁❤️🤡" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"8J-YgeKdpO-4j_CfpKE");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingMultipleServices {
|
||||
@@ -422,15 +523,23 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] :
|
||||
[@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[CBUUID UUIDWithString:@"FEF4"] :
|
||||
[@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}];
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingNotPoweredOn {
|
||||
@@ -439,10 +548,18 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
BOOL success = [gattServer startAdvertisingData:@{}];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]}
|
||||
completionHandler:^(NSError *error) {
|
||||
// The error occurs after completion, so its expected to fail silently.
|
||||
XCTAssertNil(error);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingStartFailure {
|
||||
@@ -455,10 +572,86 @@ static NSString *const kCharacteristicUUID2 = @"00000000-0000-3000-8000-00000000
|
||||
code:0
|
||||
userInfo:nil];
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{}];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]}
|
||||
completionHandler:^(NSError *error) {
|
||||
// The error occurs after completion, so its expected to fail silently.
|
||||
XCTAssertNil(error);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingAlreadyAdvertising {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
fakePeripheralManager.didStartAdvertisingError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]}
|
||||
completionHandler:nil];
|
||||
|
||||
[gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartStopStartAdvertising {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
[gattServer startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF3"] : [NSData data]}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data = fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF3"]);
|
||||
[gattServer stopAdvertisingWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
[gattServer
|
||||
startAdvertisingData:@{[CBUUID UUIDWithString:@"FEF4"] : [NSData data]}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
XCTAssertTrue(fakePeripheralManager.isAdvertising);
|
||||
NSDictionary<NSString *, id> *data =
|
||||
fakePeripheralManager.advertisementData;
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataLocalNameKey], @"");
|
||||
XCTAssertEqualObjects(data[CBAdvertisementDataServiceUUIDsKey][0],
|
||||
[CBUUID UUIDWithString:@"FEF4"]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCCentralManager;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCBLEMedium (Testing)
|
||||
|
||||
/**
|
||||
* Creates a BLE Medium with a provided central manager.
|
||||
*
|
||||
* This is only exposed for testing and can be used to inject a fake central manager.
|
||||
*
|
||||
* @param centralManager The central manager instance.
|
||||
* @param queue The queue to run on, this must match the queue that the central manager's delegate
|
||||
* is running on. Defaults to the main queue when @c nil.
|
||||
*/
|
||||
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
|
||||
queue:(nullable dispatch_queue_t)queue;
|
||||
|
||||
- (NSDictionary<CBUUID *, NSData *> *)decodeAdvertisementData:
|
||||
(NSDictionary<NSString *, id> *)advertisementData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEMedium.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCBLEMedium+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakeCentralManager.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheral.h"
|
||||
|
||||
static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
@interface GNCBLEMediumTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEMediumTest
|
||||
|
||||
#pragma mark - Supports Extended Advertisements
|
||||
|
||||
- (void)testSupportsExtendedAdvertisements {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
|
||||
XCTAssertFalse([medium supportsExtendedAdvertisements]);
|
||||
}
|
||||
|
||||
#pragma mark - Start Scanning
|
||||
|
||||
- (void)testStartScanning {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *startScanningExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
XCTestExpectation *advertisementFoundExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Advertisement found."];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
[fakeCentralManager simulateCentralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
XCTAssertEqualObjects(expected, data);
|
||||
[advertisementFoundExpectation fulfill];
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[startScanningExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ startScanningExpectation ] timeout:3];
|
||||
|
||||
XCTAssertEqualObjects(@[ serviceUUID ], fakeCentralManager.serviceUUIDs);
|
||||
|
||||
[fakeCentralManager
|
||||
simulateCentralManagerDidDiscoverPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
advertisementData:@{
|
||||
CBAdvertisementDataLocalNameKey : @"dGVzdA",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ advertisementFoundExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testAlreadyScanning {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
|
||||
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:nil];
|
||||
|
||||
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStartStopStartScanning {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
|
||||
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[medium stopScanningWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[medium startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *data) {
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Decode Advertisement Data
|
||||
|
||||
- (void)testDecodeAndroidStyleAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataServiceDataKey : @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
},
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeAndroidStyleAdvertisementDataWithLocalName {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"Nearby", // Just happens to be base64 decodable.
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
CBAdvertisementDataServiceDataKey : @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
},
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeAppleStyleAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *expected = @{
|
||||
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
};
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"dGVzdA",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeInvalidAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
|
||||
|
||||
NSDictionary<NSString *, id> *data = @{
|
||||
CBAdvertisementDataLocalNameKey : @"!@#$",
|
||||
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
|
||||
};
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
|
||||
|
||||
XCTAssertEqualObjects(@{}, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodeEmptyAdvertisementData {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
|
||||
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:@{}];
|
||||
|
||||
XCTAssertEqualObjects(@{}, actual);
|
||||
}
|
||||
|
||||
#pragma mark - Start GATT Server
|
||||
|
||||
- (void)testStartGATTServer {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start GATT server."];
|
||||
|
||||
[medium startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
|
||||
XCTAssertNotNil(server);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Start Advertising
|
||||
|
||||
- (void)testStartAdvertising {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
|
||||
|
||||
// Start advertising is fully covered with @c GNCBLEGATTServer tests. We are passing invalid
|
||||
// advertising data here so we can test code paths relevant to @c GNCBLEMedium, but bail early
|
||||
// enough to avoid making actual CoreBluetooth calls.
|
||||
[medium startAdvertisingData:@{}
|
||||
completionHandler:^(NSError *error) {
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testStopAdvertising {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Stop advertising."];
|
||||
|
||||
// Stop advertising is fully covered with @c GNCBLEGATTServer tests. We are only testing stopping
|
||||
// without having started which tests the code paths relevant to @c GNCBLEMedium.
|
||||
[medium stopAdvertisingWithCompletionHandler:^(NSError *error) {
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
#pragma mark - Connect
|
||||
|
||||
- (void)testSuccessfulConnect {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
disconnectionHandler:nil
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNotNil(client);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testFailedConnect {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
|
||||
fakeCentralManager.didFailToConnectPeripheralError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
|
||||
disconnectionHandler:nil
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNil(client);
|
||||
XCTAssertNotNil(error);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ expectation ] timeout:3];
|
||||
}
|
||||
|
||||
- (void)testDisconnect {
|
||||
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
|
||||
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
|
||||
XCTestExpectation *connectExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Connect."];
|
||||
XCTestExpectation *disconnectExpectation =
|
||||
[[XCTestExpectation alloc] initWithDescription:@"Disconnect."];
|
||||
|
||||
GNCFakePeripheral *peripheral = [[GNCFakePeripheral alloc] init];
|
||||
|
||||
[medium connectToGATTServerForPeripheral:peripheral
|
||||
disconnectionHandler:^() {
|
||||
[disconnectExpectation fulfill];
|
||||
}
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
XCTAssertNotNil(client);
|
||||
XCTAssertNil(error);
|
||||
[connectExpectation fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectations:@[ connectExpectation ] timeout:3];
|
||||
|
||||
[fakeCentralManager simulateCentralManagerDidDisconnectPeripheral:peripheral];
|
||||
|
||||
[self waitForExpectations:@[ disconnectExpectation ] timeout:3];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
@@ -25,6 +26,7 @@ using Property = ::nearby::api::ble_v2::GattCharacteristic::Property;
|
||||
using Permission = ::nearby::api::ble_v2::GattCharacteristic::Permission;
|
||||
using WriteType = ::nearby::api::ble_v2::GattClient::WriteType;
|
||||
using Uuid = ::nearby::Uuid;
|
||||
using ByteArray = ::nearby::ByteArray;
|
||||
|
||||
@interface GNCBLEUtilsTest : XCTestCase
|
||||
@end
|
||||
@@ -120,6 +122,22 @@ using Uuid = ::nearby::Uuid;
|
||||
XCTAssertEqual(actual.properties, properties);
|
||||
}
|
||||
|
||||
- (void)testObjCServiceDataFromCpp {
|
||||
CBUUID *serviceUUID1 = [CBUUID UUIDWithString:@"0000FEF3-0000-1000-8000-00805F9B34FB"];
|
||||
CBUUID *serviceUUID2 = [CBUUID UUIDWithString:@"0000FEF4-0000-1000-8000-00805F9B34FB"];
|
||||
NSData *data1 = [@"one" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *data2 = [@"two" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
absl::flat_hash_map<Uuid, ByteArray> service_data;
|
||||
service_data[Uuid(0x0000FEF300001000, 0x800000805F9B34FB)] = ByteArray("one");
|
||||
service_data[Uuid(0x0000FEF400001000, 0x800000805F9B34FB)] = ByteArray("two");
|
||||
|
||||
NSMutableDictionary<CBUUID *, NSData *> *actual =
|
||||
::nearby::apple::ObjCServiceDataFromCPP(service_data);
|
||||
XCTAssertEqualObjects(actual[serviceUUID1], data1);
|
||||
XCTAssertEqualObjects(actual[serviceUUID2], data2);
|
||||
}
|
||||
|
||||
#pragma mark - Objective-C to C++
|
||||
|
||||
- (void)testCPPUUIDFromObjC16Bit {
|
||||
|
||||
@@ -1,112 +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 <XCTest/XCTest.h>
|
||||
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/platform.h"
|
||||
|
||||
using ::nearby::ByteArray;
|
||||
using ::nearby::Uuid;
|
||||
using ::nearby::api::BluetoothAdapter;
|
||||
using ::nearby::api::ImplementationPlatform;
|
||||
using ::nearby::api::ble_v2::BleAdvertisementData;
|
||||
using ::nearby::api::ble_v2::BleMedium;
|
||||
using ::nearby::api::ble_v2::GattCharacteristic;
|
||||
using ::nearby::api::ble_v2::TxPowerLevel;
|
||||
using IOSBluetoothAdapter = ::nearby::apple::BluetoothAdapter;
|
||||
|
||||
static const char *const kAdvertisementString = "\x0a\x0b\x0c\x0d";
|
||||
static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
|
||||
|
||||
@interface GNCBleTest : XCTestCase
|
||||
@end
|
||||
|
||||
// TODO(b/222392304): More tests on GNCBleTest.
|
||||
@implementation GNCBleTest {
|
||||
std::unique_ptr<BluetoothAdapter> _adapter;
|
||||
std::unique_ptr<BleMedium> _ble;
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
_adapter = ImplementationPlatform::CreateBluetoothAdapter();
|
||||
_ble = ImplementationPlatform::CreateBleV2Medium(*_adapter);
|
||||
}
|
||||
|
||||
- (void)testStartandStopAdvertising {
|
||||
Uuid service_uuid(1234, 5678);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
|
||||
// Assemble regular advertisement data.
|
||||
BleAdvertisementData advertising_data;
|
||||
advertising_data.is_extended_advertisement = false;
|
||||
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
|
||||
|
||||
XCTAssertTrue(_ble->StartAdvertising(advertising_data,
|
||||
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
|
||||
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
|
||||
XCTAssertTrue(_ble->StopAdvertising());
|
||||
}
|
||||
|
||||
- (void)testStartandStopScanning {
|
||||
Uuid service_uuid(1234, 5678);
|
||||
|
||||
XCTAssertTrue(_ble->StartScanning(service_uuid, kTxPowerLevel, BleMedium::ScanCallback{}));
|
||||
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
|
||||
XCTAssertTrue(_ble->StopScanning());
|
||||
}
|
||||
|
||||
- (void)testGattServerWorking {
|
||||
// Test creating gatt_server.
|
||||
auto gatt_server = _ble->StartGattServer(/*ServerGattConnectionCallback=*/{});
|
||||
XCTAssert(gatt_server != nullptr);
|
||||
|
||||
// Test creating characteristic.
|
||||
Uuid service_uuid(1234, 5678);
|
||||
Uuid characteristic_uuid(5678, 1234);
|
||||
GattCharacteristic::Permission permission = GattCharacteristic::Permission::kRead;
|
||||
GattCharacteristic::Property property = GattCharacteristic::Property::kRead;
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<GattCharacteristic> gatt_characteristic =
|
||||
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid, permission, property);
|
||||
XCTAssertTrue(gatt_characteristic.has_value());
|
||||
|
||||
// Test updating characteristic.
|
||||
ByteArray any_byte("any");
|
||||
XCTAssertTrue(gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
|
||||
|
||||
gatt_server->Stop();
|
||||
}
|
||||
|
||||
- (void)testCreateGattClient {
|
||||
IOSBluetoothAdapter *adapter = static_cast<IOSBluetoothAdapter *>(_adapter.get());
|
||||
auto gatt_client = _ble->ConnectToGattServer(adapter->GetPeripheral(), kTxPowerLevel, {});
|
||||
|
||||
XCTAssert(gatt_client != nullptr);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,87 +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 <XCTest/XCTest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
|
||||
using ::nearby::apple::BlePeripheral;
|
||||
using ::nearby::apple::BluetoothAdapter;
|
||||
using ScanMode = ::nearby::api::BluetoothAdapter::ScanMode;
|
||||
using Status = ::nearby::api::BluetoothAdapter::Status;
|
||||
|
||||
static const char kAdapterName[] = "MyBtAdapter";
|
||||
static const char kMacAddress[] = "4C:8B:1D:CE:BA:D1";
|
||||
|
||||
@interface GNCBluetoothAdapterTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBluetoothAdapterTest
|
||||
|
||||
- (void)testName {
|
||||
BluetoothAdapter adapter;
|
||||
|
||||
XCTAssertTrue(adapter.SetName(kAdapterName));
|
||||
|
||||
XCTAssertEqual(adapter.GetName(), std::string(kAdapterName));
|
||||
}
|
||||
|
||||
- (void)testGetScanMode {
|
||||
BluetoothAdapter adapter;
|
||||
|
||||
// Always return kNone as ScanMode is not supported .
|
||||
XCTAssertEqual(adapter.GetScanMode(), ScanMode::kNone);
|
||||
}
|
||||
|
||||
- (void)testSetScanMode_DefaultUnsupported {
|
||||
BluetoothAdapter adapter;
|
||||
|
||||
XCTAssertFalse(adapter.SetScanMode(ScanMode::kNone));
|
||||
XCTAssertFalse(adapter.SetScanMode(ScanMode::kConnectable));
|
||||
XCTAssertFalse(adapter.SetScanMode(ScanMode::kConnectableDiscoverable));
|
||||
}
|
||||
|
||||
- (void)testSetStatus {
|
||||
BluetoothAdapter adapter;
|
||||
|
||||
XCTAssertTrue(adapter.SetStatus(Status::kDisabled));
|
||||
XCTAssertFalse(adapter.IsEnabled());
|
||||
|
||||
XCTAssertTrue(adapter.SetStatus(Status::kEnabled));
|
||||
XCTAssertTrue(adapter.IsEnabled());
|
||||
}
|
||||
|
||||
- (void)testMacAddress {
|
||||
BluetoothAdapter adapter;
|
||||
|
||||
adapter.SetMacAddress(kMacAddress);
|
||||
|
||||
XCTAssertEqual(adapter.GetMacAddress(), std::string(kMacAddress));
|
||||
}
|
||||
|
||||
- (void)testGetPeripheral {
|
||||
BluetoothAdapter adapter;
|
||||
|
||||
adapter.SetMacAddress(kMacAddress);
|
||||
|
||||
// The peripheral from adapter, the MAC address is the same.
|
||||
BlePeripheral& peripheral = adapter.GetPeripheral();
|
||||
XCTAssertEqual(adapter.GetMacAddress(), peripheral.GetAddress());
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2023 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCCentralManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** A fake implementation of @c GNCCentralManager to inject for testing. */
|
||||
@interface GNCFakeCentralManager : NSObject <GNCCentralManager>
|
||||
|
||||
/** The list of services being scanned for. */
|
||||
@property(nonatomic, nullable, readonly) NSArray<CBUUID *> *serviceUUIDs;
|
||||
|
||||
/**
|
||||
* Similates a @c connectPeripheral:options: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling
|
||||
* @c connectPeripheral:options: and will call the
|
||||
* @c gnc_centralManager:didFailToConnectPeripheral:error: delegate method with the provided error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *didFailToConnectPeripheralError;
|
||||
|
||||
/**
|
||||
* Simulates a state update event.
|
||||
*
|
||||
* Updates the central manager state to the provided value and calls the
|
||||
* @c gnc_centralManagerDidUpdateState: delegate method.
|
||||
*
|
||||
* @param fakeState The new state to transition to.
|
||||
*/
|
||||
- (void)simulateCentralManagerDidUpdateState:(CBManagerState)fakeState;
|
||||
|
||||
/**
|
||||
* Simulates a peripheral discovery event.
|
||||
*
|
||||
* Calls the @c gnc_centralManager:didDiscoverPeripheral:advertisementData:RSSI: delegate method.
|
||||
*
|
||||
* @param peripheral The discovered peripheral.
|
||||
* @param peripheral A dictionary containing any advertisement data.
|
||||
*/
|
||||
- (void)simulateCentralManagerDidDiscoverPeripheral:(id<GNCPeripheral>)peripheral
|
||||
advertisementData:
|
||||
(NSDictionary<NSString *, id> *)advertisementData;
|
||||
|
||||
/**
|
||||
* Simulates a peripheral disconnection event.
|
||||
*
|
||||
* Calls the @c gnc_centralManager:didDisconnectPeripheral:error: delegate method.
|
||||
*
|
||||
* @param peripheral The now-disconnected peripheral.
|
||||
*/
|
||||
- (void)simulateCentralManagerDidDisconnectPeripheral:(id<GNCPeripheral>)peripheral;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakeCentralManager.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCCentralManager.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheral.h"
|
||||
|
||||
@implementation GNCFakeCentralManager {
|
||||
CBManagerState _state;
|
||||
NSArray<CBUUID *> *_serviceUUIDs;
|
||||
}
|
||||
|
||||
@synthesize centralDelegate;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_state = CBManagerStateUnknown;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CBManagerState)state {
|
||||
return _state;
|
||||
}
|
||||
|
||||
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs
|
||||
options:(nullable NSDictionary<NSString *, id> *)options {
|
||||
_serviceUUIDs = serviceUUIDs;
|
||||
}
|
||||
|
||||
- (void)connectPeripheral:(id<GNCPeripheral>)peripheral
|
||||
options:(nullable NSDictionary<NSString *, id> *)options {
|
||||
if (_didFailToConnectPeripheralError) {
|
||||
[centralDelegate gnc_centralManager:self
|
||||
didFailToConnectPeripheral:peripheral
|
||||
error:_didFailToConnectPeripheralError];
|
||||
return;
|
||||
}
|
||||
[centralDelegate gnc_centralManager:self didConnectPeripheral:peripheral];
|
||||
}
|
||||
|
||||
- (void)stopScan {
|
||||
}
|
||||
|
||||
#pragma mark - Testing Helpers
|
||||
|
||||
- (NSArray<CBUUID *> *)serviceUUIDs {
|
||||
return _serviceUUIDs;
|
||||
}
|
||||
|
||||
- (void)simulateCentralManagerDidUpdateState:(CBManagerState)fakeState {
|
||||
_state = fakeState;
|
||||
[centralDelegate gnc_centralManagerDidUpdateState:self];
|
||||
}
|
||||
|
||||
- (void)simulateCentralManagerDidDiscoverPeripheral:(id<GNCPeripheral>)peripheral
|
||||
advertisementData:
|
||||
(NSDictionary<NSString *, id> *)advertisementData {
|
||||
[centralDelegate gnc_centralManager:self
|
||||
didDiscoverPeripheral:peripheral
|
||||
advertisementData:advertisementData
|
||||
RSSI:[NSNumber numberWithInt:0]];
|
||||
}
|
||||
|
||||
- (void)simulateCentralManagerDidDisconnectPeripheral:(id<GNCPeripheral>)peripheral {
|
||||
[centralDelegate gnc_centralManager:self didDisconnectPeripheral:peripheral error:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2023 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 <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** A fake implementation of @c GNCPeripheral to inject for testing. */
|
||||
@interface GNCFakePeripheral : NSObject <GNCPeripheral>
|
||||
|
||||
/**
|
||||
* Similates a @c discoverServices: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling
|
||||
* @c discoverServices: and will call the
|
||||
* @c gnc_peripheral:didDiscoverServices: delegate method with the provided
|
||||
* error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *discoverServicesError;
|
||||
|
||||
/**
|
||||
* Similates a @c discoverCharacteristics:forService: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling
|
||||
* @c discoverCharacteristics:forService: and will call the
|
||||
* @c gnc_peripheral:didDiscoverCharacteristicsForService:error: delegate method with the provided
|
||||
* error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *discoverCharacteristicsForServiceError;
|
||||
|
||||
/**
|
||||
* Similates a @c readValueForCharacteristic: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling
|
||||
* @c discoverCharacteristics:forService: and will call the
|
||||
* @c gnc_peripheral:didUpdateValueForCharacteristic:error: delegate method with the provided
|
||||
* error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *readValueForCharacteristicError;
|
||||
|
||||
/** Similates a delay in all delegate calls by the specified amount. */
|
||||
@property(nonatomic, readwrite) NSTimeInterval delegateDelay;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheral.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CBService ()
|
||||
|
||||
// Change property to readwrite for tests.
|
||||
@property(retain, readwrite, nullable) NSArray<CBCharacteristic *> *characteristics;
|
||||
|
||||
@end
|
||||
|
||||
@interface CBCharacteristic ()
|
||||
|
||||
// Change property to readwrite for tests.
|
||||
@property(retain, readwrite, nullable) NSData *value;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GNCFakePeripheral {
|
||||
NSMutableArray<CBService *> *_services;
|
||||
NSUUID *_identifier;
|
||||
}
|
||||
|
||||
@synthesize peripheralDelegate;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_services = [[NSMutableArray alloc] init];
|
||||
_identifier = [[NSUUID alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUUID *)identifier {
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
- (nullable NSArray<CBService *> *)services {
|
||||
return _services;
|
||||
}
|
||||
|
||||
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs {
|
||||
[self delayDelegateUsingBlock:^() {
|
||||
if (!_discoverServicesError) {
|
||||
for (CBUUID *serviceUUID in serviceUUIDs) {
|
||||
[_services addObject:[[CBMutableService alloc] initWithType:serviceUUID primary:YES]];
|
||||
}
|
||||
}
|
||||
|
||||
[peripheralDelegate gnc_peripheral:self didDiscoverServices:_discoverServicesError];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs
|
||||
forService:(CBService *)service {
|
||||
[self delayDelegateUsingBlock:^() {
|
||||
if (!_discoverCharacteristicsForServiceError) {
|
||||
NSMutableArray<CBCharacteristic *> *characteristics = service.characteristics.mutableCopy;
|
||||
if (!characteristics) {
|
||||
characteristics = [NSMutableArray array];
|
||||
}
|
||||
for (CBUUID *characteristicUUID in characteristicUUIDs) {
|
||||
[characteristics addObject:[[CBMutableCharacteristic alloc]
|
||||
initWithType:characteristicUUID
|
||||
properties:CBCharacteristicPropertyRead
|
||||
value:nil
|
||||
permissions:CBAttributePermissionsReadable]];
|
||||
}
|
||||
service.characteristics = characteristics;
|
||||
}
|
||||
|
||||
[peripheralDelegate gnc_peripheral:self
|
||||
didDiscoverCharacteristicsForService:service
|
||||
error:_discoverCharacteristicsForServiceError];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic {
|
||||
[self delayDelegateUsingBlock:^() {
|
||||
if (!_readValueForCharacteristicError) {
|
||||
characteristic.value = [NSData data];
|
||||
}
|
||||
|
||||
[peripheralDelegate gnc_peripheral:self
|
||||
didUpdateValueForCharacteristic:characteristic
|
||||
error:_readValueForCharacteristicError];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)delayDelegateUsingBlock:(void (^)())block {
|
||||
if (_delegateDelay <= 0) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, _delegateDelay * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(), ^{
|
||||
block();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -61,9 +61,6 @@
|
||||
BOOL _isAdvertising;
|
||||
NSDictionary<NSString *, id> *_advertisementData;
|
||||
NSMutableArray<CBService *> *_services;
|
||||
|
||||
// Used to deliver delegate callbacks.
|
||||
dispatch_queue_t _queue;
|
||||
}
|
||||
|
||||
@synthesize peripheralDelegate;
|
||||
@@ -79,7 +76,6 @@
|
||||
_state = CBManagerStateUnknown;
|
||||
_advertisementData = nil;
|
||||
_services = [[NSMutableArray alloc] init];
|
||||
_queue = dispatch_queue_create("com.nearby.GNCFakePeripheralManager", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -96,9 +92,7 @@
|
||||
if (!_didAddServiceError) {
|
||||
[_services addObject:service];
|
||||
}
|
||||
dispatch_async(_queue, ^{
|
||||
[peripheralDelegate gnc_peripheralManager:self didAddService:service error:_didAddServiceError];
|
||||
});
|
||||
[peripheralDelegate gnc_peripheralManager:self didAddService:service error:_didAddServiceError];
|
||||
}
|
||||
|
||||
- (void)removeService:(CBMutableService *)service {
|
||||
@@ -112,10 +106,8 @@
|
||||
- (void)startAdvertising:(NSDictionary<NSString *, id> *)advertisementData {
|
||||
_isAdvertising = _didStartAdvertisingError == nil;
|
||||
_advertisementData = advertisementData;
|
||||
dispatch_async(_queue, ^{
|
||||
[peripheralDelegate gnc_peripheralManagerDidStartAdvertising:self
|
||||
error:_didStartAdvertisingError];
|
||||
});
|
||||
[peripheralDelegate gnc_peripheralManagerDidStartAdvertising:self
|
||||
error:_didStartAdvertisingError];
|
||||
}
|
||||
|
||||
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result {
|
||||
@@ -126,6 +118,10 @@
|
||||
[_respondToRequestErrorExpectation fulfill];
|
||||
}
|
||||
|
||||
- (void)stopAdvertising {
|
||||
_isAdvertising = false;
|
||||
}
|
||||
|
||||
#pragma mark - Testing Helpers
|
||||
|
||||
- (NSArray<CBService *> *)services {
|
||||
@@ -138,18 +134,14 @@
|
||||
|
||||
- (void)simulatePeripheralManagerDidUpdateState:(CBManagerState)fakeState {
|
||||
_state = fakeState;
|
||||
dispatch_async(_queue, ^{
|
||||
[peripheralDelegate gnc_peripheralManagerDidUpdateState:self];
|
||||
});
|
||||
[peripheralDelegate gnc_peripheralManagerDidUpdateState:self];
|
||||
}
|
||||
|
||||
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
|
||||
characteristic:(CBUUID *)characteristic {
|
||||
CBATTRequest *request = [[CBATTRequest alloc] initWithService:service
|
||||
characteristic:characteristic];
|
||||
dispatch_async(_queue, ^{
|
||||
[peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request];
|
||||
});
|
||||
[peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
#include "internal/platform/implementation/platform.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
void GNCEnsureFileAtPath(std::string path) {
|
||||
[NSFileManager.defaultManager
|
||||
createDirectoryAtPath:[@(path.c_str()) stringByDeletingLastPathComponent]
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:nil];
|
||||
[[NSData data] writeToFile:@(path.c_str()) options:0 error:nil];
|
||||
}
|
||||
|
||||
@interface GNCPlatformTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCPlatformTest
|
||||
|
||||
- (void)testGetCustomSavePath {
|
||||
NSString *expected = [NSURL fileURLWithPath:@"a/b/c.d"].path;
|
||||
std::string actual = nearby::api::ImplementationPlatform::GetCustomSavePath("a/b", "c.d");
|
||||
XCTAssertEqualObjects(@(actual.c_str()), expected);
|
||||
}
|
||||
|
||||
- (void)testGetCustomSavePathWithIllegalCharacters {
|
||||
NSString *expected = [NSURL fileURLWithPath:@"a/b/Fi?le*: Name.ext"].path;
|
||||
std::string actual =
|
||||
nearby::api::ImplementationPlatform::GetCustomSavePath("a/b", "Fi?le*/ Name.ext");
|
||||
XCTAssertEqualObjects(@(actual.c_str()), expected);
|
||||
}
|
||||
|
||||
- (void)testGetCustomSavePathWithPathEscapingCharacters {
|
||||
NSString *expected = [NSURL fileURLWithPath:@"a/b/..:c:..:d.e"].path;
|
||||
std::string actual =
|
||||
nearby::api::ImplementationPlatform::GetCustomSavePath("a/../../b", "../c/../d.e");
|
||||
XCTAssertEqualObjects(@(actual.c_str()), expected);
|
||||
}
|
||||
|
||||
- (void)testGetCustomSavePathDuplicateNames {
|
||||
NSString *expected1 = [NSURL fileURLWithPath:@"a/b/cat.jpg"].path;
|
||||
NSString *expected2 = [NSURL fileURLWithPath:@"a/b/cat 2.jpg"].path;
|
||||
NSString *expected3 = [NSURL fileURLWithPath:@"a/b/cat 3.jpg"].path;
|
||||
|
||||
std::string actual1 = nearby::api::ImplementationPlatform::GetCustomSavePath("a/b", "cat.jpg");
|
||||
GNCEnsureFileAtPath(actual1);
|
||||
|
||||
std::string actual2 = nearby::api::ImplementationPlatform::GetCustomSavePath("a/b", "cat.jpg");
|
||||
GNCEnsureFileAtPath(actual2);
|
||||
|
||||
std::string actual3 = nearby::api::ImplementationPlatform::GetCustomSavePath("a/b", "cat.jpg");
|
||||
|
||||
// Cleanup created files.
|
||||
[NSFileManager.defaultManager removeItemAtPath:@(actual1.c_str()) error:nil];
|
||||
[NSFileManager.defaultManager removeItemAtPath:@(actual2.c_str()) error:nil];
|
||||
|
||||
XCTAssertEqualObjects(@(actual1.c_str()), expected1);
|
||||
XCTAssertEqualObjects(@(actual2.c_str()), expected2);
|
||||
XCTAssertEqualObjects(@(actual3.c_str()), expected3);
|
||||
}
|
||||
|
||||
- (void)testGetDownloadPath {
|
||||
NSString *expected =
|
||||
[[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:@"a/b/c.d"].path;
|
||||
std::string actual = nearby::api::ImplementationPlatform::GetDownloadPath("a/b", "c.d");
|
||||
XCTAssertEqualObjects(@(actual.c_str()), expected);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -25,7 +25,7 @@
|
||||
- (void)testEncodingWithPadding {
|
||||
NSString *expected = @"AQ";
|
||||
NSData *data = [[NSData alloc] initWithBase64EncodedString:@"AQ==" options:0];
|
||||
NSString *actual = [data webSafebase64EncodedString];
|
||||
NSString *actual = [data webSafeBase64EncodedString];
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,58 @@
|
||||
[[NSData alloc] initWithBase64EncodedString:
|
||||
@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
options:0];
|
||||
NSString *actual = [data webSafebase64EncodedString];
|
||||
NSString *actual = [data webSafeBase64EncodedString];
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodingWithoutPadding {
|
||||
NSData *expected = [[NSData alloc] initWithBase64EncodedString:@"AQ==" options:0];
|
||||
NSData *actual = [[NSData alloc] initWithWebSafeBase64EncodedString:@"AQ"];
|
||||
XCTAssertNotNil(actual);
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodingNoPad {
|
||||
NSData *expected = [[NSData alloc] initWithBase64EncodedString:@"aaaa" options:0];
|
||||
NSData *actual = [[NSData alloc] initWithWebSafeBase64EncodedString:@"aaaa"];
|
||||
XCTAssertNotNil(actual);
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecoding1Pad {
|
||||
NSData *expected = [[NSData alloc] initWithBase64EncodedString:@"aaa=" options:0];
|
||||
NSData *actual = [[NSData alloc] initWithWebSafeBase64EncodedString:@"aaa"];
|
||||
XCTAssertNotNil(actual);
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecoding2Pad {
|
||||
NSData *expected = [[NSData alloc] initWithBase64EncodedString:@"aa==" options:0];
|
||||
NSData *actual = [[NSData alloc] initWithWebSafeBase64EncodedString:@"aa"];
|
||||
XCTAssertNotNil(actual);
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodingSingleCharacterInLastQuadruple {
|
||||
NSData *actual = [[NSData alloc] initWithWebSafeBase64EncodedString:@"aaaab"];
|
||||
XCTAssertNil(actual);
|
||||
}
|
||||
|
||||
- (void)testDecodingWithAllValidCharacters {
|
||||
NSData *expected =
|
||||
[[NSData alloc] initWithBase64EncodedString:
|
||||
@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
options:0];
|
||||
NSData *actual =
|
||||
[[NSData alloc] initWithWebSafeBase64EncodedString:
|
||||
@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"];
|
||||
XCTAssertNotNil(actual);
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testDecodingWithIllegalCharacters {
|
||||
NSData *actual = [[NSData alloc] initWithWebSafeBase64EncodedString:@"@#$^&*()"];
|
||||
XCTAssertNil(actual);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,218 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLE_H_
|
||||
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLE_H_
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/GNCMConnection.h"
|
||||
|
||||
@class GNCMBlePeripheral, GNCMBleCentral;
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
/** InputStream that reads from GNCMConnection. */
|
||||
class BleInputStream : public InputStream {
|
||||
public:
|
||||
BleInputStream();
|
||||
~BleInputStream() override;
|
||||
|
||||
ExceptionOr<ByteArray> Read(std::int64_t size) override;
|
||||
Exception Close() override;
|
||||
|
||||
GNCMConnectionHandlers *GetConnectionHandlers() { return connectionHandlers_; }
|
||||
|
||||
private:
|
||||
GNCMConnectionHandlers *connectionHandlers_;
|
||||
NSMutableArray<NSData *> *newDataPackets_;
|
||||
NSMutableData *accumulatedData_;
|
||||
NSCondition *condition_;
|
||||
};
|
||||
|
||||
/** OutputStream that writes to GNCMConnection. */
|
||||
class BleOutputStream : public OutputStream {
|
||||
public:
|
||||
explicit BleOutputStream(id<GNCMConnection> connection)
|
||||
: connection_(connection), condition_([[NSCondition alloc] init]) {}
|
||||
~BleOutputStream() override;
|
||||
|
||||
Exception Write(const ByteArray &data) override;
|
||||
Exception Flush() override;
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
id<GNCMConnection> connection_;
|
||||
NSCondition *condition_;
|
||||
};
|
||||
|
||||
/** Concrete BleSocket implementation. */
|
||||
class BleSocket : public api::ble_v2::BleSocket {
|
||||
public:
|
||||
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 peripheral_; }
|
||||
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
std::unique_ptr<BleInputStream> input_stream_;
|
||||
std::unique_ptr<BleOutputStream> output_stream_;
|
||||
BlePeripheral *peripheral_;
|
||||
};
|
||||
|
||||
/** Concrete BleServerSocket implementation. */
|
||||
class BleServerSocket : public api::ble_v2::BleServerSocket {
|
||||
public:
|
||||
~BleServerSocket() override;
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleSocket> Accept() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool Connect(std::unique_ptr<BleSocket> socket) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
absl::CondVar cond_;
|
||||
absl::flat_hash_set<std::unique_ptr<BleSocket>> pending_sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::AnyInvocable<void()> close_notifier_ ABSL_GUARDED_BY(mutex_);
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
/** Concrete BleMedium implementation. */
|
||||
class BleMedium : public api::ble_v2::BleMedium {
|
||||
public:
|
||||
explicit BleMedium(api::BluetoothAdapter &adapter);
|
||||
|
||||
// api::BleMedium:
|
||||
bool StartAdvertising(const api::ble_v2::BleAdvertisementData &advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_set_parameters) override;
|
||||
bool StopAdvertising() override;
|
||||
std::unique_ptr<AdvertisingSession> StartAdvertising(
|
||||
const api::ble_v2::BleAdvertisementData &advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_parameters, AdvertisingCallback callback) override;
|
||||
|
||||
bool StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BleMedium::ScanCallback scan_callback) override;
|
||||
bool StopScanning() override;
|
||||
std::unique_ptr<ScanningSession> StartScanning(const Uuid &service_uuid,
|
||||
api::ble_v2::TxPowerLevel tx_power_level,
|
||||
ScanningCallback callback) override;
|
||||
std::unique_ptr<api::ble_v2::GattServer> StartGattServer(
|
||||
api::ble_v2::ServerGattConnectionCallback callback) override;
|
||||
std::unique_ptr<api::ble_v2::GattClient> ConnectToGattServer(
|
||||
api::ble_v2::BlePeripheral &peripheral, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::ClientGattConnectionCallback callback) override;
|
||||
std::unique_ptr<api::ble_v2::BleServerSocket> OpenServerSocket(
|
||||
const std::string &service_id) override;
|
||||
std::unique_ptr<api::ble_v2::BleSocket> Connect(const std::string &service_id,
|
||||
api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BlePeripheral &peripheral,
|
||||
CancellationFlag *cancellation_flag) override;
|
||||
bool IsExtendedAdvertisementsAvailable() override;
|
||||
|
||||
bool GetRemotePeripheral(const std::string& mac_address,
|
||||
GetRemotePeripheralCallback callback) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
|
||||
GetRemotePeripheralCallback callback) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// A concrete implemenation for GattServer.
|
||||
class GattServer : public api::ble_v2::GattServer {
|
||||
public:
|
||||
GattServer() = default;
|
||||
GattServer(BluetoothAdapter *adapter, GNCMBlePeripheral *peripheral)
|
||||
: adapter_(adapter), peripheral_(peripheral) {}
|
||||
|
||||
absl::optional<api::ble_v2::GattCharacteristic> CreateCharacteristic(
|
||||
const Uuid &service_uuid, const Uuid &characteristic_uuid,
|
||||
api::ble_v2::GattCharacteristic::Permission permission,
|
||||
api::ble_v2::GattCharacteristic::Property property) override;
|
||||
|
||||
bool UpdateCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
const nearby::ByteArray &value) override;
|
||||
absl::Status NotifyCharacteristicChanged(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
bool confirm, const ByteArray &new_value) override;
|
||||
void Stop() override;
|
||||
BlePeripheral &GetBlePeripheral() override { return adapter_->GetPeripheral(); }
|
||||
|
||||
private:
|
||||
BluetoothAdapter *adapter_ = nullptr;
|
||||
GNCMBlePeripheral *peripheral_;
|
||||
};
|
||||
|
||||
// A concrete implemenation for GattClient.
|
||||
class GattClient : public api::ble_v2::GattClient {
|
||||
public:
|
||||
GattClient() = default;
|
||||
explicit GattClient(GNCMBleCentral *central, const std::string &peripheral_id)
|
||||
: central_(central), peripheral_id_(peripheral_id) {}
|
||||
|
||||
bool DiscoverServiceAndCharacteristics(const Uuid &service_uuid,
|
||||
const std::vector<Uuid> &characteristic_uuids) override;
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<api::ble_v2::GattCharacteristic> GetCharacteristic(
|
||||
const Uuid &service_uuid, const Uuid &characteristic_uuid) override;
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<std::string> ReadCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic &characteristic) override;
|
||||
|
||||
bool WriteCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
absl::string_view value,
|
||||
api::ble_v2::GattClient::WriteType write_type) override;
|
||||
|
||||
bool SetCharacteristicSubscription(
|
||||
const api::ble_v2::GattCharacteristic &characteristic, bool enable,
|
||||
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) override;
|
||||
|
||||
void Disconnect() override;
|
||||
|
||||
private:
|
||||
GNCMBleCentral *central_;
|
||||
std::string peripheral_id_;
|
||||
absl::flat_hash_map<api::ble_v2::GattCharacteristic, const std::string>
|
||||
gatt_characteristic_values_;
|
||||
};
|
||||
|
||||
absl::Mutex mutex_;
|
||||
BluetoothAdapter *adapter_;
|
||||
GNCMBlePeripheral *peripheral_;
|
||||
GNCMBleCentral *central_;
|
||||
absl::flat_hash_map<std::string, BleServerSocket *> server_sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
dispatch_queue_t callback_queue_;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLE_H_
|
||||
#import "internal/platform/implementation/apple/ble_medium.h"
|
||||
|
||||
@@ -1,600 +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/apple/ble.h"
|
||||
|
||||
#include <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/platform/implementation/apple/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/apple/utils.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleCentral.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBlePeripheral.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
using Permission = api::ble_v2::GattCharacteristic::Permission;
|
||||
using Property = api::ble_v2::GattCharacteristic::Property;
|
||||
|
||||
namespace {
|
||||
|
||||
CBAttributePermissions PermissionToCBPermissions(Permission permission) {
|
||||
CBAttributePermissions characteristPermissions = 0;
|
||||
if ((permission & Permission::kRead) != Permission::kNone) {
|
||||
characteristPermissions |= CBAttributePermissionsReadable;
|
||||
}
|
||||
if ((permission & Permission::kWrite) != Permission::kNone) {
|
||||
characteristPermissions |= CBAttributePermissionsWriteable;
|
||||
}
|
||||
return characteristPermissions;
|
||||
}
|
||||
|
||||
CBCharacteristicProperties PropertiesToCBProperties(Property property) {
|
||||
CBCharacteristicProperties characteristicProperties = 0;
|
||||
if ((property & Property::kRead) != Property::kNone) {
|
||||
characteristicProperties |= CBCharacteristicPropertyRead;
|
||||
}
|
||||
if ((property & Property::kWrite) != Property::kNone) {
|
||||
characteristicProperties |= CBCharacteristicPropertyWrite;
|
||||
}
|
||||
if ((property & Property::kIndicate) != Property::kNone) {
|
||||
characteristicProperties |= CBCharacteristicPropertyIndicate;
|
||||
}
|
||||
if ((property & Property::kNotify) != Property::kNone) {
|
||||
characteristicProperties |= CBCharacteristicPropertyNotify;
|
||||
}
|
||||
return characteristicProperties;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using ::nearby::api::ble_v2::BleAdvertisementData;
|
||||
using ::nearby::api::ble_v2::TxPowerLevel;
|
||||
using ScanCallback = ::nearby::api::ble_v2::BleMedium::ScanCallback;
|
||||
|
||||
/** InputStream that reads from GNCMConnection. */
|
||||
BleInputStream::BleInputStream()
|
||||
: newDataPackets_([NSMutableArray array]),
|
||||
accumulatedData_([NSMutableData data]),
|
||||
condition_([[NSCondition alloc] init]) {
|
||||
// Create the handlers of incoming data from the remote endpoint.
|
||||
connectionHandlers_ = [GNCMConnectionHandlers
|
||||
payloadHandler:^(NSData* data) {
|
||||
[condition_ lock];
|
||||
// Add the incoming data to the data packet array to be processed in read() below.
|
||||
[newDataPackets_ addObject:data];
|
||||
[condition_ signal];
|
||||
[condition_ unlock];
|
||||
}
|
||||
disconnectedHandler:^{
|
||||
[condition_ lock];
|
||||
// Release the data packet array, meaning the stream has been closed or severed.
|
||||
newDataPackets_ = nil;
|
||||
[condition_ signal];
|
||||
[condition_ unlock];
|
||||
}];
|
||||
}
|
||||
|
||||
BleInputStream::~BleInputStream() {
|
||||
NSCAssert(!newDataPackets_, @"BleInputStream not closed before destruction");
|
||||
}
|
||||
|
||||
ExceptionOr<ByteArray> BleInputStream::Read(std::int64_t size) {
|
||||
// Block until either (a) the connection has been closed, (b) we have enough data to return.
|
||||
NSData* dataToReturn;
|
||||
[condition_ lock];
|
||||
while (true) {
|
||||
// Check if the stream has been closed or severed.
|
||||
if (!newDataPackets_) break;
|
||||
|
||||
if (newDataPackets_.count > 0) {
|
||||
// Add the packet data to the accumulated data.
|
||||
for (NSData* data in newDataPackets_) {
|
||||
if (data.length > 0) {
|
||||
[accumulatedData_ appendData:data];
|
||||
}
|
||||
}
|
||||
[newDataPackets_ removeAllObjects];
|
||||
}
|
||||
|
||||
if ((size == -1) && (accumulatedData_.length > 0)) {
|
||||
// Return all of the data.
|
||||
dataToReturn = accumulatedData_;
|
||||
accumulatedData_ = [NSMutableData data];
|
||||
break;
|
||||
} else if (accumulatedData_.length > 0) {
|
||||
// Return up to |size| bytes of the data.
|
||||
std::int64_t sizeToReturn = (accumulatedData_.length < size) ? accumulatedData_.length : size;
|
||||
NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn);
|
||||
dataToReturn = [accumulatedData_ subdataWithRange:range];
|
||||
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
|
||||
break;
|
||||
}
|
||||
|
||||
[condition_ wait];
|
||||
}
|
||||
[condition_ unlock];
|
||||
|
||||
if (dataToReturn) {
|
||||
NSLog(@"[NEARBY] Input stream: Received data of size: %lu", (unsigned long)dataToReturn.length);
|
||||
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
|
||||
} else {
|
||||
return ExceptionOr<ByteArray>{Exception::kIo};
|
||||
}
|
||||
}
|
||||
|
||||
Exception BleInputStream::Close() {
|
||||
// Unblock pending read operation.
|
||||
[condition_ lock];
|
||||
newDataPackets_ = nil;
|
||||
[condition_ signal];
|
||||
[condition_ unlock];
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
/** OutputStream that writes to GNCMConnection. */
|
||||
BleOutputStream::~BleOutputStream() {
|
||||
NSCAssert(!connection_, @"BleOutputStream not closed before destruction");
|
||||
}
|
||||
|
||||
Exception BleOutputStream::Write(const ByteArray& data) {
|
||||
[condition_ lock];
|
||||
NSLog(@"[NEARBY] Sending data of size: %lu", (unsigned long)NSDataFromByteArray(data).length);
|
||||
|
||||
NSMutableData* packet = [NSMutableData dataWithData:NSDataFromByteArray(data)];
|
||||
|
||||
// Send the data, blocking until the completion handler is called.
|
||||
__block GNCMPayloadResult sendResult = GNCMPayloadResultFailure;
|
||||
__block bool isComplete = NO;
|
||||
NSCondition* condition = condition_; // don't capture |this| in completion
|
||||
|
||||
// Check if connection_ is nil, then just don't wait and return as failure.
|
||||
if (connection_ != nil) {
|
||||
[connection_ sendData:packet
|
||||
progressHandler:^(size_t count) {
|
||||
}
|
||||
completion:^(GNCMPayloadResult result) {
|
||||
// Make sure we haven't already reported completion before. This prevents a crash
|
||||
// where we try leaving a dispatch group more times than we entered it.
|
||||
// b/79095653.
|
||||
if (isComplete) {
|
||||
return;
|
||||
}
|
||||
isComplete = YES;
|
||||
sendResult = result;
|
||||
[condition lock];
|
||||
[condition signal];
|
||||
[condition unlock];
|
||||
}];
|
||||
[condition_ wait];
|
||||
[condition_ unlock];
|
||||
} else {
|
||||
sendResult = GNCMPayloadResultFailure;
|
||||
[condition_ unlock];
|
||||
}
|
||||
|
||||
if (sendResult == GNCMPayloadResultSuccess) {
|
||||
return {Exception::kSuccess};
|
||||
} else {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
}
|
||||
|
||||
Exception BleOutputStream::Flush() {
|
||||
// The write() function blocks until the data is received by the remote endpoint, so there's
|
||||
// nothing to do here.
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
Exception BleOutputStream::Close() {
|
||||
// Unblock pending write operation.
|
||||
[condition_ lock];
|
||||
connection_ = nil;
|
||||
[condition_ signal];
|
||||
[condition_ unlock];
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
/** BleSocket implementation.*/
|
||||
BleSocket::BleSocket(id<GNCMConnection> connection, BlePeripheral* peripheral)
|
||||
: input_stream_(new BleInputStream()),
|
||||
output_stream_(new BleOutputStream(connection)),
|
||||
peripheral_(peripheral) {}
|
||||
|
||||
BleSocket::~BleSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
bool BleSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception BleSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void BleSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
input_stream_->Close();
|
||||
output_stream_->Close();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** WifiLanServerSocket implementation. */
|
||||
BleServerSocket::~BleServerSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleSocket> BleServerSocket::Accept() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
while (!closed_ && pending_sockets_.empty()) {
|
||||
cond_.Wait(&mutex_);
|
||||
}
|
||||
// Return early if closed.
|
||||
if (closed_) return {};
|
||||
|
||||
auto remote_socket = std::move(pending_sockets_.extract(pending_sockets_.begin()).value());
|
||||
return std::move(remote_socket);
|
||||
}
|
||||
|
||||
bool BleServerSocket::Connect(std::unique_ptr<BleSocket> socket) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (closed_) {
|
||||
return false;
|
||||
}
|
||||
// add client socket to the pending list
|
||||
pending_sockets_.insert(std::move(socket));
|
||||
cond_.SignalAll();
|
||||
if (closed_) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BleServerSocket::SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
close_notifier_ = std::move(notifier);
|
||||
}
|
||||
|
||||
Exception BleServerSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return DoClose();
|
||||
}
|
||||
|
||||
Exception BleServerSocket::DoClose() {
|
||||
bool should_notify = !closed_;
|
||||
closed_ = true;
|
||||
if (should_notify) {
|
||||
cond_.SignalAll();
|
||||
if (close_notifier_) {
|
||||
auto notifier = std::move(close_notifier_);
|
||||
mutex_.Unlock();
|
||||
// Notifier may contain calls to public API, and may cause deadlock, if
|
||||
// mutex_ is held during the call.
|
||||
notifier();
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
/** BleMedium implementation. */
|
||||
BleMedium::BleMedium(::nearby::api::BluetoothAdapter& adapter)
|
||||
: adapter_(static_cast<BluetoothAdapter*>(&adapter)) {}
|
||||
|
||||
bool BleMedium::StartAdvertising(
|
||||
const BleAdvertisementData& advertising_data,
|
||||
::nearby::api::ble_v2::AdvertiseParameters advertise_set_parameters) {
|
||||
if (advertising_data.service_data.empty()) {
|
||||
return false;
|
||||
}
|
||||
const auto& service_uuid = advertising_data.service_data.begin()->first.Get16BitAsString();
|
||||
const ByteArray& service_data_bytes = advertising_data.service_data.begin()->second;
|
||||
|
||||
if (!peripheral_) {
|
||||
peripheral_ = [[GNCMBlePeripheral alloc] init];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool BleMedium::StopAdvertising() {
|
||||
peripheral_ = nil;
|
||||
return true;
|
||||
}
|
||||
std::unique_ptr<BleMedium::AdvertisingSession> BleMedium::StartAdvertising(
|
||||
const api::ble_v2::BleAdvertisementData& advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_parameters,
|
||||
BleMedium::AdvertisingCallback callback) {
|
||||
// TODO(hais): add real impl for iOs StartAdvertising
|
||||
return std::make_unique<AdvertisingSession>(AdvertisingSession{});
|
||||
}
|
||||
bool BleMedium::StartScanning(const Uuid& service_uuid, TxPowerLevel tx_power_level,
|
||||
ScanCallback scan_callback) {
|
||||
if (!central_) {
|
||||
central_ = [[GNCMBleCentral alloc] init];
|
||||
}
|
||||
|
||||
__block ScanCallback callback = std::move(scan_callback);
|
||||
[central_ startScanningWithServiceUUID:ObjCStringFromCppString(service_uuid.Get16BitAsString())
|
||||
scanResultHandler:^(NSString* peripheralID, NSData* serviceData) {
|
||||
BleAdvertisementData advertisement_data;
|
||||
advertisement_data.service_data = {{service_uuid, ByteArrayFromNSData(serviceData)}};
|
||||
BlePeripheral& peripheral = adapter_->GetPeripheral();
|
||||
peripheral.SetPeripheralId(CppStringFromObjCString(peripheralID));
|
||||
callback.advertisement_found_cb(peripheral, advertisement_data);
|
||||
}
|
||||
requestConnectionHandler:^(GNCMBleConnectionRequester connectionRequester) {
|
||||
BlePeripheral& peripheral = adapter_->GetPeripheral();
|
||||
peripheral.SetConnectionRequester(connectionRequester);
|
||||
}
|
||||
callbackQueue:callback_queue_];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BleMedium::StopScanning() {
|
||||
central_ = nil;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<BleMedium::ScanningSession> BleMedium::StartScanning(
|
||||
const Uuid& service_uuid, TxPowerLevel tx_power_level, BleMedium::ScanningCallback callback) {
|
||||
// TODO(hais): add real impl for windows StartScanning.
|
||||
return std::make_unique<ScanningSession>(ScanningSession{});
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
|
||||
api::ble_v2::ServerGattConnectionCallback callback) {
|
||||
if (!peripheral_) {
|
||||
peripheral_ = [[GNCMBlePeripheral alloc] init];
|
||||
}
|
||||
return std::make_unique<GattServer>(adapter_, peripheral_);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
|
||||
api::ble_v2::BlePeripheral& peripheral, TxPowerLevel tx_power_level,
|
||||
api::ble_v2::ClientGattConnectionCallback callback) {
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError* connectedError;
|
||||
BlePeripheral iosPeripheral = static_cast<BlePeripheral&>(peripheral);
|
||||
std::string peripheral_id = iosPeripheral.GetPeripheralId();
|
||||
[central_ connectGattServerWithPeripheralID:ObjCStringFromCppString(peripheral_id)
|
||||
gattConnectionResultHandler:^(NSError* _Nullable error) {
|
||||
connectedError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
|
||||
|
||||
if (connectedError) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<GattClient>(central_, peripheral_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
|
||||
const std::string& service_id) {
|
||||
auto server_socket = std::make_unique<BleServerSocket>();
|
||||
server_socket->SetCloseNotifier([this, service_id]() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
server_sockets_.erase(service_id);
|
||||
});
|
||||
absl::MutexLock lock(&mutex_);
|
||||
server_sockets_.insert({service_id, server_socket.get()});
|
||||
return server_socket;
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleSocket> BleMedium::Connect(const std::string& service_id,
|
||||
TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BlePeripheral& peripheral,
|
||||
CancellationFlag* cancellation_flag) {
|
||||
NSString* serviceID = ObjCStringFromCppString(service_id);
|
||||
__block std::unique_ptr<BleSocket> socket;
|
||||
__block BlePeripheral ios_peripheral = static_cast<BlePeripheral&>(peripheral);
|
||||
GNCMBleConnectionRequester connection_requester = ios_peripheral.GetConnectionRequester();
|
||||
if (!connection_requester) return {};
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
if (cancellation_flag->Cancelled()) {
|
||||
NSLog(@"[NEARBY] BLE Connect: Has been cancelled: service_id=%@", serviceID);
|
||||
dispatch_group_leave(group); // unblock
|
||||
return {};
|
||||
}
|
||||
|
||||
connection_requester(serviceID, ^(id<GNCMConnection> connection) {
|
||||
// If the connection wasn't successfully established, return a NULL socket.
|
||||
if (connection) {
|
||||
socket = std::make_unique<BleSocket>(connection, &ios_peripheral);
|
||||
}
|
||||
|
||||
dispatch_group_leave(group); // unblock
|
||||
return socket != nullptr
|
||||
? static_cast<BleInputStream&>(socket->GetInputStream()).GetConnectionHandlers()
|
||||
: nullptr;
|
||||
});
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
|
||||
// Send the (empty) intro packet, which the BLE advertiser is expecting.
|
||||
if (socket != nullptr) {
|
||||
socket->GetOutputStream().Write(ByteArray());
|
||||
}
|
||||
|
||||
return std::move(socket);
|
||||
}
|
||||
|
||||
bool BleMedium::IsExtendedAdvertisementsAvailable() { return false; }
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattServer::CreateCharacteristic(
|
||||
const Uuid& service_uuid, const Uuid& characteristic_uuid,
|
||||
api::ble_v2::GattCharacteristic::Permission permission,
|
||||
api::ble_v2::GattCharacteristic::Property property) {
|
||||
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
|
||||
.service_uuid = service_uuid,
|
||||
.permission = permission,
|
||||
.property = property};
|
||||
[peripheral_
|
||||
addCBServiceWithUUID:[CBUUID
|
||||
UUIDWithString:ObjCStringFromCppString(
|
||||
characteristic.service_uuid.Get16BitAsString())]];
|
||||
[peripheral_
|
||||
addCharacteristic:[[CBMutableCharacteristic alloc]
|
||||
initWithType:[CBUUID UUIDWithString:ObjCStringFromCppString(std::string(
|
||||
characteristic.uuid))]
|
||||
properties:PropertiesToCBProperties(characteristic.property)
|
||||
value:nil
|
||||
permissions:PermissionToCBPermissions(characteristic.permission)]];
|
||||
return characteristic;
|
||||
}
|
||||
|
||||
bool BleMedium::GattServer::UpdateCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic& characteristic, const nearby::ByteArray& value) {
|
||||
[peripheral_ updateValue:NSDataFromByteArray(value)
|
||||
forCharacteristic:[CBUUID UUIDWithString:ObjCStringFromCppString(
|
||||
std::string(characteristic.uuid))]];
|
||||
return true;
|
||||
}
|
||||
|
||||
absl::Status BleMedium::GattServer::NotifyCharacteristicChanged(
|
||||
const api::ble_v2::GattCharacteristic& characteristic, bool confirm,
|
||||
const ByteArray& new_value) {
|
||||
// no-op because client cannot request notifications.
|
||||
return absl::UnimplementedError("Unimplemented!");
|
||||
}
|
||||
|
||||
void BleMedium::GattServer::Stop() { [peripheral_ stopGATTService]; }
|
||||
|
||||
bool BleMedium::GattClient::DiscoverServiceAndCharacteristics(
|
||||
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
|
||||
// Discover all characteristics that may contain the advertisement.
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
gatt_characteristic_values_.clear();
|
||||
CBUUID* serviceUUID = [CBUUID UUIDWithString:ObjCStringFromCppString(std::string(service_uuid))];
|
||||
|
||||
absl::flat_hash_map<std::string, Uuid> gatt_characteristics;
|
||||
NSMutableArray<CBUUID*>* characteristicUUIDs =
|
||||
[NSMutableArray arrayWithCapacity:characteristic_uuids.size()];
|
||||
for (const auto& characteristic_uuid : characteristic_uuids) {
|
||||
[characteristicUUIDs addObject:[CBUUID UUIDWithString:ObjCStringFromCppString(
|
||||
std::string(characteristic_uuid))]];
|
||||
gatt_characteristics.insert({std::string(characteristic_uuid), characteristic_uuid});
|
||||
}
|
||||
|
||||
[central_ discoverGattService:serviceUUID
|
||||
gattCharacteristics:characteristicUUIDs
|
||||
peripheralID:ObjCStringFromCppString(peripheral_id_)
|
||||
gattDiscoverResultHandler:^(NSOrderedSet<CBCharacteristic*>* _Nullable cb_characteristics) {
|
||||
if (cb_characteristics != nil) {
|
||||
for (CBCharacteristic* cb_characteristic in cb_characteristics) {
|
||||
auto const& it = gatt_characteristics.find(
|
||||
CppStringFromObjCString(cb_characteristic.UUID.UUIDString));
|
||||
if (it == gatt_characteristics.end()) continue;
|
||||
|
||||
api::ble_v2::GattCharacteristic characteristic = {.uuid = it->second,
|
||||
.service_uuid = service_uuid};
|
||||
gatt_characteristic_values_.insert(
|
||||
{characteristic, ByteArrayFromNSData(cb_characteristic.value).string_data()});
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
|
||||
|
||||
if (gatt_characteristic_values_.empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattClient::GetCharacteristic(
|
||||
const Uuid& service_uuid, const Uuid& characteristic_uuid) {
|
||||
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
|
||||
.service_uuid = service_uuid};
|
||||
auto const it = gatt_characteristic_values_.find(characteristic);
|
||||
if (it == gatt_characteristic_values_.end()) {
|
||||
return absl::nullopt; // NOLINT
|
||||
}
|
||||
return it->first;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<std::string> BleMedium::GattClient::ReadCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic& characteristic) {
|
||||
auto const it = gatt_characteristic_values_.find(characteristic);
|
||||
if (it == gatt_characteristic_values_.end()) {
|
||||
return absl::nullopt; // NOLINT
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool BleMedium::GattClient::WriteCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic& characteristic, absl::string_view value,
|
||||
api::ble_v2::GattClient::WriteType write_type) {
|
||||
// No op.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BleMedium::GattClient::SetCharacteristicSubscription(
|
||||
const api::ble_v2::GattCharacteristic& characteristic, bool enable,
|
||||
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) {
|
||||
// No op since we can't write characteristics.
|
||||
return false;
|
||||
}
|
||||
|
||||
void BleMedium::GattClient::Disconnect() {
|
||||
[central_ disconnectGattServiceWithPeripheralID:ObjCStringFromCppString(peripheral_id_)];
|
||||
}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/uuid.h"
|
||||
|
||||
@class GNCBLEGATTClient;
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
class GattClient : public api::ble_v2::GattClient {
|
||||
public:
|
||||
explicit GattClient(GNCBLEGATTClient *gatt_client_);
|
||||
~GattClient() override = default;
|
||||
|
||||
// Discovers the specified characteristics of a service.
|
||||
//
|
||||
// This method blocks until discovery has finished.
|
||||
//
|
||||
// Returns whether or not discovery finished successfully.
|
||||
bool DiscoverServiceAndCharacteristics(const Uuid &service_uuid,
|
||||
const std::vector<Uuid> &characteristic_uuids) override;
|
||||
|
||||
// Retrieves a GATT characteristic.
|
||||
//
|
||||
// DiscoverServiceAndCharacteristics() must be called before this method to fetch all available
|
||||
// services and characteristics first.
|
||||
//
|
||||
// On success, returns the characteristic. On error, returns nullptr.
|
||||
std::optional<api::ble_v2::GattCharacteristic> GetCharacteristic(
|
||||
const Uuid &service_uuid, const Uuid &characteristic_uuid) override;
|
||||
|
||||
// Reads the requested characteristic from the associated remote device.
|
||||
//
|
||||
// On success, returns the characteristic's value. On error, returns nullptr.
|
||||
std::optional<std::string> ReadCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic &characteristic) override;
|
||||
|
||||
// Writes a given characteristic and its values to the associated remote device.
|
||||
//
|
||||
// Returns whether or not the write was successful.
|
||||
bool WriteCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
absl::string_view value,
|
||||
api::ble_v2::GattClient::WriteType type) override;
|
||||
|
||||
// Enable or disable notifications/indications for a given characteristic.
|
||||
//
|
||||
// Once notifications are enabled for a characteristic, on_characteristic_changed_cb will be
|
||||
// triggered if the remote device indicates that the given characteristic has changed.
|
||||
//
|
||||
// Returns whether or not the subscription was successful.
|
||||
bool SetCharacteristicSubscription(
|
||||
const api::ble_v2::GattCharacteristic &characteristic, bool enable,
|
||||
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) override;
|
||||
|
||||
// Disconnects an established connection, or cancels a connection attempt currently in progress.
|
||||
void Disconnect() override;
|
||||
|
||||
private:
|
||||
GNCBLEGATTClient *gatt_client_;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_gatt_client.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTClient.h"
|
||||
#import "internal/platform/implementation/apple/ble_utils.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
GattClient::GattClient(GNCBLEGATTClient *gatt_client) : gatt_client_(gatt_client) {}
|
||||
|
||||
bool GattClient::DiscoverServiceAndCharacteristics(const Uuid &service_uuid,
|
||||
const std::vector<Uuid> &characteristic_uuids) {
|
||||
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
|
||||
NSMutableArray<CBUUID *> *characteristics = [[NSMutableArray alloc] init];
|
||||
for (const auto &uuid : characteristic_uuids) {
|
||||
[characteristics addObject:CBUUID128FromCPP(uuid)];
|
||||
}
|
||||
|
||||
NSCondition *condition = [[NSCondition alloc] init];
|
||||
[condition lock];
|
||||
__block NSError *blockError = nil;
|
||||
[gatt_client_ discoverCharacteristicsWithUUIDs:characteristics
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(NSError *error) {
|
||||
[condition lock];
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error discovering characteristics: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
[condition signal];
|
||||
[condition unlock];
|
||||
}];
|
||||
[condition wait];
|
||||
[condition unlock];
|
||||
return blockError == nil;
|
||||
}
|
||||
|
||||
std::optional<api::ble_v2::GattCharacteristic> GattClient::GetCharacteristic(
|
||||
const Uuid &service_uuid, const Uuid &characteristic_uuid) {
|
||||
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
|
||||
CBUUID *characteristicUUID = CBUUID128FromCPP(characteristic_uuid);
|
||||
|
||||
NSCondition *condition = [[NSCondition alloc] init];
|
||||
[condition lock];
|
||||
__block GNCBLEGATTCharacteristic *blockCharacteristic = nil;
|
||||
__block NSError *blockError = nil;
|
||||
[gatt_client_ characteristicWithUUID:characteristicUUID
|
||||
serviceUUID:serviceUUID
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic, NSError *error) {
|
||||
[condition lock];
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error retrieving characteristic: %@", error);
|
||||
}
|
||||
blockCharacteristic = characteristic;
|
||||
blockError = error;
|
||||
[condition signal];
|
||||
[condition unlock];
|
||||
}];
|
||||
[condition wait];
|
||||
[condition unlock];
|
||||
if (blockCharacteristic == nil) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return CPPGATTCharacteristicFromObjC(blockCharacteristic);
|
||||
}
|
||||
|
||||
std::optional<std::string> GattClient::ReadCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic &characteristic) {
|
||||
NSCondition *condition = [[NSCondition alloc] init];
|
||||
[condition lock];
|
||||
__block NSData *blockValue = nil;
|
||||
__block NSError *blockError = nil;
|
||||
[gatt_client_ readValueForCharacteristic:ObjCGATTCharacteristicFromCPP(characteristic)
|
||||
completionHandler:^(NSData *value, NSError *error) {
|
||||
[condition lock];
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error reading characteristic: %@", error);
|
||||
}
|
||||
blockValue = value;
|
||||
blockError = error;
|
||||
[condition signal];
|
||||
[condition unlock];
|
||||
}];
|
||||
[condition wait];
|
||||
[condition unlock];
|
||||
if (blockValue == nil) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::string((const char *)blockValue.bytes, blockValue.length);
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
bool GattClient::WriteCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
absl::string_view value,
|
||||
api::ble_v2::GattClient::WriteType type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
bool GattClient::SetCharacteristicSubscription(
|
||||
const api::ble_v2::GattCharacteristic &characteristic, bool enable,
|
||||
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
void GattClient::Disconnect() {}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/uuid.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_peripheral.h"
|
||||
|
||||
@class GNCBLEGATTServer;
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
class GattServer : public api::ble_v2::GattServer {
|
||||
public:
|
||||
explicit GattServer(GNCBLEGATTServer *gatt_server_);
|
||||
~GattServer() override = default;
|
||||
|
||||
// Returns an empty BlePeripheral object.
|
||||
//
|
||||
// Use of this method should be avoided and its only purpose seems to be a check that the GATT
|
||||
// server is valid.
|
||||
api::ble_v2::BlePeripheral &GetBlePeripheral() override;
|
||||
|
||||
// Creates a characteristic and adds it to the GATT server under the given characteristic and
|
||||
// service UUIDs.
|
||||
//
|
||||
// Characteristics of the same service UUID will be put under one service rather than many
|
||||
// services with the same UUID.
|
||||
//
|
||||
// Returns no value upon error.
|
||||
std::optional<api::ble_v2::GattCharacteristic> CreateCharacteristic(
|
||||
const Uuid &service_uuid, const Uuid &characteristic_uuid,
|
||||
api::ble_v2::GattCharacteristic::Permission permission,
|
||||
api::ble_v2::GattCharacteristic::Property property) override;
|
||||
|
||||
// Updates a local characteristic with the provided value.
|
||||
//
|
||||
// Returns whether or not the update was successful.
|
||||
bool UpdateCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
const nearby::ByteArray &value) override;
|
||||
|
||||
// Send a notification or indication that a local characteristic has been updated.
|
||||
//
|
||||
// Returns an absl::Status indicating success or what went wrong.
|
||||
absl::Status NotifyCharacteristicChanged(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
bool confirm, const ByteArray &new_value) override;
|
||||
|
||||
// Stops a GATT server.
|
||||
void Stop() override;
|
||||
|
||||
private:
|
||||
GNCBLEGATTServer *gatt_server_;
|
||||
EmptyBlePeripheral peripheral_;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_gatt_server.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTServer.h"
|
||||
#import "internal/platform/implementation/apple/ble_utils.h"
|
||||
#import "internal/platform/implementation/apple/utils.h"
|
||||
#import "GoogleToolboxForMac/GTMLogger.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
GattServer::GattServer(GNCBLEGATTServer *gatt_server) : gatt_server_(gatt_server) {}
|
||||
|
||||
std::optional<api::ble_v2::GattCharacteristic> GattServer::CreateCharacteristic(
|
||||
const Uuid &service_uuid, const Uuid &characteristic_uuid,
|
||||
api::ble_v2::GattCharacteristic::Permission permission,
|
||||
api::ble_v2::GattCharacteristic::Property property) {
|
||||
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
|
||||
CBUUID *characteristicUUID = CBUUID128FromCPP(characteristic_uuid);
|
||||
CBAttributePermissions permissions = CBAttributePermissionsFromCPP(permission);
|
||||
CBCharacteristicProperties properties = CBCharacteristicPropertiesFromCPP(property);
|
||||
|
||||
NSCondition *condition = [[NSCondition alloc] init];
|
||||
[condition lock];
|
||||
__block GNCBLEGATTCharacteristic *blockCharacteristic = nil;
|
||||
[gatt_server_ createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:permissions
|
||||
properties:properties
|
||||
completionHandler:^(GNCBLEGATTCharacteristic *characteristic,
|
||||
NSError *error) {
|
||||
[condition lock];
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error creating characteristic: %@", error);
|
||||
}
|
||||
blockCharacteristic = characteristic;
|
||||
[condition signal];
|
||||
[condition unlock];
|
||||
}];
|
||||
[condition wait];
|
||||
[condition unlock];
|
||||
if (blockCharacteristic == nil) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return CPPGATTCharacteristicFromObjC(blockCharacteristic);
|
||||
}
|
||||
|
||||
bool GattServer::UpdateCharacteristic(const api::ble_v2::GattCharacteristic &characteristic,
|
||||
const nearby::ByteArray &value) {
|
||||
NSCondition *condition = [[NSCondition alloc] init];
|
||||
[condition lock];
|
||||
__block NSError *blockError = nil;
|
||||
[gatt_server_ updateCharacteristic:ObjCGATTCharacteristicFromCPP(characteristic)
|
||||
value:NSDataFromByteArray(value)
|
||||
completionHandler:^(NSError *error) {
|
||||
[condition lock];
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error updating characteristic: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
[condition signal];
|
||||
[condition unlock];
|
||||
}];
|
||||
[condition wait];
|
||||
[condition unlock];
|
||||
return blockError == nil;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
absl::Status GattServer::NotifyCharacteristicChanged(
|
||||
const api::ble_v2::GattCharacteristic &characteristic, bool confirm,
|
||||
const ByteArray &new_value) {
|
||||
return absl::UnimplementedError("");
|
||||
}
|
||||
|
||||
void GattServer::Stop() {
|
||||
[gatt_server_ stop];
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
api::ble_v2::BlePeripheral &GattServer::GetBlePeripheral() {
|
||||
return peripheral_;
|
||||
}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include "internal/platform/uuid.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_peripheral.h"
|
||||
#import "internal/platform/implementation/apple/ble_server_socket.h"
|
||||
#import "internal/platform/implementation/apple/bluetooth_adapter_v2.h"
|
||||
|
||||
@class GNCBLEMedium;
|
||||
@class GNSCentralManager;
|
||||
@class GNSPeripheralManager;
|
||||
@class GNSPeripheralServiceManager;
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
// The main BLE medium used inside of Nearby. This serves as the entry point for all BLE and GATT
|
||||
// related operations.
|
||||
class BleMedium : public api::ble_v2::BleMedium {
|
||||
public:
|
||||
BleMedium();
|
||||
~BleMedium() override = default;
|
||||
|
||||
// Async interface for StartAdvertising.
|
||||
//
|
||||
// Result status will be passed to start_advertising_result callback. To stop advertising, invoke
|
||||
// the stop_advertising callback in AdvertisingSession.
|
||||
//
|
||||
// Advertising must be stopped before attempting to start advertising again.
|
||||
std::unique_ptr<api::ble_v2::BleMedium::AdvertisingSession> StartAdvertising(
|
||||
const api::ble_v2::BleAdvertisementData &advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_set_parameters,
|
||||
api::ble_v2::BleMedium::AdvertisingCallback callback) override;
|
||||
|
||||
// Starts BLE advertising and returns whether or not it was successful.
|
||||
//
|
||||
// Advertising must be stopped before attempting to start advertising again.
|
||||
bool StartAdvertising(const api::ble_v2::BleAdvertisementData &advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_set_parameters) override;
|
||||
|
||||
// Stops advertising.
|
||||
//
|
||||
// Returns whether or not advertising was successfully stopped.
|
||||
bool StopAdvertising() override;
|
||||
|
||||
// Async interface for StartScanning.
|
||||
//
|
||||
// Result status will be passed to start_scanning_result callback on a private queue. To stop
|
||||
// scanning, invoke the stop_scanning callback in ScanningSession.
|
||||
//
|
||||
// Scanning must be stopped before attempting to start scanning again.
|
||||
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> StartScanning(
|
||||
const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BleMedium::ScanningCallback callback) override;
|
||||
|
||||
// Starts scanning and returns whether or not it was successful.
|
||||
//
|
||||
// Scanning must be stopped before attempting to start scanning again.
|
||||
bool StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BleMedium::ScanCallback callback) override;
|
||||
|
||||
// Stops scanning.
|
||||
//
|
||||
// Returns whether or not scanning was successfully stopped.
|
||||
bool StopScanning() override;
|
||||
|
||||
// TODO(b/290385712): ServerGattConnectionCallback methods are not yet implemented.
|
||||
//
|
||||
// Starts a GATT server. Returns a nullptr upon error.
|
||||
std::unique_ptr<api::ble_v2::GattServer> StartGattServer(
|
||||
api::ble_v2::ServerGattConnectionCallback callback) override;
|
||||
|
||||
// Connects to a GATT server and negotiates the specified connection parameters. Returns nullptr
|
||||
// upon error.
|
||||
//
|
||||
// The peripheral must outlive the GATT client or undefined behavior will occur. The peripheral
|
||||
// should not be modified by this method.
|
||||
std::unique_ptr<api::ble_v2::GattClient> ConnectToGattServer(
|
||||
api::ble_v2::BlePeripheral &peripheral, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::ClientGattConnectionCallback callback) override;
|
||||
|
||||
// Opens a BLE server socket based on service ID.
|
||||
//
|
||||
// On success, returns a new BleServerSocket. On error, returns nullptr.
|
||||
std::unique_ptr<api::ble_v2::BleServerSocket> OpenServerSocket(
|
||||
const std::string &service_id) override;
|
||||
|
||||
// TODO(b/290385712): cancellation_flag support is not yet implemented.
|
||||
//
|
||||
// Connects to a BLE peripheral.
|
||||
//
|
||||
// The peripheral must outlive the socket or undefined behavior will occur. The peripheral
|
||||
// should not be modified by this method.
|
||||
//
|
||||
// On success, returns a new BleSocket. On error, returns nullptr.
|
||||
std::unique_ptr<api::ble_v2::BleSocket> Connect(const std::string &service_id,
|
||||
api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BlePeripheral &peripheral,
|
||||
CancellationFlag *cancellation_flag) override;
|
||||
|
||||
// Returns whether the hardware supports BOTH advertising extensions and extended scans.
|
||||
//
|
||||
// This is currently always false for all Apple hardware.
|
||||
bool IsExtendedAdvertisementsAvailable() override;
|
||||
|
||||
// A peripheral cannot be retreived via MAC address on Apple platforms.
|
||||
//
|
||||
// This always returns false and does not call the callback.
|
||||
bool GetRemotePeripheral(const std::string &mac_address,
|
||||
api::ble_v2::BleMedium::GetRemotePeripheralCallback callback) override;
|
||||
|
||||
// Returns true if `id` refers to a known BLE peripheral and calls `callback` with a reference to
|
||||
// said peripheral that is only guaranteed to be available for the duration of the callback.
|
||||
// Otherwise, does not call the callback and returns false.
|
||||
bool GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId id,
|
||||
api::ble_v2::BleMedium::GetRemotePeripheralCallback callback) override;
|
||||
|
||||
private:
|
||||
void HandleAdvertisementFound(
|
||||
id<GNCPeripheral> peripheral, NSDictionary<CBUUID *, NSData *> *serviceData,
|
||||
absl::AnyInvocable<void(api::ble_v2::BlePeripheral &peripheral,
|
||||
api::ble_v2::BleAdvertisementData advertisement_data)>
|
||||
callback);
|
||||
|
||||
GNCBLEMedium *medium_;
|
||||
|
||||
absl::Mutex peripherals_mutex_;
|
||||
absl::flat_hash_map<api::ble_v2::BlePeripheral::UniqueId, std::unique_ptr<BlePeripheral>>
|
||||
peripherals_ ABSL_GUARDED_BY(peripherals_mutex_);
|
||||
|
||||
GNSPeripheralServiceManager *socketPeripheralServiceManager_;
|
||||
GNSPeripheralManager *socketPeripheralManager_;
|
||||
GNSCentralManager *socketCentralManager_;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,384 @@
|
||||
// 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/apple/ble_medium.h"
|
||||
#import "internal/platform/implementation/apple/utils.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "internal/platform/implementation/apple/ble_utils.h"
|
||||
#include "internal/platform/implementation/apple/utils.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
|
||||
#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/GNCBLEMedium.h"
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.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_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/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"
|
||||
|
||||
static NSString *const kWeaveServiceUUID = @"FEF3";
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
BleMedium::BleMedium() : medium_([[GNCBLEMedium alloc] init]) {}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleMedium::AdvertisingSession> BleMedium::StartAdvertising(
|
||||
const api::ble_v2::BleAdvertisementData &advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_set_parameters,
|
||||
api::ble_v2::BleMedium::AdvertisingCallback callback) {
|
||||
NSMutableDictionary<CBUUID *, NSData *> *serviceData =
|
||||
ObjCServiceDataFromCPP(advertising_data.service_data);
|
||||
|
||||
__block api::ble_v2::BleMedium::AdvertisingCallback blockCallback = std::move(callback);
|
||||
|
||||
[medium_ startAdvertisingData:serviceData
|
||||
completionHandler:^(NSError *error) {
|
||||
blockCallback.start_advertising_result(
|
||||
error == nil ? absl::OkStatus()
|
||||
: absl::InternalError(error.localizedDescription.UTF8String));
|
||||
}];
|
||||
|
||||
return std::make_unique<AdvertisingSession>(AdvertisingSession{.stop_advertising = [this] {
|
||||
return StopAdvertising() ? absl::OkStatus() : absl::InternalError("Failed to stop advertising");
|
||||
}});
|
||||
}
|
||||
|
||||
bool BleMedium::StartAdvertising(const api::ble_v2::BleAdvertisementData &advertising_data,
|
||||
api::ble_v2::AdvertiseParameters advertise_set_parameters) {
|
||||
NSMutableDictionary<CBUUID *, NSData *> *serviceData =
|
||||
ObjCServiceDataFromCPP(advertising_data.service_data);
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError *blockError = nil;
|
||||
[medium_ startAdvertisingData:serviceData
|
||||
completionHandler:^(NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Failed to start advertising: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
return blockError == nil;
|
||||
}
|
||||
|
||||
bool BleMedium::StopAdvertising() {
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError *blockError = nil;
|
||||
[medium_ stopAdvertisingWithCompletionHandler:^(NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Failed to stop advertising: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
return blockError == nil;
|
||||
}
|
||||
|
||||
void BleMedium::HandleAdvertisementFound(
|
||||
id<GNCPeripheral> peripheral, NSDictionary<CBUUID *, NSData *> *serviceData,
|
||||
absl::AnyInvocable<void(api::ble_v2::BlePeripheral &peripheral,
|
||||
api::ble_v2::BleAdvertisementData advertisement_data)>
|
||||
callback) {
|
||||
absl::MutexLock lock(&peripherals_mutex_);
|
||||
[socketCentralManager_ retrievePeripheralWithIdentifier:peripheral.identifier
|
||||
advertisementData:@{}];
|
||||
|
||||
api::ble_v2::BleAdvertisementData data;
|
||||
for (CBUUID *key in serviceData.allKeys) {
|
||||
data.service_data[CPPUUIDFromObjC(key)] = ByteArrayFromNSData(serviceData[key]);
|
||||
}
|
||||
|
||||
// Add the peripheral to the map if we haven't discovered it yet.
|
||||
auto ble_peripheral = std::make_unique<BlePeripheral>(peripheral);
|
||||
auto unique_id = ble_peripheral->GetUniqueId();
|
||||
auto it = peripherals_.find(unique_id);
|
||||
if (it == peripherals_.end()) {
|
||||
peripherals_[unique_id] = std::move(ble_peripheral);
|
||||
}
|
||||
callback(*peripherals_[unique_id], data);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> BleMedium::StartScanning(
|
||||
const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BleMedium::ScanningCallback callback) {
|
||||
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
|
||||
__block api::ble_v2::BleMedium::ScanningCallback blockCallback = std::move(callback);
|
||||
|
||||
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
|
||||
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
|
||||
|
||||
[medium_ startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData) {
|
||||
HandleAdvertisementFound(peripheral, serviceData,
|
||||
std::move(blockCallback.advertisement_found_cb));
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
blockCallback.start_scanning_result(
|
||||
error == nil ? absl::OkStatus()
|
||||
: absl::InternalError(error.localizedDescription.UTF8String));
|
||||
}];
|
||||
|
||||
return std::make_unique<ScanningSession>(ScanningSession{.stop_scanning = [this] {
|
||||
return StopScanning() ? absl::OkStatus() : absl::InternalError("Failed to stop scanning");
|
||||
}});
|
||||
}
|
||||
|
||||
bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BleMedium::ScanCallback callback) {
|
||||
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
|
||||
__block api::ble_v2::BleMedium::ScanCallback blockCallback = std::move(callback);
|
||||
|
||||
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
|
||||
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError *blockError = nil;
|
||||
[medium_ startScanningForService:serviceUUID
|
||||
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
|
||||
NSDictionary<CBUUID *, NSData *> *serviceData) {
|
||||
HandleAdvertisementFound(peripheral, serviceData,
|
||||
std::move(blockCallback.advertisement_found_cb));
|
||||
}
|
||||
completionHandler:^(NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Failed to start scanning: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
return blockError == nil;
|
||||
}
|
||||
|
||||
bool BleMedium::StopScanning() {
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError *blockError = nil;
|
||||
[medium_ stopScanningWithCompletionHandler:^(NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Failed to stop scanning: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
return blockError == nil;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Add implementation that calls ServerGattConnectionCallback methods.
|
||||
std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
|
||||
api::ble_v2::ServerGattConnectionCallback callback) {
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block GNCBLEGATTServer *blockServer = nil;
|
||||
[medium_ startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error starting GATT server: %@", error);
|
||||
}
|
||||
blockServer = server;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
if (!blockServer) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<GattServer>(blockServer);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
|
||||
api::ble_v2::BlePeripheral &peripheral, api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::ClientGattConnectionCallback callback) {
|
||||
// Check that the @c api::ble_v2::BlePeripheral is a @c nearby::apple::BlePeripheral and not a
|
||||
// @c nearby::apple::EmptyBlePeripheral instance, so we can retreive the CBPeripheral object.
|
||||
BlePeripheral *non_empty_peripheral = dynamic_cast<BlePeripheral *>(&peripheral);
|
||||
if (non_empty_peripheral == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
__block api::ble_v2::ClientGattConnectionCallback blockCallback = std::move(callback);
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block GNCBLEGATTClient *blockClient = nil;
|
||||
[medium_ connectToGATTServerForPeripheral:non_empty_peripheral->GetPeripheral()
|
||||
disconnectionHandler:^(void) {
|
||||
blockCallback.disconnected_cb();
|
||||
}
|
||||
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Error connecting to GATT server: %@", error);
|
||||
}
|
||||
blockClient = client;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
if (!blockClient) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<GattClient>(blockClient);
|
||||
}
|
||||
|
||||
// TODO(b/293336684): Old Weave code that need to be deleted once shared Weave is complete.
|
||||
std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
|
||||
const std::string &service_id) {
|
||||
__block auto server_socket = std::make_unique<BleServerSocket>();
|
||||
socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc]
|
||||
initWithBleServiceUUID:[CBUUID UUIDWithString:kWeaveServiceUUID]
|
||||
addPairingCharacteristic:NO
|
||||
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
|
||||
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
|
||||
GNCMBleConnection *connection =
|
||||
[GNCMBleConnection connectionWithSocket:socket
|
||||
serviceID:@(service_id.c_str())
|
||||
expectedIntroPacket:YES
|
||||
callbackQueue:dispatch_get_main_queue()];
|
||||
|
||||
auto socket = std::make_unique<BleSocket>(connection);
|
||||
connection.connectionHandlers = socket->GetInputStream().GetConnectionHandlers();
|
||||
server_socket->Connect(std::move(socket));
|
||||
});
|
||||
return YES;
|
||||
}];
|
||||
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
|
||||
restoreIdentifier:nil];
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError *blockError = nil;
|
||||
[socketPeripheralManager_ addPeripheralServiceManager:socketPeripheralServiceManager_
|
||||
bleServiceAddedCompletion:^(NSError *error) {
|
||||
if (error != nil) {
|
||||
GTMLoggerError(@"Failed to add Weave service: %@", error);
|
||||
}
|
||||
blockError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
[socketPeripheralManager_ start];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
if (blockError != nil) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::move(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,
|
||||
api::ble_v2::TxPowerLevel tx_power_level,
|
||||
api::ble_v2::BlePeripheral &peripheral,
|
||||
CancellationFlag *cancellation_flag) {
|
||||
// Check that the @c api::ble_v2::BlePeripheral is a @c nearby::apple::BlePeripheral and not a
|
||||
// @c nearby::apple::EmptyBlePeripheral instance, so we can retreive the CBPeripheral object.
|
||||
BlePeripheral *non_empty_peripheral = dynamic_cast<BlePeripheral *>(&peripheral);
|
||||
if (non_empty_peripheral == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GNSCentralPeerManager *updatedCentralPeerManager = [socketCentralManager_
|
||||
retrieveCentralPeerWithIdentifier:non_empty_peripheral->GetPeripheral().identifier];
|
||||
if (!updatedCentralPeerManager) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block std::unique_ptr<BleSocket> socket;
|
||||
[updatedCentralPeerManager
|
||||
socketWithPairingCharacteristic:NO
|
||||
completion:^(GNSSocket *nssocket, NSError *error) {
|
||||
if (error) {
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
return;
|
||||
}
|
||||
GNCMWaitForConnection(nssocket, ^(BOOL didConnect) {
|
||||
if (!didConnect) {
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
return;
|
||||
}
|
||||
|
||||
GNCMBleConnection *connection = [GNCMBleConnection
|
||||
connectionWithSocket:nssocket
|
||||
serviceID:@(service_id.c_str())
|
||||
expectedIntroPacket:NO
|
||||
callbackQueue:dispatch_get_main_queue()];
|
||||
socket =
|
||||
std::make_unique<BleSocket>(connection, non_empty_peripheral);
|
||||
connection.connectionHandlers =
|
||||
socket->GetInputStream().GetConnectionHandlers();
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
});
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
if (socket == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Send the (empty) intro packet, which the BLE advertiser is expecting.
|
||||
socket->GetOutputStream().Write(ByteArray());
|
||||
return std::move(socket);
|
||||
}
|
||||
|
||||
bool BleMedium::IsExtendedAdvertisementsAvailable() {
|
||||
return [medium_ supportsExtendedAdvertisements];
|
||||
}
|
||||
|
||||
bool BleMedium::GetRemotePeripheral(const std::string &mac_address,
|
||||
api::ble_v2::BleMedium::GetRemotePeripheralCallback callback) {
|
||||
// Apple does not expose MAC address information, so we cannot retreive a peripheral via MAC
|
||||
// address.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BleMedium::GetRemotePeripheral(api::ble_v2::BlePeripheral::UniqueId unique_id,
|
||||
api::ble_v2::BleMedium::GetRemotePeripheralCallback callback) {
|
||||
BlePeripheral *peripheral;
|
||||
{
|
||||
absl::MutexLock lock(&peripherals_mutex_);
|
||||
auto it = peripherals_.find(unique_id);
|
||||
if (it == peripherals_.end()) {
|
||||
return false;
|
||||
}
|
||||
peripheral = it->second.get();
|
||||
if (peripheral == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// We need to unlock before calling the callback, otherwise we will deadlock.
|
||||
callback(*peripheral);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
@protocol GNCPeripheral;
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
// An empty peripheral.
|
||||
//
|
||||
// Apple APIs do not expose a peripheral's MAC address and does not provide a
|
||||
// way to directly connect to a given MAC address. Instead a connection can only
|
||||
// be made using a CoreBluetooth peripheral object. Many times a CoreBluetooth
|
||||
// peripheral is not available, namely, when the remote device is a central. For
|
||||
// these cases, an EmptyBlePeripheral should be used.
|
||||
class EmptyBlePeripheral : public api::ble_v2::BlePeripheral {
|
||||
public:
|
||||
EmptyBlePeripheral();
|
||||
~EmptyBlePeripheral() override = default;
|
||||
|
||||
// Returns an empty string.
|
||||
std::string GetAddress() const override;
|
||||
|
||||
// Returns an immutable unique identifier. The identifier does not change when
|
||||
// the peripheral's address is rotated.
|
||||
api::ble_v2::BlePeripheral::UniqueId GetUniqueId() const override;
|
||||
|
||||
private:
|
||||
api::ble_v2::BlePeripheral::UniqueId unique_id_;
|
||||
};
|
||||
|
||||
// A wrapper of a CoreBluetooth peripheral object. This can be used to uniquely
|
||||
// identify a peripheral and connect to its GATT server.
|
||||
//
|
||||
// Many times a CoreBluetooth peripheral is not available, namely, when the
|
||||
// remote device is a central. For these cases, an EmptyBlePeripheral should be
|
||||
// used instead.
|
||||
class BlePeripheral : public api::ble_v2::BlePeripheral {
|
||||
public:
|
||||
explicit BlePeripheral(id<GNCPeripheral> peripheral);
|
||||
~BlePeripheral() override = default;
|
||||
|
||||
// Returns an empty string.
|
||||
std::string GetAddress() const override;
|
||||
|
||||
// Returns an immutable unique identifier. The identifier does not change when
|
||||
// the peripheral's address is rotated.
|
||||
api::ble_v2::BlePeripheral::UniqueId GetUniqueId() const override;
|
||||
|
||||
// Returns the CoreBluetooth peripheral object.
|
||||
id<GNCPeripheral> GetPeripheral() const;
|
||||
|
||||
private:
|
||||
id<GNCPeripheral> peripheral_;
|
||||
api::ble_v2::BlePeripheral::UniqueId unique_id_;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_peripheral.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/prng.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
#pragma mark - EmptyBlePeripheral
|
||||
|
||||
EmptyBlePeripheral::EmptyBlePeripheral() : unique_id_(Prng().NextInt64()) {}
|
||||
|
||||
std::string EmptyBlePeripheral::GetAddress() const { return ""; }
|
||||
|
||||
api::ble_v2::BlePeripheral::UniqueId EmptyBlePeripheral::GetUniqueId() const { return unique_id_; }
|
||||
|
||||
#pragma mark - BlePeripheral
|
||||
|
||||
BlePeripheral::BlePeripheral(id<GNCPeripheral> peripheral)
|
||||
: peripheral_(peripheral), unique_id_(peripheral.identifier.hash) {}
|
||||
|
||||
std::string BlePeripheral::GetAddress() const { return ""; }
|
||||
|
||||
api::ble_v2::BlePeripheral::UniqueId BlePeripheral::GetUniqueId() const { return unique_id_; }
|
||||
|
||||
id<GNCPeripheral> BlePeripheral::GetPeripheral() const { return peripheral_; }
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
// TODO(b/293336684): Remove this file when shared Weave is complete.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_socket.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
// A BLE server socket for listening for incoming Weave sockets.
|
||||
class BleServerSocket : public api::ble_v2::BleServerSocket {
|
||||
public:
|
||||
BleServerSocket() = default;
|
||||
~BleServerSocket() override;
|
||||
|
||||
// Wait for an available socket.
|
||||
//
|
||||
// Blocks until either:
|
||||
// - at least one incoming connection request is available, or
|
||||
// - ServerSocket is closed.
|
||||
//
|
||||
// On success, returns connected socket, ready to exchange data or nullptr on
|
||||
// error. Once error is reported, it is permanent, and ServerSocket must be
|
||||
// closed.
|
||||
std::unique_ptr<api::ble_v2::BleSocket> Accept() override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Close the server socket.
|
||||
//
|
||||
// Returns Exception::kIo on error, otherwise Exception::kSuccess.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool Connect(std::unique_ptr<BleSocket> socket) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
void SetCloseNotifier(absl::AnyInvocable<void()> notifier)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
absl::CondVar cond_;
|
||||
absl::flat_hash_set<std::unique_ptr<BleSocket>> pending_sockets_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
absl::AnyInvocable<void()> close_notifier_ ABSL_GUARDED_BY(mutex_);
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// TODO(b/293336684): Remove this file when shared Weave is complete.
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_server_socket.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
BleServerSocket::~BleServerSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleSocket> BleServerSocket::Accept() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
while (!closed_ && pending_sockets_.empty()) {
|
||||
cond_.Wait(&mutex_);
|
||||
}
|
||||
if (closed_) return {};
|
||||
|
||||
std::unique_ptr<BleSocket> remote_socket =
|
||||
std::move(pending_sockets_.extract(pending_sockets_.begin()).value());
|
||||
return std::move(remote_socket);
|
||||
}
|
||||
|
||||
bool BleServerSocket::Connect(std::unique_ptr<BleSocket> socket) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (closed_) {
|
||||
return false;
|
||||
}
|
||||
pending_sockets_.insert(std::move(socket));
|
||||
cond_.SignalAll();
|
||||
return !closed_;
|
||||
}
|
||||
|
||||
void BleServerSocket::SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
close_notifier_ = std::move(notifier);
|
||||
}
|
||||
|
||||
Exception BleServerSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return DoClose();
|
||||
}
|
||||
|
||||
Exception BleServerSocket::DoClose() {
|
||||
bool should_notify = !closed_;
|
||||
closed_ = true;
|
||||
if (should_notify) {
|
||||
cond_.SignalAll();
|
||||
if (close_notifier_) {
|
||||
auto notifier = std::move(close_notifier_);
|
||||
mutex_.Unlock();
|
||||
// Notifier may contain calls to public API, and may cause deadlock, if
|
||||
// mutex_ is held during the call.
|
||||
notifier();
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
// TODO(b/293336684): Remove this file when shared Weave is complete.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_peripheral.h"
|
||||
|
||||
@class GNCMConnectionHandlers;
|
||||
@protocol GNCMConnection;
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
// A readable stream of bytes.
|
||||
class BleInputStream : public InputStream {
|
||||
public:
|
||||
BleInputStream();
|
||||
~BleInputStream() override;
|
||||
|
||||
// Reads at most `size` bytes from the input stream.
|
||||
//
|
||||
// Returns an empty byte array on end of file, or Exception::kIo on error.
|
||||
ExceptionOr<ByteArray> Read(std::int64_t size) override;
|
||||
|
||||
// Closes the stream preventing further reads.
|
||||
//
|
||||
// Returns Exception::kIo on error, otherwise Exception::kSuccess.
|
||||
Exception Close() override;
|
||||
|
||||
GNCMConnectionHandlers *GetConnectionHandlers() { return connectionHandlers_; }
|
||||
|
||||
private:
|
||||
GNCMConnectionHandlers *connectionHandlers_;
|
||||
NSMutableArray<NSData *> *newDataPackets_;
|
||||
NSMutableData *accumulatedData_;
|
||||
NSCondition *condition_;
|
||||
};
|
||||
|
||||
// A writable stream of bytes.
|
||||
class BleOutputStream : public OutputStream {
|
||||
public:
|
||||
explicit BleOutputStream(id<GNCMConnection> connection)
|
||||
: connection_(connection), condition_([[NSCondition alloc] init]) {}
|
||||
~BleOutputStream() override;
|
||||
|
||||
// Write the provided bytes to the output stream.
|
||||
//
|
||||
// Returns Exception::kIo on error, otherwise Exception::kSuccess.
|
||||
Exception Write(const ByteArray &data) override;
|
||||
|
||||
// no-op
|
||||
//
|
||||
// Always returns Exception::kSuccess.
|
||||
Exception Flush() override;
|
||||
|
||||
// Closes the stream preventing further writes.
|
||||
//
|
||||
// Returns Exception::kIo on error, otherwise Exception::kSuccess.
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
id<GNCMConnection> connection_;
|
||||
NSCondition *condition_;
|
||||
};
|
||||
|
||||
// A BLE Weave socket.
|
||||
class BleSocket : public api::ble_v2::BleSocket {
|
||||
public:
|
||||
explicit BleSocket(id<GNCMConnection> connection);
|
||||
|
||||
// The peripheral used to create the socket must outlive the socket or undefined behavior will
|
||||
// occur.
|
||||
BleSocket(id<GNCMConnection> connection, api::ble_v2::BlePeripheral *peripheral);
|
||||
~BleSocket() override;
|
||||
|
||||
// Returns the InputStream of the BleSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the BleSocket object is destroyed.
|
||||
BleInputStream &GetInputStream() override { return *input_stream_; }
|
||||
|
||||
// Returns the OutputStream of the BleSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the BleSocket object is destroyed.
|
||||
BleOutputStream &GetOutputStream() override { return *output_stream_; }
|
||||
|
||||
// Returns Exception::kIo on error, otherwise Exception::kSuccess.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns valid BlePeripheral pointer if there is a connection, and
|
||||
// nullptr otherwise.
|
||||
api::ble_v2::BlePeripheral *GetRemotePeripheral() override { return peripheral_; }
|
||||
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
std::unique_ptr<BleInputStream> input_stream_;
|
||||
std::unique_ptr<BleOutputStream> output_stream_;
|
||||
api::ble_v2::BlePeripheral *peripheral_;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/apple/ble_socket.h"
|
||||
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h"
|
||||
#import "internal/platform/implementation/apple/ble_peripheral.h"
|
||||
#import "internal/platform/implementation/apple/ble_utils.h"
|
||||
#import "internal/platform/implementation/apple/utils.h"
|
||||
|
||||
// TODO(b/293336684): Remove this file when shared Weave is complete.
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
#pragma mark - BleInputStream
|
||||
|
||||
BleInputStream::BleInputStream()
|
||||
: newDataPackets_([NSMutableArray array]),
|
||||
accumulatedData_([NSMutableData data]),
|
||||
condition_([[NSCondition alloc] init]) {
|
||||
// Create the handlers of incoming data from the remote endpoint.
|
||||
connectionHandlers_ = [GNCMConnectionHandlers
|
||||
payloadHandler:^(NSData *data) {
|
||||
[condition_ lock];
|
||||
// Add the incoming data to the data packet array to be processed in read() below.
|
||||
[newDataPackets_ addObject:data];
|
||||
[condition_ broadcast];
|
||||
[condition_ unlock];
|
||||
}
|
||||
disconnectedHandler:^{
|
||||
[condition_ lock];
|
||||
// Release the data packet array, meaning the stream has been closed or severed.
|
||||
newDataPackets_ = nil;
|
||||
[condition_ broadcast];
|
||||
[condition_ unlock];
|
||||
}];
|
||||
}
|
||||
|
||||
BleInputStream::~BleInputStream() {
|
||||
NSCAssert(!newDataPackets_, @"BleInputStream not closed before destruction");
|
||||
}
|
||||
|
||||
ExceptionOr<ByteArray> BleInputStream::Read(std::int64_t size) {
|
||||
// Block until either (a) the connection has been closed, (b) we have enough data to return.
|
||||
NSData *dataToReturn;
|
||||
[condition_ lock];
|
||||
while (true) {
|
||||
// Check if the stream has been closed or severed.
|
||||
if (!newDataPackets_) break;
|
||||
|
||||
if (newDataPackets_.count > 0) {
|
||||
// Add the packet data to the accumulated data.
|
||||
for (NSData *data in newDataPackets_) {
|
||||
if (data.length > 0) {
|
||||
[accumulatedData_ appendData:data];
|
||||
}
|
||||
}
|
||||
[newDataPackets_ removeAllObjects];
|
||||
}
|
||||
|
||||
if ((size == -1) && (accumulatedData_.length > 0)) {
|
||||
// Return all of the data.
|
||||
dataToReturn = accumulatedData_;
|
||||
accumulatedData_ = [NSMutableData data];
|
||||
break;
|
||||
} else if (accumulatedData_.length > 0) {
|
||||
// Return up to |size| bytes of the data.
|
||||
std::int64_t sizeToReturn = (accumulatedData_.length < size) ? accumulatedData_.length : size;
|
||||
NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn);
|
||||
dataToReturn = [accumulatedData_ subdataWithRange:range];
|
||||
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
|
||||
break;
|
||||
}
|
||||
|
||||
[condition_ wait];
|
||||
}
|
||||
[condition_ unlock];
|
||||
|
||||
if (dataToReturn) {
|
||||
NSLog(@"[NEARBY] Input stream: Received data of size: %lu", (unsigned long)dataToReturn.length);
|
||||
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
|
||||
} else {
|
||||
return ExceptionOr<ByteArray>{Exception::kIo};
|
||||
}
|
||||
}
|
||||
|
||||
Exception BleInputStream::Close() {
|
||||
// Unblock pending read operation.
|
||||
[condition_ lock];
|
||||
newDataPackets_ = nil;
|
||||
[condition_ broadcast];
|
||||
[condition_ unlock];
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
#pragma mark - BleOutputStream
|
||||
|
||||
BleOutputStream::~BleOutputStream() {
|
||||
NSCAssert(!connection_, @"BleOutputStream not closed before destruction");
|
||||
}
|
||||
|
||||
Exception BleOutputStream::Write(const ByteArray &data) {
|
||||
[condition_ lock];
|
||||
NSLog(@"[NEARBY] Sending data of size: %lu", NSDataFromByteArray(data).length);
|
||||
|
||||
if (!connection_) {
|
||||
[condition_ unlock];
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
NSMutableData *packet = [NSMutableData dataWithData:NSDataFromByteArray(data)];
|
||||
|
||||
// Send the data, blocking until the completion handler is called.
|
||||
__block bool isComplete = NO;
|
||||
__block GNCMPayloadResult sendResult = GNCMPayloadResultFailure;
|
||||
NSCondition *condition = condition_; // don't capture |this| in completion
|
||||
|
||||
[connection_ sendData:packet
|
||||
progressHandler:^(size_t count) {
|
||||
}
|
||||
completion:^(GNCMPayloadResult result) {
|
||||
[condition lock];
|
||||
// Make sure we haven't already reported completion before. This prevents a crash
|
||||
// where we try leaving a dispatch group more times than we entered it.
|
||||
// b/79095653.
|
||||
if (isComplete) {
|
||||
[condition unlock];
|
||||
return;
|
||||
}
|
||||
isComplete = YES;
|
||||
sendResult = result;
|
||||
[condition broadcast];
|
||||
[condition unlock];
|
||||
}];
|
||||
|
||||
while (connection_ && !isComplete) {
|
||||
[condition_ wait];
|
||||
}
|
||||
|
||||
if (sendResult == GNCMPayloadResultSuccess) {
|
||||
[condition_ unlock];
|
||||
return {Exception::kSuccess};
|
||||
} else {
|
||||
[condition_ unlock];
|
||||
return {Exception::kIo};
|
||||
}
|
||||
}
|
||||
|
||||
Exception BleOutputStream::Flush() {
|
||||
// The write() function blocks until the data is received by the remote endpoint, so there's
|
||||
// nothing to do here.
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
Exception BleOutputStream::Close() {
|
||||
// Unblock pending write operation.
|
||||
[condition_ lock];
|
||||
connection_ = nil;
|
||||
[condition_ broadcast];
|
||||
[condition_ unlock];
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
#pragma mark - BleSocket
|
||||
|
||||
BleSocket::BleSocket(id<GNCMConnection> connection)
|
||||
: input_stream_(new BleInputStream()),
|
||||
output_stream_(new BleOutputStream(connection)),
|
||||
peripheral_(new EmptyBlePeripheral()) {}
|
||||
|
||||
BleSocket::BleSocket(id<GNCMConnection> connection, api::ble_v2::BlePeripheral *peripheral)
|
||||
: input_stream_(new BleInputStream()),
|
||||
output_stream_(new BleOutputStream(connection)),
|
||||
peripheral_(peripheral) {}
|
||||
|
||||
BleSocket::~BleSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
bool BleSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception BleSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void BleSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
input_stream_->Close();
|
||||
output_stream_->Close();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -56,6 +56,9 @@ CBCharacteristicProperties CBCharacteristicPropertiesFromCPP(
|
||||
/** Converts a C++ characteristic to an Objective-C characteristic. */
|
||||
GNCBLEGATTCharacteristic *ObjCGATTCharacteristicFromCPP(const api::ble_v2::GattCharacteristic &c);
|
||||
|
||||
NSMutableDictionary<CBUUID *, NSData *> *ObjCServiceDataFromCPP(
|
||||
const absl::flat_hash_map<Uuid, nearby::ByteArray> &sd);
|
||||
|
||||
/** Converts a 16 or 128 bit CoreBluetooth UUID to a C++ UUID. */
|
||||
Uuid CPPUUIDFromObjC(CBUUID *uuid);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <string>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h"
|
||||
#import "internal/platform/implementation/apple/utils.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
@@ -98,6 +99,17 @@ GNCBLEGATTCharacteristic *ObjCGATTCharacteristicFromCPP(const GattCharacteristic
|
||||
properties:properties];
|
||||
}
|
||||
|
||||
NSMutableDictionary<CBUUID *, NSData *> *ObjCServiceDataFromCPP(
|
||||
const absl::flat_hash_map<Uuid, nearby::ByteArray> &sd) {
|
||||
NSMutableDictionary<CBUUID *, NSData *> *serviceData = [NSMutableDictionary dictionary];
|
||||
for (const auto &pair : sd) {
|
||||
CBUUID *key = CBUUID16FromCPP(pair.first);
|
||||
NSData *data = NSDataFromByteArray(pair.second);
|
||||
[serviceData setObject:data forKey:key];
|
||||
}
|
||||
return serviceData;
|
||||
}
|
||||
|
||||
Uuid CPPUUIDFromObjC(CBUUID *uuid) {
|
||||
NSString *uuidString = uuid.UUIDString;
|
||||
if (uuidString.length == 4) {
|
||||
|
||||
@@ -1,108 +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.
|
||||
|
||||
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLUETOOTH_ADAPTER_H_
|
||||
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLUETOOTH_ADAPTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleCentral.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include "internal/platform/prng.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
class BluetoothAdapter;
|
||||
|
||||
// Concrete BlePeripheral implementation.
|
||||
class BlePeripheral : public api::ble_v2::BlePeripheral {
|
||||
public:
|
||||
std::string GetAddress() const override;
|
||||
|
||||
api::ble_v2::BlePeripheral::UniqueId GetUniqueId() const override {
|
||||
return unique_id_;
|
||||
}
|
||||
|
||||
std::string GetPeripheralId() const { return peripheral_id_; }
|
||||
|
||||
void SetPeripheralId(const std::string& peripheral_id) {
|
||||
peripheral_id_ = peripheral_id;
|
||||
}
|
||||
|
||||
void SetConnectionRequester(GNCMBleConnectionRequester connection_requester) {
|
||||
connection_requester_ = connection_requester;
|
||||
}
|
||||
|
||||
GNCMBleConnectionRequester GetConnectionRequester() {
|
||||
return connection_requester_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Only BluetoothAdapter may instantiate BlePeripheral.
|
||||
friend class BluetoothAdapter;
|
||||
|
||||
explicit BlePeripheral(BluetoothAdapter* adapter) : adapter_(*adapter) {
|
||||
unique_id_ = Prng().NextInt64();
|
||||
}
|
||||
|
||||
BluetoothAdapter& adapter_;
|
||||
std::string peripheral_id_;
|
||||
GNCMBleConnectionRequester connection_requester_;
|
||||
api::ble_v2::BlePeripheral::UniqueId unique_id_;
|
||||
};
|
||||
|
||||
// Concrete BluetoothAdapter implementation.
|
||||
class BluetoothAdapter : public api::BluetoothAdapter {
|
||||
public:
|
||||
using Status = api::BluetoothAdapter::Status;
|
||||
using ScanMode = api::BluetoothAdapter::ScanMode;
|
||||
|
||||
~BluetoothAdapter() override { SetStatus(Status::kDisabled); }
|
||||
|
||||
bool SetStatus(Status status) override {
|
||||
enabled_ = status == Status::kEnabled;
|
||||
return true;
|
||||
}
|
||||
bool IsEnabled() const override { return enabled_; }
|
||||
ScanMode GetScanMode() const override { return mode_; }
|
||||
bool SetScanMode(ScanMode mode) override { return false; }
|
||||
std::string GetName() const override { return name_; }
|
||||
bool SetName(absl::string_view name) {
|
||||
return SetName(name, /* persist= */ true);
|
||||
}
|
||||
bool SetName(absl::string_view name, bool persist) override {
|
||||
name_ = std::string(name);
|
||||
return true;
|
||||
}
|
||||
std::string GetMacAddress() const override { return mac_address_; }
|
||||
void SetMacAddress(absl::string_view mac_address) {
|
||||
mac_address_ = std::string(mac_address);
|
||||
}
|
||||
|
||||
BlePeripheral& GetPeripheral() { return peripheral_; }
|
||||
|
||||
private:
|
||||
BlePeripheral peripheral_{this};
|
||||
ScanMode mode_ = ScanMode::kNone;
|
||||
std::string name_;
|
||||
std::string mac_address_;
|
||||
bool enabled_ = true;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_BLUETOOTH_ADAPTER_H_
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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.
|
||||
|
||||
// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly
|
||||
// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation
|
||||
// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and
|
||||
// Objective-C++ scores. Oddly, "#import <Foundation/Foundation.h>" does not contribute any points.
|
||||
// This comment alone should be enough to trick the IDE in to believing this is actually some sort
|
||||
// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
// Concrete implementation representing the local device Bluetooth adapter.
|
||||
class BluetoothAdapter : public api::BluetoothAdapter {
|
||||
public:
|
||||
BluetoothAdapter() = default;
|
||||
~BluetoothAdapter() override = default;
|
||||
|
||||
// Synchronously sets the status of the BluetoothAdapter to @c status, and
|
||||
// returns true if the operation was a success.
|
||||
bool SetStatus(api::BluetoothAdapter::Status status) override;
|
||||
|
||||
// Returns true if the BluetoothAdapter's current status is @c kEnabled.
|
||||
bool IsEnabled() const override;
|
||||
|
||||
// Get the current Bluetooth scan mode of the local Bluetooth adapter.
|
||||
//
|
||||
// The Bluetooth scan mode determines if the local adapter is connectable
|
||||
// and/or discoverable from remote Bluetooth devices.
|
||||
//
|
||||
// Possible values are: @c kNone, @c kConnectable,
|
||||
// @c kConnectableDiscoverable.
|
||||
//
|
||||
// Returns @c kUnknown on error.
|
||||
api::BluetoothAdapter::ScanMode GetScanMode() const override;
|
||||
|
||||
// Synchronously sets the scan mode of the adapter, and returns true if the
|
||||
// operation was a success.
|
||||
bool SetScanMode(api::BluetoothAdapter::ScanMode scan_mode) override;
|
||||
|
||||
// Get the friendly Bluetooth name of the local Bluetooth adapter.
|
||||
//
|
||||
// Returns an empty string on error
|
||||
std::string GetName() const override;
|
||||
|
||||
// Set and persist the friendly Bluetooth name of the local Bluetooth adapter.
|
||||
//
|
||||
// This name is visible to remote Bluetooth devices.
|
||||
//
|
||||
// Valid Bluetooth names are a maximum of 248 bytes using UTF-8 encoding,
|
||||
// although many remote devices can only display the first 40 characters, and
|
||||
// some may be limited to just 20.
|
||||
bool SetName(absl::string_view name) override;
|
||||
|
||||
// Set the friendly Bluetooth name of the local Bluetooth adapter.
|
||||
//
|
||||
// This name is visible to remote Bluetooth devices.
|
||||
//
|
||||
// Valid Bluetooth names are a maximum of 248 bytes using UTF-8 encoding,
|
||||
// although many remote devices can only display the first 40 characters, and
|
||||
// some may be limited to just 20.
|
||||
//
|
||||
// If persist is false, we will restore the original radio names when complete.
|
||||
bool SetName(absl::string_view name, bool persist) override;
|
||||
|
||||
// Returns BT MAC address assigned to this adapter.
|
||||
std::string GetMacAddress() const override;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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/apple/bluetooth_adapter_v2.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace apple {
|
||||
|
||||
bool BluetoothAdapter::SetStatus(api::BluetoothAdapter::Status status) {
|
||||
// We can't force a radio state in macOS/iOS.
|
||||
// NOTE: This is supposed to return the success/failure of the change (which should always be
|
||||
// "fail" for our case). However, usage in the codebase expects this to return with "success" if
|
||||
// the radio is on, so we just return whether the radio is currently enabled.
|
||||
return IsEnabled();
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
bool BluetoothAdapter::IsEnabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
api::BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const {
|
||||
return api::BluetoothAdapter::ScanMode::kUnknown;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
bool BluetoothAdapter::SetScanMode(api::BluetoothAdapter::ScanMode scan_mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
std::string BluetoothAdapter::GetName() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
bool BluetoothAdapter::SetName(absl::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
bool BluetoothAdapter::SetName(absl::string_view name, bool persist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/290385712): Implement.
|
||||
std::string BluetoothAdapter::GetMacAddress() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
@@ -58,6 +58,20 @@ class DeviceInfo : public api::DeviceInfo {
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> callback) override;
|
||||
|
||||
void UnregisterScreenLockedListener(absl::string_view listener_name) override;
|
||||
|
||||
// Request the system to prevent the device from going to sleep.
|
||||
//
|
||||
// To allow the system to sleep again, call @c AllowSleep().
|
||||
//
|
||||
// Returns @c true on success or if the platform does not allow preventing
|
||||
// sleep. Returns @c false on any other error.
|
||||
bool PreventSleep() override;
|
||||
|
||||
// Release any requests preventing the device from going to sleep.
|
||||
//
|
||||
// Returns @c true on success or if the system is already allowed to sleep.
|
||||
// Returns @c false if an error occurred.
|
||||
bool AllowSleep() override;
|
||||
};
|
||||
|
||||
} // namespace apple
|
||||
|
||||
@@ -168,5 +168,9 @@ void DeviceInfo::RegisterScreenLockedListener(
|
||||
|
||||
void DeviceInfo::UnregisterScreenLockedListener(absl::string_view listener_name) {}
|
||||
|
||||
bool DeviceInfo::PreventSleep() { return true; }
|
||||
|
||||
bool DeviceInfo::AllowSleep() { return true; }
|
||||
|
||||
} // namespace apple
|
||||
} // namespace nearby
|
||||
|
||||
@@ -44,37 +44,45 @@ namespace api {
|
||||
|
||||
std::string ImplementationPlatform::GetCustomSavePath(const std::string& parent_folder,
|
||||
const std::string& file_name) {
|
||||
NSFileManager* manager = [NSFileManager defaultManager];
|
||||
// Collapse any path escaping characters.
|
||||
NSString* parentFolder = [@(parent_folder.c_str()) stringByReplacingOccurrencesOfString:@"../"
|
||||
withString:@""];
|
||||
NSURL* parentFolderURL = [NSURL fileURLWithPath:parentFolder];
|
||||
|
||||
NSURL* parentFolder = [NSURL fileURLWithPath:@(parent_folder.c_str())];
|
||||
|
||||
NSString* fileName = @(file_name.c_str());
|
||||
// The only reserved character in a file name on macOS is the forward-slash. It's unclear if iOS
|
||||
// has any additional restrictions.
|
||||
//
|
||||
// """
|
||||
// In the Finder, filenames containing `/` can be created, but `/` is stored as a colon (:) in the
|
||||
// filesystem, and is shown as such on the command line. Filenames containing `:` created from the
|
||||
// command line are shown with `/` instead of `:` in the Finder, so that it is impossible to
|
||||
// create a file that the Finder shows as having a `:` in its filename.
|
||||
// """
|
||||
//
|
||||
// See: https://en.wikipedia.org/wiki/Filename
|
||||
NSString* fileName = [@(file_name.c_str()) stringByReplacingOccurrencesOfString:@"/"
|
||||
withString:@":"];
|
||||
NSString* baseName = [fileName stringByDeletingPathExtension];
|
||||
NSString* extension = [fileName pathExtension];
|
||||
|
||||
NSURL* url = [parentFolder URLByAppendingPathComponent:fileName];
|
||||
NSURL* url = [parentFolderURL URLByAppendingPathComponent:fileName];
|
||||
|
||||
NSInteger index = 1;
|
||||
while ([manager fileExistsAtPath:url.path]) {
|
||||
while ([NSFileManager.defaultManager fileExistsAtPath:url.path]) {
|
||||
index++;
|
||||
NSString* fileName =
|
||||
[NSString stringWithFormat:@"%@ %@.%@", baseName, [@(index) stringValue], extension];
|
||||
url = [parentFolder URLByAppendingPathComponent:fileName];
|
||||
url = [parentFolderURL URLByAppendingPathComponent:fileName];
|
||||
}
|
||||
|
||||
return [url.path UTF8String];
|
||||
return url.path.UTF8String;
|
||||
}
|
||||
|
||||
std::string ImplementationPlatform::GetDownloadPath(const std::string& parent_folder,
|
||||
const std::string& file_name) {
|
||||
// TODO(jfcarroll): This needs to be done correctly, we now have a file name and parent folder,
|
||||
// they should be combined with the default download path
|
||||
NSString* fileName = ObjCStringFromCppString(file_name);
|
||||
|
||||
// TODO(b/227535777): If file name matches an existing file, it will be overwritten. Append a
|
||||
// number until a unique file name is reached 'foobar (2).png'.
|
||||
|
||||
return CppStringFromObjCString([NSTemporaryDirectory() stringByAppendingPathComponent:fileName]);
|
||||
NSString* customSavePath =
|
||||
[NSTemporaryDirectory() stringByAppendingPathComponent:@(parent_folder.c_str())];
|
||||
return GetCustomSavePath(customSavePath.UTF8String, file_name);
|
||||
}
|
||||
|
||||
OSName ImplementationPlatform::GetCurrentOS() { return OSName::kApple; }
|
||||
@@ -162,7 +170,7 @@ std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(api::Bluetoot
|
||||
|
||||
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
|
||||
api::BluetoothAdapter& adapter) {
|
||||
return std::make_unique<apple::BleMedium>(adapter);
|
||||
return std::make_unique<apple::BleMedium>();
|
||||
}
|
||||
|
||||
std::unique_ptr<ServerSyncMedium> ImplementationPlatform::CreateServerSyncMedium() {
|
||||
|
||||
@@ -99,10 +99,8 @@ class BleMedium {
|
||||
virtual bool StopScanning(const std::string& service_id) = 0;
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
struct AcceptedConnectionCallback {
|
||||
absl::AnyInvocable<void(BleSocket& socket, const std::string& service_id)>
|
||||
accepted_cb = DefaultCallback<BleSocket&, const std::string&>();
|
||||
};
|
||||
using AcceptedConnectionCallback = absl::AnyInvocable<void(
|
||||
BleSocket& socket, const std::string& service_id)>;
|
||||
|
||||
// Returns true once BLE socket connection requests to service_id can be
|
||||
// accepted.
|
||||
|
||||
@@ -92,6 +92,8 @@ class BlePeripheral {
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice#getAddress()
|
||||
//
|
||||
// Returns the current address.
|
||||
//
|
||||
// This will always be an empty string on Apple platforms.
|
||||
virtual std::string GetAddress() const = 0;
|
||||
|
||||
// Returns an immutable unique identifier. The identifier must not change when
|
||||
@@ -510,6 +512,9 @@ class BleMedium {
|
||||
|
||||
// Calls `callback` and returns true if `mac_address` is a valid BLE address.
|
||||
// Otherwise, does not call the callback and returns false.
|
||||
//
|
||||
// This method is not available on Apple platforms and will always return
|
||||
// false, ignoring the callback.
|
||||
virtual bool GetRemotePeripheral(const std::string& mac_address,
|
||||
GetRemotePeripheralCallback callback) = 0;
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ class DeviceInfo {
|
||||
std::function<void(ScreenStatus)> callback) = 0;
|
||||
virtual void UnregisterScreenLockedListener(
|
||||
absl::string_view listener_name) = 0;
|
||||
|
||||
// Control device sleep
|
||||
virtual bool PreventSleep() = 0;
|
||||
virtual bool AllowSleep() = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -30,7 +30,6 @@ cc_library(
|
||||
"log_message.h",
|
||||
"multi_thread_executor.h",
|
||||
"mutex.h",
|
||||
"pipe.h",
|
||||
"preferences_manager.h",
|
||||
"scheduled_executor.h",
|
||||
"single_thread_executor.h",
|
||||
@@ -44,8 +43,8 @@ cc_library(
|
||||
deps = [
|
||||
":preferences_repository",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:test_util",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform:util",
|
||||
"//internal/platform/implementation:types",
|
||||
"//internal/platform/implementation/shared:count_down_latch",
|
||||
@@ -85,6 +84,7 @@ cc_library(
|
||||
"bluetooth_adapter.h",
|
||||
"bluetooth_classic.h",
|
||||
"credential_storage_impl.h",
|
||||
"socket_base.h",
|
||||
"webrtc.h",
|
||||
"wifi.h",
|
||||
"wifi_direct.h",
|
||||
@@ -97,13 +97,15 @@ cc_library(
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/functional:any_invocable",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:cancellation_flag",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:test_util",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform:uuid",
|
||||
|
||||
@@ -17,10 +17,18 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/cancellation_flag_listener.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/ble.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/shared/count_down_latch.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
@@ -28,76 +36,11 @@
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
BleSocket::~BleSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void BleSocket::Connect(BleSocket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
InputStream& BleSocket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
CHECK(remote_socket != nullptr);
|
||||
return remote_socket->GetLocalInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BleSocket::GetOutputStream() { return GetLocalOutputStream(); }
|
||||
|
||||
BleSocket* BleSocket::GetRemoteSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
bool BleSocket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
bool BleSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception BleSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
BlePeripheral* BleSocket::GetRemotePeripheral() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return peripheral_;
|
||||
}
|
||||
|
||||
void BleSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
if (IsConnectedLocked()) {
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
}
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool BleSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& BleSocket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BleSocket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::BleSocket> BleServerSocket::Accept(
|
||||
BlePeripheral* peripheral) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/g3/bluetooth_classic.h"
|
||||
#include "internal/platform/implementation/g3/multi_thread_executor.h"
|
||||
#include "internal/platform/implementation/g3/pipe.h"
|
||||
#include "internal/platform/implementation/g3/socket_base.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/output_stream.h"
|
||||
|
||||
@@ -36,64 +36,36 @@ namespace g3 {
|
||||
|
||||
class BleMedium;
|
||||
|
||||
class BleSocket : public api::BleSocket {
|
||||
class BleSocket : public api::BleSocket, public SocketBase {
|
||||
public:
|
||||
BleSocket() = default;
|
||||
explicit BleSocket(BlePeripheral* peripheral) : peripheral_(peripheral) {}
|
||||
~BleSocket() override;
|
||||
|
||||
// Connect to another BleSocket, to form a functional low-level channel.
|
||||
// from this point on, and until Close is called, connection exists.
|
||||
void Connect(BleSocket& other) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the InputStream of this connected BleSocket.
|
||||
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
InputStream& GetInputStream() override {
|
||||
return SocketBase::GetInputStream();
|
||||
}
|
||||
|
||||
// Returns the OutputStream of this connected BleSocket.
|
||||
// This stream is for local side to write.
|
||||
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
OutputStream& GetOutputStream() override {
|
||||
return SocketBase::GetOutputStream();
|
||||
}
|
||||
|
||||
// Returns address of a remote BleSocket or nullptr.
|
||||
BleSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
BleSocket* GetRemoteSocket() {
|
||||
return static_cast<BleSocket*>(SocketBase::GetRemoteSocket());
|
||||
}
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
Exception Close() override { return SocketBase::Close(); }
|
||||
|
||||
// Returns valid BlePeripheral pointer if there is a connection, and
|
||||
// nullptr otherwise.
|
||||
BlePeripheral* GetRemotePeripheral() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
BlePeripheral* peripheral_;
|
||||
BleSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
class BleServerSocket {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "internal/platform/implementation/g3/ble_v2.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -23,15 +22,26 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/borrowable.h"
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/cancellation_flag_listener.h"
|
||||
#include "internal/platform/count_down_latch.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
#include "internal/platform/prng.h"
|
||||
#include "internal/platform/uuid.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
@@ -69,82 +79,14 @@ api::ble_v2::BlePeripheral::UniqueId BleV2Peripheral::GetUniqueId() const {
|
||||
return adapter_.GetUniqueId();
|
||||
}
|
||||
|
||||
BleV2Socket::~BleV2Socket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void BleV2Socket::Connect(BleV2Socket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
InputStream& BleV2Socket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
CHECK(remote_socket != nullptr);
|
||||
return remote_socket->GetLocalInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BleV2Socket::GetOutputStream() { return GetLocalOutputStream(); }
|
||||
|
||||
BleV2Socket* BleV2Socket::GetRemoteSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
bool BleV2Socket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
bool BleV2Socket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception BleV2Socket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
BleV2Peripheral* BleV2Socket::GetRemotePeripheral() {
|
||||
BluetoothAdapter* remote_adapter = nullptr;
|
||||
{
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (remote_socket_ == nullptr || remote_socket_->adapter_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
remote_adapter = remote_socket_->adapter_;
|
||||
}
|
||||
if (remote_adapter == nullptr || remote_adapter->GetBleV2Medium() == nullptr)
|
||||
BleV2Socket* remote_socket = GetRemoteSocket();
|
||||
if (remote_socket == nullptr || remote_socket->adapter_ == nullptr ||
|
||||
remote_socket->adapter_->GetBleV2Medium() == nullptr) {
|
||||
return nullptr;
|
||||
return &(static_cast<BleV2Medium*>(remote_adapter->GetBleV2Medium())
|
||||
->GetPeripheral());
|
||||
}
|
||||
|
||||
void BleV2Socket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool BleV2Socket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& BleV2Socket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BleV2Socket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
return &(static_cast<BleV2Medium*>(remote_socket->adapter_->GetBleV2Medium())
|
||||
->GetPeripheral());
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleSocket> BleV2ServerSocket::Accept() {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/implementation/ble_v2.h"
|
||||
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/g3/pipe.h"
|
||||
#include "internal/platform/implementation/g3/socket_base.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
#include "internal/platform/prng.h"
|
||||
#include "internal/platform/uuid.h"
|
||||
@@ -50,67 +50,35 @@ class BleV2Peripheral : public api::ble_v2::BlePeripheral {
|
||||
BluetoothAdapter& adapter_;
|
||||
};
|
||||
|
||||
class BleV2Socket : public api::ble_v2::BleSocket {
|
||||
class BleV2Socket : public api::ble_v2::BleSocket, public SocketBase {
|
||||
public:
|
||||
explicit BleV2Socket(BluetoothAdapter* adapter) : adapter_(adapter) {}
|
||||
BleV2Socket(const BleV2Socket&) = default;
|
||||
BleV2Socket& operator=(const BleV2Socket&) = default;
|
||||
BleV2Socket(BleV2Socket&&) = default;
|
||||
BleV2Socket& operator=(BleV2Socket&&) = default;
|
||||
~BleV2Socket() override;
|
||||
|
||||
// Connect to another BleSocket, to form a functional low-level channel.
|
||||
// from this point on, and until Close is called, connection exists.
|
||||
void Connect(BleV2Socket& other) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the InputStream of this connected BleSocket.
|
||||
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
InputStream& GetInputStream() override {
|
||||
return SocketBase::GetInputStream();
|
||||
}
|
||||
|
||||
// Returns the OutputStream of this connected BleSocket.
|
||||
// This stream is for local side to write.
|
||||
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
OutputStream& GetOutputStream() override {
|
||||
return SocketBase::GetOutputStream();
|
||||
}
|
||||
|
||||
// Returns address of a remote BleSocket or nullptr.
|
||||
BleV2Socket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
BleV2Socket* GetRemoteSocket() {
|
||||
return static_cast<BleV2Socket*>(SocketBase::GetRemoteSocket());
|
||||
}
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
Exception Close() override { return SocketBase::Close(); }
|
||||
|
||||
// Returns valid BlePeripheral pointer if there is a connection, and
|
||||
// nullptr otherwise.
|
||||
BleV2Peripheral* GetRemotePeripheral() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only.
|
||||
BleV2Socket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
class BleV2ServerSocket : public api::ble_v2::BleServerSocket {
|
||||
|
||||
@@ -17,92 +17,31 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/cancellation_flag_listener.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/bluetooth_classic.h"
|
||||
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
BluetoothSocket::~BluetoothSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void BluetoothSocket::Connect(BluetoothSocket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
bool BluetoothSocket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
bool BluetoothSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
bool BluetoothSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& BluetoothSocket::GetInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (IsConnectedLocked()) {
|
||||
return input_->GetInputStream();
|
||||
} else {
|
||||
return invalid_input_stream_;
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream& BluetoothSocket::GetOutputStream() {
|
||||
return GetLocalOutputStream();
|
||||
}
|
||||
|
||||
InputStream& BluetoothSocket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BluetoothSocket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
Exception BluetoothSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void BluetoothSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
input_.reset();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothDevice* BluetoothSocket::GetRemoteDevice() {
|
||||
BluetoothAdapter* remote_adapter = nullptr;
|
||||
{
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (remote_socket_ == nullptr || remote_socket_->adapter_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
remote_adapter = remote_socket_->adapter_;
|
||||
BluetoothSocket* remote_socket =
|
||||
static_cast<BluetoothSocket*>(GetRemoteSocket());
|
||||
if (remote_socket == nullptr || remote_socket->adapter_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return remote_adapter ? &remote_adapter->GetDevice() : nullptr;
|
||||
return &remote_socket->adapter_->GetDevice();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::BluetoothSocket> BluetoothServerSocket::Accept() {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/bluetooth_classic.h"
|
||||
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
|
||||
#include "internal/platform/implementation/g3/pipe.h"
|
||||
#include "internal/platform/implementation/g3/socket_base.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/listeners.h"
|
||||
#include "internal/platform/output_stream.h"
|
||||
@@ -35,83 +35,34 @@ namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html.
|
||||
class BluetoothSocket : public api::BluetoothSocket {
|
||||
class BluetoothSocket : public api::BluetoothSocket, public SocketBase {
|
||||
public:
|
||||
BluetoothSocket() = default;
|
||||
explicit BluetoothSocket(BluetoothAdapter* adapter) : adapter_(adapter) {}
|
||||
~BluetoothSocket() override;
|
||||
|
||||
// Connects to another BluetoothSocket, to form a functional low-level
|
||||
// channel. From this point on, and until Close is called, connection exists.
|
||||
void Connect(BluetoothSocket& other);
|
||||
|
||||
// NOTE:
|
||||
// It is an undefined behavior if GetInputStream() or GetOutputStream() is
|
||||
// called for a not-connected BluetoothSocket, i.e. any object that is not
|
||||
// returned by BluetoothClassicMedium::ConnectToService() for client side or
|
||||
// BluetoothServerSocket::Accept() for server side of connection.
|
||||
|
||||
// Returns the InputStream of this connected BluetoothSocket.
|
||||
InputStream& GetInputStream() override;
|
||||
InputStream& GetInputStream() override {
|
||||
return SocketBase::GetInputStream();
|
||||
}
|
||||
|
||||
// Returns the OutputStream of this connected BluetoothSocket.
|
||||
// This stream is for local side to write.
|
||||
OutputStream& GetOutputStream() override;
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
OutputStream& GetOutputStream() override {
|
||||
return SocketBase::GetOutputStream();
|
||||
}
|
||||
|
||||
// Closes both input and output streams, marks Socket as closed.
|
||||
// After this call object should be treated as not connected.
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
Exception Close() override { return SocketBase::Close(); }
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice()
|
||||
// Returns valid BluetoothDevice pointer if there is a connection, and
|
||||
// nullptr otherwise.
|
||||
BluetoothDevice* GetRemoteDevice() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
BluetoothDevice* GetRemoteDevice() override;
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
class InvalidInputStream : public InputStream {
|
||||
public:
|
||||
ExceptionOr<ByteArray> Read(std::int64_t size) override {
|
||||
return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
}
|
||||
ExceptionOr<size_t> Skip(size_t offset) override {
|
||||
return ExceptionOr<size_t>(Exception::kIo);
|
||||
}
|
||||
Exception Close() override { return {Exception::kIo}; }
|
||||
};
|
||||
// Returned to the caller if the remote socket is destroyed.
|
||||
InvalidInputStream invalid_input_stream_;
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only.
|
||||
BluetoothSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html.
|
||||
|
||||
@@ -38,7 +38,7 @@ class DeviceInfo : public api::DeviceInfo {
|
||||
return api::DeviceInfo::DeviceType::kLaptop;
|
||||
}
|
||||
|
||||
api::DeviceInfo::OsType GetOsType() const override{
|
||||
api::DeviceInfo::OsType GetOsType() const override {
|
||||
return api::DeviceInfo::OsType::kChromeOs;
|
||||
}
|
||||
|
||||
@@ -92,6 +92,10 @@ class DeviceInfo : public api::DeviceInfo {
|
||||
screen_locked_listeners_.erase(listener_name);
|
||||
}
|
||||
|
||||
bool PreventSleep() override { return true; }
|
||||
|
||||
bool AllowSleep() override { return true; }
|
||||
|
||||
private:
|
||||
absl::flat_hash_map<std::string,
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)>>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
#ifndef PLATFORM_IMPL_G3_PIPE_H_
|
||||
#define PLATFORM_IMPL_G3_PIPE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "internal/platform/base_pipe.h"
|
||||
#include "internal/platform/implementation/g3/condition_variable.h"
|
||||
#include "internal/platform/implementation/g3/mutex.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
class Pipe : public BasePipe {
|
||||
public:
|
||||
Pipe() {
|
||||
auto mutex = std::make_unique<g3::Mutex>(/*check=*/true);
|
||||
auto cond = std::make_unique<g3::ConditionVariable>(mutex.get());
|
||||
Setup(std::move(mutex), std::move(cond));
|
||||
}
|
||||
~Pipe() override = default;
|
||||
Pipe(Pipe&&) = delete;
|
||||
Pipe& operator=(Pipe&&) = delete;
|
||||
};
|
||||
|
||||
} // namespace g3
|
||||
} // namespace nearby
|
||||
|
||||
#endif // PLATFORM_IMPL_G3_PIPE_H_
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_G3_SOCKET_BASE_H_
|
||||
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_G3_SOCKET_BASE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/output_stream.h"
|
||||
#include "internal/platform/pipe.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
// Common base for BT, BLE and Wifi socket implementations.
|
||||
class SocketBase {
|
||||
public:
|
||||
SocketBase() { std::tie(input_for_remote_, output_) = CreatePipe(); }
|
||||
virtual ~SocketBase() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
// Connects to another Socket, to form a functional low-level
|
||||
// channel. From this point on, and until Close is called, connection exists.
|
||||
void Connect(SocketBase& other) ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = std::move(other.input_for_remote_);
|
||||
}
|
||||
|
||||
// Returns the InputStream of this connected socket.
|
||||
InputStream& GetInputStream() { return input_proxy_; }
|
||||
|
||||
// Returns the OutputStream of this connected socket.
|
||||
// This stream is for local side to write.
|
||||
OutputStream& GetOutputStream() ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return *output_;
|
||||
}
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
// Closes both input and output streams, marks Socket as closed.
|
||||
// After this call object should be treated as not connected.
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
SocketBase* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
protected:
|
||||
mutable absl::Mutex mutex_;
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
// The client can hold references to `output_` and `input_` streams. We
|
||||
// can close them but we cannot destroy them.
|
||||
output_->Close();
|
||||
if (input_) {
|
||||
input_->Close();
|
||||
}
|
||||
// The client does not hold a reference to `input_for_remote_`, so we can
|
||||
// destroy it. Connecting to this socket will fail after that.
|
||||
input_for_remote_.reset();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
|
||||
return input_ != nullptr;
|
||||
}
|
||||
|
||||
class InputProxyStream : public InputStream {
|
||||
public:
|
||||
explicit InputProxyStream(SocketBase* socket) : socket_(socket) {}
|
||||
ExceptionOr<ByteArray> Read(std::int64_t size) override {
|
||||
if (!socket_->IsConnected()) {
|
||||
return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
}
|
||||
return socket_->input_->Read(size);
|
||||
}
|
||||
ExceptionOr<size_t> Skip(size_t offset) override {
|
||||
if (!socket_->IsConnected()) {
|
||||
return ExceptionOr<size_t>(Exception::kIo);
|
||||
}
|
||||
return socket_->input_->Skip(offset);
|
||||
}
|
||||
Exception Close() override {
|
||||
if (!socket_->IsConnected()) {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
return socket_->input_->Close();
|
||||
}
|
||||
|
||||
private:
|
||||
SocketBase* socket_;
|
||||
};
|
||||
InputProxyStream input_proxy_{this};
|
||||
|
||||
// Output stream is initialized by constructor, it remains always valid. It
|
||||
// represents output part of a local socket. Input stream of a local socket
|
||||
// comes from the peer socket, after connection.
|
||||
std::unique_ptr<OutputStream> output_;
|
||||
std::unique_ptr<InputStream> input_;
|
||||
// `input_for_remote_` is the other end of the pipe formed with `output_`. We
|
||||
// give this stream to the remote socket when they connect to us, and it
|
||||
// becomes their `input_` stream.
|
||||
std::unique_ptr<InputStream> input_for_remote_;
|
||||
SocketBase* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
} // namespace g3
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_G3_SOCKET_BASE_H_
|
||||
@@ -16,80 +16,25 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/wifi_direct.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
#include "internal/platform/prng.h"
|
||||
#include "internal/platform/wifi_credential.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
// Code for WifiDirectSocket
|
||||
WifiDirectSocket::~WifiDirectSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void WifiDirectSocket::Connect(WifiDirectSocket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
InputStream& WifiDirectSocket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
CHECK(remote_socket != nullptr);
|
||||
return remote_socket->GetLocalInputStream();
|
||||
}
|
||||
|
||||
OutputStream& WifiDirectSocket::GetOutputStream() {
|
||||
return GetLocalOutputStream();
|
||||
}
|
||||
|
||||
WifiDirectSocket* WifiDirectSocket::GetRemoteSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
bool WifiDirectSocket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
Exception WifiDirectSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void WifiDirectSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool WifiDirectSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& WifiDirectSocket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& WifiDirectSocket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
// Code for WifiDirectServerSocket
|
||||
std::string WifiDirectServerSocket::GetName(absl::string_view ip_address,
|
||||
int port) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/implementation/g3/multi_thread_executor.h"
|
||||
#include "internal/platform/implementation/g3/pipe.h"
|
||||
#include "internal/platform/implementation/g3/socket_base.h"
|
||||
#include "internal/platform/implementation/wifi_direct.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/output_stream.h"
|
||||
@@ -32,66 +32,28 @@ namespace g3 {
|
||||
|
||||
class WifiDirectMedium;
|
||||
|
||||
class WifiDirectSocket : public api::WifiDirectSocket {
|
||||
class WifiDirectSocket : public api::WifiDirectSocket, public SocketBase {
|
||||
public:
|
||||
WifiDirectSocket() = default;
|
||||
~WifiDirectSocket() override;
|
||||
WifiDirectSocket(const WifiDirectSocket&) = default;
|
||||
WifiDirectSocket(WifiDirectSocket&&) = default;
|
||||
WifiDirectSocket& operator=(const WifiDirectSocket&) = default;
|
||||
WifiDirectSocket& operator=(WifiDirectSocket&&) = default;
|
||||
|
||||
// Connect to another WifiDirectSocket, to form a functional low-level
|
||||
// channel. from this point on, and until Close is called, connection exists.
|
||||
void Connect(WifiDirectSocket& other) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the InputStream of the WifiDirectSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the WifiDirectSocket object is destroyed.
|
||||
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
InputStream& GetInputStream() override {
|
||||
return SocketBase::GetInputStream();
|
||||
}
|
||||
|
||||
// Returns the OutputStream of the WifiDirectSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the WifiDirectSocket object is destroyed.
|
||||
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns address of a remote WifiDirectSocket or nullptr.
|
||||
WifiDirectSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
OutputStream& GetOutputStream() override {
|
||||
return SocketBase::GetOutputStream();
|
||||
}
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
WifiDirectSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
Exception Close() override { return SocketBase::Close(); }
|
||||
};
|
||||
|
||||
// WifiDirectServerSocket provides the support to server socket, this server
|
||||
|
||||
@@ -16,86 +16,26 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/cancellation_flag_listener.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/wifi_hotspot.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
#include "internal/platform/prng.h"
|
||||
#include "internal/platform/wifi_credential.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
// Code for WifiHotspotSocket
|
||||
WifiHotspotSocket::~WifiHotspotSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void WifiHotspotSocket::Connect(WifiHotspotSocket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
InputStream& WifiHotspotSocket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
CHECK(remote_socket != nullptr);
|
||||
return remote_socket->GetLocalInputStream();
|
||||
}
|
||||
|
||||
OutputStream& WifiHotspotSocket::GetOutputStream() {
|
||||
return GetLocalOutputStream();
|
||||
}
|
||||
|
||||
WifiHotspotSocket* WifiHotspotSocket::GetRemoteSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
bool WifiHotspotSocket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
bool WifiHotspotSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception WifiHotspotSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void WifiHotspotSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool WifiHotspotSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& WifiHotspotSocket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& WifiHotspotSocket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
// Code for WifiHotspotServerSocket
|
||||
std::string WifiHotspotServerSocket::GetName(absl::string_view ip_address,
|
||||
int port) {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/implementation/g3/multi_thread_executor.h"
|
||||
#include "internal/platform/implementation/g3/pipe.h"
|
||||
#include "internal/platform/implementation/g3/socket_base.h"
|
||||
#include "internal/platform/implementation/wifi_hotspot.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/output_stream.h"
|
||||
@@ -33,69 +33,28 @@ namespace g3 {
|
||||
|
||||
class WifiHotspotMedium;
|
||||
|
||||
class WifiHotspotSocket : public api::WifiHotspotSocket {
|
||||
class WifiHotspotSocket : public api::WifiHotspotSocket, public SocketBase {
|
||||
public:
|
||||
WifiHotspotSocket() = default;
|
||||
~WifiHotspotSocket() override;
|
||||
WifiHotspotSocket(const WifiHotspotSocket&) = default;
|
||||
WifiHotspotSocket(WifiHotspotSocket&&) = default;
|
||||
WifiHotspotSocket& operator=(const WifiHotspotSocket&) = default;
|
||||
WifiHotspotSocket& operator=(WifiHotspotSocket&&) = default;
|
||||
|
||||
// Connect to another WifiHotspotSocket, to form a functional low-level
|
||||
// channel. from this point on, and until Close is called, connection exists.
|
||||
void Connect(WifiHotspotSocket& other) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the InputStream of the WifiHotspotSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the WifiHotspotSocket object is destroyed.
|
||||
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
InputStream& GetInputStream() override {
|
||||
return SocketBase::GetInputStream();
|
||||
}
|
||||
|
||||
// Returns the OutputStream of the WifiHotspotSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the WifiHotspotSocket object is destroyed.
|
||||
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns address of a remote WifiHotspotSocket or nullptr.
|
||||
WifiHotspotSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
OutputStream& GetOutputStream() override {
|
||||
return SocketBase::GetOutputStream();
|
||||
}
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
WifiHotspotSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
Exception Close() override { return SocketBase::Close(); }
|
||||
};
|
||||
|
||||
// WifiHotspotServerSocket provides the support to server socket, this server
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/cancellation_flag.h"
|
||||
#include "internal/platform/cancellation_flag_listener.h"
|
||||
#include "internal/platform/exception.h"
|
||||
#include "internal/platform/implementation/wifi_lan.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/medium_environment.h"
|
||||
@@ -31,71 +34,6 @@
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
WifiLanSocket::~WifiLanSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void WifiLanSocket::Connect(WifiLanSocket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
InputStream& WifiLanSocket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
CHECK(remote_socket != nullptr);
|
||||
return remote_socket->GetLocalInputStream();
|
||||
}
|
||||
|
||||
OutputStream& WifiLanSocket::GetOutputStream() {
|
||||
return GetLocalOutputStream();
|
||||
}
|
||||
|
||||
WifiLanSocket* WifiLanSocket::GetRemoteSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
bool WifiLanSocket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
bool WifiLanSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception WifiLanSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void WifiLanSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool WifiLanSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& WifiLanSocket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& WifiLanSocket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
std::string WifiLanServerSocket::GetName(const std::string& ip_address,
|
||||
int port) {
|
||||
std::string dot_delimited_string;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/byte_array.h"
|
||||
#include "internal/platform/implementation/g3/multi_thread_executor.h"
|
||||
#include "internal/platform/implementation/g3/pipe.h"
|
||||
#include "internal/platform/implementation/g3/socket_base.h"
|
||||
#include "internal/platform/implementation/wifi_lan.h"
|
||||
#include "internal/platform/input_stream.h"
|
||||
#include "internal/platform/nsd_service_info.h"
|
||||
@@ -35,58 +35,26 @@ namespace g3 {
|
||||
|
||||
class WifiLanMedium;
|
||||
|
||||
class WifiLanSocket : public api::WifiLanSocket {
|
||||
class WifiLanSocket : public api::WifiLanSocket, public SocketBase {
|
||||
public:
|
||||
WifiLanSocket() = default;
|
||||
~WifiLanSocket() override;
|
||||
|
||||
// Connect to another WifiLanSocket, to form a functional low-level channel.
|
||||
// from this point on, and until Close is called, connection exists.
|
||||
void Connect(WifiLanSocket& other) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the InputStream of this connected WifiLanSocket.
|
||||
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
InputStream& GetInputStream() override {
|
||||
return SocketBase::GetInputStream();
|
||||
}
|
||||
|
||||
// Returns the OutputStream of this connected WifiLanSocket.
|
||||
// This stream is for local side to write.
|
||||
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
OutputStream& GetOutputStream() override {
|
||||
return SocketBase::GetOutputStream();
|
||||
}
|
||||
|
||||
// Returns address of a remote WifiLanSocket or nullptr.
|
||||
WifiLanSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
WifiLanSocket* GetRemoteSocket() {
|
||||
return static_cast<WifiLanSocket*>(SocketBase::GetRemoteSocket());
|
||||
}
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
WifiLanSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
Exception Close() override { return SocketBase::Close(); }
|
||||
};
|
||||
|
||||
class WifiLanServerSocket : public api::WifiLanServerSocket {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# 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.
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
@@ -49,7 +50,6 @@ cc_library(
|
||||
"//base:stringprintf",
|
||||
"//internal/base:bluetooth_address",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform:uuid",
|
||||
"//internal/platform/implementation:types",
|
||||
@@ -95,6 +95,7 @@ cc_library(
|
||||
"mutex.h",
|
||||
"scheduled_executor.h",
|
||||
"server_sync.h",
|
||||
"session_manager.h",
|
||||
"submittable_executor.h",
|
||||
"thread_pool.h",
|
||||
"webrtc.h",
|
||||
@@ -166,6 +167,7 @@ cc_library(
|
||||
"preferences_repository.cc",
|
||||
"preferences_repository.h",
|
||||
"scheduled_executor.cc",
|
||||
"session_manager.cc",
|
||||
"submittable_executor.cc",
|
||||
"system_clock.cc",
|
||||
"thread_pool.cc",
|
||||
@@ -202,7 +204,6 @@ cc_library(
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:cancellation_flag",
|
||||
"//internal/platform:comm",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform:uuid",
|
||||
"//internal/platform/flags:platform_flags",
|
||||
@@ -283,7 +284,7 @@ cc_test(
|
||||
":types",
|
||||
":windows",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform:logging",
|
||||
"//internal/platform:types",
|
||||
"//internal/platform/implementation:comm",
|
||||
"//internal/platform/implementation:platform",
|
||||
"//internal/platform/implementation:types",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "winrt/Windows.Devices.Bluetooth.Advertisement.h"
|
||||
#include "winrt/Windows.Devices.Bluetooth.h"
|
||||
#include "winrt/Windows.Foundation.Collections.h"
|
||||
#include "winrt/Windows.Storage.Streams.h" // NOLINT(misc-include-cleaner)
|
||||
|
||||
namespace nearby {
|
||||
namespace windows {
|
||||
@@ -68,6 +69,8 @@ using ::winrt::Windows::Devices::Bluetooth::Advertisement::
|
||||
BluetoothLEAdvertisementDataSection;
|
||||
using ::winrt::Windows::Devices::Bluetooth::Advertisement::
|
||||
BluetoothLEAdvertisementDataTypes;
|
||||
using ::winrt::Windows::Devices::Bluetooth::Advertisement::
|
||||
BluetoothLEAdvertisementFilter; // NOLINT(misc-include-cleaner)
|
||||
using ::winrt::Windows::Devices::Bluetooth::Advertisement::
|
||||
BluetoothLEAdvertisementPublisher;
|
||||
using ::winrt::Windows::Devices::Bluetooth::Advertisement::
|
||||
@@ -230,9 +233,19 @@ bool BleV2Medium::StartScanning(const Uuid& service_uuid,
|
||||
// Active mode indicates that scan request packets will be sent to query for
|
||||
// Scan Response
|
||||
watcher_.ScanningMode(BluetoothLEScanningMode::Active);
|
||||
::winrt::Windows::Devices::Bluetooth::BluetoothSignalStrengthFilter filter;
|
||||
filter.SamplingInterval(TimeSpan(std::chrono::seconds(2)));
|
||||
watcher_.SignalStrengthFilter(filter);
|
||||
|
||||
BluetoothLEAdvertisementDataSection data_section;
|
||||
data_section.DataType(0x16);
|
||||
DataWriter data_writer; // NOLINT(misc-include-cleaner)
|
||||
std::array<char, 16> service_id_data = service_uuid_.data();
|
||||
data_writer.WriteByte(service_id_data[3] & 0xff);
|
||||
data_writer.WriteByte(service_id_data[2] & 0xff);
|
||||
data_section.Data(data_writer.DetachBuffer());
|
||||
BluetoothLEAdvertisement advertisement;
|
||||
advertisement.DataSections().Append(data_section);
|
||||
BluetoothLEAdvertisementFilter advertisement_filter;
|
||||
advertisement_filter.Advertisement(advertisement);
|
||||
watcher_.AdvertisementFilter(advertisement_filter);
|
||||
watcher_.Start();
|
||||
|
||||
is_watcher_started_ = true;
|
||||
|
||||
@@ -311,7 +311,10 @@ std::unique_ptr<api::BluetoothSocket> BluetoothClassicMedium::ConnectToService(
|
||||
RfcommDeviceService requested_service(
|
||||
GetRequestedService(current_device, service));
|
||||
|
||||
if (!CheckSdp(requested_service)) {
|
||||
if (!FeatureFlags::GetInstance()
|
||||
.GetFlags()
|
||||
.skip_service_discovery_before_connecting_to_rfcomm &&
|
||||
!CheckSdp(requested_service)) {
|
||||
NEARBY_LOGS(ERROR) << __func__ << ": Invalid SDP.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -23,11 +23,14 @@
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/base/bluetooth_address.h"
|
||||
#include "internal/platform/implementation/device_info.h"
|
||||
#include "internal/platform/implementation/windows/session_manager.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "winrt/Windows.Foundation.Collections.h"
|
||||
#include "winrt/Windows.Foundation.h"
|
||||
@@ -49,89 +52,10 @@ using IVectorView = winrt::Windows::Foundation::Collections::IVectorView<T>;
|
||||
template <typename T>
|
||||
using IAsyncOperation = winrt::Windows::Foundation::IAsyncOperation<T>;
|
||||
|
||||
constexpr char window_class_name[] = "NearbySharingDLL_MessageWindowClass";
|
||||
constexpr char window_name[] = "NearbySharingDLL_MessageWindow";
|
||||
constexpr char logs_relative_path[] = "Google\\Nearby\\Sharing\\Logs";
|
||||
constexpr char crash_dumps_relative_path[] =
|
||||
"Google\\Nearby\\Sharing\\CrashDumps";
|
||||
|
||||
namespace {
|
||||
// This WindowProc method must be static for the successful initialization of
|
||||
// WNDCLASS
|
||||
// window_class.lpfnWndProc = (WNDPROC) &DeviceInfo::WindowProc;
|
||||
// where a WNDPROC typed function pointer is expected
|
||||
// typedef LRESULT (CALLBACK* WNDPROC)(HWND,UINT,WPARAM,LPARAM)
|
||||
// the calling convention used here CALLBACK is a macro defined as
|
||||
// #define CALLBACK __stdcall
|
||||
//
|
||||
// If WindProc is not static and defined as a member function, it uses the
|
||||
// __thiscall calling convention instead
|
||||
// https://docs.microsoft.com/en-us/cpp/cpp/thiscall?view=msvc-170
|
||||
// https://isocpp.org/wiki/faq/pointers-to-members
|
||||
// https://en.cppreference.com/w/cpp/language/pointer
|
||||
//
|
||||
// This is problematic because the function pointer now looks like this
|
||||
// typedef LRESULT (CALLBACK* DeviceInfo_WNDPROC)(DeviceInfo*
|
||||
// this,HWND,UINT,WPARAM,LPARAM)
|
||||
// which causes casting errors
|
||||
LRESULT CALLBACK WindowProc(HWND window_handle, UINT message, WPARAM wparam,
|
||||
LPARAM lparam) {
|
||||
DeviceInfo* self = reinterpret_cast<DeviceInfo*>(
|
||||
GetWindowLongPtr(window_handle, GWLP_USERDATA));
|
||||
CREATESTRUCT* create_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
LONG_PTR result = 0L;
|
||||
switch (message) {
|
||||
case WM_CREATE:
|
||||
self = reinterpret_cast<DeviceInfo*>(create_struct->lpCreateParams);
|
||||
self->message_window_handle_ = window_handle;
|
||||
|
||||
// Store pointer to the self to the window's user data.
|
||||
SetLastError(0);
|
||||
result = SetWindowLongPtr(window_handle, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(self));
|
||||
if (result == 0 && GetLastError() != 0) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< __func__
|
||||
<< ": Error connecting message window to Nearby Sharing DLL.";
|
||||
}
|
||||
break;
|
||||
case WM_WTSSESSION_CHANGE:
|
||||
if (self) {
|
||||
switch (wparam) {
|
||||
case WTS_SESSION_LOCK:
|
||||
for (auto& listener : self->screen_locked_listeners_) {
|
||||
listener.second(api::DeviceInfo::ScreenStatus::
|
||||
kLocked); // Trigger registered callbacks
|
||||
}
|
||||
break;
|
||||
case WTS_SESSION_UNLOCK:
|
||||
for (auto& listener : self->screen_locked_listeners_) {
|
||||
listener.second(api::DeviceInfo::ScreenStatus::
|
||||
kUnlocked); // Trigger registered callbacks
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
SetLastError(0);
|
||||
result = SetWindowLongPtr(window_handle, GWLP_USERDATA, NULL);
|
||||
if (result == 0 && GetLastError() != 0) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< __func__
|
||||
<< ": Error disconnecting message window to Nearby Sharing DLL.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProc(window_handle, message, wparam, lparam);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DeviceInfo::~DeviceInfo() {
|
||||
UnregisterClass(MAKEINTATOM(registered_class_), instance_);
|
||||
}
|
||||
|
||||
std::optional<std::u16string> DeviceInfo::GetOsDeviceName() const {
|
||||
DWORD size = 0;
|
||||
|
||||
@@ -404,78 +328,39 @@ std::optional<std::filesystem::path> DeviceInfo::GetCrashDumpPath() const {
|
||||
}
|
||||
|
||||
bool DeviceInfo::IsScreenLocked() const {
|
||||
DWORD session_id = WTSGetActiveConsoleSessionId();
|
||||
WTS_INFO_CLASS wts_info_class = WTSSessionInfoEx;
|
||||
LPTSTR session_info_buffer = nullptr;
|
||||
DWORD session_info_buffer_size_bytes = 0;
|
||||
|
||||
WTSINFOEXW* wts_info = nullptr;
|
||||
LONG session_state = WTS_SESSIONSTATE_UNKNOWN;
|
||||
|
||||
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, session_id,
|
||||
wts_info_class, &session_info_buffer,
|
||||
&session_info_buffer_size_bytes)) {
|
||||
if (session_info_buffer_size_bytes > 0) {
|
||||
wts_info = (WTSINFOEXW*)session_info_buffer;
|
||||
if (wts_info->Level == 1) {
|
||||
session_state = wts_info->Data.WTSInfoExLevel1.SessionFlags;
|
||||
}
|
||||
}
|
||||
WTSFreeMemory(session_info_buffer);
|
||||
session_info_buffer = nullptr;
|
||||
}
|
||||
|
||||
return (session_state == WTS_SESSIONSTATE_LOCK);
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return session_manager_.IsScreenLocked();
|
||||
}
|
||||
|
||||
void DeviceInfo::RegisterScreenLockedListener(
|
||||
absl::string_view listener_name,
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> callback) {
|
||||
if (message_window_handle_ == nullptr) {
|
||||
instance_ = (HINSTANCE)GetModuleHandle(NULL);
|
||||
|
||||
WNDCLASS window_class;
|
||||
window_class.style = 0;
|
||||
window_class.lpfnWndProc = (WNDPROC)&WindowProc;
|
||||
window_class.cbClsExtra = 0;
|
||||
window_class.cbWndExtra = 0;
|
||||
window_class.hInstance = instance_;
|
||||
window_class.hIcon = nullptr;
|
||||
window_class.hCursor = nullptr;
|
||||
window_class.hbrBackground = nullptr;
|
||||
window_class.lpszMenuName = nullptr;
|
||||
window_class.lpszClassName = window_class_name;
|
||||
|
||||
registered_class_ = RegisterClass(&window_class);
|
||||
|
||||
message_window_handle_ = CreateWindow(
|
||||
MAKEINTATOM(registered_class_), // class atom from RegisterClass
|
||||
window_name, // window name
|
||||
0, // window style
|
||||
0, // initial x position of window
|
||||
0, // initial y position of window
|
||||
0, // width
|
||||
0, // height
|
||||
HWND_MESSAGE, // handle to the parent of window
|
||||
// (message-only window in this case)
|
||||
nullptr, // handle to a menu
|
||||
instance_, // handle to the instance of the module
|
||||
// associated to the window
|
||||
this); // pointer to be passed to the window for additional data
|
||||
|
||||
if (!message_window_handle_) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< __func__
|
||||
<< ": Failed to create message window for Nearby Sharing DLL.";
|
||||
}
|
||||
}
|
||||
|
||||
screen_locked_listeners_.emplace(listener_name, callback);
|
||||
absl::MutexLock lock(&mutex_);
|
||||
session_manager_.RegisterSessionListener(
|
||||
listener_name,
|
||||
[callback = std::move(callback)](SessionManager::SessionState state) {
|
||||
if (state == SessionManager::SessionState::kLock) {
|
||||
callback(api::DeviceInfo::ScreenStatus::kLocked);
|
||||
} else if (state == SessionManager::SessionState::kUnlock) {
|
||||
callback(api::DeviceInfo::ScreenStatus::kUnlocked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DeviceInfo::UnregisterScreenLockedListener(
|
||||
absl::string_view listener_name) {
|
||||
screen_locked_listeners_.erase(listener_name);
|
||||
absl::MutexLock lock(&mutex_);
|
||||
session_manager_.UnregisterSessionListener(listener_name);
|
||||
}
|
||||
|
||||
bool DeviceInfo::PreventSleep() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return session_manager_.PreventSleep();
|
||||
}
|
||||
|
||||
bool DeviceInfo::AllowSleep() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return session_manager_.AllowSleep();
|
||||
}
|
||||
|
||||
} // namespace windows
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Google LLC
|
||||
// Copyright 2021-2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,25 +15,21 @@
|
||||
#ifndef PLATFORM_IMPL_WINDOWS_DEVICE_INFO_H_
|
||||
#define PLATFORM_IMPL_WINDOWS_DEVICE_INFO_H_
|
||||
|
||||
#include <guiddef.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/implementation/device_info.h"
|
||||
#include "winrt/Windows.Foundation.h"
|
||||
#include "internal/platform/implementation/windows/session_manager.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace windows {
|
||||
|
||||
class DeviceInfo : public api::DeviceInfo {
|
||||
public:
|
||||
~DeviceInfo() override;
|
||||
~DeviceInfo() override = default;
|
||||
|
||||
std::optional<std::u16string> GetOsDeviceName() const override;
|
||||
api::DeviceInfo::DeviceType GetDeviceType() const override;
|
||||
@@ -55,13 +51,13 @@ class DeviceInfo : public api::DeviceInfo {
|
||||
absl::string_view listener_name,
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> callback) override;
|
||||
void UnregisterScreenLockedListener(absl::string_view listener_name) override;
|
||||
absl::flat_hash_map<std::string,
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)>>
|
||||
screen_locked_listeners_;
|
||||
|
||||
HINSTANCE instance_ = nullptr;
|
||||
ATOM registered_class_ = NULL;
|
||||
HWND message_window_handle_ = nullptr;
|
||||
bool PreventSleep() override;
|
||||
bool AllowSleep() override;
|
||||
|
||||
private:
|
||||
mutable absl::Mutex mutex_;
|
||||
SessionManager session_manager_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace windows
|
||||
|
||||
@@ -77,59 +77,12 @@ TEST(DeviceInfo, DISABLED_IsScreenLocked) {
|
||||
EXPECT_FALSE(DeviceInfo().IsScreenLocked());
|
||||
}
|
||||
|
||||
TEST(DeviceInfo, DISABLED_RegisterScreenLockedListener) {
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> listener_1 =
|
||||
[](api::DeviceInfo::ScreenStatus) {};
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> listener_2 =
|
||||
[](api::DeviceInfo::ScreenStatus) {};
|
||||
|
||||
DeviceInfo device_info;
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0);
|
||||
|
||||
device_info.RegisterScreenLockedListener("listener_1", listener_1);
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1);
|
||||
|
||||
device_info.RegisterScreenLockedListener("listener_2", listener_2);
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2);
|
||||
TEST(DeviceInfo, DISABLED_PreventSleep) {
|
||||
EXPECT_TRUE(DeviceInfo().PreventSleep());
|
||||
}
|
||||
|
||||
TEST(DeviceInfo, DISABLED_UnregisterScreenLockedListener) {
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> listener_1 =
|
||||
[](api::DeviceInfo::ScreenStatus) {};
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> listener_2 =
|
||||
[](api::DeviceInfo::ScreenStatus) {};
|
||||
|
||||
DeviceInfo device_info;
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0);
|
||||
|
||||
device_info.RegisterScreenLockedListener("listener_1", listener_1);
|
||||
device_info.RegisterScreenLockedListener("listener_2", listener_2);
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2);
|
||||
|
||||
device_info.UnregisterScreenLockedListener("listener_1");
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1);
|
||||
|
||||
device_info.UnregisterScreenLockedListener("listener_2");
|
||||
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0);
|
||||
}
|
||||
|
||||
TEST(DeviceInfo, DISABLED_UpdateScreenLockedListener) {
|
||||
absl::Notification notification;
|
||||
|
||||
api::DeviceInfo::ScreenStatus screen_locked_tracker =
|
||||
api::DeviceInfo::ScreenStatus::kUndetermined;
|
||||
|
||||
std::function<void(api::DeviceInfo::ScreenStatus)> listener =
|
||||
[&screen_locked_tracker,
|
||||
¬ification](api::DeviceInfo::ScreenStatus status) {
|
||||
screen_locked_tracker = api::DeviceInfo::ScreenStatus::kLocked;
|
||||
notification.Notify();
|
||||
};
|
||||
|
||||
DeviceInfo device_info;
|
||||
device_info.RegisterScreenLockedListener("listener", listener);
|
||||
EXPECT_TRUE(notification.WaitForNotificationWithTimeout(absl::Seconds(5)));
|
||||
EXPECT_EQ(screen_locked_tracker, api::DeviceInfo::ScreenStatus::kLocked);
|
||||
TEST(DeviceInfo, DISABLED_AllowSleep) {
|
||||
EXPECT_TRUE(DeviceInfo().AllowSleep());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
#include "internal/platform/implementation/windows/session_manager.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wtsapi32.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/const_init.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/synchronization/notification.h"
|
||||
#include "internal/platform/implementation/windows/submittable_executor.h"
|
||||
#include "internal/platform/logging.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace windows {
|
||||
namespace {
|
||||
|
||||
constexpr char kMessageWindowClass[] = "Nearby Message Window Class";
|
||||
constexpr char kMessageWindowTitle[] = "Nearby Message Dummy Window";
|
||||
|
||||
LRESULT CALLBACK NearbyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
switch (uMsg) {
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
case WM_WTSSESSION_CHANGE:
|
||||
if (wParam == WTS_SESSION_LOCK) {
|
||||
SessionManager::NotifySessionState(SessionManager::SessionState::kLock);
|
||||
} else if (wParam == WTS_SESSION_UNLOCK) {
|
||||
SessionManager::NotifySessionState(
|
||||
SessionManager::SessionState::kUnlock);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
HWND CreateNearbyWindow() {
|
||||
HINSTANCE instance = (HINSTANCE)GetModuleHandle(nullptr);
|
||||
|
||||
WNDCLASS window_class = {};
|
||||
window_class.lpfnWndProc = NearbyWindowProc;
|
||||
window_class.hInstance = instance;
|
||||
window_class.lpszClassName = kMessageWindowClass;
|
||||
|
||||
RegisterClass(&window_class);
|
||||
|
||||
HWND hwnd =
|
||||
CreateWindowA(kMessageWindowClass, kMessageWindowTitle, /*dwStyle=*/0,
|
||||
/*X=*/0, /*Y=*/0, /*nWidth=*/0, /*nHeight=*/0, HWND_MESSAGE,
|
||||
/*hMenu=*/nullptr, instance,
|
||||
/*lpParam=*/nullptr);
|
||||
|
||||
return hwnd;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Initialize class static variables.
|
||||
ABSL_CONST_INIT absl::Mutex SessionManager::session_mutex_{absl::kConstInit};
|
||||
HWND SessionManager::session_hwnd_ = nullptr;
|
||||
SubmittableExecutor* SessionManager::session_thread_ = nullptr;
|
||||
absl::flat_hash_map<std::string,
|
||||
absl::AnyInvocable<void(SessionManager::SessionState)>>*
|
||||
SessionManager::session_callbacks_ = nullptr;
|
||||
|
||||
SessionManager::~SessionManager() { StopSession(); }
|
||||
|
||||
bool SessionManager::RegisterSessionListener(
|
||||
absl::string_view listener_name,
|
||||
absl::AnyInvocable<void(SessionState)> callback) {
|
||||
absl::MutexLock lock(&session_mutex_);
|
||||
NEARBY_LOGS(INFO) << __func__ << ": Registering listener: " << listener_name;
|
||||
|
||||
// Create session thread if no running thread.
|
||||
if (session_thread_ == nullptr) {
|
||||
absl::Notification notification;
|
||||
session_thread_ = new SubmittableExecutor();
|
||||
session_callbacks_ = new absl::flat_hash_map<
|
||||
std::string, absl::AnyInvocable<void(SessionManager::SessionState)>>();
|
||||
session_thread_->Execute(
|
||||
[this, ¬ification]() { StartSession(notification); });
|
||||
notification.WaitForNotification();
|
||||
if (session_thread_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (session_callbacks_->contains(listener_name) ||
|
||||
listeners_.contains(listener_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
session_callbacks_->emplace(listener_name, std::move(callback));
|
||||
listeners_.emplace(listener_name);
|
||||
NEARBY_LOGS(INFO) << __func__ << ": Session listener: " << listener_name
|
||||
<< " is registered.";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionManager::UnregisterSessionListener(
|
||||
absl::string_view listener_name) {
|
||||
absl::MutexLock lock(&session_mutex_);
|
||||
NEARBY_LOGS(INFO) << __func__
|
||||
<< ": Unregistering listener: " << listener_name;
|
||||
if (session_thread_ == nullptr) {
|
||||
NEARBY_LOGS(ERROR) << __func__ << ": No running listener.";
|
||||
return false;
|
||||
}
|
||||
if (!session_callbacks_->contains(listener_name) ||
|
||||
!listeners_.contains(listener_name)) {
|
||||
NEARBY_LOGS(ERROR) << __func__
|
||||
<< ": No listener with name:" << listener_name;
|
||||
return false;
|
||||
}
|
||||
session_callbacks_->erase(listener_name);
|
||||
listeners_.erase(listener_name);
|
||||
|
||||
if (!session_callbacks_->empty()) {
|
||||
NEARBY_LOGS(INFO) << __func__ << ": Session listener: " << listener_name
|
||||
<< " is unregistered.";
|
||||
return true;
|
||||
}
|
||||
|
||||
CleanUp();
|
||||
NEARBY_LOGS(INFO) << __func__ << ": Session listener: " << listener_name
|
||||
<< " is unregistered.";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionManager::IsScreenLocked() const {
|
||||
DWORD session_id = WTSGetActiveConsoleSessionId();
|
||||
WTS_INFO_CLASS wts_info_class = WTSSessionInfoEx;
|
||||
LPTSTR session_info_buffer = nullptr;
|
||||
DWORD session_info_buffer_size_bytes = 0;
|
||||
|
||||
WTSINFOEXW* wts_info = nullptr;
|
||||
LONG session_state = WTS_SESSIONSTATE_UNKNOWN;
|
||||
|
||||
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, session_id,
|
||||
wts_info_class, &session_info_buffer,
|
||||
&session_info_buffer_size_bytes)) {
|
||||
if (session_info_buffer_size_bytes > 0) {
|
||||
wts_info = (WTSINFOEXW*)session_info_buffer;
|
||||
if (wts_info->Level == 1) {
|
||||
session_state = wts_info->Data.WTSInfoExLevel1.SessionFlags;
|
||||
}
|
||||
}
|
||||
WTSFreeMemory(session_info_buffer);
|
||||
session_info_buffer = nullptr;
|
||||
}
|
||||
|
||||
return (session_state == WTS_SESSIONSTATE_LOCK);
|
||||
}
|
||||
|
||||
bool SessionManager::PreventSleep() const {
|
||||
EXECUTION_STATE execution_state =
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
|
||||
if (execution_state == 0) {
|
||||
NEARBY_LOGS(ERROR) << __func__
|
||||
<< ": Failed to set execution state of the thread.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionManager::AllowSleep() const {
|
||||
EXECUTION_STATE execution_state = SetThreadExecutionState(ES_CONTINUOUS);
|
||||
if (execution_state == 0) {
|
||||
NEARBY_LOGS(ERROR) << __func__
|
||||
<< ": Failed to set execution state of the thread.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SessionManager::NotifySessionState(SessionState state) {
|
||||
NEARBY_LOGS(INFO) << __func__
|
||||
<< ": Notifying session state: " << static_cast<int>(state);
|
||||
if (state == SessionManager::SessionState::kLock) {
|
||||
absl::MutexLock lock(&session_mutex_);
|
||||
for (auto& it : *SessionManager::session_callbacks_) {
|
||||
it.second(SessionManager::SessionState::kLock);
|
||||
}
|
||||
} else if (state == SessionManager::SessionState::kUnlock) {
|
||||
absl::MutexLock lock(&session_mutex_);
|
||||
for (auto& it : *SessionManager::session_callbacks_) {
|
||||
it.second(SessionManager::SessionState::kUnlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionManager::StartSession(absl::Notification& notification) {
|
||||
session_hwnd_ = CreateNearbyWindow();
|
||||
if (session_hwnd_ == nullptr) {
|
||||
NEARBY_LOGS(ERROR) << __func__ << ": Failed to create session Window.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!WTSRegisterSessionNotification(session_hwnd_, NOTIFY_FOR_THIS_SESSION)) {
|
||||
NEARBY_LOGS(ERROR) << __func__
|
||||
<< ":Failed to register session notification.";
|
||||
return;
|
||||
}
|
||||
|
||||
notification.Notify();
|
||||
|
||||
NEARBY_LOGS(INFO) << __func__ << ": Session thread started.";
|
||||
|
||||
// Main message loop
|
||||
MSG msg = {};
|
||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||
while (GetMessage(&msg, session_hwnd_, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
if (!WTSUnRegisterSessionNotification(session_hwnd_)) {
|
||||
NEARBY_LOGS(ERROR) << __func__
|
||||
<< ": Failed to register session notification.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UnregisterClassA(/*lpClassName=*/kMessageWindowClass,
|
||||
/*hInstance=*/(HINSTANCE)GetModuleHandle(nullptr))) {
|
||||
NEARBY_LOGS(ERROR) << __func__ << ": Failed to unregister window class.";
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO) << __func__ << ": Completed Message loop.";
|
||||
}
|
||||
|
||||
void SessionManager::StopSession() {
|
||||
if (session_thread_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
absl::MutexLock lock(&session_mutex_);
|
||||
if (session_thread_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& it : listeners_) {
|
||||
session_callbacks_->erase(it);
|
||||
}
|
||||
|
||||
listeners_.clear();
|
||||
if (!session_callbacks_->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
void SessionManager::CleanUp() {
|
||||
if (session_hwnd_ != nullptr) {
|
||||
// Send message to destroy message window.
|
||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
||||
PostMessageA(session_hwnd_, WM_DESTROY, 0, 0);
|
||||
}
|
||||
|
||||
session_thread_->Shutdown();
|
||||
delete session_thread_;
|
||||
delete session_callbacks_;
|
||||
session_thread_ = nullptr;
|
||||
session_callbacks_ = nullptr;
|
||||
session_hwnd_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace windows
|
||||
} // namespace nearby
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SESSION_MANAGER_H_
|
||||
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SESSION_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/synchronization/notification.h"
|
||||
#include "internal/platform/implementation/windows/submittable_executor.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace windows {
|
||||
|
||||
// SessionManager provides methods to access/control platform session.
|
||||
class SessionManager {
|
||||
public:
|
||||
enum class SessionState { kLock, kUnlock };
|
||||
~SessionManager();
|
||||
|
||||
// Setups session listener.
|
||||
// listener_name - Listener name. it should be unique in the SDK level.
|
||||
// callback - It will be called when session state changed, such as
|
||||
// lock/unlock screen.
|
||||
bool RegisterSessionListener(absl::string_view listener_name,
|
||||
absl::AnyInvocable<void(SessionState)> callback);
|
||||
|
||||
// Removes session listener by its name.
|
||||
bool UnregisterSessionListener(absl::string_view listener_name);
|
||||
|
||||
bool IsScreenLocked() const;
|
||||
|
||||
// Prevents the Windows device from sleeping state.
|
||||
bool PreventSleep() const;
|
||||
|
||||
// Allows the Windows device to sleep state.
|
||||
bool AllowSleep() const;
|
||||
|
||||
static void NotifySessionState(SessionState state);
|
||||
|
||||
private:
|
||||
void StartSession(absl::Notification& notification);
|
||||
void StopSession();
|
||||
void CleanUp();
|
||||
|
||||
absl::flat_hash_set<std::string> listeners_;
|
||||
|
||||
// Static class members
|
||||
static absl::Mutex session_mutex_;
|
||||
static HWND session_hwnd_;
|
||||
static SubmittableExecutor* session_thread_;
|
||||
static absl::flat_hash_map<
|
||||
std::string, absl::AnyInvocable<void(SessionManager::SessionState)>>*
|
||||
session_callbacks_;
|
||||
};
|
||||
|
||||
} // namespace windows
|
||||
} // namespace nearby
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SESSION_MANAGER_H_
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 Google LLC
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -14,9 +14,10 @@
|
||||
|
||||
#include "internal/platform/implementation/windows/timer.h"
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include <memory>
|
||||
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/logging.h"
|
||||
#include "internal/platform/mutex_lock.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace windows {
|
||||
@@ -25,7 +26,7 @@ Timer::~Timer() { Stop(); }
|
||||
|
||||
bool Timer::Create(int delay, int interval,
|
||||
absl::AnyInvocable<void()> callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
absl::MutexLock lock(&mutex_);
|
||||
|
||||
if ((delay < 0) || (interval < 0)) {
|
||||
NEARBY_LOGS(WARNING) << "Delay and interval shouldn\'t be negative value.";
|
||||
@@ -45,17 +46,10 @@ bool Timer::Create(int delay, int interval,
|
||||
delay_ = delay;
|
||||
interval_ = interval;
|
||||
callback_ = std::move(callback);
|
||||
timer_callback_ = [&]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (timer_queue_handle_ != nullptr && callback_ != nullptr) {
|
||||
callback_();
|
||||
}
|
||||
};
|
||||
|
||||
if (!CreateTimerQueueTimer(&handle_, timer_queue_handle_,
|
||||
static_cast<WAITORTIMERCALLBACK>(TimerRoutine),
|
||||
&timer_callback_, delay, interval,
|
||||
WT_EXECUTEDEFAULT)) {
|
||||
&callback_, delay, interval, WT_EXECUTEDEFAULT)) {
|
||||
if (!DeleteTimerQueueEx(timer_queue_handle_, nullptr)) {
|
||||
NEARBY_LOGS(ERROR) << "Failed to create timer in timer queue.";
|
||||
}
|
||||
@@ -67,7 +61,7 @@ bool Timer::Create(int delay, int interval,
|
||||
}
|
||||
|
||||
bool Timer::Stop() {
|
||||
MutexLock lock(&mutex_);
|
||||
absl::MutexLock lock(&mutex_);
|
||||
|
||||
if (timer_queue_handle_ == nullptr) {
|
||||
return true;
|
||||
@@ -92,7 +86,7 @@ bool Timer::Stop() {
|
||||
}
|
||||
|
||||
bool Timer::FireNow() {
|
||||
MutexLock lock(&mutex_);
|
||||
absl::MutexLock lock(&mutex_);
|
||||
|
||||
if (!timer_queue_handle_ || !callback_) {
|
||||
return false;
|
||||
@@ -107,7 +101,8 @@ bool Timer::FireNow() {
|
||||
<< "Failed to fire the task due to cannot create executor.";
|
||||
return false;
|
||||
}
|
||||
task_executor_->Execute([&]() { timer_callback_(); });
|
||||
|
||||
task_executor_->Execute([&]() { callback_(); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 Google LLC
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -20,10 +20,9 @@
|
||||
#include <memory>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "internal/platform/implementation/timer.h"
|
||||
#include "internal/platform/implementation/windows/submittable_executor.h"
|
||||
#include "internal/platform/mutex.h"
|
||||
|
||||
namespace nearby {
|
||||
namespace windows {
|
||||
@@ -42,11 +41,10 @@ class Timer : public api::Timer {
|
||||
private:
|
||||
static void CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired);
|
||||
|
||||
mutable RecursiveMutex mutex_;
|
||||
mutable absl::Mutex mutex_;
|
||||
int delay_ ABSL_GUARDED_BY(mutex_);
|
||||
int interval_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::AnyInvocable<void()> callback_;
|
||||
absl::AnyInvocable<void()> timer_callback_ = nullptr;
|
||||
HANDLE handle_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
HANDLE timer_queue_handle_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
std::unique_ptr<SubmittableExecutor> task_executor_ ABSL_GUARDED_BY(mutex_) =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 Google LLC
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -14,8 +14,12 @@
|
||||
|
||||
#include "internal/platform/implementation/timer.h"
|
||||
|
||||
#include <chrono> // NOLINT
|
||||
// NOLINT
|
||||
#include <memory>
|
||||
#include <thread> // NOLINT
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/implementation/platform.h"
|
||||
|
||||
namespace nearby {
|
||||
@@ -42,7 +46,7 @@ TEST(Timer, DISABLED_TestRepeatTimer) {
|
||||
|
||||
ASSERT_TRUE(timer != nullptr);
|
||||
EXPECT_TRUE(timer->Create(300, 300, [&]() { ++count; }));
|
||||
absl::SleepFor(absl::Seconds(1));
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
EXPECT_TRUE(timer->Stop());
|
||||
EXPECT_EQ(count, 3);
|
||||
}
|
||||
@@ -53,27 +57,8 @@ TEST(Timer, DISABLED_TestFireNow) {
|
||||
auto timer = nearby::api::ImplementationPlatform::CreateTimer();
|
||||
|
||||
EXPECT_TRUE(timer != nullptr);
|
||||
EXPECT_TRUE(timer->Create(3000, 3000, [&]() {
|
||||
absl::SleepFor(absl::Milliseconds(1000));
|
||||
++count;
|
||||
}));
|
||||
EXPECT_TRUE(timer->Create(3000, 3000, [&]() { ++count; }));
|
||||
EXPECT_TRUE(timer->FireNow());
|
||||
absl::SleepFor(absl::Milliseconds(100));
|
||||
EXPECT_TRUE(timer->Stop());
|
||||
EXPECT_EQ(count, 1);
|
||||
}
|
||||
|
||||
TEST(Timer, DISABLED_TestWaitForRunningCallback) {
|
||||
int count = 0;
|
||||
|
||||
auto timer = nearby::api::ImplementationPlatform::CreateTimer();
|
||||
|
||||
EXPECT_TRUE(timer != nullptr);
|
||||
EXPECT_TRUE(timer->Create(1000, 0, [&]() {
|
||||
absl::SleepFor(absl::Milliseconds(3000));
|
||||
++count;
|
||||
}));
|
||||
absl::SleepFor(absl::Milliseconds(1050));
|
||||
EXPECT_TRUE(timer->Stop());
|
||||
EXPECT_EQ(count, 1);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
#define PLATFORM_IMPL_WINDOWS_WIFI_HOTSPOT_H_
|
||||
|
||||
// Windows headers
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <wlanapi.h>
|
||||
|
||||
// Standard C/C++ headers
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <string>
|
||||
|
||||
// Nearby connections headers
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "internal/platform/implementation/wifi_hotspot.h"
|
||||
#include "internal/platform/implementation/windows/scheduled_executor.h"
|
||||
#include "internal/platform/implementation/windows/submittable_executor.h"
|
||||
@@ -123,7 +124,7 @@ class WifiHotspotSocket : public api::WifiHotspotSocket {
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
enum class SocketType {kWinRTSocket = 0, kWin32Socket};
|
||||
enum class SocketType { kWinRTSocket = 0, kWin32Socket };
|
||||
// A simple wrapper to handle input stream of socket
|
||||
class SocketInputStream : public InputStream {
|
||||
public:
|
||||
@@ -204,6 +205,10 @@ class WifiHotspotServerSocket : public api::WifiHotspotServerSocket {
|
||||
bool listen();
|
||||
|
||||
private:
|
||||
static constexpr int kSocketEventsCount = 2;
|
||||
static constexpr int kSocketEventListen = 0;
|
||||
static constexpr int kSocketEventClose = 1;
|
||||
|
||||
// The listener is accepting incoming connections
|
||||
fire_and_forget Listener_ConnectionReceived(
|
||||
StreamSocketListener listener,
|
||||
@@ -214,6 +219,8 @@ class WifiHotspotServerSocket : public api::WifiHotspotServerSocket {
|
||||
// Retrieves hotspot IP address from local machine
|
||||
std::string GetHotspotIpAddress() const;
|
||||
|
||||
void SocketErrorNotice(absl::string_view reason);
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
absl::CondVar cond_;
|
||||
SubmittableExecutor submittable_executor_;
|
||||
@@ -225,6 +232,13 @@ class WifiHotspotServerSocket : public api::WifiHotspotServerSocket {
|
||||
std::deque<SOCKET> pending_client_sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
SOCKET listen_socket_ = INVALID_SOCKET;
|
||||
SOCKET client_socket_ = INVALID_SOCKET;
|
||||
|
||||
// closesocket cannot trigger FD_CLOSE on listener socket. In order to avoid
|
||||
// blocking in WSAWaitForMultipleEvents, we use a socket event to trigger
|
||||
// WSAWaitForMultipleEvents safely.
|
||||
// The socket_events_ has 2 events, the first one is to handle normal socket
|
||||
// event, and the second one is to handle event to close the socket manually.
|
||||
WSAEVENT socket_events_[kSocketEventsCount];
|
||||
// Close notifier
|
||||
absl::AnyInvocable<void()> close_notifier_ = nullptr;
|
||||
|
||||
|
||||
@@ -36,7 +36,11 @@ namespace {
|
||||
using ::winrt::Windows::Networking::Sockets::SocketQualityOfService;
|
||||
} // namespace
|
||||
|
||||
WifiHotspotServerSocket::WifiHotspotServerSocket(int port) : port_(port) {}
|
||||
WifiHotspotServerSocket::WifiHotspotServerSocket(int port) : port_(port) {
|
||||
for (auto &it : socket_events_) {
|
||||
it = WSA_INVALID_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
WifiHotspotServerSocket::~WifiHotspotServerSocket() { Close(); }
|
||||
|
||||
@@ -55,7 +59,7 @@ std::string WifiHotspotServerSocket::GetIPAddress() const {
|
||||
|
||||
std::string hotspot_ip_address = GetHotspotIpAddress();
|
||||
NEARBY_LOGS(INFO) << __func__
|
||||
<< ": Return hotspot IP address: " << hotspot_ip_address;
|
||||
<< ": Return hotspot IP address: " << hotspot_ip_address;
|
||||
|
||||
return hotspot_ip_address;
|
||||
}
|
||||
@@ -126,18 +130,26 @@ Exception WifiHotspotServerSocket::Close() {
|
||||
kEnableHotspotWin32Socket)) {
|
||||
if (listen_socket_ != INVALID_SOCKET) {
|
||||
NEARBY_LOGS(INFO) << ": Close listen_socket_: " << listen_socket_;
|
||||
// Trigger close event manually
|
||||
WSASetEvent(socket_events_[kSocketEventClose]);
|
||||
shutdown(listen_socket_, 2);
|
||||
shutdown(client_socket_, 2);
|
||||
closesocket(listen_socket_);
|
||||
closesocket(client_socket_);
|
||||
listen_socket_ = INVALID_SOCKET;
|
||||
client_socket_ = INVALID_SOCKET;
|
||||
for (const auto &pending_socket : pending_client_sockets_) {
|
||||
if (pending_socket != INVALID_SOCKET) closesocket(pending_socket);
|
||||
}
|
||||
submittable_executor_.Shutdown();
|
||||
listen_socket_ = INVALID_SOCKET;
|
||||
client_socket_ = INVALID_SOCKET;
|
||||
for (auto &it : socket_events_) {
|
||||
WSACloseEvent(it);
|
||||
it = WSA_INVALID_EVENT;
|
||||
}
|
||||
WSACleanup();
|
||||
|
||||
pending_client_sockets_ = {};
|
||||
}
|
||||
submittable_executor_.Shutdown();
|
||||
} else {
|
||||
if (stream_socket_listener_ != nullptr) {
|
||||
stream_socket_listener_.ConnectionReceived(listener_event_token_);
|
||||
@@ -254,14 +266,16 @@ bool WifiHotspotServerSocket::SetupServerSocketWinRT() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks for SOCKET_ERROR, this error can come up when trying to bind, listen,
|
||||
// Getsockname, WSACreateEvent, WSAEventSelect etc.
|
||||
void SocketErrorNotice(SOCKET socket_to_close, const char *action) {
|
||||
// const char *actionAttempted = action;
|
||||
NEARBY_LOGS(WARNING) << "socket error. " << action
|
||||
void WifiHotspotServerSocket::SocketErrorNotice(absl::string_view reason) {
|
||||
NEARBY_LOGS(WARNING) << "socket error. " << reason
|
||||
<< " failed with error: " << WSAGetLastError();
|
||||
|
||||
closesocket(socket_to_close);
|
||||
for (auto &it : socket_events_) {
|
||||
if (it != WSA_INVALID_EVENT) {
|
||||
WSACloseEvent(it);
|
||||
it = WSA_INVALID_EVENT;
|
||||
}
|
||||
}
|
||||
closesocket(listen_socket_);
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
@@ -293,7 +307,7 @@ bool WifiHotspotServerSocket::SetupServerSocketWinSock() {
|
||||
sizeof(flag));
|
||||
if (bind(listen_socket_, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) ==
|
||||
SOCKET_ERROR) {
|
||||
SocketErrorNotice(listen_socket_, "Bind");
|
||||
SocketErrorNotice("Bind");
|
||||
return false;
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "Bind socket successful";
|
||||
@@ -302,50 +316,63 @@ bool WifiHotspotServerSocket::SetupServerSocketWinSock() {
|
||||
memset(&serv_addr, 0, size);
|
||||
if (getsockname(listen_socket_, (struct sockaddr *)&serv_addr, &size) ==
|
||||
SOCKET_ERROR) {
|
||||
SocketErrorNotice(listen_socket_, "Getsockname");
|
||||
SocketErrorNotice("Getsockname");
|
||||
return false;
|
||||
}
|
||||
port_ = ntohs(serv_addr.sin_port);
|
||||
NEARBY_LOGS(INFO) << "Hotspot Server bound to port: " << port_;
|
||||
|
||||
socket_event = WSACreateEvent();
|
||||
if (socket_event == nullptr) {
|
||||
SocketErrorNotice(listen_socket_, "WSACreateEvent");
|
||||
socket_events_[kSocketEventListen] = WSACreateEvent();
|
||||
if (socket_events_[kSocketEventListen] == WSA_INVALID_EVENT) {
|
||||
SocketErrorNotice("WSACreateEvent");
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_events_[kSocketEventClose] = WSACreateEvent();
|
||||
if (socket_events_[kSocketEventClose] == WSA_INVALID_EVENT) {
|
||||
SocketErrorNotice("WSACreateEvent");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Associate event types FD_ACCEPT and FD_CLOSE with the listen_socket_ and
|
||||
// socket_event
|
||||
if (WSAEventSelect(listen_socket_, socket_event, FD_ACCEPT | FD_CLOSE) ==
|
||||
SOCKET_ERROR) {
|
||||
SocketErrorNotice(listen_socket_, "WSAEventSelect");
|
||||
if (WSAEventSelect(listen_socket_, socket_events_[kSocketEventListen],
|
||||
FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR) {
|
||||
SocketErrorNotice("WSAEventSelect");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (::listen(listen_socket_, SOMAXCONN) == SOCKET_ERROR) {
|
||||
SocketErrorNotice(listen_socket_, "Listen");
|
||||
SocketErrorNotice("Listen");
|
||||
return false;
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "Hotspot Server Socket " << listen_socket_
|
||||
<< " started to listen with socket event: " << socket_event;
|
||||
|
||||
submittable_executor_.Execute([this, socket_event]() {
|
||||
submittable_executor_.Execute([this]() {
|
||||
DWORD index;
|
||||
WSANETWORKEVENTS network_events;
|
||||
// Wait for network events on all sockets
|
||||
index =
|
||||
WSAWaitForMultipleEvents(1, &socket_event, FALSE, WSA_INFINITE, FALSE);
|
||||
index = WSAWaitForMultipleEvents(kSocketEventsCount, socket_events_, FALSE,
|
||||
WSA_INFINITE, FALSE);
|
||||
|
||||
NEARBY_LOGS(INFO) << "Hotspot Server Socket " << listen_socket_
|
||||
<< " received event: " << socket_event;
|
||||
<< " received event index: " << index;
|
||||
if (index == WSA_WAIT_TIMEOUT || index == WSA_WAIT_FAILED) {
|
||||
NEARBY_LOGS(INFO) << "Hotspot Server Socket timout or failed ";
|
||||
return false;
|
||||
}
|
||||
|
||||
index = index - WSA_WAIT_EVENT_0;
|
||||
if (index == kSocketEventClose) {
|
||||
// the socket is closed by SDK
|
||||
NEARBY_LOGS(INFO) << "listner socket is closed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate through all events and enumerate
|
||||
if (WSAEnumNetworkEvents(listen_socket_, socket_event, &network_events) ==
|
||||
SOCKET_ERROR) {
|
||||
if (WSAEnumNetworkEvents(listen_socket_, socket_events_[index],
|
||||
&network_events) == SOCKET_ERROR) {
|
||||
NEARBY_LOGS(INFO) << "Iterate through all events failed";
|
||||
return false;
|
||||
}
|
||||
@@ -361,7 +388,8 @@ bool WifiHotspotServerSocket::SetupServerSocketWinSock() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WSAEventSelect(listen_socket_, socket_event, 0) == SOCKET_ERROR) {
|
||||
if (WSAEventSelect(listen_socket_, socket_events_[kSocketEventListen],
|
||||
0) == SOCKET_ERROR) {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< "Remove association between listen_socket_ and event failed: "
|
||||
<< WSAGetLastError();
|
||||
|
||||
Reference in New Issue
Block a user