mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Add GATT server
PiperOrigin-RevId: 549080231
This commit is contained in:
committed by
Copybara-Service
parent
99053e4df3
commit
0f8e5283a4
@@ -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.
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class GNCBLEGATTCharacteristic;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@interface GNCBLEGATTServer : NSObject
|
||||
|
||||
/**
|
||||
* Creates a characteristic and adds it to the GATT server.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
- (nullable GNCBLEGATTCharacteristic *)
|
||||
createCharacteristicWithServiceID:(CBUUID *)serviceUUID
|
||||
characteristicUUID:(CBUUID *)characteristicUUID
|
||||
permissions:(CBAttributePermissions)permissions
|
||||
properties:(CBCharacteristicProperties)properties;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
- (BOOL)updateCharacteristic:(GNCBLEGATTCharacteristic *)characteristic
|
||||
value:(nullable NSData *)value;
|
||||
|
||||
/** Removes all published services from the local GATT database. */
|
||||
- (void)stop;
|
||||
|
||||
/**
|
||||
* 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 method blocks until the service data is being advertised or an error occurs.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
- (BOOL)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,300 @@
|
||||
// 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/GNCBLEGATTServer.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.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"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// An arbitrary timeout that should be pretty lenient.
|
||||
static NSTimeInterval const GNCBLEGATTServerTimeoutInSeconds = 2;
|
||||
|
||||
@interface GNCBLEGATTServer () <GNCPeripheralManagerDelegate, CBPeripheralManagerDelegate>
|
||||
@end
|
||||
|
||||
@implementation GNCBLEGATTServer {
|
||||
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.
|
||||
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 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
- (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) {
|
||||
_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];
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
[_condition unlock];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
[_condition lock];
|
||||
[_services removeAllObjects];
|
||||
[_condition unlock];
|
||||
[_peripheralManager removeAllServices];
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
// Ensure we are in a powered on state.
|
||||
if (![self waitUntilPoweredOn]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
// 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.
|
||||
if (encoded.length > 22) {
|
||||
encoded = [encoded substringToIndex:22];
|
||||
}
|
||||
|
||||
[_peripheralManager startAdvertising:@{
|
||||
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;
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (BOOL)waitUntilPoweredOn {
|
||||
NSDate *timeLimit = [NSDate dateWithTimeIntervalSinceNow:GNCBLEGATTServerTimeoutInSeconds];
|
||||
BOOL timedOut = NO;
|
||||
[_condition lock];
|
||||
while (_peripheralManager.state != CBManagerStatePoweredOn && !timedOut) {
|
||||
timedOut = ![_condition waitUntilDate:timeLimit];
|
||||
}
|
||||
[_condition unlock];
|
||||
return _peripheralManager.state == CBManagerStatePoweredOn;
|
||||
}
|
||||
|
||||
#pragma mark - GNCPeripheralManagerDelegate
|
||||
|
||||
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
|
||||
[_condition lock];
|
||||
[_condition signal];
|
||||
[_condition unlock];
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
[_condition lock];
|
||||
[_condition signal];
|
||||
[_condition unlock];
|
||||
}
|
||||
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
[_condition lock];
|
||||
[_pendingServiceAdditions removeObject:service.UUID];
|
||||
if (error) {
|
||||
[_serviceErrors setObject:error forKey:service.UUID];
|
||||
}
|
||||
[_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];
|
||||
[_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];
|
||||
}
|
||||
|
||||
#pragma mark - CBPeripheralManagerDelegate
|
||||
|
||||
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
|
||||
[self gnc_peripheralManagerDidUpdateState:peripheral];
|
||||
}
|
||||
|
||||
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
[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];
|
||||
}
|
||||
|
||||
- (void)peripheralManager:(CBPeripheralManager *)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request {
|
||||
[self gnc_peripheralManager:peripheral didReceiveReadRequest:request];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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 GNCPeripheralManagerDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** Protocol which helps create a fake of a @c CBPeripheralManager to inject for testing. */
|
||||
@protocol GNCPeripheralManager
|
||||
|
||||
/** Shadow property of a @c CBPeripheralManagerDelegate. */
|
||||
@property(nonatomic, nullable) id<GNCPeripheralManagerDelegate> peripheralDelegate;
|
||||
|
||||
@property(nonatomic, assign, readonly) CBManagerState state;
|
||||
|
||||
@property(nonatomic, assign, readonly) BOOL isAdvertising;
|
||||
|
||||
/**
|
||||
* Publishes a service and any of its associated characteristics and characteristic descriptors to
|
||||
* the local GATT database.
|
||||
*
|
||||
* When you add a service to the database, the peripheral manager calls the
|
||||
* @c gnc_peripheralManager:didAddService:error: method of its delegate object. If the service
|
||||
* contains any included services, you must first publish them.
|
||||
*
|
||||
* @param service The service you want to publish.
|
||||
*/
|
||||
- (void)addService:(CBMutableService *)service;
|
||||
|
||||
/**
|
||||
* Removes a specified published service from the local GATT database.
|
||||
*
|
||||
* Because apps on the local peripheral device share the GATT database, more than one instance of a
|
||||
* service may exist in the database. As a result, this method removes only the instance of the
|
||||
* service that your app added to the database (using the @c addService: method). If any other
|
||||
* services contains this service, you must first remove them.
|
||||
*
|
||||
* @param service The service you want to remove.
|
||||
*/
|
||||
- (void)removeService:(CBMutableService *)service;
|
||||
|
||||
/**
|
||||
* Removes all published services from the local GATT database.
|
||||
*
|
||||
* Use this when you want to remove all services you’ve previously published, for example, if your
|
||||
* app has a toggle button to expose GATT services.
|
||||
*
|
||||
* Because apps on the local peripheral device share the GATT database, this method removes only the
|
||||
* services that you added using the @c addService: method. This call doesn’t remove any services
|
||||
* published by other apps on the local peripheral device.
|
||||
*/
|
||||
- (void)removeAllServices;
|
||||
|
||||
/**
|
||||
* Advertises peripheral manager data.
|
||||
*
|
||||
* When you start advertising peripheral data, the peripheral manager calls the
|
||||
* @c gnc_peripheralManagerDidStartAdvertising:error: method of its delegate object.
|
||||
*
|
||||
* Core Bluetooth advertises data on a “best effort” basis, due to limited space and because there
|
||||
* may be multiple apps advertising simultaneously. While in the foreground, your app can use up to
|
||||
* 28 bytes of space in the initial advertisement data for any combination of the supported
|
||||
* advertising data keys. If no this space remains, there’s an additional 10 bytes of space in the
|
||||
* scan response, usable only for the local name (represented by the value of the
|
||||
* @c CBAdvertisementDataLocalNameKey key). Note that these sizes don’t include the 2 bytes of
|
||||
* header information required for each new data type.
|
||||
*
|
||||
* Any service UUIDs contained in the value of the @c CBAdvertisementDataServiceUUIDsKey key that
|
||||
* don’t fit in the allotted space go to a special “overflow” area. These services are discoverable
|
||||
* only by an iOS device explicitly scanning for them.
|
||||
*
|
||||
* While your app is in the background, the local name isn’t advertised and all service UUIDs are in
|
||||
* the overflow area.
|
||||
*
|
||||
* For details about the format of advertising and response data, see the Bluetooth 4.0
|
||||
* specification, Volume 3, Part C, Section 11.
|
||||
*
|
||||
* @param advertisementData An optional dictionary containing the data you want to advertise. The
|
||||
* peripheral manager only supports two keys:
|
||||
* @c CBAdvertisementDataLocalNameKey and
|
||||
* @c CBAdvertisementDataServiceUUIDsKey.
|
||||
*/
|
||||
- (void)startAdvertising:(nullable NSDictionary<NSString *, id> *)advertisementData;
|
||||
|
||||
/**
|
||||
* Responds to a read or write request from a connected central.
|
||||
*
|
||||
* When the peripheral manager receives a request from a connected central to read or write a
|
||||
* characteristic’s value, it calls the @c gnc_peripheralManager:didReceiveReadRequest: or
|
||||
* @c gnc_peripheralManager:didReceiveWriteRequests: method of its delegate object. To respond to
|
||||
* the corresponding read or write request, you call this method whenever you recevie one of these
|
||||
* delegate method callbacks.
|
||||
*
|
||||
* @param request The read or write request received from the connected central. For more
|
||||
* information about read and write requests, see @c CBATTRequest.
|
||||
* @param result The result of attempting to fulfill the request.
|
||||
*/
|
||||
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Protocol which helps the @c GNCPeripheralManager wrap a @c CBPeripheralManagerDelegate for
|
||||
* testing.
|
||||
*/
|
||||
@protocol GNCPeripheralManagerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Tells the delegate the peripheral manager’s state updated.
|
||||
*
|
||||
* You implement this required method to ensure that Bluetooth low energy is available to use on the
|
||||
* local peripheral device.
|
||||
*
|
||||
* Issue commands to the peripheral manager only when the peripheral manager is in the powered-on
|
||||
* state, as indicated by the @c CBPeripheralManagerStatePoweredOn constant. A state with a value
|
||||
* lower than @c CBPeripheralManagerStatePoweredOn implies that advertising has stopped and that any
|
||||
* connected centrals have been disconnected. If the state moves below
|
||||
* @c CBPeripheralManagerStatePoweredOff, advertising has stopped you must explicitly restart it. In
|
||||
* addition, the powered off state clears the local database; in this case you must explicitly
|
||||
* re-add all services. For a complete list and discussion of the possible values representing the
|
||||
* state of the peripheral manager, see the @c CBPeripheralManagerState enumeration in
|
||||
* @c CBPeripheralManager.
|
||||
*
|
||||
* @param peripheral The peripheral manager whose state has changed.
|
||||
*/
|
||||
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral;
|
||||
|
||||
/**
|
||||
* Tells the delegate the peripheral manager started advertising the local peripheral device’s data.
|
||||
*
|
||||
* Called when your app calls the @c startAdvertising: method to advertise the local peripheral
|
||||
* device’s data. If successful, the @c error parameter is @c nil. If a problem prevents advertising
|
||||
* the data, the @c error parameter returns the cause of the failure.
|
||||
*
|
||||
* @param peripheral The peripheral manager that is starting advertising.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
/**
|
||||
* Tells the delegate the peripheral manager published a service to the local GATT database.
|
||||
*
|
||||
* Called when your app calls the @c addService: method to publish a service to the local
|
||||
* peripheral’s GATT database. If the service published successfully to the local database, the
|
||||
* @c error parameter is @c nil. If unsuccessful, the @c error parameter provides the cause of the
|
||||
* failure.
|
||||
*
|
||||
* @param peripheral The peripheral manager adding the service.
|
||||
* @param service The service added to the local GATT database.
|
||||
* @param error The reason the call failed, or @c nil if no error occurred.
|
||||
*/
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didAddService:(CBService *)service
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
/**
|
||||
* Tells the delegate that a local peripheral received an Attribute Protocol (ATT) read request for
|
||||
* a characteristic with a dynamic value.
|
||||
*
|
||||
* When you receive this callback, call the @c respondToRequest:withResult: method of the
|
||||
* @c GNCPeripheralManager class exactly once to respond to the read request.
|
||||
*
|
||||
* @param peripheral The peripheral manager that received the request.
|
||||
* @param request A @c CBATTRequest object that represents a request to read a characteristic’s
|
||||
* value.
|
||||
*/
|
||||
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
|
||||
didReceiveReadRequest:(CBATTRequest *)request;
|
||||
|
||||
@end
|
||||
|
||||
@interface CBPeripheralManager () <GNCPeripheralManager>
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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/GNCPeripheralManager.h"
|
||||
#import <objc/NSObject.h>
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
- (nullable id<GNCPeripheralManagerDelegate>)peripheralDelegate {
|
||||
return (id<GNCPeripheralManagerDelegate>)self.delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSData (GNCWebSafeBase64)
|
||||
|
||||
/** Creates a Base64 encoded string from the data using websafe characters and no padding. */
|
||||
- (NSString *)webSafebase64EncodedString;
|
||||
|
||||
@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/NSData+GNCWebSafeBase64.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation NSData (GNCWebSafeBase64)
|
||||
|
||||
- (NSString *)webSafebase64EncodedString {
|
||||
NSString *encoded = [self base64EncodedStringWithOptions:0];
|
||||
|
||||
// Convert the standard base64 characters to URL safe variants.
|
||||
encoded = [encoded stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
|
||||
encoded = [encoded stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
|
||||
encoded = [encoded stringByReplacingOccurrencesOfString:@"=" withString:@""];
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -19,6 +19,9 @@ objc_library(
|
||||
name = "Mediums",
|
||||
srcs = [
|
||||
"BLEv2/GNCBLEGATTCharacteristic.m",
|
||||
"BLEv2/GNCBLEGATTServer.m",
|
||||
"BLEv2/GNCPeripheralManager.m",
|
||||
"BLEv2/NSData+GNCWebSafeBase64.m",
|
||||
"Ble/GNCMBleCentral.m",
|
||||
"Ble/GNCMBleConnection.m",
|
||||
"Ble/GNCMBlePeripheral.m",
|
||||
@@ -35,6 +38,9 @@ objc_library(
|
||||
],
|
||||
hdrs = [
|
||||
"BLEv2/GNCBLEGATTCharacteristic.h",
|
||||
"BLEv2/GNCBLEGATTServer.h",
|
||||
"BLEv2/GNCPeripheralManager.h",
|
||||
"BLEv2/NSData+GNCWebSafeBase64.h",
|
||||
"Ble/GNCMBleCentral.h",
|
||||
"Ble/GNCMBleConnection.h",
|
||||
"Ble/GNCMBlePeripheral.h",
|
||||
@@ -55,6 +61,7 @@ objc_library(
|
||||
"//third_party/apple_frameworks:CoreBluetooth",
|
||||
"//third_party/apple_frameworks:Foundation",
|
||||
"//third_party/apple_frameworks:Network",
|
||||
"//third_party/apple_frameworks:ObjectiveC",
|
||||
"//third_party/objective_c/google_toolbox_for_mac:GTM_Logger",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -24,15 +24,20 @@ objc_library(
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"GNCBLEGATTCharacteristicTest.mm",
|
||||
"GNCBLEGATTServer+Testing.h",
|
||||
"GNCBLEGATTServerTest.m",
|
||||
"GNCBLEUtilsTest.mm",
|
||||
"GNCBleTest.mm",
|
||||
"GNCBluetoothAdapterTest.mm",
|
||||
"GNCCryptoTest.mm",
|
||||
"GNCFakePeripheralManager.h",
|
||||
"GNCFakePeripheralManager.m",
|
||||
"GNCIPAddressTest.mm",
|
||||
"GNCMultiThreadExecutorTest.mm",
|
||||
"GNCScheduledExecutorTest.mm",
|
||||
"GNCSingleThreadExecutorTest.mm",
|
||||
"GNCUtilsTest.mm",
|
||||
"NSData+GNCWebSafeBase64Test.m",
|
||||
],
|
||||
deps = [
|
||||
"//internal/platform:base",
|
||||
|
||||
@@ -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/GNCBLEGATTServer.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol GNCPeripheralManager;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface GNCBLEGATTServer (Testing)
|
||||
|
||||
/**
|
||||
* Creates a GATT server with a provided peripheral manager.
|
||||
*
|
||||
* This is only exposed for testing and can be used to inject a fake peripheral manager.
|
||||
*
|
||||
* @param peripheralManager The peripheral manager instance.
|
||||
*/
|
||||
- (instancetype)initWithPeripheralManager:(id<GNCPeripheralManager>)peripheralManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,464 @@
|
||||
// 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/GNCBLEGATTCharacteristic.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/Tests/GNCBLEGATTServer+Testing.h"
|
||||
#import "internal/platform/implementation/apple/Tests/GNCFakePeripheralManager.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 GNCBLEGATTServerTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation GNCBLEGATTServerTest
|
||||
|
||||
#pragma mark - Create Characteristic
|
||||
|
||||
- (void)testCreateCharacteristic {
|
||||
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 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
XCTAssertNotNil(characteristic);
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 1);
|
||||
}
|
||||
|
||||
- (void)testCreateMultipleCharacteristicsForOneService {
|
||||
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];
|
||||
GNCBLEGATTCharacteristic *characteristic1 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID1
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
GNCBLEGATTCharacteristic *characteristic2 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID2
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
XCTAssertNotNil(characteristic1);
|
||||
XCTAssertNotNil(characteristic2);
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 1);
|
||||
XCTAssertEqual(fakePeripheralManager.services[0].characteristics.count, 2);
|
||||
}
|
||||
|
||||
- (void)testCreateCharacteristicNotPoweredOn {
|
||||
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];
|
||||
|
||||
XCTAssertNil(characteristic);
|
||||
}
|
||||
|
||||
- (void)testCreateCharacteristicServiceFailure {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
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];
|
||||
|
||||
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
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#pragma mark - Read Request
|
||||
|
||||
- (void)testReadRequest {
|
||||
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 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
[gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
|
||||
[fakePeripheralManager
|
||||
simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID
|
||||
characteristic:characteristicUUID];
|
||||
|
||||
[self waitForExpectations:@[ fakePeripheralManager.respondToRequestSuccessExpectation ]
|
||||
timeout:0];
|
||||
}
|
||||
|
||||
- (void)testReadRequestInvalidService {
|
||||
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 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
[gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
|
||||
CBUUID *invalidServiceUUID = [CBUUID UUIDWithString:kServiceUUID2];
|
||||
|
||||
[fakePeripheralManager
|
||||
simulatePeripheralManagerDidReceiveReadRequestForService:invalidServiceUUID
|
||||
characteristic:characteristicUUID];
|
||||
|
||||
[self waitForExpectations:@[ fakePeripheralManager.respondToRequestErrorExpectation ] timeout:0];
|
||||
}
|
||||
|
||||
- (void)testReadRequestInvalidCharacteristic {
|
||||
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 =
|
||||
[gattServer createCharacteristicWithServiceID:serviceUUID
|
||||
characteristicUUID:characteristicUUID
|
||||
permissions:CBAttributePermissionsReadable
|
||||
properties:CBCharacteristicPropertyRead];
|
||||
|
||||
[gattServer updateCharacteristic:characteristic value:[NSData data]];
|
||||
|
||||
CBUUID *invalidCharacteristicUUID =
|
||||
[CBUUID UUIDWithString:kCharacteristicUUID2];
|
||||
|
||||
[fakePeripheralManager
|
||||
simulatePeripheralManagerDidReceiveReadRequestForService:serviceUUID
|
||||
characteristic:invalidCharacteristicUUID];
|
||||
|
||||
[self waitForExpectations:@[ fakePeripheralManager.respondToRequestErrorExpectation ] timeout:0];
|
||||
}
|
||||
|
||||
#pragma mark - Stop
|
||||
|
||||
- (void)testStop {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[gattServer stop];
|
||||
|
||||
XCTAssertEqual(fakePeripheralManager.services.count, 0);
|
||||
}
|
||||
|
||||
#pragma mark - Start Advertising
|
||||
|
||||
- (void)testStartAdvertisingNoServiceData {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[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);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingEmptyServiceData {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[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"]);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingShortServiceData {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[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"]);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertising22ByteServiceData {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[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"]);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingLongServiceData {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[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"]);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingWithEmojiServiceData {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[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"]);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingMultipleServices {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{
|
||||
[CBUUID UUIDWithString:@"FEF3"] :
|
||||
[@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[CBUUID UUIDWithString:@"FEF4"] :
|
||||
[@"012345678901234567890123456789" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
}];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingNotPoweredOn {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
BOOL success = [gattServer startAdvertisingData:@{}];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
}
|
||||
|
||||
- (void)testStartAdvertisingStartFailure {
|
||||
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
|
||||
|
||||
GNCBLEGATTServer *gattServer =
|
||||
[[GNCBLEGATTServer alloc] initWithPeripheralManager:fakePeripheralManager];
|
||||
|
||||
fakePeripheralManager.didStartAdvertisingError = [NSError errorWithDomain:@"fake"
|
||||
code:0
|
||||
userInfo:nil];
|
||||
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
|
||||
BOOL success = [gattServer startAdvertisingData:@{}];
|
||||
|
||||
XCTAssertFalse(success);
|
||||
XCTAssertFalse(fakePeripheralManager.isAdvertising);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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/GNCPeripheralManager.h"
|
||||
|
||||
@class XCTestExpectation;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/** A fake implementation of @c GNCPeripheralManager to inject for testing. */
|
||||
@interface GNCFakePeripheralManager : NSObject <GNCPeripheralManager>
|
||||
|
||||
/**
|
||||
* The list of services added.
|
||||
*
|
||||
* Unlike CoreBluetooth, this list will contain duplicates if the same service is added more than
|
||||
* once.
|
||||
*/
|
||||
@property(nonatomic, nullable, readonly) NSArray<CBService *> *services;
|
||||
|
||||
/** The data being advertised. */
|
||||
@property(nonatomic, nullable, readonly) NSDictionary<NSString *, id> *advertisementData;
|
||||
|
||||
/** Expectation fulfilled when peripheral responds to a request with success. */
|
||||
@property(nonatomic, readonly) XCTestExpectation *respondToRequestSuccessExpectation;
|
||||
|
||||
/** Expectation fulfilled when peripheral responds to a request with an error. */
|
||||
@property(nonatomic, readonly) XCTestExpectation *respondToRequestErrorExpectation;
|
||||
|
||||
/**
|
||||
* Similates an @c addService: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling @c
|
||||
* addService: and will call the @c gnc_peripheralManager:didAddService:error: delegate method with
|
||||
* the provided error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *didAddServiceError;
|
||||
|
||||
/**
|
||||
* Similates a @c startAdvertising: error.
|
||||
*
|
||||
* Setting this error to a value other than @c nil will simulate a failure when calling @c
|
||||
* startAdvertising: and will call the @c gnc_peripheralManagerDidStartAdvertising:error: delegate
|
||||
* method with the provided error.
|
||||
*/
|
||||
@property(nonatomic, nullable, readwrite) NSError *didStartAdvertisingError;
|
||||
|
||||
/**
|
||||
* Simulates a state update event.
|
||||
*
|
||||
* Updates the peripheral manager state to the provided value and calls the
|
||||
* @c gnc_peripheralManagerDidUpdateState: delegate method.
|
||||
*
|
||||
* @param fakeState The new state to transition to.
|
||||
*/
|
||||
- (void)simulatePeripheralManagerDidUpdateState:(CBManagerState)fakeState;
|
||||
|
||||
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
|
||||
characteristic:(CBUUID *)characteristic;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,155 @@
|
||||
// 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/GNCFakePeripheralManager.h"
|
||||
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheralManager.h"
|
||||
|
||||
@interface CBCharacteristic ()
|
||||
|
||||
// Change property to readwrite for tests.
|
||||
@property(weak, readwrite, nonatomic) CBService *service;
|
||||
|
||||
@end
|
||||
|
||||
@interface CBATTRequest ()
|
||||
|
||||
// Change property to readwrite for tests.
|
||||
@property(readwrite, nonatomic) CBCharacteristic *characteristic;
|
||||
|
||||
// Keep a strong reference to the service.
|
||||
@property(readwrite, nonatomic) CBService *service;
|
||||
|
||||
- (instancetype)initWithService:(CBUUID *)service characteristic:(CBUUID *)characteristic;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CBATTRequest
|
||||
|
||||
- (instancetype)initWithService:(CBUUID *)service characteristic:(CBUUID *)characteristic {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristic
|
||||
properties:0
|
||||
value:nil
|
||||
permissions:0];
|
||||
_service = [[CBMutableService alloc] initWithType:service primary:YES];
|
||||
_characteristic.service = _service;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GNCFakePeripheralManager {
|
||||
CBManagerState _state;
|
||||
BOOL _isAdvertising;
|
||||
NSDictionary<NSString *, id> *_advertisementData;
|
||||
NSMutableArray<CBService *> *_services;
|
||||
|
||||
// Used to deliver delegate callbacks.
|
||||
dispatch_queue_t _queue;
|
||||
}
|
||||
|
||||
@synthesize peripheralDelegate;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_respondToRequestSuccessExpectation = [[XCTestExpectation alloc]
|
||||
initWithDescription:@"Fulfilled when peripheral responds to a request with success."];
|
||||
_respondToRequestErrorExpectation = [[XCTestExpectation alloc]
|
||||
initWithDescription:@"Fulfilled when peripheral responds to a request with an error."];
|
||||
_isAdvertising = NO;
|
||||
_state = CBManagerStateUnknown;
|
||||
_advertisementData = nil;
|
||||
_services = [[NSMutableArray alloc] init];
|
||||
_queue = dispatch_queue_create("com.nearby.GNCFakePeripheralManager", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CBManagerState)state {
|
||||
return _state;
|
||||
}
|
||||
|
||||
- (BOOL)isAdvertising {
|
||||
return _isAdvertising;
|
||||
}
|
||||
|
||||
- (void)addService:(CBMutableService *)service {
|
||||
if (!_didAddServiceError) {
|
||||
[_services addObject:service];
|
||||
}
|
||||
dispatch_async(_queue, ^{
|
||||
[peripheralDelegate gnc_peripheralManager:self didAddService:service error:_didAddServiceError];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeService:(CBMutableService *)service {
|
||||
[_services removeObject:service];
|
||||
}
|
||||
|
||||
- (void)removeAllServices {
|
||||
[_services removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)startAdvertising:(NSDictionary<NSString *, id> *)advertisementData {
|
||||
_isAdvertising = _didStartAdvertisingError == nil;
|
||||
_advertisementData = advertisementData;
|
||||
dispatch_async(_queue, ^{
|
||||
[peripheralDelegate gnc_peripheralManagerDidStartAdvertising:self
|
||||
error:_didStartAdvertisingError];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result {
|
||||
if (result == CBATTErrorSuccess) {
|
||||
[_respondToRequestSuccessExpectation fulfill];
|
||||
return;
|
||||
}
|
||||
[_respondToRequestErrorExpectation fulfill];
|
||||
}
|
||||
|
||||
#pragma mark - Testing Helpers
|
||||
|
||||
- (NSArray<CBService *> *)services {
|
||||
return _services;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, id> *)advertisementData {
|
||||
return _advertisementData;
|
||||
}
|
||||
|
||||
- (void)simulatePeripheralManagerDidUpdateState:(CBManagerState)fakeState {
|
||||
_state = fakeState;
|
||||
dispatch_async(_queue, ^{
|
||||
[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];
|
||||
});
|
||||
}
|
||||
|
||||
@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/NSData+GNCWebSafeBase64.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface NSData_GNCWebSafeBase64Test : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation NSData_GNCWebSafeBase64Test
|
||||
|
||||
- (void)testEncodingWithPadding {
|
||||
NSString *expected = @"AQ";
|
||||
NSData *data = [[NSData alloc] initWithBase64EncodedString:@"AQ==" options:0];
|
||||
NSString *actual = [data webSafebase64EncodedString];
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
- (void)testEncodingWithNonWebSafeCharacters {
|
||||
NSString *expected = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
NSData *data =
|
||||
[[NSData alloc] initWithBase64EncodedString:
|
||||
@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
options:0];
|
||||
NSString *actual = [data webSafebase64EncodedString];
|
||||
XCTAssertEqualObjects(expected, actual);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user