]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/coreBLE.m
mDNSResponder-1310.40.42.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / coreBLE.m
1 /* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2015-2016 Apple Inc. All rights reserved.
4 *
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
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
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.
16 */
17
18 #if ENABLE_BLE_TRIGGERED_BONJOUR
19
20 #include "mDNSEmbeddedAPI.h"
21 #include "DNSCommon.h"
22
23 #import <Foundation/Foundation.h>
24 #import <CoreBluetooth/CoreBluetooth.h>
25 #import <CoreBluetooth/CoreBluetooth_Private.h>
26 #import "mDNSMacOSX.h"
27 #import "BLE.h"
28 #import "coreBLE.h"
29
30 static coreBLE * coreBLEptr;
31
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)
35 {
36 if (coreBLEptr == 0)
37 coreBLEptr = [[coreBLE alloc] init];
38
39 LogInfo("updateBLEBeacon: bloomFilter = 0x%lx", bloomFilter);
40
41 [coreBLEptr updateBeacon:bloomFilter];
42 }
43
44 // Stop the current BLE beacon.
45 void stopBLEBeacon(void)
46 {
47 if (coreBLEptr == 0)
48 coreBLEptr = [[coreBLE alloc] init];
49
50 [coreBLEptr stopBeacon];
51 }
52
53 bool currentlyBeaconing(void)
54 {
55 if (coreBLEptr == 0)
56 coreBLEptr = [[coreBLE alloc] init];
57
58 return [coreBLEptr isBeaconing];
59 }
60
61 // Start the scan.
62 void startBLEScan(void)
63 {
64 if (coreBLEptr == 0)
65 coreBLEptr = [[coreBLE alloc] init];
66 [coreBLEptr startScan];
67 }
68
69 // Stop the scan.
70 void stopBLEScan(void)
71 {
72 if (coreBLEptr == 0)
73 coreBLEptr = [[coreBLE alloc] init];
74
75 [coreBLEptr stopScan];
76 }
77
78 @implementation coreBLE
79 {
80 CBCentralManager *_centralManager;
81 CBPeripheralManager *_peripheralManager;
82
83 NSData *_currentlyAdvertisedData;
84
85 // [_centralManager isScanning] is only available on iOS and not OSX,
86 // so track scanning state locally.
87 BOOL _isScanning;
88 BOOL _centralManagerIsOn;
89 BOOL _peripheralManagerIsOn;
90 }
91
92 - (id)init
93 {
94 self = [super init];
95
96 if (self)
97 {
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;
101 _isScanning = NO;
102 _centralManagerIsOn = NO;
103 _peripheralManagerIsOn = NO;
104
105 if (_centralManager == nil || _peripheralManager == nil )
106 {
107 LogMsg("coreBLE initialization failed!");
108 }
109 else
110 {
111 LogInfo("coreBLE initialized");
112 }
113 }
114
115 return self;
116 }
117
118 #define ADVERTISEMENTDATALENGTH 28 // 31 - 3 (3 bytes for flags)
119
120 // TODO:
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
125
126 // Beacon flags and version byte
127 #define BonjourBLEVersion 1
128
129 extern mDNS mDNSStorage;
130 extern mDNSInterfaceID AWDLInterfaceID;
131
132 // Transmit the last beacon indicating we are no longer advertising or browsing any services for two seconds.
133 #define LastBeaconTime 2
134
135 - (void) updateBeacon:(serviceHash_t) bloomFilter
136 {
137 uint8_t advertisingData[ADVERTISEMENTDATALENGTH] = {0, 0xff, 0x4c, 0x00 };
138 uint8_t advertisingLength = 4;
139
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)
143 {
144 LogInfo("updateBeacon: Stopping beacon in %d seconds", LastBeaconTime);
145
146 if (mDNSStorage.timenow == 0)
147 {
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 ??");
151 }
152
153 mDNSStorage.NextBLEServiceTime = NonZeroTime(mDNSStorage.timenow + (LastBeaconTime * mDNSPlatformOneSecond));
154 finalBeacon = true;
155 }
156 else
157 {
158 // Cancel any pending final beacon processing.
159 finalBeacon = false;
160 }
161
162 // The beacon type.
163 advertisingData[advertisingLength++] = DBDeviceTypeBonjour;
164
165 // Flags and Version field
166 advertisingData[advertisingLength++] = BonjourBLEVersion;
167
168 memcpy(& advertisingData[advertisingLength], & bloomFilter, sizeof(serviceHash_t));
169 advertisingLength += sizeof(serviceHash_t);
170
171 // Add the MAC address of the awdl0 interface. Don't cache it since
172 // it can get updated periodically.
173 if (AWDLInterfaceID)
174 {
175 NetworkInterfaceInfoOSX *intf = IfindexToInterfaceInfoOSX(AWDLInterfaceID);
176 if (intf)
177 memcpy(& advertisingData[advertisingLength], & intf->ifinfo.MAC, sizeof(mDNSEthAddr));
178 else
179 memset( & advertisingData[advertisingLength], 0, sizeof(mDNSEthAddr));
180 }
181 else
182 {
183 // Just use zero if not avaiblable.
184 memset( & advertisingData[advertisingLength], 0, sizeof(mDNSEthAddr));
185 }
186 advertisingLength += sizeof(mDNSEthAddr);
187
188 // Total length of data advertised, minus this length byte.
189 advertisingData[0] = (advertisingLength - 1);
190
191 LogInfo("updateBeacon: advertisingLength = %d", advertisingLength);
192
193 if (_currentlyAdvertisedData)
194 [_currentlyAdvertisedData release];
195 _currentlyAdvertisedData = [[NSData alloc] initWithBytes:advertisingData length:advertisingLength];
196 [self startBeacon];
197 }
198
199 - (void) startBeacon
200 {
201 if (!_peripheralManagerIsOn)
202 {
203 LogInfo("startBeacon: Not starting beacon, CBPeripheralManager not powered on");
204 return;
205 }
206
207 if (_currentlyAdvertisedData == nil)
208 {
209 LogInfo("startBeacon: Not starting beacon, no data to advertise");
210 return;
211 }
212
213 if ([_peripheralManager isAdvertising])
214 {
215 LogInfo("startBeacon: Stop current beacon transmission before restarting");
216 [_peripheralManager stopAdvertising];
217 }
218 LogInfo("startBeacon: Starting beacon");
219
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 }];
222 #else
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
230 #endif
231 }
232
233 - (bool) isBeaconing
234 {
235 return (_currentlyAdvertisedData != nil);
236 }
237
238 - (void) stopBeacon
239 {
240 if (!_peripheralManagerIsOn)
241 {
242 LogInfo("stopBeacon: CBPeripheralManager is not powered on");
243 return;
244 }
245
246 // Only beaconing if we have advertised data to send.
247 if (_currentlyAdvertisedData)
248 {
249 LogInfo("stoptBeacon: Stopping beacon");
250 [_peripheralManager stopAdvertising];
251 [_currentlyAdvertisedData release];
252 _currentlyAdvertisedData = nil;
253 }
254 else
255 LogInfo("stoptBeacon: Note currently beaconing");
256 }
257
258 - (void) startScan
259 {
260 if (!_centralManagerIsOn)
261 {
262 LogInfo("startScan: Not starting scan, CBCentralManager is not powered on");
263 return;
264 }
265
266 if (_isScanning)
267 {
268 LogInfo("startScan: already scanning, stopping scan before restarting");
269 [_centralManager stopScan];
270 }
271
272 LogInfo("startScan: Starting scan");
273
274 _isScanning = YES;
275
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}];
278 #else
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
286 #endif
287 }
288
289 - (void) stopScan
290 {
291 if (!_centralManagerIsOn)
292 {
293 LogInfo("stopScan: Not stopping scan, CBCentralManager is not powered on");
294 return;
295 }
296
297 if (_isScanning)
298 {
299 LogInfo("stopScan: Stopping scan");
300 [_centralManager stopScan];
301 _isScanning = NO;
302 }
303 else
304 {
305 LogInfo("stopScan: Not currently scanning");
306 }
307 }
308
309 #pragma mark - CBCentralManagerDelegate protocol
310
311 - (void)centralManagerDidUpdateState:(CBCentralManager *)central
312 {
313 switch (central.state) {
314 case CBManagerStateUnknown:
315 LogInfo("centralManagerDidUpdateState: CBManagerStateUnknown");
316 break;
317
318 case CBManagerStateResetting:
319 LogInfo("centralManagerDidUpdateState: CBManagerStateResetting");
320 break;
321
322 case CBManagerStateUnsupported:
323 LogInfo("centralManagerDidUpdateState: CBManagerStateUnsupported");
324 break;
325
326 case CBManagerStateUnauthorized:
327 LogInfo("centralManagerDidUpdateState: CBManagerStateUnauthorized");
328 break;
329
330 case CBManagerStatePoweredOff:
331 LogInfo("centralManagerDidUpdateState: CBManagerStatePoweredOff");
332 break;
333
334 case CBManagerStatePoweredOn:
335 // Hold lock to synchronize with main thread from this callback thread.
336 KQueueLock();
337
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)
343 [self startScan];
344 else
345 LogInfo("centralManagerDidUpdateState:: Not starting scan");
346
347 KQueueUnlock("CBManagerStatePoweredOn");
348 break;
349
350 default:
351 LogInfo("centralManagerDidUpdateState: Unknown state ??");
352 break;
353 }
354 }
355
356 #define beaconTypeByteIndex 2 // Offset of beacon type in received CBAdvertisementDataManufacturerDataKey byte array.
357 #define beaconDataLength 18 // Total number of bytes in the CBAdvertisementDataManufacturerDataKey.
358
359 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
360 {
361 (void) central;
362 (void) peripheral;
363 (void) RSSI;
364
365 NSData *data = [advertisementData objectForKey:CBAdvertisementDataManufacturerDataKey];
366
367 // Just return if the beacon data does not match what we are looking for.
368 if (!data || ([data length] != beaconDataLength))
369 {
370 return;
371 }
372
373 unsigned char *bytes = (unsigned char *)data.bytes;
374
375 // Just parse the DBDeviceTypeBonjour beacons.
376 if (bytes[beaconTypeByteIndex] == DBDeviceTypeBonjour)
377 {
378 serviceHash_t peerBloomFilter;
379 mDNSEthAddr peerMAC;
380 unsigned char flagsAndVersion;
381 unsigned char *ptr;
382
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
387
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
391
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));
397
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]);
403 #else
404 (void)flagsAndVersion; // Unused
405 #endif // VERBOSE_BLE_DEBUG
406
407 responseReceived(peerBloomFilter, & peerMAC);
408 }
409 }
410
411 #pragma mark - CBPeripheralManagerDelegate protocol
412
413 - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
414 {
415
416 switch (peripheral.state) {
417 case CBManagerStateUnknown:
418 LogInfo("peripheralManagerDidUpdateState: CBManagerStateUnknown");
419 break;
420
421 case CBManagerStateResetting:
422 LogInfo("peripheralManagerDidUpdateState: CBManagerStateResetting");
423 break;
424
425 case CBManagerStateUnsupported:
426 LogInfo("peripheralManagerDidUpdateState: CBManagerStateUnsupported");
427 break;
428
429 case CBManagerStateUnauthorized:
430 LogInfo("peripheralManagerDidUpdateState: CBManagerStateUnauthorized");
431 break;
432
433 case CBManagerStatePoweredOff:
434 LogInfo("peripheralManagerDidUpdateState: CBManagerStatePoweredOff");
435 break;
436
437 case CBManagerStatePoweredOn:
438 // Hold lock to synchronize with main thread from this callback thread.
439 KQueueLock();
440
441 LogInfo("peripheralManagerDidUpdateState: CBManagerStatePoweredOn");
442 _peripheralManagerIsOn = YES;
443
444 // Start beaconing if we have initialized beacon data to send.
445 if (_currentlyAdvertisedData)
446 [self startBeacon];
447
448 KQueueUnlock("CBManagerStatePoweredOn");
449 break;
450
451 default:
452 LogInfo("peripheralManagerDidUpdateState: Unknown state ??");
453 break;
454 }
455 }
456
457 - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(nullable NSError *)error
458 {
459 (void) peripheral;
460
461 if (error)
462 {
463 const char * errorString = [[error localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding];
464 LogInfo("peripheralManagerDidStartAdvertising: error = %s", errorString ? errorString: "unknown");
465 }
466 else
467 {
468 LogInfo("peripheralManagerDidStartAdvertising:");
469 }
470 }
471
472 @end
473 #endif // ENABLE_BLE_TRIGGERED_BONJOUR