blob: 6858594620257c8cf5163c193530084b45a416b3 [file] [log] [blame]
/*
*
* Copyright (c) 2020-2021 Project CHIP Authors
* Copyright (c) 2015-2017 Nest Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* Provides an implementation of BleConnectionDelegate for Darwin platforms.
*/
#if !__has_feature(objc_arc)
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#include <ble/BleConfig.h>
#include <ble/BleError.h>
#include <ble/BleLayer.h>
#include <ble/BleUUID.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/Darwin/BleConnectionDelegate.h>
#include <setup_payload/SetupPayload.h>
#import "UUIDHelper.h"
using namespace chip::Ble;
constexpr uint64_t kScanningWithDiscriminatorTimeoutInSeconds = 60;
constexpr uint64_t kScanningWithoutDiscriminatorTimeoutInSeconds = 120;
@interface BleConnection : NSObject <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (strong, nonatomic) dispatch_queue_t workQueue;
@property (strong, nonatomic) dispatch_queue_t chipWorkQueue;
@property (strong, nonatomic) CBCentralManager * centralManager;
@property (strong, nonatomic) CBPeripheral * peripheral;
@property (strong, nonatomic) CBUUID * shortServiceUUID;
@property (nonatomic, readonly, nullable) dispatch_source_t timer;
@property (strong, nonatomic) NSMutableDictionary * cachedPeripherals;
@property (unsafe_unretained, nonatomic) bool hasDeviceDiscriminator;
@property (unsafe_unretained, nonatomic) bool found;
@property (unsafe_unretained, nonatomic) chip::SetupDiscriminator deviceDiscriminator;
@property (unsafe_unretained, nonatomic) void * appState;
@property (unsafe_unretained, nonatomic) BleConnectionDelegate::OnConnectionCompleteFunct onConnectionComplete;
@property (unsafe_unretained, nonatomic) BleConnectionDelegate::OnConnectionErrorFunct onConnectionError;
@property (unsafe_unretained, nonatomic) chip::Ble::BleLayer * mBleLayer;
- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator;
- (void)setBleLayer:(chip::Ble::BleLayer *)bleLayer;
- (void)start;
- (void)stop;
- (BOOL)hasDiscriminator;
- (void)updateWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator;
- (void)update;
@end
namespace chip {
namespace DeviceLayer {
namespace Internal {
BleConnection * ble;
void BleConnectionDelegateImpl::NewConnection(
Ble::BleLayer * bleLayer, void * appState, const SetupDiscriminator & deviceDiscriminator)
{
ChipLogProgress(Ble, "%s", __FUNCTION__);
// If the previous connection delegate was a scan without a discriminator, just reuse it instead of
// creating a brand new connection but update the discriminator and the ble layer members.
if (ble and ![ble hasDiscriminator]) {
[ble setBleLayer:bleLayer];
ble.appState = appState;
ble.onConnectionComplete = OnConnectionComplete;
ble.onConnectionError = OnConnectionError;
[ble updateWithDiscriminator:deviceDiscriminator];
return;
}
CancelConnection();
ble = [[BleConnection alloc] initWithDiscriminator:deviceDiscriminator];
[ble setBleLayer:bleLayer];
ble.appState = appState;
ble.onConnectionComplete = OnConnectionComplete;
ble.onConnectionError = OnConnectionError;
ble.centralManager = [ble.centralManager initWithDelegate:ble queue:ble.workQueue];
}
void BleConnectionDelegateImpl::PrepareConnection()
{
ChipLogProgress(Ble, "%s", __FUNCTION__);
// If the previous connection delegate was a scan without a discriminator, just reuse it instead of
// creating a brand new connection but clear the cache and reset the timer.
if (ble and ![ble hasDiscriminator]) {
[ble update];
return;
}
CancelConnection();
ble = [[BleConnection alloc] init];
ble.onConnectionComplete = OnConnectionComplete;
ble.onConnectionError = OnConnectionError;
ble.centralManager = [ble.centralManager initWithDelegate:ble queue:ble.workQueue];
}
CHIP_ERROR BleConnectionDelegateImpl::CancelConnection()
{
ChipLogProgress(Ble, "%s", __FUNCTION__);
if (ble) {
[ble stop];
ble = nil;
}
return CHIP_NO_ERROR;
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip
@interface BleConnection ()
@end
@implementation BleConnection
- (id)init
{
self = [super init];
if (self) {
self.shortServiceUUID = [UUIDHelper GetShortestServiceUUID:&chip::Ble::CHIP_BLE_SVC_ID];
_workQueue
= dispatch_queue_create("org.csa-iot.matter.framework.ble.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
_chipWorkQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue();
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue);
_centralManager = [CBCentralManager alloc];
_found = false;
_cachedPeripherals = [[NSMutableDictionary alloc] init];
_hasDeviceDiscriminator = false;
dispatch_source_set_event_handler(_timer, ^{
[self stop];
[self dispatchConnectionError:BLE_ERROR_APP_CLOSED_CONNECTION];
});
[self resetTimer];
}
return self;
}
- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator
{
self = [self init];
if (self) {
_deviceDiscriminator = deviceDiscriminator;
_hasDeviceDiscriminator = true;
[self resetTimer];
}
return self;
}
- (void)resetTimer
{
auto timeout =
[self hasDiscriminator] ? kScanningWithDiscriminatorTimeoutInSeconds : kScanningWithoutDiscriminatorTimeoutInSeconds;
dispatch_source_set_timer(_timer, dispatch_walltime(nullptr, timeout * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 5 * NSEC_PER_SEC);
}
// All our callback dispatch must happen on _chipWorkQueue
- (void)dispatchConnectionError:(CHIP_ERROR)error
{
if (self.onConnectionError == nil) {
return;
}
dispatch_async(_chipWorkQueue, ^{
self.onConnectionError(self.appState, error);
});
}
- (void)dispatchConnectionComplete:(CBPeripheral *)peripheral
{
if (self.onConnectionComplete == nil) {
return;
}
dispatch_async(_chipWorkQueue, ^{
self.onConnectionComplete(self.appState, (__bridge void *) peripheral);
});
}
// Start CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBManagerStatePoweredOn:
ChipLogDetail(Ble, "CBManagerState: ON");
[self start];
break;
case CBManagerStatePoweredOff:
ChipLogDetail(Ble, "CBManagerState: OFF");
[self stop];
[self dispatchConnectionError:BLE_ERROR_APP_CLOSED_CONNECTION];
break;
case CBManagerStateUnauthorized:
ChipLogDetail(Ble, "CBManagerState: Unauthorized");
break;
case CBManagerStateResetting:
ChipLogDetail(Ble, "CBManagerState: RESETTING");
break;
case CBManagerStateUnsupported:
ChipLogDetail(Ble, "CBManagerState: UNSUPPORTED");
break;
case CBManagerStateUnknown:
ChipLogDetail(Ble, "CBManagerState: UNKNOWN");
break;
}
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
NSNumber * isConnectable = [advertisementData objectForKey:CBAdvertisementDataIsConnectable];
if ([isConnectable boolValue]) {
NSDictionary * servicesData = [advertisementData objectForKey:CBAdvertisementDataServiceDataKey];
for (CBUUID * serviceUUID in servicesData) {
if ([serviceUUID.data isEqualToData:_shortServiceUUID.data]) {
NSData * serviceData = [servicesData objectForKey:serviceUUID];
NSUInteger length = [serviceData length];
if (length == 8) {
const uint8_t * bytes = (const uint8_t *) [serviceData bytes];
uint8_t opCode = bytes[0];
uint16_t discriminator = (bytes[1] | (bytes[2] << 8)) & 0xfff;
if (opCode == 0 || opCode == 1) {
if (![self hasDiscriminator]) {
ChipLogProgress(Ble, "Storing device %p with discriminator: %d", peripheral, discriminator);
_cachedPeripherals[@(discriminator)] = peripheral;
} else if ([self checkDiscriminator:discriminator]) {
ChipLogProgress(Ble, "Connecting to device %p with discriminator: %d", peripheral, discriminator);
[self connect:peripheral];
[self stopScanning];
}
}
}
break;
}
}
}
}
- (BOOL)hasDiscriminator
{
return _hasDeviceDiscriminator;
}
- (BOOL)checkDiscriminator:(uint16_t)discriminator
{
return _deviceDiscriminator.MatchesLongDiscriminator(discriminator);
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
[peripheral setDelegate:self];
[peripheral discoverServices:nil];
}
// End CBCentralManagerDelegate
// Start CBPeripheralDelegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (nil != error) {
ChipLogError(Ble, "BLE:Error finding Chip Service in the device: [%s]", [error.localizedDescription UTF8String]);
}
for (CBService * service in peripheral.services) {
if ([service.UUID.data isEqualToData:_shortServiceUUID.data] && !self.found) {
[peripheral discoverCharacteristics:nil forService:service];
self.found = true;
break;
}
}
if (!self.found || error != nil) {
ChipLogError(Ble, "Service not found on the device.");
[self dispatchConnectionError:CHIP_ERROR_INCORRECT_STATE];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (nil != error) {
ChipLogError(
Ble, "BLE:Error finding Characteristics in Chip service on the device: [%s]", [error.localizedDescription UTF8String]);
}
// XXX error ?
[self dispatchConnectionComplete:peripheral];
}
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error
{
if (nil == error) {
chip::Ble::ChipBleUUID svcId;
chip::Ble::ChipBleUUID charId;
[BleConnection fillServiceWithCharacteristicUuids:characteristic svcId:&svcId charId:&charId];
dispatch_async(_chipWorkQueue, ^{
_mBleLayer->HandleWriteConfirmation((__bridge void *) peripheral, &svcId, &charId);
});
} else {
ChipLogError(
Ble, "BLE:Error writing Characteristics in Chip service on the device: [%s]", [error.localizedDescription UTF8String]);
dispatch_async(_chipWorkQueue, ^{
_mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_WRITE_FAILED);
});
}
}
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error
{
bool isNotifying = characteristic.isNotifying;
if (nil == error) {
chip::Ble::ChipBleUUID svcId;
chip::Ble::ChipBleUUID charId;
[BleConnection fillServiceWithCharacteristicUuids:characteristic svcId:&svcId charId:&charId];
dispatch_async(_chipWorkQueue, ^{
if (isNotifying) {
_mBleLayer->HandleSubscribeComplete((__bridge void *) peripheral, &svcId, &charId);
} else {
_mBleLayer->HandleUnsubscribeComplete((__bridge void *) peripheral, &svcId, &charId);
}
});
} else {
ChipLogError(Ble, "BLE:Error subscribing/unsubcribing some characteristic on the device: [%s]",
[error.localizedDescription UTF8String]);
dispatch_async(_chipWorkQueue, ^{
if (isNotifying) {
// we're still notifying, so we must failed the unsubscription
_mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_UNSUBSCRIBE_FAILED);
} else {
// we're not notifying, so we must failed the subscription
_mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_SUBSCRIBE_FAILED);
}
});
}
}
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error
{
if (nil == error) {
chip::Ble::ChipBleUUID svcId;
chip::Ble::ChipBleUUID charId;
[BleConnection fillServiceWithCharacteristicUuids:characteristic svcId:&svcId charId:&charId];
// build a inet buffer from the rxEv and send to blelayer.
__block chip::System::PacketBufferHandle msgBuf
= chip::System::PacketBufferHandle::NewWithData(characteristic.value.bytes, characteristic.value.length);
if (!msgBuf.IsNull()) {
dispatch_async(_chipWorkQueue, ^{
if (!_mBleLayer->HandleIndicationReceived((__bridge void *) peripheral, &svcId, &charId, std::move(msgBuf))) {
// since this error comes from device manager core
// we assume it would do the right thing, like closing the connection
ChipLogError(Ble, "Failed at handling incoming BLE data");
}
});
} else {
ChipLogError(Ble, "Failed at allocating buffer for incoming BLE data");
dispatch_async(_chipWorkQueue, ^{
_mBleLayer->HandleConnectionError((__bridge void *) peripheral, CHIP_ERROR_NO_MEMORY);
});
}
} else {
ChipLogError(
Ble, "BLE:Error receiving indication of Characteristics on the device: [%s]", [error.localizedDescription UTF8String]);
dispatch_async(_chipWorkQueue, ^{
_mBleLayer->HandleConnectionError((__bridge void *) peripheral, BLE_ERROR_GATT_INDICATE_FAILED);
});
}
}
// End CBPeripheralDelegate
- (void)start
{
dispatch_resume(_timer);
[self startScanning];
}
- (void)stop
{
[self stopScanning];
[self disconnect];
[_cachedPeripherals removeAllObjects];
_cachedPeripherals = nil;
_centralManager.delegate = nil;
_centralManager = nil;
_peripheral = nil;
}
- (void)startScanning
{
if (!_centralManager) {
return;
}
[_centralManager scanForPeripheralsWithServices:@[ _shortServiceUUID ] options:nil];
}
- (void)stopScanning
{
if (!_centralManager) {
return;
}
dispatch_source_cancel(_timer);
[_centralManager stopScan];
}
- (void)connect:(CBPeripheral *)peripheral
{
if (!_centralManager || !peripheral) {
return;
}
_peripheral = peripheral;
[_centralManager connectPeripheral:peripheral options:nil];
}
- (void)disconnect
{
if (!_centralManager || !_peripheral) {
return;
}
_mBleLayer->CloseAllBleConnections();
_peripheral = nil;
}
- (void)update
{
[_cachedPeripherals removeAllObjects];
[self resetTimer];
}
- (void)updateWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator
{
_deviceDiscriminator = deviceDiscriminator;
_hasDeviceDiscriminator = true;
CBPeripheral * peripheral = nil;
if (deviceDiscriminator.IsShortDiscriminator()) {
for (NSNumber * longDiscriminator in _cachedPeripherals) {
if ([self checkDiscriminator:[longDiscriminator unsignedShortValue]]) {
peripheral = _cachedPeripherals[longDiscriminator];
break;
}
}
} else {
peripheral = _cachedPeripherals[@(deviceDiscriminator.GetLongValue())];
}
if (peripheral) {
ChipLogProgress(Ble, "Connecting to cached device: %p", peripheral);
[self connect:peripheral];
[self stopScanning];
} else {
[self resetTimer];
}
}
/**
* private static method to copy service and characteristic UUIDs from CBCharacteristic to a pair of ChipBleUUID objects.
* this is used in calls into Chip layer to decouple it from CoreBluetooth
*
* @param[in] characteristic the source characteristic
* @param[in] svcId the destination service UUID
* @param[in] charId the destination characteristic UUID
*
*/
+ (void)fillServiceWithCharacteristicUuids:(CBCharacteristic *)characteristic
svcId:(chip::Ble::ChipBleUUID *)svcId
charId:(chip::Ble::ChipBleUUID *)charId
{
static const size_t FullUUIDLength = 16;
if ((FullUUIDLength != sizeof(charId->bytes)) || (FullUUIDLength != sizeof(svcId->bytes))
|| (FullUUIDLength != characteristic.UUID.data.length)) {
// we're dead. we expect the data length to be the same (16-byte) across the board
ChipLogError(Ble, "UUID of characteristic is incompatible");
return;
}
memcpy(charId->bytes, characteristic.UUID.data.bytes, sizeof(charId->bytes));
memset(svcId->bytes, 0, sizeof(svcId->bytes));
// Expand service UUID back to 16-byte long as that's what the BLE Layer expects
// this is a buffer pre-filled with BLE service UUID Base
// byte 0 to 3 are reserved for shorter versions of BLE service UUIDs
// For 4-byte service UUIDs, all bytes from 0 to 3 are used
// For 2-byte service UUIDs, byte 0 and 1 shall be 0
uint8_t serviceFullUUID[FullUUIDLength]
= { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB };
switch (characteristic.service.UUID.data.length) {
case 2:
// copy the 2-byte service UUID onto the right offset
memcpy(serviceFullUUID + 2, characteristic.service.UUID.data.bytes, 2);
break;
case 4:
// flow through
case 16:
memcpy(serviceFullUUID, characteristic.service.UUID.data.bytes, characteristic.service.UUID.data.length);
break;
default:
// we're dead. we expect the data length to be the same (16-byte) across the board
ChipLogError(Ble, "Service UUIDs are incompatible");
}
memcpy(svcId->bytes, serviceFullUUID, sizeof(svcId->bytes));
}
- (void)setBleLayer:(chip::Ble::BleLayer *)bleLayer
{
_mBleLayer = bleLayer;
}
@end