1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2015-2016 Apple Inc. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 #if ENABLE_BLE_TRIGGERED_BONJOUR
20 #include "mDNSEmbeddedAPI.h"
21 #include "DNSCommon.h"
23 #import <Foundation/Foundation.h>
24 #import <CoreBluetooth/CoreBluetooth.h>
25 #import <CoreBluetooth/CoreBluetooth_Private.h>
26 #import "mDNSMacOSX.h"
30 static coreBLE * coreBLEptr;
32 // Call Bluetooth subsystem to start/stop the the Bonjour BLE beacon and
33 // beacon scanning based on the current Bloom filter.
34 void updateBLEBeacon(serviceHash_t bloomFilter)
37 coreBLEptr = [[coreBLE alloc] init];
39 LogInfo("updateBLEBeacon: bloomFilter = 0x%lx", bloomFilter);
41 [coreBLEptr updateBeacon:bloomFilter];
44 // Stop the current BLE beacon.
45 void stopBLEBeacon(void)
48 coreBLEptr = [[coreBLE alloc] init];
50 [coreBLEptr stopBeacon];
53 bool currentlyBeaconing(void)
56 coreBLEptr = [[coreBLE alloc] init];
58 return [coreBLEptr isBeaconing];
62 void startBLEScan(void)
65 coreBLEptr = [[coreBLE alloc] init];
66 [coreBLEptr startScan];
70 void stopBLEScan(void)
73 coreBLEptr = [[coreBLE alloc] init];
75 [coreBLEptr stopScan];
78 @implementation coreBLE
80 CBCentralManager *_centralManager;
81 CBPeripheralManager *_peripheralManager;
83 NSData *_currentlyAdvertisedData;
85 // [_centralManager isScanning] is only available on iOS and not OSX,
86 // so track scanning state locally.
88 BOOL _centralManagerIsOn;
89 BOOL _peripheralManagerIsOn;
98 _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
99 _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
100 _currentlyAdvertisedData = nil;
102 _centralManagerIsOn = NO;
103 _peripheralManagerIsOn = NO;
105 if (_centralManager == nil || _peripheralManager == nil )
107 LogMsg("coreBLE initialization failed!");
111 LogInfo("coreBLE initialized");
118 #define ADVERTISEMENTDATALENGTH 28 // 31 - 3 (3 bytes for flags)
121 // Define DBDeviceTypeBonjour for prototyping until we move to the TDS beacon format.
122 // The Bluetooth team recommended using a value < 32 for prototyping, since 32 is the number of
123 // beacon types they can track in their duplicate beacon filtering logic.
124 #define DBDeviceTypeBonjour 26
126 // Beacon flags and version byte
127 #define BonjourBLEVersion 1
129 extern mDNS mDNSStorage;
130 extern mDNSInterfaceID AWDLInterfaceID;
132 // Transmit the last beacon indicating we are no longer advertising or browsing any services for two seconds.
133 #define LastBeaconTime 2
135 - (void) updateBeacon:(serviceHash_t) bloomFilter
137 uint8_t advertisingData[ADVERTISEMENTDATALENGTH] = {0, 0xff, 0x4c, 0x00 };
138 uint8_t advertisingLength = 4;
140 // If no longer browsing or advertising, beacon this state for 'LastBeaconTime' seconds
141 // so that peers have a chance to notice the state change.
142 if (bloomFilter == 0)
144 LogInfo("updateBeacon: Stopping beacon in %d seconds", LastBeaconTime);
146 if (mDNSStorage.timenow == 0)
148 // This should never happen since all calling code paths should have called mDNS_Lock(), which
149 // initializes the mDNSStorage.timenow value.
150 LogMsg("updateBeacon: NOTE, timenow == 0 ??");
153 mDNSStorage.NextBLEServiceTime = NonZeroTime(mDNSStorage.timenow + (LastBeaconTime * mDNSPlatformOneSecond));
158 // Cancel any pending final beacon processing.
163 advertisingData[advertisingLength++] = DBDeviceTypeBonjour;
165 // Flags and Version field
166 advertisingData[advertisingLength++] = BonjourBLEVersion;
168 memcpy(& advertisingData[advertisingLength], & bloomFilter, sizeof(serviceHash_t));
169 advertisingLength += sizeof(serviceHash_t);
171 // Add the MAC address of the awdl0 interface. Don't cache it since
172 // it can get updated periodically.
175 NetworkInterfaceInfoOSX *intf = IfindexToInterfaceInfoOSX(AWDLInterfaceID);
177 memcpy(& advertisingData[advertisingLength], & intf->ifinfo.MAC, sizeof(mDNSEthAddr));
179 memset( & advertisingData[advertisingLength], 0, sizeof(mDNSEthAddr));
183 // Just use zero if not avaiblable.
184 memset( & advertisingData[advertisingLength], 0, sizeof(mDNSEthAddr));
186 advertisingLength += sizeof(mDNSEthAddr);
188 // Total length of data advertised, minus this length byte.
189 advertisingData[0] = (advertisingLength - 1);
191 LogInfo("updateBeacon: advertisingLength = %d", advertisingLength);
193 if (_currentlyAdvertisedData)
194 [_currentlyAdvertisedData release];
195 _currentlyAdvertisedData = [[NSData alloc] initWithBytes:advertisingData length:advertisingLength];
201 if (!_peripheralManagerIsOn)
203 LogInfo("startBeacon: Not starting beacon, CBPeripheralManager not powered on");
207 if (_currentlyAdvertisedData == nil)
209 LogInfo("startBeacon: Not starting beacon, no data to advertise");
213 if ([_peripheralManager isAdvertising])
215 LogInfo("startBeacon: Stop current beacon transmission before restarting");
216 [_peripheralManager stopAdvertising];
218 LogInfo("startBeacon: Starting beacon");
220 #if 0 // Move to this code during Fall 2018 develelopment if still using these APIs.
221 [_peripheralManager startAdvertising:@{ CBAdvertisementDataAppleMfgData : _currentlyAdvertisedData, CBManagerIsPrivilegedDaemonKey : @YES, @"kCBAdvOptionUseFGInterval" : @YES }];
223 // While CBCentralManagerScanOptionIsPrivilegedDaemonKey is deprecated in current MobileBluetooth project, it's still defined in the current and
224 // previous train SDKs. Suppress deprecated warning for now since we intend to move to a different Bluetooth API to manage the BLE Triggered Bonjour
225 // beacons when this code is enabled by default.
226 #pragma GCC diagnostic push
227 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
228 [_peripheralManager startAdvertising:@{ CBAdvertisementDataAppleMfgData : _currentlyAdvertisedData, CBCentralManagerScanOptionIsPrivilegedDaemonKey : @YES, @"kCBAdvOptionUseFGInterval" : @YES }];
229 #pragma GCC diagnostic pop
235 return (_currentlyAdvertisedData != nil);
240 if (!_peripheralManagerIsOn)
242 LogInfo("stopBeacon: CBPeripheralManager is not powered on");
246 // Only beaconing if we have advertised data to send.
247 if (_currentlyAdvertisedData)
249 LogInfo("stoptBeacon: Stopping beacon");
250 [_peripheralManager stopAdvertising];
251 [_currentlyAdvertisedData release];
252 _currentlyAdvertisedData = nil;
255 LogInfo("stoptBeacon: Note currently beaconing");
260 if (!_centralManagerIsOn)
262 LogInfo("startScan: Not starting scan, CBCentralManager is not powered on");
268 LogInfo("startScan: already scanning, stopping scan before restarting");
269 [_centralManager stopScan];
272 LogInfo("startScan: Starting scan");
276 #if 0 // Move to this code during Fall 2018 develelopment if still using these APIs.
277 [_centralManager scanForPeripheralsWithServices:nil options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES , CBManagerIsPrivilegedDaemonKey : @YES}];
279 // While CBCentralManagerScanOptionIsPrivilegedDaemonKey is deprecated in current MobileBluetooth project, it's still defined in the current and
280 // previous train SDKs. Suppress deprecated warning for now since we intend to move to a different Bluetooth API to manage the BLE Triggered Bonjour
281 // beacons when this code is enabled by default.
282 #pragma GCC diagnostic push
283 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
284 [_centralManager scanForPeripheralsWithServices:nil options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES , CBCentralManagerScanOptionIsPrivilegedDaemonKey : @YES}];
285 #pragma GCC diagnostic pop
291 if (!_centralManagerIsOn)
293 LogInfo("stopScan: Not stopping scan, CBCentralManager is not powered on");
299 LogInfo("stopScan: Stopping scan");
300 [_centralManager stopScan];
305 LogInfo("stopScan: Not currently scanning");
309 #pragma mark - CBCentralManagerDelegate protocol
311 - (void)centralManagerDidUpdateState:(CBCentralManager *)central
313 switch (central.state) {
314 case CBManagerStateUnknown:
315 LogInfo("centralManagerDidUpdateState: CBManagerStateUnknown");
318 case CBManagerStateResetting:
319 LogInfo("centralManagerDidUpdateState: CBManagerStateResetting");
322 case CBManagerStateUnsupported:
323 LogInfo("centralManagerDidUpdateState: CBManagerStateUnsupported");
326 case CBManagerStateUnauthorized:
327 LogInfo("centralManagerDidUpdateState: CBManagerStateUnauthorized");
330 case CBManagerStatePoweredOff:
331 LogInfo("centralManagerDidUpdateState: CBManagerStatePoweredOff");
334 case CBManagerStatePoweredOn:
335 // Hold lock to synchronize with main thread from this callback thread.
338 LogInfo("centralManagerDidUpdateState: CBManagerStatePoweredOn");
339 _centralManagerIsOn = YES;
340 // Only start scan if we have data we will be transmitting or if "suppressBeacons"
341 // is set, indicating we should be scanning, but not beaconing.
342 if (_currentlyAdvertisedData || suppressBeacons)
345 LogInfo("centralManagerDidUpdateState:: Not starting scan");
347 KQueueUnlock("CBManagerStatePoweredOn");
351 LogInfo("centralManagerDidUpdateState: Unknown state ??");
356 #define beaconTypeByteIndex 2 // Offset of beacon type in received CBAdvertisementDataManufacturerDataKey byte array.
357 #define beaconDataLength 18 // Total number of bytes in the CBAdvertisementDataManufacturerDataKey.
359 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
365 NSData *data = [advertisementData objectForKey:CBAdvertisementDataManufacturerDataKey];
367 // Just return if the beacon data does not match what we are looking for.
368 if (!data || ([data length] != beaconDataLength))
373 unsigned char *bytes = (unsigned char *)data.bytes;
375 // Just parse the DBDeviceTypeBonjour beacons.
376 if (bytes[beaconTypeByteIndex] == DBDeviceTypeBonjour)
378 serviceHash_t peerBloomFilter;
380 unsigned char flagsAndVersion;
383 #if VERBOSE_BLE_DEBUG
384 LogInfo("didDiscoverPeripheral: received DBDeviceTypeBonjour beacon, length = %d", [data length]);
385 LogInfo("didDiscoverPeripheral: central = 0x%x, peripheral = 0x%x", central, peripheral);
386 #endif // VERBOSE_BLE_DEBUG
388 // The DBDeviceTypeBonjour beacon bytes will be:
389 // 0x4C (1 byte), 0x0 (1 byte), DBDeviceTypeBonjour byte, flags and version byte, 8 byte Bloom filter,
390 // 6 byte sender AWDL MAC address
392 ptr = & bytes[beaconTypeByteIndex + 1];
393 flagsAndVersion = *ptr++;
394 memcpy(& peerBloomFilter, ptr, sizeof(serviceHash_t));
395 ptr += sizeof(serviceHash_t);
396 memcpy(& peerMAC, ptr, sizeof(peerMAC));
398 #if VERBOSE_BLE_DEBUG
399 LogInfo("didDiscoverPeripheral: version = 0x%x, peerBloomFilter = 0x%x",
400 flagsAndVersion, peerBloomFilter);
401 LogInfo("didDiscoverPeripheral: sender MAC = 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x",
402 peerMAC.b[0], peerMAC.b[1], peerMAC.b[2], peerMAC.b[3], peerMAC.b[4], peerMAC.b[5]);
404 (void)flagsAndVersion; // Unused
405 #endif // VERBOSE_BLE_DEBUG
407 responseReceived(peerBloomFilter, & peerMAC);
411 #pragma mark - CBPeripheralManagerDelegate protocol
413 - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
416 switch (peripheral.state) {
417 case CBManagerStateUnknown:
418 LogInfo("peripheralManagerDidUpdateState: CBManagerStateUnknown");
421 case CBManagerStateResetting:
422 LogInfo("peripheralManagerDidUpdateState: CBManagerStateResetting");
425 case CBManagerStateUnsupported:
426 LogInfo("peripheralManagerDidUpdateState: CBManagerStateUnsupported");
429 case CBManagerStateUnauthorized:
430 LogInfo("peripheralManagerDidUpdateState: CBManagerStateUnauthorized");
433 case CBManagerStatePoweredOff:
434 LogInfo("peripheralManagerDidUpdateState: CBManagerStatePoweredOff");
437 case CBManagerStatePoweredOn:
438 // Hold lock to synchronize with main thread from this callback thread.
441 LogInfo("peripheralManagerDidUpdateState: CBManagerStatePoweredOn");
442 _peripheralManagerIsOn = YES;
444 // Start beaconing if we have initialized beacon data to send.
445 if (_currentlyAdvertisedData)
448 KQueueUnlock("CBManagerStatePoweredOn");
452 LogInfo("peripheralManagerDidUpdateState: Unknown state ??");
457 - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(nullable NSError *)error
463 const char * errorString = [[error localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding];
464 LogInfo("peripheralManagerDidStartAdvertising: error = %s", errorString ? errorString: "unknown");
468 LogInfo("peripheralManagerDidStartAdvertising:");
473 #endif // ENABLE_BLE_TRIGGERED_BONJOUR