Imported Upstream version 878.70.2
[platform/upstream/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