mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Add GATT client
PiperOrigin-RevId: 557919250
This commit is contained in:
committed by
Copybara-Service
parent
78dd1bab2c
commit
4aa52b45f9
@@ -23,4 +23,7 @@ typedef NS_ERROR_ENUM(GNCBLEErrorDomain, GNCBLEError){
|
||||
GNCBLEErrorDuplicateCharacteristic,
|
||||
GNCBLEErrorInvalidServiceData,
|
||||
GNCBLEErrorAlreadyAdvertising,
|
||||
GNCBLEErrorInvalidCharacteristic,
|
||||
GNCBLEErrorAlreadyDiscoveringSpecifiedCharacteristics,
|
||||
GNCBLEErrorAlreadyReadingCharacteristic,
|
||||
};
|
||||
|
||||
@@ -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:(CBPeripheral *)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:(CBPeripheral *)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
|
||||
@@ -41,8 +41,8 @@ typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
* @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.
|
||||
* @param completionHandler Called on the main queue with the characteristic if successfully created
|
||||
* or an error if one has occured.
|
||||
* @param completionHandler Called on a private queue with the characteristic if successfully
|
||||
* created or an error if one has occured.
|
||||
*/
|
||||
- (void)createCharacteristicWithServiceID:(CBUUID *)serviceUUID
|
||||
characteristicUUID:(CBUUID *)characteristicUUID
|
||||
@@ -56,8 +56,8 @@ typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
*
|
||||
* @param characteristic The characteristic to update.
|
||||
* @param value The new value for the characteristic.
|
||||
* @param completionHandler Called on the main queue with @c nil if successfully updated or an error
|
||||
* if one has occured.
|
||||
* @param completionHandler Called on a private queue with @c nil if successfully updated or an
|
||||
* error if one has occured.
|
||||
*/
|
||||
- (void)updateCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
value:(nullable NSData *)value
|
||||
@@ -76,8 +76,8 @@ typedef void (^GNCStartAdvertisingCompletionHandler)(NSError *_Nullable error);
|
||||
* 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 the main queue with @c nil if successfully started advertising
|
||||
* or an error if one has occured.
|
||||
* @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;
|
||||
|
||||
@@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static char *const kGNCBLEGATTServerQueueLabel = "com.nearby.GNCBLEGATTServer";
|
||||
|
||||
@interface GNCBLEGATTServer () <GNCPeripheralManagerDelegate, CBPeripheralManagerDelegate>
|
||||
@interface GNCBLEGATTServer () <GNCPeripheralManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCBLEGATTServer {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -124,7 +124,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* 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 {
|
||||
|
||||
@@ -20,7 +20,9 @@ objc_library(
|
||||
srcs = [
|
||||
"BLEv2/GNCBLEError.m",
|
||||
"BLEv2/GNCBLEGATTCharacteristic.m",
|
||||
"BLEv2/GNCBLEGATTClient.m",
|
||||
"BLEv2/GNCBLEGATTServer.m",
|
||||
"BLEv2/GNCPeripheral.m",
|
||||
"BLEv2/GNCPeripheralManager.m",
|
||||
"BLEv2/NSData+GNCWebSafeBase64.m",
|
||||
"Ble/GNCMBleCentral.m",
|
||||
@@ -40,7 +42,9 @@ objc_library(
|
||||
hdrs = [
|
||||
"BLEv2/GNCBLEError.h",
|
||||
"BLEv2/GNCBLEGATTCharacteristic.h",
|
||||
"BLEv2/GNCBLEGATTClient.h",
|
||||
"BLEv2/GNCBLEGATTServer.h",
|
||||
"BLEv2/GNCPeripheral.h",
|
||||
"BLEv2/GNCPeripheralManager.h",
|
||||
"BLEv2/NSData+GNCWebSafeBase64.h",
|
||||
"Ble/GNCMBleCentral.h",
|
||||
|
||||
@@ -24,12 +24,15 @@ objc_library(
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"GNCBLEGATTCharacteristicTest.mm",
|
||||
"GNCBLEGATTClientTest.m",
|
||||
"GNCBLEGATTServer+Testing.h",
|
||||
"GNCBLEGATTServerTest.m",
|
||||
"GNCBLEUtilsTest.mm",
|
||||
"GNCBleTest.mm",
|
||||
"GNCBluetoothAdapterTest.mm",
|
||||
"GNCCryptoTest.mm",
|
||||
"GNCFakePeripheral.h",
|
||||
"GNCFakePeripheral.m",
|
||||
"GNCFakePeripheralManager.h",
|
||||
"GNCFakePeripheralManager.m",
|
||||
"GNCIPAddressTest.mm",
|
||||
@@ -40,6 +43,7 @@ objc_library(
|
||||
"NSData+GNCWebSafeBase64Test.m",
|
||||
],
|
||||
deps = [
|
||||
":GNCBLEGATTClient_Testing",
|
||||
"//internal/platform:base",
|
||||
"//internal/platform/implementation:comm",
|
||||
"//internal/platform/implementation:platform",
|
||||
@@ -54,6 +58,15 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "GNCBLEGATTClient_Testing",
|
||||
hdrs = ["GNCBLEGATTClient+Testing.h"],
|
||||
deps = [
|
||||
"//internal/platform/implementation/apple/Mediums",
|
||||
"//third_party/apple_frameworks:Foundation",
|
||||
],
|
||||
)
|
||||
|
||||
ios_unit_test(
|
||||
name = "PlatformTests",
|
||||
minimum_os_version = IOS_MINIMUM_OS,
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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,118 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
@synthesize peripheralDelegate;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_services = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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
|
||||
Reference in New Issue
Block a user