blob: d1759e3008eee8430a059b2ea69bf3c8d19aba67 [file] [log] [blame]
/**
*
* Copyright (c) 2020-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 <Matter/MTRDefines.h>
#import <Matter/MTRDeviceControllerParameters.h>
#import "MTRDeviceController_Internal.h"
#import "MTRAsyncWorkQueue.h"
#import "MTRAttestationTrustStoreBridge.h"
#import "MTRBaseDevice_Internal.h"
#import "MTRCommissionableBrowser.h"
#import "MTRCommissionableBrowserResult_Internal.h"
#import "MTRCommissioningParameters.h"
#import "MTRConversion.h"
#import "MTRDefines_Internal.h"
#import "MTRDeviceControllerDelegateBridge.h"
#import "MTRDeviceControllerFactory_Internal.h"
#import "MTRDeviceControllerLocalTestStorage.h"
#import "MTRDeviceControllerStartupParams.h"
#import "MTRDeviceControllerStartupParams_Internal.h"
#import "MTRDeviceControllerXPCParameters.h"
#import "MTRDeviceController_Concrete.h"
#import "MTRDeviceController_XPC.h"
#import "MTRDeviceController_XPC_Internal.h"
#import "MTRDevice_Concrete.h"
#import "MTRDevice_Internal.h"
#import "MTRError_Internal.h"
#import "MTRKeypair.h"
#import "MTRLogging_Internal.h"
#import "MTRMetricKeys.h"
#import "MTRMetricsCollector.h"
#import "MTROperationalCredentialsDelegate.h"
#import "MTRP256KeypairBridge.h"
#import "MTRPersistentStorageDelegateBridge.h"
#import "MTRServerEndpoint_Internal.h"
#import "MTRSetupPayload.h"
#import "MTRTimeUtils.h"
#import "MTRUnfairLock.h"
#import "MTRUtilities.h"
#import "NSDataSpanConversion.h"
#import "NSStringSpanConversion.h"
#import <setup_payload/ManualSetupPayloadGenerator.h>
#import <setup_payload/SetupPayload.h>
#import <zap-generated/MTRBaseClusters.h>
#import "MTRDeviceAttestationDelegateBridge.h"
#import "MTRDeviceConnectionBridge.h"
#include <platform/CHIPDeviceConfig.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app/data-model/List.h>
#include <app/server/Dnssd.h>
#include <controller/CHIPDeviceController.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <controller/CommissioningWindowOpener.h>
#include <credentials/FabricTable.h>
#include <credentials/GroupDataProvider.h>
#include <credentials/attestation_verifier/DacOnlyPartialAttestationVerifier.h>
#include <credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h>
#include <inet/InetInterface.h>
#include <lib/core/CHIPVendorIdentifiers.hpp>
#include <platform/LockTracker.h>
#include <platform/PlatformManager.h>
#include <setup_payload/ManualSetupPayloadGenerator.h>
#include <system/SystemClock.h>
#include <atomic>
#include <dns_sd.h>
#include <optional>
#include <string>
#import <os/lock.h>
// TODO: These strings and their consumers in this file should probably go away,
// since none of them really apply to all controllers.
static NSString * const kErrorNotRunning = @"Controller is not running. Call startup first.";
static NSString * const kErrorSpake2pVerifierGenerationFailed = @"PASE verifier generation failed";
static NSString * const kErrorSpake2pVerifierSerializationFailed = @"PASE verifier serialization failed";
typedef void (^SyncWorkQueueBlock)(void);
typedef id (^SyncWorkQueueBlockWithReturnValue)(void);
typedef BOOL (^SyncWorkQueueBlockWithBoolReturnValue)(void);
using namespace chip::Tracing::DarwinFramework;
@interface MTRDeviceControllerDelegateInfo : NSObject
- (instancetype)initWithDelegate:(id<MTRDeviceControllerDelegate>)delegate queue:(dispatch_queue_t)queue;
@property (nonatomic, weak, readonly) id<MTRDeviceControllerDelegate> delegate;
@property (nonatomic, readonly) dispatch_queue_t queue;
@end
@implementation MTRDeviceControllerDelegateInfo
@synthesize delegate = _delegate, queue = _queue;
- (instancetype)initWithDelegate:(id<MTRDeviceControllerDelegate>)delegate queue:(dispatch_queue_t)queue
{
if (!(self = [super init])) {
return nil;
}
_delegate = delegate;
_queue = queue;
return self;
}
@end
@implementation MTRDeviceController {
chip::Controller::DeviceCommissioner * _cppCommissioner;
chip::Credentials::PartialDACVerifier * _partialDACVerifier;
chip::Credentials::DefaultDACVerifier * _defaultDACVerifier;
MTRDeviceControllerDelegateBridge * _deviceControllerDelegateBridge;
MTROperationalCredentialsDelegate * _operationalCredentialsDelegate;
MTRDeviceAttestationDelegateBridge * _deviceAttestationDelegateBridge;
MTRDeviceControllerFactory * _factory;
os_unfair_lock _underlyingDeviceMapLock;
MTRCommissionableBrowser * _commissionableBrowser;
MTRAttestationTrustStoreBridge * _attestationTrustStoreBridge;
// _serverEndpoints is only touched on the Matter queue.
NSMutableArray<MTRServerEndpoint *> * _serverEndpoints;
MTRDeviceStorageBehaviorConfiguration * _storageBehaviorConfiguration;
std::atomic<chip::FabricIndex> _storedFabricIndex;
std::atomic<std::optional<uint64_t>> _storedCompressedFabricID;
MTRP256KeypairBridge _signingKeypairBridge;
MTRP256KeypairBridge _operationalKeypairBridge;
// For now, we just ensure that access to _suspended is atomic, but don't
// guarantee atomicity of the the entire suspend/resume operation. The
// expectation is that suspend/resume on a given controller happen on some
// specific queue, so can't race against each other.
std::atomic<bool> _suspended;
NSMutableArray<MTRDeviceControllerDelegateInfo *> * _delegates;
id<MTRDeviceControllerDelegate> _strongDelegateForSetDelegateAPI;
}
@synthesize uniqueIdentifier = _uniqueIdentifier;
- (os_unfair_lock_t)deviceMapLock
{
return &_underlyingDeviceMapLock;
}
- (instancetype)initForSubclasses:(BOOL)startSuspended
{
if (self = [super init]) {
// nothing, as superclass of MTRDeviceController is NSObject
}
_underlyingDeviceMapLock = OS_UNFAIR_LOCK_INIT;
_suspended = startSuspended;
_nodeIDToDeviceMap = [NSMapTable strongToWeakObjectsMapTable];
_delegates = [NSMutableArray array];
return self;
}
- (nullable MTRDeviceController *)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters error:(NSError * __autoreleasing *)error
{
// Dispatch to the right non-abstract implementation.
if ([parameters isKindOfClass:MTRXPCDeviceControllerParameters.class]) {
MTR_LOG("Starting up with XPC Device Controller Parameters: %@", parameters);
return [[MTRDeviceController_XPC alloc] initWithParameters:parameters error:error];
}
if ([parameters isKindOfClass:MTRDeviceControllerMachServiceXPCParameters.class]) {
// TODO: This will need to at least pass in the uniqueIdentifier, no? initWithMachServiceName:options: seems to
// be declared but not actually implemented...
auto * xpcParameters = static_cast<MTRDeviceControllerMachServiceXPCParameters *>(parameters);
MTR_LOG("Starting up with Mach Service XPC Device Controller Parameters: %@", parameters);
return [[MTRDeviceController_XPC alloc] initWithMachServiceName:xpcParameters.machServiceName options:xpcParameters.connectionOptions];
}
if ([parameters isKindOfClass:MTRDeviceControllerParameters.class]) {
MTR_LOG("Starting up with Device Controller Parameters: %@", parameters);
return [[MTRDeviceController_Concrete alloc] initWithParameters:parameters error:error];
}
MTR_LOG_ERROR("Unsupported type of MTRDeviceControllerAbstractParameters: %@", parameters);
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p, uuid: %@, suspended: %@>", NSStringFromClass(self.class), self, self.uniqueIdentifier, MTR_YES_NO(self.suspended)];
}
- (BOOL)isRunning
{
return _cppCommissioner != nullptr;
}
#pragma mark - Suspend/resume support
- (BOOL)isSuspended
{
return _suspended;
}
- (void)_notifyDelegatesOfSuspendState
{
BOOL isSuspended = [self isSuspended];
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
if ([delegate respondsToSelector:@selector(controller:suspendedChangedTo:)]) {
[delegate controller:self suspendedChangedTo:isSuspended];
}
} logString:__PRETTY_FUNCTION__];
}
- (void)suspend
{
MTR_LOG("%@ suspending", self);
if (![self isRunning]) {
MTR_LOG_ERROR("%@ not running; can't suspend", self);
return;
}
NSArray * devicesToSuspend;
{
std::lock_guard lock(*self.deviceMapLock);
// Set _suspended under the device map lock. This guarantees that
// for any given device exactly one of two things is true:
// * It is in the snapshot we are creating
// * It is created after we have changed our _suspended state.
if (_suspended) {
MTR_LOG("%@ already suspended", self);
return;
}
_suspended = YES;
devicesToSuspend = [self.nodeIDToDeviceMap objectEnumerator].allObjects;
}
MTR_LOG("%@ found %lu devices to suspend", self, static_cast<unsigned long>(devicesToSuspend.count));
for (MTRDevice * device in devicesToSuspend) {
[device controllerSuspended];
}
// TODO: In the concrete class, consider what should happen with:
//
// * Active commissioning sessions (presumably close them?)
// * CASE sessions in general.
// * Possibly try to see whether we can change our fabric entry to not advertise and restart advertising.
[self _notifyDelegatesOfSuspendState];
[self _controllerSuspended];
}
- (void)_controllerSuspended
{
// Subclass hook; nothing to do.
}
- (void)resume
{
MTR_LOG("%@ resuming", self);
if (![self isRunning]) {
MTR_LOG_ERROR("%@ not running; can't resume", self);
return;
}
NSArray * devicesToResume;
{
std::lock_guard lock(*self.deviceMapLock);
// Set _suspended under the device map lock. This guarantees that
// for any given device exactly one of two things is true:
// * It is in the snapshot we are creating
// * It is created after we have changed our _suspended state.
if (!_suspended) {
MTR_LOG("%@ already not suspended", self);
return;
}
_suspended = NO;
devicesToResume = [self.nodeIDToDeviceMap objectEnumerator].allObjects;
}
MTR_LOG("%@ found %lu devices to resume", self, static_cast<unsigned long>(devicesToResume.count));
for (MTRDevice * device in devicesToResume) {
[device controllerResumed];
}
[self _notifyDelegatesOfSuspendState];
[self _controllerResumed];
}
- (void)_controllerResumed
{
// Subclass hook; nothing to do.
}
- (void)shutdown
{
MTR_ABSTRACT_METHOD();
}
- (NSNumber *)controllerNodeID
{
auto block = ^NSNumber * { return @(self->_cppCommissioner->GetNodeId()); };
NSNumber * nodeID = [self syncRunOnWorkQueueWithReturnValue:block error:nil];
if (!nodeID) {
MTR_LOG_ERROR("%@ A controller has no node id if it has not been started", self);
}
return nodeID;
}
- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload
newNodeID:(NSNumber *)newNodeID
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)setupCommissioningSessionWithDiscoveredDevice:(MTRCommissionableBrowserResult *)discoveredDevice
payload:(MTRSetupPayload *)payload
newNodeID:(NSNumber *)newNodeID
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)commissionNodeWithID:(NSNumber *)nodeID
commissioningParams:(MTRCommissioningParameters *)commissioningParams
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)continueCommissioningDevice:(void *)device
ignoreAttestationFailure:(BOOL)ignoreAttestationFailure
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)cancelCommissioningForNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)startBrowseForCommissionables:(id<MTRCommissionableBrowserDelegate>)delegate queue:(dispatch_queue_t)queue
{
MTR_ABSTRACT_METHOD();
return NO;
}
- (BOOL)stopBrowseForCommissionables
{
MTR_ABSTRACT_METHOD();
return NO;
}
- (void)preWarmCommissioningSession
{
MTR_ABSTRACT_METHOD();
}
- (nullable MTRBaseDevice *)deviceBeingCommissionedWithNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
- (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID
{
return [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:self];
}
- (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *)prefetchedClusterData
{
MTR_ABSTRACT_METHOD();
return nil;
}
- (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID
{
std::lock_guard lock(*self.deviceMapLock);
MTRDevice * deviceToReturn = [_nodeIDToDeviceMap objectForKey:nodeID];
if (!deviceToReturn) {
deviceToReturn = [self _setupDeviceForNodeID:nodeID prefetchedClusterData:nil];
}
return deviceToReturn;
}
- (void)removeDevice:(MTRDevice *)device
{
std::lock_guard lock(*self.deviceMapLock);
auto * nodeID = device.nodeID;
MTRDevice * deviceToRemove = [_nodeIDToDeviceMap objectForKey:nodeID];
if (deviceToRemove == device) {
[deviceToRemove invalidate];
[_nodeIDToDeviceMap removeObjectForKey:nodeID];
} else {
MTR_LOG_ERROR("%@ Error: Cannot remove device %p with nodeID %llu", self, device, nodeID.unsignedLongLongValue);
}
}
#ifdef DEBUG
- (NSDictionary<NSNumber *, NSNumber *> *)unitTestGetDeviceAttributeCounts
{
std::lock_guard lock(*self.deviceMapLock);
NSMutableDictionary<NSNumber *, NSNumber *> * deviceAttributeCounts = [NSMutableDictionary dictionary];
for (NSNumber * nodeID in _nodeIDToDeviceMap) {
deviceAttributeCounts[nodeID] = @([[_nodeIDToDeviceMap objectForKey:nodeID] unitTestAttributeCount]);
}
return deviceAttributeCounts;
}
#endif
- (BOOL)setOperationalCertificateIssuer:(nullable id<MTROperationalCertificateIssuer>)operationalCertificateIssuer
queue:(nullable dispatch_queue_t)queue
{
MTR_ABSTRACT_METHOD();
return NO;
}
+ (nullable NSData *)computePASEVerifierForSetupPasscode:(NSNumber *)setupPasscode
iterations:(NSNumber *)iterations
salt:(NSData *)salt
error:(NSError * __autoreleasing *)error
{
chip::Crypto::Spake2pVerifier verifier;
CHIP_ERROR err = verifier.Generate(iterations.unsignedIntValue, AsByteSpan(salt), setupPasscode.unsignedIntValue);
MATTER_LOG_METRIC_SCOPE(kMetricPASEVerifierForSetupCode, err);
if ([MTRDeviceController checkForError:err logMsg:kErrorSpake2pVerifierGenerationFailed error:error]) {
return nil;
}
uint8_t serializedBuffer[chip::Crypto::kSpake2p_VerifierSerialized_Length];
chip::MutableByteSpan serializedBytes(serializedBuffer);
err = verifier.Serialize(serializedBytes);
if ([MTRDeviceController checkForError:err logMsg:kErrorSpake2pVerifierSerializationFailed error:error]) {
return nil;
}
return AsData(serializedBytes);
}
- (NSData * _Nullable)attestationChallengeForDeviceID:(NSNumber *)deviceID
{
MTR_ABSTRACT_METHOD();
return nil;
}
- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint
{
MTR_ABSTRACT_METHOD();
return NO;
}
- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint queue:(dispatch_queue_t)queue completion:(dispatch_block_t)completion
{
[self removeServerEndpointInternal:endpoint queue:queue completion:completion];
}
- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint
{
[self removeServerEndpointInternal:endpoint queue:nil completion:nil];
}
- (void)removeServerEndpointInternal:(MTRServerEndpoint *)endpoint queue:(dispatch_queue_t _Nullable)queue completion:(dispatch_block_t _Nullable)completion
{
MTR_ABSTRACT_METHOD();
if (queue != nil && completion != nil) {
dispatch_async(queue, completion);
}
}
+ (BOOL)checkForError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg error:(NSError * __autoreleasing *)error
{
if (CHIP_NO_ERROR == errorCode) {
return NO;
}
MTR_LOG_ERROR("Error(%" CHIP_ERROR_FORMAT "): %@ %s", errorCode.Format(), self, [logMsg UTF8String]);
if (error) {
*error = [MTRError errorForCHIPErrorCode:errorCode];
}
return YES;
}
- (BOOL)checkIsRunning
{
return [self checkIsRunning:nil];
}
- (BOOL)checkIsRunning:(NSError * __autoreleasing *)error
{
if ([self isRunning]) {
return YES;
}
MTR_LOG_ERROR("%@: %@ Error: %s", NSStringFromClass(self.class), self, [kErrorNotRunning UTF8String]);
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (void)getSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion
{
// Get the corresponding MTRDevice object to determine if the case/subscription pool is to be used
MTRDevice * device = [self deviceForNodeID:@(nodeID)];
// In the case that this device is known to use thread, queue this with subscription attempts as well, to
// help with throttling Thread traffic.
if ([device deviceUsesThread]) {
MTRAsyncWorkItem * workItem = [[MTRAsyncWorkItem alloc] initWithQueue:dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)];
[workItem setReadyHandler:^(id _Nonnull context, NSInteger retryCount, MTRAsyncWorkCompletionBlock _Nonnull workItemCompletion) {
MTRInternalDeviceConnectionCallback completionWrapper = ^(chip::Messaging::ExchangeManager * _Nullable exchangeManager,
const chip::Optional<chip::SessionHandle> & session, NSError * _Nullable error, NSNumber * _Nullable retryDelay) {
completion(exchangeManager, session, error, retryDelay);
workItemCompletion(MTRAsyncWorkComplete);
};
[self directlyGetSessionForNode:nodeID completion:completionWrapper];
}];
[_concurrentSubscriptionPool enqueueWorkItem:workItem descriptionWithFormat:@"device controller getSessionForNode nodeID: 0x%016llX", nodeID];
} else {
[self directlyGetSessionForNode:nodeID completion:completion];
}
}
- (void)directlyGetSessionForNode:(chip::NodeId)nodeID completion:(MTRInternalDeviceConnectionCallback)completion
{
[self
asyncGetCommissionerOnMatterQueue:^(chip::Controller::DeviceCommissioner * commissioner) {
auto connectionBridge = new MTRDeviceConnectionBridge(completion);
// MTRDeviceConnectionBridge always delivers errors async via
// completion.
connectionBridge->connect(commissioner, nodeID);
}
errorHandler:^(NSError * error) {
completion(nullptr, chip::NullOptional, error, nil);
}];
}
- (void)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRInternalDeviceConnectionCallback)completion
{
[self
asyncGetCommissionerOnMatterQueue:^(chip::Controller::DeviceCommissioner * commissioner) {
chip::CommissioneeDeviceProxy * deviceProxy;
CHIP_ERROR err = commissioner->GetDeviceBeingCommissioned(deviceID, &deviceProxy);
if (err != CHIP_NO_ERROR) {
completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:err], nil);
return;
}
chip::Optional<chip::SessionHandle> session = deviceProxy->GetSecureSession();
if (!session.HasValue() || !session.Value()->AsSecureSession()->IsPASESession()) {
completion(nullptr, chip::NullOptional, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE], nil);
return;
}
completion(deviceProxy->GetExchangeManager(), session, nil, nil);
}
errorHandler:^(NSError * error) {
completion(nullptr, chip::NullOptional, error, nil);
}];
}
- (MTRTransportType)sessionTransportTypeForDevice:(MTRBaseDevice *)device
{
VerifyOrReturnValue([self checkIsRunning], MTRTransportTypeUndefined);
__block MTRTransportType result = MTRTransportTypeUndefined;
dispatch_sync(_chipWorkQueue, ^{
VerifyOrReturn([self checkIsRunning]);
if (device.isPASEDevice) {
chip::CommissioneeDeviceProxy * deviceProxy;
VerifyOrReturn(CHIP_NO_ERROR == self->_cppCommissioner->GetDeviceBeingCommissioned(device.nodeID, &deviceProxy));
result = MTRMakeTransportType(deviceProxy->GetDeviceTransportType());
} else {
auto scopedNodeID = self->_cppCommissioner->GetPeerScopedId(device.nodeID);
auto sessionHandle = self->_cppCommissioner->SessionMgr()->FindSecureSessionForNode(scopedNodeID);
VerifyOrReturn(sessionHandle.HasValue());
result = MTRMakeTransportType(sessionHandle.Value()->AsSecureSession()->GetPeerAddress().GetTransportType());
}
});
return result;
}
- (void)asyncGetCommissionerOnMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
errorHandler:(nullable MTRDeviceErrorHandler)errorHandler
{
{
NSError * error;
if (![self checkIsRunning:&error]) {
if (errorHandler != nil) {
errorHandler(error);
}
return;
}
}
dispatch_async(_chipWorkQueue, ^{
NSError * error;
if (![self checkIsRunning:&error]) {
if (errorHandler != nil) {
errorHandler(error);
}
return;
}
block(self->_cppCommissioner);
});
}
- (void)asyncDispatchToMatterQueue:(dispatch_block_t)block errorHandler:(nullable MTRDeviceErrorHandler)errorHandler
{
auto adapter = ^(chip::Controller::DeviceCommissioner *) {
block();
};
[self asyncGetCommissionerOnMatterQueue:adapter errorHandler:errorHandler];
}
- (void)syncRunOnWorkQueue:(SyncWorkQueueBlock)block error:(NSError * __autoreleasing *)error
{
VerifyOrDie(!chip::DeviceLayer::PlatformMgrImpl().IsWorkQueueCurrentQueue());
VerifyOrReturn([self checkIsRunning:error]);
dispatch_sync(_chipWorkQueue, ^{
VerifyOrReturn([self checkIsRunning:error]);
block();
});
}
- (id)syncRunOnWorkQueueWithReturnValue:(SyncWorkQueueBlockWithReturnValue)block error:(NSError * __autoreleasing *)error
{
__block id rv = nil;
auto adapter = ^{
rv = block();
};
[self syncRunOnWorkQueue:adapter error:error];
return rv;
}
- (BOOL)syncRunOnWorkQueueWithBoolReturnValue:(SyncWorkQueueBlockWithBoolReturnValue)block error:(NSError * __autoreleasing *)error
{
__block BOOL success = NO;
auto adapter = ^{
success = block();
};
[self syncRunOnWorkQueue:adapter error:error];
return success;
}
- (chip::FabricIndex)fabricIndex
{
return _storedFabricIndex;
}
- (nullable NSNumber *)compressedFabricID
{
auto storedValue = _storedCompressedFabricID.load();
return storedValue.has_value() ? @(storedValue.value()) : nil;
}
- (void)invalidateCASESessionForNode:(chip::NodeId)nodeID;
{
auto block = ^{
auto sessionMgr = self->_cppCommissioner->SessionMgr();
VerifyOrDie(sessionMgr != nullptr);
sessionMgr->MarkSessionsAsDefunct(
self->_cppCommissioner->GetPeerScopedId(nodeID), chip::MakeOptional(chip::Transport::SecureSession::Type::kCASE));
};
[self syncRunOnWorkQueue:block error:nil];
}
- (void)operationalInstanceAdded:(chip::NodeId)nodeID
{
// Don't use deviceForNodeID here, because we don't want to create the
// device if it does not already exist.
os_unfair_lock_lock(self.deviceMapLock);
MTRDevice * device = [_nodeIDToDeviceMap objectForKey:@(nodeID)];
os_unfair_lock_unlock(self.deviceMapLock);
if (device == nil) {
return;
}
ChipLogProgress(Controller, "Notifying device about node 0x" ChipLogFormatX64 " advertising", ChipLogValueX64(nodeID));
[device nodeMayBeAdvertisingOperational];
}
- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID
type:(MTRDiagnosticLogType)type
timeout:(NSTimeInterval)timeout
queue:(dispatch_queue_t)queue
completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion
{
[self asyncDispatchToMatterQueue:^() {
[self->_factory downloadLogFromNodeWithID:nodeID
controller:self
type:type
timeout:timeout
queue:queue
completion:completion];
}
errorHandler:^(NSError * error) {
completion(nil, error);
}];
}
- (NSArray<MTRAccessGrant *> *)accessGrantsForClusterPath:(MTRClusterPath *)clusterPath
{
assertChipStackLockedByCurrentThread();
for (MTRServerEndpoint * endpoint in _serverEndpoints) {
if ([clusterPath.endpoint isEqual:endpoint.endpointID]) {
return [endpoint matterAccessGrantsForCluster:clusterPath.cluster];
}
}
// Nothing matched, no grants.
return @[];
}
- (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
{
assertChipStackLockedByCurrentThread();
for (MTRServerEndpoint * endpoint in _serverEndpoints) {
for (MTRServerCluster * cluster in endpoint.serverClusters) {
if (![cluster.clusterID isEqual:clusterID]) {
continue;
}
for (MTRServerAttribute * attr in cluster.attributes) {
if (![attr.attributeID isEqual:attributeID]) {
continue;
}
return @(attr.requiredReadPrivilege);
}
}
}
return nil;
}
#ifdef DEBUG
+ (void)forceLocalhostAdvertisingOnly
{
auto interfaceIndex = chip::Inet::InterfaceId::PlatformType(kDNSServiceInterfaceIndexLocalOnly);
auto interfaceId = chip::Inet::InterfaceId(interfaceIndex);
chip::app::DnssdServer::Instance().SetInterfaceId(interfaceId);
}
#endif // DEBUG
#pragma mark - MTRDeviceControllerDelegate management
// Note these are implemented in the base class so that XPC subclass can use it as well
- (void)setDeviceControllerDelegate:(id<MTRDeviceControllerDelegate>)delegate queue:(dispatch_queue_t)queue
{
@synchronized(self) {
if (_strongDelegateForSetDelegateAPI) {
if (_strongDelegateForSetDelegateAPI == delegate) {
MTR_LOG("%@ setDeviceControllerDelegate: delegate %p is already set", self, delegate);
return;
}
MTR_LOG("%@ setDeviceControllerDelegate: replacing %p with %p", self, _strongDelegateForSetDelegateAPI, delegate);
[self removeDeviceControllerDelegate:_strongDelegateForSetDelegateAPI];
}
_strongDelegateForSetDelegateAPI = delegate;
[self addDeviceControllerDelegate:delegate queue:queue];
}
}
- (void)addDeviceControllerDelegate:(id<MTRDeviceControllerDelegate>)delegate queue:(dispatch_queue_t)queue
{
@synchronized(self) {
__block BOOL delegateAlreadyAdded = NO;
[self _iterateDelegateInfoWithBlock:^(MTRDeviceControllerDelegateInfo * delegateInfo) {
if (delegateInfo.delegate == delegate) {
delegateAlreadyAdded = YES;
}
}];
if (delegateAlreadyAdded) {
MTR_LOG("%@ addDeviceControllerDelegate: delegate already added", self);
return;
}
MTRDeviceControllerDelegateInfo * newDelegateInfo = [[MTRDeviceControllerDelegateInfo alloc] initWithDelegate:delegate queue:queue];
[_delegates addObject:newDelegateInfo];
MTR_LOG("%@ addDeviceControllerDelegate: added %p total %lu", self, delegate, static_cast<unsigned long>(_delegates.count));
}
}
- (void)removeDeviceControllerDelegate:(id<MTRDeviceControllerDelegate>)delegate
{
@synchronized(self) {
if (_strongDelegateForSetDelegateAPI == delegate) {
_strongDelegateForSetDelegateAPI = nil;
}
__block MTRDeviceControllerDelegateInfo * delegateInfoToRemove = nil;
[self _iterateDelegateInfoWithBlock:^(MTRDeviceControllerDelegateInfo * delegateInfo) {
if (delegateInfo.delegate == delegate) {
delegateInfoToRemove = delegateInfo;
}
}];
if (delegateInfoToRemove) {
[_delegates removeObject:delegateInfoToRemove];
MTR_LOG("%@ removeDeviceControllerDelegate: removed %p remaining %lu", self, delegate, static_cast<unsigned long>(_delegates.count));
} else {
MTR_LOG("%@ removeDeviceControllerDelegate: delegate %p not found in %lu", self, delegate, static_cast<unsigned long>(_delegates.count));
}
}
}
// Iterates the delegates, and remove delegate info objects if the delegate object has dealloc'ed
// Returns number of delegates called
- (NSUInteger)_iterateDelegateInfoWithBlock:(void (^_Nullable)(MTRDeviceControllerDelegateInfo * delegateInfo))block
{
@synchronized(self) {
if (!_delegates.count) {
MTR_LOG("%@ No delegates to iterate", self);
return 0;
}
// Opportunistically remove defunct delegate references on every iteration
NSMutableArray * delegatesToRemove = nil;
for (MTRDeviceControllerDelegateInfo * delegateInfo in _delegates) {
id<MTRDeviceControllerDelegate> strongDelegate = delegateInfo.delegate;
if (strongDelegate) {
if (block) {
block(delegateInfo);
}
} else {
if (!delegatesToRemove) {
delegatesToRemove = [NSMutableArray array];
}
[delegatesToRemove addObject:delegateInfo];
}
}
if (delegatesToRemove.count) {
[_delegates removeObjectsInArray:delegatesToRemove];
MTR_LOG("%@ _iterateDelegatesWithBlock: removed %lu remaining %lu", self, static_cast<unsigned long>(delegatesToRemove.count), static_cast<unsigned long>(_delegates.count));
}
return _delegates.count;
}
}
- (void)_callDelegatesWithBlock:(void (^_Nullable)(id<MTRDeviceControllerDelegate> delegate))block logString:(const char *)logString;
{
NSUInteger delegatesCalled = [self _iterateDelegateInfoWithBlock:^(MTRDeviceControllerDelegateInfo * delegateInfo) {
id<MTRDeviceControllerDelegate> strongDelegate = delegateInfo.delegate;
dispatch_async(delegateInfo.queue, ^{
block(strongDelegate);
});
}];
MTR_LOG("%@ %lu delegates called for %s", self, static_cast<unsigned long>(delegatesCalled), logString);
}
#if DEBUG
- (NSUInteger)unitTestDelegateCount
{
return [self _iterateDelegateInfoWithBlock:nil];
}
#endif
- (void)controller:(MTRDeviceController *)controller statusUpdate:(MTRCommissioningStatus)status
{
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
if ([delegate respondsToSelector:@selector(controller:statusUpdate:)]) {
[delegate controller:controller statusUpdate:status];
}
} logString:__PRETTY_FUNCTION__];
}
- (void)controller:(MTRDeviceController *)controller commissioningSessionEstablishmentDone:(NSError * _Nullable)error
{
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
if ([delegate respondsToSelector:@selector(controller:commissioningSessionEstablishmentDone:)]) {
[delegate controller:controller commissioningSessionEstablishmentDone:error];
}
} logString:__PRETTY_FUNCTION__];
}
- (void)controller:(MTRDeviceController *)controller
commissioningComplete:(NSError * _Nullable)error
nodeID:(NSNumber * _Nullable)nodeID
metrics:(MTRMetrics *)metrics
{
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
if ([delegate respondsToSelector:@selector(controller:commissioningComplete:nodeID:metrics:)]) {
[delegate controller:controller commissioningComplete:error nodeID:nodeID metrics:metrics];
} else if ([delegate respondsToSelector:@selector(controller:commissioningComplete:nodeID:)]) {
[delegate controller:controller commissioningComplete:error nodeID:nodeID];
} else if ([delegate respondsToSelector:@selector(controller:commissioningComplete:)]) {
[delegate controller:controller commissioningComplete:error];
}
} logString:__PRETTY_FUNCTION__];
}
- (void)controller:(MTRDeviceController *)controller readCommissioningInfo:(MTRProductIdentity *)info
{
[self _callDelegatesWithBlock:^(id<MTRDeviceControllerDelegate> delegate) {
if ([delegate respondsToSelector:@selector(controller:readCommissioningInfo:)]) {
[delegate controller:controller readCommissioningInfo:info];
}
} logString:__PRETTY_FUNCTION__];
}
@end
// TODO: This should not be in the superclass: either move to
// MTRDeviceController_Concrete.mm, or move into a separate .h/.mm pair of
// files.
@implementation MTRDevicePairingDelegateShim
- (instancetype)initWithDelegate:(id<MTRDevicePairingDelegate>)delegate
{
if (self = [super init]) {
_delegate = delegate;
}
return self;
}
- (BOOL)respondsToSelector:(SEL)selector
{
if (selector == @selector(controller:statusUpdate:)) {
return [self.delegate respondsToSelector:@selector(onStatusUpdate:)];
}
if (selector == @selector(controller:commissioningSessionEstablishmentDone:)) {
return [self.delegate respondsToSelector:@selector(onPairingComplete:)];
}
if (selector == @selector(controller:commissioningComplete:)) {
return [self.delegate respondsToSelector:@selector(onCommissioningComplete:)];
}
return [super respondsToSelector:selector];
}
- (void)controller:(MTRDeviceController *)controller statusUpdate:(MTRCommissioningStatus)status
{
[self.delegate onStatusUpdate:static_cast<MTRPairingStatus>(status)];
}
- (void)controller:(MTRDeviceController *)controller commissioningSessionEstablishmentDone:(NSError * _Nullable)error
{
[self.delegate onPairingComplete:error];
}
- (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError * _Nullable)error
{
[self.delegate onCommissioningComplete:error];
}
- (void)onPairingDeleted:(NSError * _Nullable)error
{
[self.delegate onPairingDeleted:error];
}
@end
/**
* Shim to allow us to treat an MTRNOCChainIssuer as an
* MTROperationalCertificateIssuer.
*/
// TODO: This should not be in the superclass: either move to
// MTRDeviceController_Concrete.mm, or move into a separate .h/.mm pair of
// files.
@interface MTROperationalCertificateChainIssuerShim : NSObject <MTROperationalCertificateIssuer>
@property (nonatomic, readonly) id<MTRNOCChainIssuer> nocChainIssuer;
@property (nonatomic, readonly) BOOL shouldSkipAttestationCertificateValidation;
- (instancetype)initWithIssuer:(id<MTRNOCChainIssuer>)nocChainIssuer;
@end
@implementation MTROperationalCertificateChainIssuerShim
- (instancetype)initWithIssuer:(id<MTRNOCChainIssuer>)nocChainIssuer
{
if (self = [super init]) {
_nocChainIssuer = nocChainIssuer;
_shouldSkipAttestationCertificateValidation = YES;
}
return self;
}
- (void)issueOperationalCertificateForRequest:(MTROperationalCSRInfo *)csrInfo
attestationInfo:(MTRDeviceAttestationInfo *)attestationInfo
controller:(MTRDeviceController *)controller
completion:(void (^)(MTROperationalCertificateChain * _Nullable info,
NSError * _Nullable error))completion
{
CSRInfo * oldCSRInfo = [[CSRInfo alloc] initWithNonce:csrInfo.csrNonce
elements:csrInfo.csrElementsTLV
elementsSignature:csrInfo.attestationSignature
csr:csrInfo.csr];
NSData * _Nullable firmwareInfo = attestationInfo.firmwareInfo;
if (firmwareInfo == nil) {
firmwareInfo = [NSData data];
}
AttestationInfo * oldAttestationInfo =
[[AttestationInfo alloc] initWithChallenge:attestationInfo.challenge
nonce:attestationInfo.nonce
elements:attestationInfo.elementsTLV
elementsSignature:attestationInfo.elementsSignature
dac:attestationInfo.deviceAttestationCertificate
pai:attestationInfo.productAttestationIntermediateCertificate
certificationDeclaration:attestationInfo.certificationDeclaration
firmwareInfo:firmwareInfo];
[self.nocChainIssuer
onNOCChainGenerationNeeded:oldCSRInfo
attestationInfo:oldAttestationInfo
onNOCChainGenerationComplete:^(NSData * operationalCertificate, NSData * intermediateCertificate, NSData * rootCertificate,
NSData * _Nullable ipk, NSNumber * _Nullable adminSubject, NSError * __autoreleasing * error) {
auto * chain = [[MTROperationalCertificateChain alloc] initWithOperationalCertificate:operationalCertificate
intermediateCertificate:intermediateCertificate
rootCertificate:rootCertificate
adminSubject:adminSubject];
completion(chain, nil);
if (error != nil) {
*error = nil;
}
}];
}
@end
@implementation MTRDeviceController (Deprecated)
- (NSNumber *)controllerNodeId
{
return self.controllerNodeID;
}
- (nullable NSData *)fetchAttestationChallengeForDeviceId:(uint64_t)deviceId
{
return [self attestationChallengeForDeviceID:@(deviceId)];
}
- (BOOL)getBaseDevice:(uint64_t)deviceID queue:(dispatch_queue_t)queue completionHandler:(MTRDeviceConnectionCallback)completion
{
MTR_ABSTRACT_METHOD();
return NO;
}
- (BOOL)pairDevice:(uint64_t)deviceID
discriminator:(uint16_t)discriminator
setupPINCode:(uint32_t)setupPINCode
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)pairDevice:(uint64_t)deviceID
address:(NSString *)address
port:(uint16_t)port
setupPINCode:(uint32_t)setupPINCode
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)pairDevice:(uint64_t)deviceID onboardingPayload:(NSString *)onboardingPayload error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (BOOL)commissionDevice:(uint64_t)deviceID
commissioningParams:(MTRCommissioningParameters *)commissioningParams
error:(NSError * __autoreleasing *)error
{
return [self commissionNodeWithID:@(deviceID) commissioningParams:commissioningParams error:error];
}
- (BOOL)stopDevicePairing:(uint64_t)deviceID error:(NSError * __autoreleasing *)error
{
return [self cancelCommissioningForNodeID:@(deviceID) error:error];
}
- (MTRBaseDevice *)getDeviceBeingCommissioned:(uint64_t)deviceId error:(NSError * __autoreleasing *)error
{
return [self deviceBeingCommissionedWithNodeID:@(deviceId) error:error];
}
- (BOOL)openPairingWindow:(uint64_t)deviceID duration:(NSUInteger)duration error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return NO;
}
- (nullable NSString *)openPairingWindowWithPIN:(uint64_t)deviceID
duration:(NSUInteger)duration
discriminator:(NSUInteger)discriminator
setupPIN:(NSUInteger)setupPIN
error:(NSError * __autoreleasing *)error
{
MTR_ABSTRACT_METHOD();
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
- (nullable NSData *)computePaseVerifier:(uint32_t)setupPincode iterations:(uint32_t)iterations salt:(NSData *)salt
{
return [MTRDeviceController computePASEVerifierForSetupPasscode:@(setupPincode) iterations:@(iterations) salt:salt error:nil];
}
- (void)setPairingDelegate:(id<MTRDevicePairingDelegate>)delegate queue:(dispatch_queue_t)queue
{
auto * delegateShim = [[MTRDevicePairingDelegateShim alloc] initWithDelegate:delegate];
[self setDeviceControllerDelegate:delegateShim queue:queue];
}
- (void)setNocChainIssuer:(id<MTRNOCChainIssuer>)nocChainIssuer queue:(dispatch_queue_t)queue
{
[self setOperationalCertificateIssuer:[[MTROperationalCertificateChainIssuerShim alloc] initWithIssuer:nocChainIssuer]
queue:queue];
}
@end