blob: c6fd8d8fe6d55959eacecdd9dba72d062e678560 [file] [log] [blame]
/**
*
* Copyright (c) 2023 Project CHIP Authors
*
* 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.
*/
#import "MTRCommissionableBrowser.h"
#import "MTRCommissionableBrowserDelegate.h"
#import "MTRCommissionableBrowserResult_Internal.h"
#import "MTRDeviceController.h"
#import "MTRLogging_Internal.h"
#include <controller/CHIPDeviceController.h>
#include <lib/dnssd/platform/Dnssd.h>
#include <platform/CHIPDeviceLayer.h>
using namespace chip::Dnssd;
using namespace chip::DeviceLayer;
#if CONFIG_NETWORK_LAYER_BLE
#include <platform/Darwin/BleScannerDelegate.h>
using namespace chip::Ble;
#endif // CONFIG_NETWORK_LAYER_BLE
constexpr const char * kBleKey = "BLE";
@implementation MTRCommissionableBrowserResultInterfaces
@end
@interface MTRCommissionableBrowserResult ()
@property (nonatomic) NSString * instanceName;
@property (nonatomic) NSNumber * vendorID;
@property (nonatomic) NSNumber * productID;
@property (nonatomic) NSNumber * discriminator;
@property (nonatomic) BOOL commissioningMode;
@end
@implementation MTRCommissionableBrowserResult
@end
class CommissionableBrowserInternal : public CommissioningResolveDelegate,
public DnssdBrowseDelegate
#if CONFIG_NETWORK_LAYER_BLE
,
public BleScannerDelegate
#endif // CONFIG_NETWORK_LAYER_BLE
{
public:
CHIP_ERROR Start(id<MTRCommissionableBrowserDelegate> delegate, MTRDeviceController * controller, dispatch_queue_t queue)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mDelegate == nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mController == nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDispatchQueue == nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDiscoveredResults == nil, CHIP_ERROR_INCORRECT_STATE);
mDelegate = delegate;
mController = controller;
mDispatchQueue = queue;
mDiscoveredResults = [[NSMutableDictionary alloc] init];
#if CONFIG_NETWORK_LAYER_BLE
ReturnErrorOnFailure(PlatformMgrImpl().StartBleScan(this));
#endif // CONFIG_NETWORK_LAYER_BLE
ReturnErrorOnFailure(Resolver::Instance().Init(chip::DeviceLayer::UDPEndPointManager()));
char serviceName[kMaxCommissionableServiceNameSize];
auto filter = DiscoveryFilterType::kNone;
ReturnErrorOnFailure(MakeServiceTypeName(serviceName, sizeof(serviceName), filter, DiscoveryType::kCommissionableNode));
return ChipDnssdBrowse(serviceName, DnssdServiceProtocol::kDnssdProtocolUdp, chip::Inet::IPAddressType::kAny,
chip::Inet::InterfaceId::Null(), this);
}
CHIP_ERROR Stop()
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mDelegate != nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mController != nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDispatchQueue != nil, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mDiscoveredResults != nil, CHIP_ERROR_INCORRECT_STATE);
mDelegate = nil;
mController = nil;
mDispatchQueue = nil;
ClearBleDiscoveredDevices();
ClearDnssdDiscoveredDevices();
mDiscoveredResults = nil;
#if CONFIG_NETWORK_LAYER_BLE
ReturnErrorOnFailure(PlatformMgrImpl().StopBleScan());
#endif // CONFIG_NETWORK_LAYER_BLE
return ChipDnssdStopBrowse(this);
}
void ClearBleDiscoveredDevices()
{
NSMutableDictionary<NSString *, MTRCommissionableBrowserResult *> * discoveredResultsCopy = @ {}.mutableCopy;
for (NSString * key in mDiscoveredResults) {
if (![mDiscoveredResults[key].instanceName isEqual:[NSString stringWithUTF8String:kBleKey]]) {
discoveredResultsCopy[key] = mDiscoveredResults[key];
}
}
mDiscoveredResults = discoveredResultsCopy;
}
void ClearDnssdDiscoveredDevices()
{
NSMutableDictionary<NSString *, MTRCommissionableBrowserResult *> * discoveredResultsCopy = @ {}.mutableCopy;
for (NSString * key in mDiscoveredResults) {
auto * interfaces = mDiscoveredResults[key].interfaces;
for (id interfaceKey in interfaces) {
// Check if the interface data has been resolved already, otherwise, just inform the
// back end that we may not need it anymore.
if (!interfaces[interfaceKey].resolutionData.HasValue()) {
ChipDnssdResolveNoLongerNeeded(key.UTF8String);
}
}
if ([mDiscoveredResults[key].instanceName isEqual:[NSString stringWithUTF8String:kBleKey]]) {
discoveredResultsCopy[key] = mDiscoveredResults[key];
}
}
mDiscoveredResults = discoveredResultsCopy;
}
/////////// CommissioningResolveDelegate Interface /////////
void OnNodeDiscovered(const DiscoveredNodeData & nodeData) override
{
assertChipStackLockedByCurrentThread();
auto & commissionData = nodeData.commissionData;
auto key = [NSString stringWithUTF8String:commissionData.instanceName];
if ([mDiscoveredResults objectForKey:key] == nil) {
// It should not happens.
return;
}
auto result = mDiscoveredResults[key];
result.instanceName = key;
result.vendorID = @(commissionData.vendorId);
result.productID = @(commissionData.productId);
result.discriminator = @(commissionData.longDiscriminator);
result.commissioningMode = commissionData.commissioningMode != 0;
auto & resolutionData = nodeData.resolutionData;
auto * interfaces = result.interfaces;
interfaces[@(resolutionData.interfaceId.GetPlatformInterface())].resolutionData = chip::MakeOptional(resolutionData);
// Check if any interface for the advertised service has been resolved already. If so,
// we don't need to inform the delegate about it since it already knows that something
// is available.
auto shouldDispatchToDelegate = YES;
for (id interfaceKey in interfaces) {
if (![interfaceKey isEqual:@(resolutionData.interfaceId.GetPlatformInterface())]
&& interfaces[interfaceKey].resolutionData.HasValue()) {
shouldDispatchToDelegate = NO;
break;
}
}
if (!shouldDispatchToDelegate) {
return;
}
dispatch_async(mDispatchQueue, ^{
[mDelegate controller:mController didFindCommissionableDevice:result];
});
}
/////////// DnssdBrowseDelegate Interface /////////
void OnBrowseAdd(DnssdService service) override
{
assertChipStackLockedByCurrentThread();
VerifyOrReturn(mDelegate != nil);
VerifyOrReturn(mController != nil);
VerifyOrReturn(mDispatchQueue != nil);
auto key = [NSString stringWithUTF8String:service.mName];
if ([mDiscoveredResults objectForKey:key] == nil) {
mDiscoveredResults[key] = [[MTRCommissionableBrowserResult alloc] init];
mDiscoveredResults[key].interfaces = [[NSMutableDictionary alloc] init];
}
auto * interfaces = mDiscoveredResults[key].interfaces;
auto interfaceKey = @(service.mInterface.GetPlatformInterface());
interfaces[interfaceKey] = [[MTRCommissionableBrowserResultInterfaces alloc] init];
LogErrorOnFailure(ChipDnssdResolve(&service, service.mInterface, this));
}
void OnBrowseRemove(DnssdService service) override
{
assertChipStackLockedByCurrentThread();
VerifyOrReturn(mDelegate != nil);
VerifyOrReturn(mController != nil);
VerifyOrReturn(mDispatchQueue != nil);
auto key = [NSString stringWithUTF8String:service.mName];
if ([mDiscoveredResults objectForKey:key] == nil) {
// It should not happens.
return;
}
auto result = mDiscoveredResults[key];
auto * interfaces = result.interfaces;
auto interfaceKey = @(service.mInterface.GetPlatformInterface());
// Check if the interface data has been resolved already, otherwise, just inform the
// back end that we may not need it anymore.
if (!interfaces[interfaceKey].resolutionData.HasValue()) {
ChipDnssdResolveNoLongerNeeded(service.mName);
}
// Delete the interface placeholder.
interfaces[interfaceKey] = nil;
// If there is nothing else to resolve for the given instance name, just remove it
// too and informs the delegate that it is gone.
if ([interfaces count] == 0) {
dispatch_async(mDispatchQueue, ^{
[mDelegate controller:mController didRemoveCommissionableDevice:result];
});
mDiscoveredResults[key] = nil;
}
}
void OnBrowseStop(CHIP_ERROR error) override
{
assertChipStackLockedByCurrentThread();
ClearDnssdDiscoveredDevices();
}
#if CONFIG_NETWORK_LAYER_BLE
/////////// BleScannerDelegate Interface /////////
void OnBleScanAdd(BLE_CONNECTION_OBJECT connObj, const ChipBLEDeviceIdentificationInfo & info) override
{
assertChipStackLockedByCurrentThread();
auto result = [[MTRCommissionableBrowserResult alloc] init];
result.instanceName = [NSString stringWithUTF8String:kBleKey];
result.vendorID = @(info.GetVendorId());
result.productID = @(info.GetProductId());
result.discriminator = @(info.GetDeviceDiscriminator());
result.commissioningMode = YES;
result.params = chip::MakeOptional(chip::Controller::SetUpCodePairerParameters(connObj, false /* connected */));
auto key = [NSString stringWithFormat:@"%@", connObj];
mDiscoveredResults[key] = result;
dispatch_async(mDispatchQueue, ^{
[mDelegate controller:mController didFindCommissionableDevice:result];
});
}
void OnBleScanRemove(BLE_CONNECTION_OBJECT connObj) override
{
assertChipStackLockedByCurrentThread();
auto key = [NSString stringWithFormat:@"%@", connObj];
if ([mDiscoveredResults objectForKey:key] == nil) {
// It should not happens.
return;
}
auto result = mDiscoveredResults[key];
mDiscoveredResults[key] = nil;
dispatch_async(mDispatchQueue, ^{
[mDelegate controller:mController didFindCommissionableDevice:result];
});
}
#endif // CONFIG_NETWORK_LAYER_BLE
private:
dispatch_queue_t mDispatchQueue;
id<MTRCommissionableBrowserDelegate> mDelegate;
MTRDeviceController * mController;
NSMutableDictionary<NSString *, MTRCommissionableBrowserResult *> * mDiscoveredResults;
};
@interface MTRCommissionableBrowser ()
@property (strong, nonatomic) dispatch_queue_t queue;
@property (nonatomic, readonly) id<MTRCommissionableBrowserDelegate> delegate;
@property (nonatomic, readonly) MTRDeviceController * controller;
@property (unsafe_unretained, nonatomic) CommissionableBrowserInternal browser;
@end
@implementation MTRCommissionableBrowser
- (instancetype)initWithDelegate:(id<MTRCommissionableBrowserDelegate>)delegate
controller:(MTRDeviceController *)controller
queue:(dispatch_queue_t)queue
{
if (self = [super init]) {
_delegate = delegate;
_controller = controller;
_queue = queue;
}
return self;
}
- (BOOL)start
{
VerifyOrReturnValue(CHIP_NO_ERROR == _browser.Start(_delegate, _controller, _queue), NO);
return YES;
}
- (BOOL)stop
{
VerifyOrReturnValue(CHIP_NO_ERROR == _browser.Stop(), NO);
_delegate = nil;
_controller = nil;
_queue = nil;
return YES;
}
@end