| // Copyright 2016 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| #import "CBAdvertisingDriver.h" |
| #import "CBDriver.h" |
| #import "CBLog.h" |
| #import "CBScanningDriver.h" |
| #import "CBUtil.h" |
| |
| static CBDriver *_instance = nil; |
| |
| #define kCBDriverQueueName "io.v.v23.CoreBluetoothDriver" |
| NSString *kCBDriverErrorDomain = @"io.v.v23.CoreBluetoothDriver"; |
| |
| @interface CBDriver () |
| @end |
| |
| @implementation CBDriver |
| |
| + (CBDriver *_Nonnull)instance { |
| @synchronized(self) { |
| if (!_instance) { |
| _instance = [CBDriver new]; |
| } |
| } |
| return _instance; |
| } |
| |
| - (id)init { |
| if (self = [super init]) { |
| // Create the serial dispatch queue for the driver |
| self.queue = dispatch_queue_create(kCBDriverQueueName, NULL); |
| self.advertisingDriver = [[CBAdvertisingDriver alloc] initWithQueue:self.queue]; |
| self.scanningDriver = [[CBScanningDriver alloc] initWithQueue:self.queue]; |
| } |
| return self; |
| } |
| |
| + (void)shutdown { |
| @synchronized(self) { |
| if (!_instance) return; |
| CBDispatchSync(_instance.queue, ^{ |
| _instance.scanningDriver = nil; |
| _instance.advertisingDriver = nil; |
| }); |
| _instance = nil; |
| } |
| } |
| |
| - (NSString *)debugDescription { |
| return [NSString stringWithFormat:@"[CBDriver\nadvertising=%@\ndiscovery=%@]", |
| self.scanningDriver, self.advertisingDriver]; |
| } |
| |
| @end |
| |
| static void copyString(NSString *string, char **dst); |
| |
| char *v23_cbdriver_debug_string() { |
| char *dst = NULL; |
| copyString([[CBDriver instance] debugDescription], &dst); |
| return dst; |
| } |
| |
| BOOL v23_cbdriver_addService(const char *_Nonnull cUuid, |
| CBDriverCharacteristicMapEntry *_Nonnull entries, int entriesLength, |
| char *_Nullable *_Nullable errorOut) { |
| CBUUID *uuid = [CBUUID UUIDWithString:[NSString stringWithUTF8String:cUuid]]; |
| NSMutableDictionary<CBUUID *, NSData *> *_Nonnull characteristics = [NSMutableDictionary new]; |
| for (int i = 0; i < entriesLength; i++) { |
| CBDriverCharacteristicMapEntry entry = entries[i]; |
| CBUUID *characteristicUuid = [CBUUID UUIDWithString:[NSString stringWithUTF8String:entry.uuid]]; |
| NSData *data = [[NSData alloc] initWithBytes:entry.data length:entry.dataLength]; |
| characteristics[characteristicUuid] = data; |
| } |
| // We're on a go thread -- addService in obj-c will run on the bluetooth queue and callback |
| // from that queue. Thus we are able to block until we get the response (or timeout). |
| dispatch_semaphore_t condition = dispatch_semaphore_create(0); |
| __block NSString *err = nil; |
| CBInfoLog(@"Adding service %@ with characteristics %@", uuid, characteristics); |
| [CBDriver.instance.advertisingDriver |
| addService:uuid |
| characteristics:characteristics |
| callback:^(CBUUID *_Nonnull callbackUuid, NSError *_Nullable error) { |
| assert([uuid isEqual:callbackUuid]); |
| CBDebugLog(@"Got callback on add service %@ with error %@", callbackUuid, error); |
| if (error) { |
| err = [NSString stringWithFormat:@"%@", error]; |
| } |
| dispatch_semaphore_signal(condition); |
| }]; |
| // Wait up to 5 seconds for the callback |
| dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)); |
| if (dispatch_semaphore_wait(condition, timeout) && !err) { |
| err = @"Timeout adding service -- CoreBluetooth did not return in time"; |
| CBInfoLog(err); |
| } |
| if (err) { |
| copyString(err, errorOut); |
| return NO; |
| } |
| return YES; |
| } |
| |
| int v23_cbdriver_advertisingServiceCount() { |
| return (int)[CBDriver instance].advertisingDriver.serviceCount; |
| } |
| |
| static CBDriverCharacteristicMapEntry *characteristicsMapToEntries( |
| NSDictionary<CBUUID *, NSData *> *_Nullable characteristics); |
| |
| static void freeCharacteristicsEntriesMap(CBDriverCharacteristicMapEntry *entries, int length); |
| |
| // This is exported from go |
| extern void v23_corebluetooth_scan_handler_on_discovered( |
| const char *_Nonnull uuid, CBDriverCharacteristicMapEntry *_Nonnull entries, int entriesLength, |
| int rssi); |
| |
| void v23_cbdriver_removeService(const char *_Nonnull cUuid) { |
| CBUUID *uuid = [CBUUID UUIDWithString:[NSString stringWithUTF8String:cUuid]]; |
| [[CBDriver instance].advertisingDriver removeService:uuid]; |
| } |
| |
| void v23_cbdriver_setAdRotateDelay(float seconds) { |
| [CBDriver instance].advertisingDriver.rotateAdDelay = (NSTimeInterval)seconds; |
| } |
| |
| BOOL v23_cbdriver_startScan(const char *_Nonnull *_Nonnull cUuids, int uuidsLength, |
| const char *_Nonnull cBaseUuid, const char *_Nonnull cMaskUuid, |
| char *_Nullable *_Nullable errorOut) { |
| NSMutableArray<CBUUID *> *uuids = [NSMutableArray new]; |
| for (int i = 0; i < uuidsLength; i++) { |
| [uuids addObject:[CBUUID UUIDWithString:[NSString stringWithUTF8String:cUuids[i]]]]; |
| } |
| CBUUID *baseUuid = [CBUUID UUIDWithString:[NSString stringWithUTF8String:cBaseUuid]]; |
| CBUUID *maskUuid = [CBUUID UUIDWithString:[NSString stringWithUTF8String:cMaskUuid]]; |
| NSError *err = nil; |
| BOOL success = [CBDriver.instance.scanningDriver |
| startScan:uuids |
| baseUuid:baseUuid |
| maskUuid:maskUuid |
| handler:^(CBUUID *_Nonnull uuid, |
| NSDictionary<CBUUID *, NSData *> *_Nullable characteristics, int rssi) { |
| // Go assumes characteristics -- if empty then it crashes. |
| if (characteristics.count == 0) { |
| CBErrorLog(@"Got vanadium service %@ but no characteristics; ignoring", uuid); |
| return; |
| } |
| // Call go |
| CBDriverCharacteristicMapEntry *entries = characteristicsMapToEntries(characteristics); |
| v23_corebluetooth_scan_handler_on_discovered(uuid.UUIDString.UTF8String, entries, |
| (int)characteristics.count, rssi); |
| // Clean up |
| freeCharacteristicsEntriesMap(entries, (int)characteristics.count); |
| } |
| error:&err]; |
| if (!success || err) { |
| copyString([NSString stringWithFormat:@"%@", err], errorOut); |
| } |
| return success; |
| } |
| |
| void v23_cbdriver_stopScan() { [[CBDriver instance].scanningDriver stopScan]; } |
| |
| void v23_cbdriver_clean() { [CBDriver shutdown]; } |
| |
| static CBDriverCharacteristicMapEntry *characteristicsMapToEntries( |
| NSDictionary<CBUUID *, NSData *> *_Nullable characteristics) { |
| if (!characteristics.count) return NULL; |
| CBDriverCharacteristicMapEntry *entries = |
| malloc(sizeof(CBDriverCharacteristicMapEntry) * characteristics.count); |
| if (!entries) { |
| return NULL; |
| } |
| int i = 0; |
| for (CBUUID *uuid in characteristics) { |
| CBDriverCharacteristicMapEntry entry; |
| NSData *data = characteristics[uuid]; |
| copyString(uuid.UUIDString, &entry.uuid); |
| entry.data = data.bytes; |
| entry.dataLength = (int)data.length; |
| entries[i] = entry; |
| i++; |
| } |
| return entries; |
| } |
| |
| static void freeCharacteristicsEntriesMap(CBDriverCharacteristicMapEntry *entries, int length) { |
| if (!entries) { |
| return; |
| } |
| for (int i = 0; i < length; i++) { |
| CBDriverCharacteristicMapEntry entry = entries[i]; |
| free(entry.uuid); |
| // The data will free itself as it was never copied |
| } |
| free(entries); |
| } |
| |
| static void copyString(NSString *string, char **dst) { |
| if (!dst) { |
| CBErrorLog(@"Missing dst string, not copying %@", string); |
| return; |
| } |
| NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + sizeof("\0"); |
| *dst = malloc(length); |
| if (*dst) { |
| [string getCString:*dst maxLength:length encoding:NSUTF8StringEncoding]; |
| } |
| } |
| |
| @implementation CBUUID (Description) |
| |
| - (NSString *)description { |
| return [NSString stringWithFormat:@"[CBUUID %@]", self.UUIDString]; |
| } |
| |
| @end |