blob: f39b7fe38e9fc11345c6d34190dc5cdd1656b387 [file] [log] [blame]
// 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