blob: 0ca254acb341157812dff03ce86854f32bf00fc4 [file] [log] [blame]
/**
* Copyright (c) 2022 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 "MTRDeviceControllerFactory.h"
#import "MTRDeviceControllerFactory_Internal.h"
#import <Matter/MTRDefines.h>
#import <Matter/MTRClusterConstants.h>
#import <Matter/MTRDeviceControllerParameters.h>
#import <Matter/MTRServerCluster.h>
#import "MTRCertificates.h"
#import "MTRDemuxingStorage.h"
#import "MTRDeviceController.h"
#import "MTRDeviceControllerStartupParams.h"
#import "MTRDeviceControllerStartupParams_Internal.h"
#import "MTRDeviceController_Internal.h"
#import "MTRDiagnosticLogsDownloader.h"
#import "MTRError_Internal.h"
#import "MTRFabricInfo_Internal.h"
#import "MTRFramework.h"
#import "MTRLogging_Internal.h"
#import "MTRMetricKeys.h"
#import "MTRMetricsCollector.h"
#import "MTROTAProviderDelegateBridge.h"
#import "MTROperationalBrowser.h"
#import "MTRP256KeypairBridge.h"
#import "MTRPersistentStorageDelegateBridge.h"
#import "MTRServerAccessControl.h"
#import "MTRServerCluster_Internal.h"
#import "MTRServerEndpoint_Internal.h"
#import "MTRSessionResumptionStorageBridge.h"
#import "MTRUnfairLock.h"
#import "NSDataSpanConversion.h"
#import <os/lock.h>
#include <app/server/Dnssd.h>
#include <controller/CHIPDeviceControllerFactory.h>
#include <credentials/CHIPCert.h>
#include <credentials/FabricTable.h>
#include <credentials/GroupDataProviderImpl.h>
#include <credentials/PersistentStorageOpCertStore.h>
#include <crypto/PersistentStorageOperationalKeystore.h>
#include <crypto/RawKeySessionKeystore.h>
#include <lib/support/Pool.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <messaging/ReliableMessageMgr.h>
#include <messaging/ReliableMessageProtocolConfig.h>
#include <platform/PlatformManager.h>
#include <cstdlib>
using namespace chip;
using namespace chip::Controller;
using namespace chip::Tracing::DarwinFramework;
static bool sExitHandlerRegistered = false;
static void ShutdownOnExit()
{
MTR_LOG_INFO("ShutdownOnExit invoked on exit");
[[MTRDeviceControllerFactory sharedInstance] stopControllerFactory];
}
@interface MTRDeviceControllerFactoryParams ()
// Flag to keep track of whether our .storage is real consumer-provided storage
// or just the fake thing we made up.
@property (nonatomic, assign) BOOL hasStorage;
@end
MTR_DIRECT_MEMBERS
@interface MTRDeviceControllerFactory ()
- (void)preWarmCommissioningSessionDone;
@end
MTR_DIRECT_MEMBERS
@implementation MTRDeviceControllerFactory {
dispatch_queue_t _chipWorkQueue;
DeviceControllerFactory * _controllerFactory;
Credentials::IgnoreCertificateValidityPeriodPolicy _certificateValidityPolicy;
Crypto::RawKeySessionKeystore _sessionKeystore;
// We use TestPersistentStorageDelegate just to get an in-memory store to back
// our group data provider impl. We initialize this store correctly on every
// controller startup, so don't need to actually persist it.
TestPersistentStorageDelegate _groupStorageDelegate;
Credentials::GroupDataProviderImpl _groupDataProvider;
// _usingPerControllerStorage is only written once, during controller
// factory start. After that it is only read, and can be read from
// arbitrary threads.
BOOL _usingPerControllerStorage;
PersistentStorageDelegate * _persistentStorageDelegate;
MTRSessionResumptionStorageBridge * _sessionResumptionStorage;
PersistentStorageOperationalKeystore * _keystore;
Credentials::PersistentStorageOpCertStore * _opCertStore;
MTROperationalBrowser * _operationalBrowser;
// productAttestationAuthorityCertificates and certificationDeclarationCertificates are just copied
// from MTRDeviceControllerFactoryParams.
NSArray<MTRCertificateDERBytes> * _Nullable _productAttestationAuthorityCertificates;
NSArray<MTRCertificateDERBytes> * _Nullable _certificationDeclarationCertificates;
BOOL _advertiseOperational;
// Lock used to serialize access to the "controllers" array and the
// "_controllerBeingStarted" and "_controllerBeingShutDown" ivars, since those
// need to be touched from both whatever queue is starting controllers and from
// the Matter queue. The way this lock is used assumes that:
//
// 1) The only mutating accesses to the controllers array and the ivars happen
// when the current queue is not the Matter queue or in a block that was
// sync-dispatched to the Matter queue. This is a good assumption, because
// the implementations of the functions that mutate these do sync dispatch to
// the Matter queue, which would deadlock if they were called when that queue
// was the current queue.
//
// 2) It's our API consumer's responsibility to serialize access to us from
// outside.
//
// These assumptions mean that if we are in a block that was sync-dispatched to
// the Matter queue, that block cannot race with either the Matter queue nor the
// non-Matter queue. Similarly, if we are in a situation where the Matter queue
// has been shut down, any accesses to the variables cannot race anything else.
//
// This means that:
//
// A. In a sync-dispatched block, or if the Matter queue has been shut down, we
// do not need to lock and can do read or write access.
// B. Apart from item A, mutations of the array and ivars must happen outside the
// Matter queue and must lock.
// C. Apart from item A, accesses on the Matter queue must be reads only and
// must lock.
// D. Locking around reads not from the Matter queue is OK but not required.
os_unfair_lock _controllersLock;
NSMutableArray<MTRDeviceController *> * _controllers;
MTRDeviceController * _controllerBeingStarted;
MTRDeviceController * _controllerBeingShutDown;
// Next available fabric index. Only valid when _controllerBeingStarted is
// non-nil, and then it corresponds to the controller being started. This
// is only accessed on the Matter queue or after the Matter queue has shut
// down.
FabricIndex _nextAvailableFabricIndex;
id<MTROTAProviderDelegate> _Nullable _otaProviderDelegate;
dispatch_queue_t _Nullable _otaProviderDelegateQueue;
MTRServerEndpoint * _otaProviderEndpoint;
std::unique_ptr<MTROTAProviderDelegateBridge> _otaProviderDelegateBridge;
MTRDiagnosticLogsDownloader * _Nullable _diagnosticLogsDownloader;
// Array of all server endpoints across all controllers, used to ensure
// in an atomic way that endpoint IDs are unique.
NSMutableArray<MTRServerEndpoint *> * _serverEndpoints;
os_unfair_lock _serverEndpointsLock; // Protects access to _serverEndpoints.
class final : public DeviceLayer::BleScannerDelegate {
void OnBleScanStopped() override
{
[MTRDeviceControllerFactory.sharedInstance preWarmCommissioningSessionDone];
}
} _preWarmingDelegate;
}
+ (void)initialize
{
MTRFrameworkInit();
}
+ (MTRDeviceControllerFactory *)sharedInstance
{
static MTRDeviceControllerFactory * factory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// initialize the factory.
factory = [[MTRDeviceControllerFactory alloc] init];
});
return factory;
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
// Start the work queue and leave it running. There is no performance
// cost to having an idle dispatch queue, and it simplifies our logic.
DeviceLayer::PlatformMgrImpl().StartEventLoopTask();
_chipWorkQueue = DeviceLayer::PlatformMgrImpl().GetWorkQueue();
_controllerFactory = &DeviceControllerFactory::GetInstance();
// Initialize our default-constructed GroupDataProviderImpl.
// For now default args are fine, since we are just using this for the IPK.
_groupDataProvider.SetStorageDelegate(&_groupStorageDelegate);
_groupDataProvider.SetSessionKeystore(&_sessionKeystore);
CHIP_ERROR err = _groupDataProvider.Init();
VerifyOrDieWithMsg(err == CHIP_NO_ERROR, NotSpecified,
"GroupDataProviderImpl::Init() failed: %" CHIP_ERROR_FORMAT, err.Format());
_controllersLock = OS_UNFAIR_LOCK_INIT;
_controllers = [[NSMutableArray alloc] init];
_serverEndpointsLock = OS_UNFAIR_LOCK_INIT;
_serverEndpoints = [[NSMutableArray alloc] init];
return self;
}
- (void)dealloc
{
[self stopControllerFactory];
_groupDataProvider.Finish();
}
- (void)_assertCurrentQueueIsNotMatterQueue
{
VerifyOrDie(!DeviceLayer::PlatformMgrImpl().IsWorkQueueCurrentQueue());
}
- (void)cleanupStartupObjects
{
assertChipStackLockedByCurrentThread();
MTR_LOG_INFO("Cleaning startup objects in controller factory");
// Make sure the deinit order here is the reverse of the init order in
// startControllerFactory:
_certificationDeclarationCertificates = nil;
_productAttestationAuthorityCertificates = nil;
if (_opCertStore) {
_opCertStore->Finish();
delete _opCertStore;
_opCertStore = nullptr;
}
if (_keystore) {
_keystore->Finish();
delete _keystore;
_keystore = nullptr;
}
_otaProviderDelegateQueue = nil;
_otaProviderDelegate = nil;
if (_sessionResumptionStorage) {
delete _sessionResumptionStorage;
_sessionResumptionStorage = nullptr;
}
if (_persistentStorageDelegate) {
delete _persistentStorageDelegate;
_persistentStorageDelegate = nullptr;
}
_diagnosticLogsDownloader = nil;
ShutdownMetricsCollection();
}
- (CHIP_ERROR)_initFabricTable:(FabricTable &)fabricTable
{
return fabricTable.Init(
{ .storage = _persistentStorageDelegate, .operationalKeystore = _keystore, .opCertStore = _opCertStore });
}
- (nullable NSArray<MTRFabricInfo *> *)knownFabrics
{
[self _assertCurrentQueueIsNotMatterQueue];
if (!_running) { // Note: reading _running from outside of the Matter work queue
return nil;
}
__block NSMutableArray<MTRFabricInfo *> * fabricList;
__block BOOL listFilled = NO;
dispatch_sync(_chipWorkQueue, ^{
FabricTable fabricTable;
CHIP_ERROR err = [self _initFabricTable:fabricTable];
if (err != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't initialize fabric table when getting known fabrics: %s", err.AsString());
return;
}
fabricList = [NSMutableArray<MTRFabricInfo *> arrayWithCapacity:fabricTable.FabricCount()];
for (const auto & fabricInfo : fabricTable) {
auto * info = [[MTRFabricInfo alloc] initWithFabricTable:fabricTable fabricInfo:fabricInfo];
if (info == nil) {
// Failed to read one of our fabrics.
return;
}
[fabricList addObject:info];
}
listFilled = YES;
});
if (listFilled == NO) {
return nil;
}
return fabricList;
}
- (BOOL)startControllerFactory:(MTRDeviceControllerFactoryParams *)startupParams error:(NSError * __autoreleasing *)error
{
return [self _startControllerFactory:startupParams startingController:NO error:error];
}
- (BOOL)_startControllerFactory:(MTRDeviceControllerFactoryParams *)startupParams
startingController:(BOOL)startingController
error:(NSError * __autoreleasing *)error
{
[self _assertCurrentQueueIsNotMatterQueue];
__block CHIP_ERROR err = CHIP_ERROR_INTERNAL;
dispatch_sync(_chipWorkQueue, ^{
if (_running) {
// TODO: When treating a duplicate call as success we should validate parameters match
MTR_LOG_DEBUG("Ignoring duplicate call to startup, Matter controller factory already started...");
ExitNow(err = CHIP_NO_ERROR);
}
StartupMetricsCollection();
InitializeServerAccessControl();
if (startupParams.hasStorage) {
_persistentStorageDelegate = new MTRPersistentStorageDelegateBridge(startupParams.storage);
_sessionResumptionStorage = nullptr;
_usingPerControllerStorage = NO;
} else {
_persistentStorageDelegate = new MTRDemuxingStorage(self);
_sessionResumptionStorage = new MTRSessionResumptionStorageBridge(self);
_usingPerControllerStorage = YES;
}
_otaProviderDelegate = startupParams.otaProviderDelegate;
if (_otaProviderDelegate != nil) {
_otaProviderDelegateQueue = dispatch_queue_create(
"org.csa-iot.matter.framework.otaprovider.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
}
// TODO: Allow passing a different keystore implementation via startupParams.
_keystore = new PersistentStorageOperationalKeystore();
SuccessOrExit(err = _keystore->Init(_persistentStorageDelegate));
// TODO Allow passing a different opcert store implementation via startupParams.
_opCertStore = new Credentials::PersistentStorageOpCertStore();
SuccessOrExit(err = _opCertStore->Init(_persistentStorageDelegate));
_productAttestationAuthorityCertificates = [startupParams.productAttestationAuthorityCertificates copy];
_certificationDeclarationCertificates = [startupParams.certificationDeclarationCertificates copy];
{
chip::Controller::FactoryInitParams params;
if (startupParams.port != nil) {
params.listenPort = [startupParams.port unsignedShortValue];
}
params.enableServerInteractions = startupParams.shouldStartServer;
params.groupDataProvider = &_groupDataProvider;
params.sessionKeystore = &_sessionKeystore;
params.fabricIndependentStorage = _persistentStorageDelegate;
params.operationalKeystore = _keystore;
params.opCertStore = _opCertStore;
params.certificateValidityPolicy = &_certificateValidityPolicy;
params.sessionResumptionStorage = _sessionResumptionStorage;
SuccessOrExit(err = _controllerFactory->Init(params));
}
// This needs to happen after DeviceControllerFactory::Init,
// because that creates (lazily, by calling functions with
// static variables in them) some static-lifetime objects.
if (!sExitHandlerRegistered) {
if (atexit(ShutdownOnExit) != 0) {
char error[128];
strerror_r(errno, error, sizeof(error));
MTR_LOG_ERROR("Warning: Failed to register atexit handler: %s", error);
}
sExitHandlerRegistered = true;
}
HeapObjectPoolExitHandling::IgnoreLeaksOnExit();
// Make sure we don't leave a system state running while we have no
// controllers started. This is working around the fact that a system
// state is brought up live on factory init, and not when it comes time
// to actually start a controller, and does not actually clean itself up
// until its refcount (which starts as 0) goes to 0.
// TODO: Don't cause a stack shutdown and restart if startingController
_controllerFactory->RetainSystemState();
_controllerFactory->ReleaseSystemState();
_advertiseOperational = startupParams.shouldStartServer;
_running = YES;
err = CHIP_NO_ERROR;
exit:
if (err != CHIP_NO_ERROR) {
// Note: Since we have failed no later than _controllerFactory->Init(),
// there is no need to call _controllerFactory->Shutdown() here.
[self cleanupStartupObjects];
}
});
if (err != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Failed to start Matter controller factory: %" CHIP_ERROR_FORMAT, err.Format());
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:err];
}
return NO;
}
return YES;
}
- (void)stopControllerFactory
{
[self _assertCurrentQueueIsNotMatterQueue];
while ([_controllers count] != 0) {
[_controllers[0] shutdown];
}
dispatch_sync(_chipWorkQueue, ^{
VerifyOrReturn(_running);
MTR_LOG_INFO("Shutting down the Matter controller factory");
_controllerFactory->Shutdown();
[self cleanupStartupObjects];
_running = NO;
_advertiseOperational = NO;
});
}
/**
* Helper function to start a device controller with the given startup params.
* The fabricChecker block will run on the Matter queue, and is expected to
* return nil if pre-startup fabric table checks fail, and set fabricError to
* the right error value in that situation.
*
* The provided controller is expected to have just been allocated and to not be
* initialized yet.
*/
- (MTRDeviceController * _Nullable)_startDeviceController:(MTRDeviceController *)controller
startupParams:(id)startupParams
fabricChecker:(MTRDeviceControllerStartupParamsInternal * (^)(FabricTable * fabricTable,
MTRDeviceController * controller,
CHIP_ERROR & fabricError))fabricChecker
error:(NSError * __autoreleasing *)error
{
[self _assertCurrentQueueIsNotMatterQueue];
id<MTRDeviceControllerStorageDelegate> _Nullable storageDelegate;
dispatch_queue_t _Nullable storageDelegateQueue;
NSUUID * uniqueIdentifier;
id<MTROTAProviderDelegate> _Nullable otaProviderDelegate;
dispatch_queue_t _Nullable otaProviderDelegateQueue;
NSUInteger concurrentSubscriptionPoolSize = 0;
if ([startupParams isKindOfClass:[MTRDeviceControllerParameters class]]) {
MTRDeviceControllerParameters * params = startupParams;
storageDelegate = params.storageDelegate;
storageDelegateQueue = params.storageDelegateQueue;
uniqueIdentifier = params.uniqueIdentifier;
otaProviderDelegate = params.otaProviderDelegate;
otaProviderDelegateQueue = params.otaProviderDelegateQueue;
concurrentSubscriptionPoolSize = params.concurrentSubscriptionEstablishmentsAllowedOnThread;
} else if ([startupParams isKindOfClass:[MTRDeviceControllerStartupParams class]]) {
MTRDeviceControllerStartupParams * params = startupParams;
storageDelegate = nil;
storageDelegateQueue = nil;
uniqueIdentifier = params.uniqueIdentifier;
otaProviderDelegate = nil;
otaProviderDelegateQueue = nil;
} else {
MTR_LOG_ERROR("Unknown kind of startup params: %@", startupParams);
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
if (!_running) { // Note: reading _running from outside of the Matter work queue
if (storageDelegate != nil) {
MTR_LOG_DEFAULT("Auto-starting Matter controller factory in per-controller storage mode");
auto * params = [[MTRDeviceControllerFactoryParams alloc] initWithoutStorage];
if (![self _startControllerFactory:params startingController:YES error:error]) {
return nil;
}
} else {
MTR_LOG_ERROR("Trying to start controller while Matter controller factory is not running");
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
}
if (_usingPerControllerStorage && storageDelegate == nil) {
MTR_LOG_ERROR("Must have a controller storage delegate when we do not have storage for the controller factory");
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
if (!_usingPerControllerStorage && storageDelegate != nil) {
MTR_LOG_ERROR("Must not have a controller storage delegate when we have storage for the controller factory");
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
// Fall back to the factory-wide OTA provider delegate if one is not
// provided in the startup params.
if (otaProviderDelegate == nil) {
otaProviderDelegate = _otaProviderDelegate;
otaProviderDelegateQueue = _otaProviderDelegateQueue;
}
controller = [controller initWithFactory:self
queue:_chipWorkQueue
storageDelegate:storageDelegate
storageDelegateQueue:storageDelegateQueue
otaProviderDelegate:otaProviderDelegate
otaProviderDelegateQueue:otaProviderDelegateQueue
uniqueIdentifier:uniqueIdentifier
concurrentSubscriptionPoolSize:concurrentSubscriptionPoolSize];
if (controller == nil) {
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
if ([_controllers count] == 0) {
dispatch_sync(_chipWorkQueue, ^{
self->_operationalBrowser = new MTROperationalBrowser(self, self->_chipWorkQueue);
});
}
// Add the controller to _controllers now, so if we fail partway through its
// startup we will still do the right cleanups.
os_unfair_lock_lock(&_controllersLock);
[_controllers addObject:controller];
os_unfair_lock_unlock(&_controllersLock);
__block MTRDeviceControllerStartupParamsInternal * params = nil;
__block CHIP_ERROR fabricError = CHIP_ERROR_INTERNAL;
// Create a temporary FabricTable instance for the fabricChecker logic.
// We want the block to end up with just a pointer to the fabric table,
// since we know our on-stack instance will outlive the block.
FabricTable fabricTableInstance;
FabricTable * fabricTable = &fabricTableInstance;
dispatch_sync(_chipWorkQueue, ^{
fabricError = [self _initFabricTable:*fabricTable];
if (fabricError != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't initialize fabric table: %s", fabricError.AsString());
return;
}
params = fabricChecker(fabricTable, controller, fabricError);
if (params == nil) {
return;
}
// Check that we are not trying to start a controller with a uniqueIdentifier that
// matches a running controller.
auto * controllersCopy = [self getRunningControllers];
for (MTRDeviceController * existing in controllersCopy) {
if (existing != controller && [existing.uniqueIdentifier isEqual:params.uniqueIdentifier]) {
MTR_LOG_ERROR("Already have running controller with uniqueIdentifier %@", existing.uniqueIdentifier);
fabricError = CHIP_ERROR_INVALID_ARGUMENT;
params = nil;
return;
}
}
// Save off the next available fabric index, in case we are starting a
// controller with a new fabric index. This just needs to happen before
// we set _controllerBeingStarted below.
fabricError = fabricTable->PeekFabricIndexForNextAddition(self->_nextAvailableFabricIndex);
if (fabricError != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Out of space in the fabric table");
params = nil;
return;
}
});
if (params == nil) {
[self controllerShuttingDown:controller];
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:fabricError];
}
return nil;
}
os_unfair_lock_lock(&_controllersLock);
_controllerBeingStarted = controller;
os_unfair_lock_unlock(&_controllersLock);
BOOL ok = [controller startup:params];
os_unfair_lock_lock(&_controllersLock);
_controllerBeingStarted = nil;
os_unfair_lock_unlock(&_controllersLock);
if (ok == NO) {
// TODO: get error from controller's startup.
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTERNAL];
}
return nil;
}
controller = [self maybeInitializeOTAProvider:controller];
if (controller == nil) {
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTERNAL];
}
}
return controller;
}
- (MTRDeviceController * _Nullable)createControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
error:(NSError * __autoreleasing *)error
{
[self _assertCurrentQueueIsNotMatterQueue];
if (_usingPerControllerStorage) {
// We can never have an "existing fabric" for a new controller to be
// created on, in the sense of createControllerOnExistingFabric.
MTR_LOG_ERROR("Can't createControllerOnExistingFabric when using per-controller data store");
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
return [self _startDeviceController:[MTRDeviceController alloc]
startupParams:startupParams
fabricChecker:^MTRDeviceControllerStartupParamsInternal *(
FabricTable * fabricTable, MTRDeviceController * controller, CHIP_ERROR & fabricError) {
const FabricInfo * fabric = nullptr;
BOOL ok = [self findMatchingFabric:*fabricTable params:startupParams fabric:&fabric];
if (!ok) {
MTR_LOG_ERROR("Can't start on existing fabric: fabric matching failed");
fabricError = CHIP_ERROR_INTERNAL;
return nil;
}
if (fabric == nullptr) {
MTR_LOG_ERROR("Can't start on existing fabric: fabric not found");
fabricError = CHIP_ERROR_NOT_FOUND;
return nil;
}
auto * controllersCopy = [self getRunningControllers];
for (MTRDeviceController * existing in controllersCopy) {
BOOL isRunning = YES; // assume the worst
if ([existing isRunningOnFabric:fabricTable
fabricIndex:fabric->GetFabricIndex()
isRunning:&isRunning]
!= CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't tell what fabric a controller is running on. Not safe to start.");
fabricError = CHIP_ERROR_INTERNAL;
return nil;
}
if (isRunning) {
MTR_LOG_ERROR("Can't start on existing fabric: another controller is running on it");
fabricError = CHIP_ERROR_INCORRECT_STATE;
return nil;
}
}
auto * params =
[[MTRDeviceControllerStartupParamsInternal alloc] initForExistingFabric:fabricTable
fabricIndex:fabric->GetFabricIndex()
keystore:self->_keystore
advertiseOperational:self->_advertiseOperational
params:startupParams];
params.productAttestationAuthorityCertificates = self->_productAttestationAuthorityCertificates;
params.certificationDeclarationCertificates = self->_certificationDeclarationCertificates;
return params;
}
error:error];
}
- (MTRDeviceController * _Nullable)createControllerOnNewFabric:(MTRDeviceControllerStartupParams *)startupParams
error:(NSError * __autoreleasing *)error
{
[self _assertCurrentQueueIsNotMatterQueue];
if (startupParams.vendorID == nil) {
MTR_LOG_ERROR("Must provide vendor id when starting controller on new fabric");
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
if (startupParams.intermediateCertificate != nil && startupParams.rootCertificate == nil) {
MTR_LOG_ERROR("Must provide a root certificate when using an intermediate certificate");
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
return [self _startDeviceController:[MTRDeviceController alloc]
startupParams:startupParams
fabricChecker:^MTRDeviceControllerStartupParamsInternal *(
FabricTable * fabricTable, MTRDeviceController * controller, CHIP_ERROR & fabricError) {
const FabricInfo * fabric = nullptr;
BOOL ok = [self findMatchingFabric:*fabricTable params:startupParams fabric:&fabric];
if (!ok) {
MTR_LOG_ERROR("Can't start on new fabric: fabric matching failed");
fabricError = CHIP_ERROR_INTERNAL;
return nil;
}
if (fabric != nullptr) {
MTR_LOG_ERROR("Can't start on new fabric that matches existing fabric");
fabricError = CHIP_ERROR_INCORRECT_STATE;
return nil;
}
auto * params =
[[MTRDeviceControllerStartupParamsInternal alloc] initForNewFabric:fabricTable
keystore:self->_keystore
advertiseOperational:self->_advertiseOperational
params:startupParams];
params.productAttestationAuthorityCertificates = self->_productAttestationAuthorityCertificates;
params.certificationDeclarationCertificates = self->_certificationDeclarationCertificates;
return params;
}
error:error];
}
- (void)preWarmCommissioningSession
{
dispatch_async(_chipWorkQueue, ^{
CHIP_ERROR err = CHIP_ERROR_INCORRECT_STATE;
if (!self->_running) {
MTR_LOG_ERROR("Can't pre-warm, Matter controller factory is not running");
} else {
MTR_LOG_DEFAULT("Pre-warming commissioning session");
self->_controllerFactory->EnsureAndRetainSystemState();
err = DeviceLayer::PlatformMgrImpl().StartBleScan(&self->_preWarmingDelegate, DeviceLayer::BleScanMode::kPreWarm);
if (err != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Pre-warming failed: %" CHIP_ERROR_FORMAT, err.Format());
self->_controllerFactory->ReleaseSystemState();
}
}
MATTER_LOG_METRIC(kMetricPreWarmCommissioning, err);
});
}
- (void)preWarmCommissioningSessionDone
{
assertChipStackLockedByCurrentThread();
MTR_LOG_DEFAULT("Pre-warming done");
self->_controllerFactory->ReleaseSystemState();
}
// Finds a fabric that matches the given params, if one exists.
//
// Returns NO on failure, YES on success. If YES is returned, the
// outparam will be written to, but possibly with a null value.
//
// fabricTable should be an initialized fabric table. It needs to
// outlive the consumer's use of the FabricInfo we return, which is
// why it's provided by the caller.
- (BOOL)findMatchingFabric:(FabricTable &)fabricTable
params:(MTRDeviceControllerStartupParams *)params
fabric:(const FabricInfo * _Nullable * _Nonnull)fabric
{
assertChipStackLockedByCurrentThread();
Crypto::P256PublicKey pubKey;
if (params.rootCertificate != nil) {
CHIP_ERROR err = ExtractPubkeyFromX509Cert(AsByteSpan(params.rootCertificate), pubKey);
if (err != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't extract public key from root certificate: %s", ErrorStr(err));
return NO;
}
} else {
// No root certificate means the nocSigner is using the root keys, because
// consumers must provide a root certificate whenever an ICA is used.
CHIP_ERROR err = MTRP256KeypairBridge::MatterPubKeyFromSecKeyRef(params.nocSigner.publicKey, &pubKey);
if (err != CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't extract public key from MTRKeypair: %s", ErrorStr(err));
return NO;
}
}
*fabric = fabricTable.FindFabric(pubKey, params.fabricID.unsignedLongLongValue);
return YES;
}
// Initialize the MTROTAProviderDelegateBridge if it has not been initialized already
//
// Returns nil on failure, the input controller on success.
// If the provider has been initialized already, it is not considered as a failure.
//
- (MTRDeviceController * _Nullable)maybeInitializeOTAProvider:(MTRDeviceController * _Nonnull)controller
{
[self _assertCurrentQueueIsNotMatterQueue];
VerifyOrReturnValue([_controllers count] == 1, controller);
_otaProviderEndpoint = [MTRServerEndpoint rootNodeEndpoint];
// TODO: Have the OTA Provider cluster revision accessible somewhere?
auto * otaProviderCluster = [[MTRServerCluster alloc] initWithClusterID:@(MTRClusterIDTypeOTASoftwareUpdateProviderID) revision:@(1)];
otaProviderCluster.acceptedCommands = @[
@(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandQueryImageID),
@(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandApplyUpdateRequestID),
@(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandNotifyUpdateAppliedID),
];
otaProviderCluster.generatedCommands = @[
@(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandQueryImageResponseID),
@(MTRCommandIDTypeClusterOTASoftwareUpdateProviderCommandApplyUpdateResponseID),
];
[otaProviderCluster addAccessGrant:[MTRAccessGrant accessGrantForAllNodesWithPrivilege:MTRAccessControlEntryPrivilegeOperate]];
// Not expected to fail, since we are following the rules for clusters here.
[_otaProviderEndpoint addServerCluster:otaProviderCluster];
if (![self addServerEndpoint:_otaProviderEndpoint]) {
MTR_LOG_ERROR("Failed to add OTA endpoint on factory. Why?");
[controller shutdown];
return nil;
}
// This endpoint is not actually associated with a specific controller; we
// just need to have a working Matter event loop to bring it up.
[_otaProviderEndpoint associateWithController:nil];
__block CHIP_ERROR err;
dispatch_sync(_chipWorkQueue, ^{
[self->_otaProviderEndpoint registerMatterEndpoint];
// Now that our endpoint exists, go ahead and create the OTA delegate
// bridge. Its constructor relies on the endpoint existing.
_otaProviderDelegateBridge = std::make_unique<MTROTAProviderDelegateBridge>();
auto systemState = _controllerFactory->GetSystemState();
err = _otaProviderDelegateBridge->Init(systemState->SystemLayer(), systemState->ExchangeMgr());
});
if (CHIP_NO_ERROR != err) {
MTR_LOG_ERROR("Failed to init provider delegate bridge: %" CHIP_ERROR_FORMAT, err.Format());
[controller shutdown];
return nil;
}
return controller;
}
- (void)resetOperationalAdvertising
{
if (!_advertiseOperational) {
// No need to reset anything; we are not advertising the things that
// would need to get reset.
return;
}
std::lock_guard lock(_controllersLock);
if (_controllers.count != 0) {
// We have a running controller. That means we likely need to reset
// operational advertising for that controller.
dispatch_async(_chipWorkQueue, ^{
// StartServer() is the only API we have for resetting DNS-SD
// advertising. It sure would be nice if there were a "restart"
// that was a no-op if the DNS-SD server was not already
// running.
app::DnssdServer::Instance().StartServer();
});
}
}
- (void)controllerShuttingDown:(MTRDeviceController *)controller
{
[self _assertCurrentQueueIsNotMatterQueue];
if (![_controllers containsObject:controller]) {
MTR_LOG_ERROR("Controller we don't know about shutting down");
return;
}
os_unfair_lock_lock(&_controllersLock);
// Make sure to set _controllerBeingShutDown and do the remove in the same
// locked section, so there is never a time when the controller is gone from
// both places as viewed from the Matter thread, as long as it's locking
// around its reads.
_controllerBeingShutDown = controller;
[_controllers removeObject:controller];
os_unfair_lock_unlock(&_controllersLock);
// Do the controller shutdown on the Matter work queue.
dispatch_sync(_chipWorkQueue, ^{
FabricIndex fabricIndex = controller.fabricIndex;
if (fabricIndex != kUndefinedFabricIndex) {
// Clear out out group keys for this fabric index, in case fabric
// indices get reused later. If a new controller is started on the
// same fabric it will be handed the IPK at that point.
self->_groupDataProvider.RemoveGroupKeys(fabricIndex);
}
// If there are no other controllers left, we can shut down some things.
// Do this before we shut down the controller itself, because the
// OtaProviderDelegateBridge uses some services provided by the system
// state without retaining it.
if (_controllers.count == 0) {
delete self->_operationalBrowser;
self->_operationalBrowser = nullptr;
if (_otaProviderDelegateBridge) {
_otaProviderDelegateBridge->Shutdown();
_otaProviderDelegateBridge.reset();
}
if (_otaProviderEndpoint != nil) {
[_otaProviderEndpoint unregisterMatterEndpoint];
[_otaProviderEndpoint invalidate];
[self removeServerEndpoint:_otaProviderEndpoint];
_otaProviderEndpoint = nil;
}
} else if (_otaProviderDelegateBridge) {
_otaProviderDelegateBridge->ControllerShuttingDown(controller);
}
[controller shutDownCppController];
self->_controllerBeingShutDown = nil;
if (self->_controllerBeingStarted == controller) {
self->_controllerBeingStarted = nil;
}
});
[controller deinitFromFactory];
}
- (NSArray<MTRDeviceController *> *)getRunningControllers
{
std::lock_guard lock(_controllersLock);
return [_controllers copy];
}
- (nullable MTRDeviceController *)runningControllerForFabricIndex:(FabricIndex)fabricIndex
includeControllerStartingUp:(BOOL)includeControllerStartingUp
includeControllerShuttingDown:(BOOL)includeControllerShuttingDown
{
assertChipStackLockedByCurrentThread();
auto * controllersCopy = [self getRunningControllers];
os_unfair_lock_lock(&_controllersLock);
MTRDeviceController * controllerBeingStarted = _controllerBeingStarted;
MTRDeviceController * controllerBeingShutDown = _controllerBeingShutDown;
os_unfair_lock_unlock(&_controllersLock);
for (MTRDeviceController * existing in controllersCopy) {
if (existing.fabricIndex == fabricIndex) {
return existing;
}
}
if (includeControllerStartingUp == YES && controllerBeingStarted != nil && fabricIndex == _nextAvailableFabricIndex) {
return controllerBeingStarted;
}
if (includeControllerShuttingDown == YES && controllerBeingShutDown != nil
&& controllerBeingShutDown.fabricIndex == fabricIndex) {
return controllerBeingShutDown;
}
return nil;
}
- (nullable MTRDeviceController *)runningControllerForFabricIndex:(chip::FabricIndex)fabricIndex
{
return [self runningControllerForFabricIndex:fabricIndex includeControllerStartingUp:YES includeControllerShuttingDown:YES];
}
- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint
{
os_unfair_lock_lock(&_serverEndpointsLock);
if (_serverEndpoints.count == CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT) {
os_unfair_lock_unlock(&_serverEndpointsLock);
MTR_LOG_ERROR("Can't add a server endpoint with endpoint ID %u, because we already have %u endpoints defined", static_cast<EndpointId>(endpoint.endpointID.unsignedLongLongValue), CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT);
return NO;
}
BOOL haveExisting = NO;
for (MTRServerEndpoint * existing in _serverEndpoints) {
if ([endpoint.endpointID isEqual:existing.endpointID]) {
haveExisting = YES;
break;
}
}
if (!haveExisting) {
[_serverEndpoints addObject:endpoint];
}
os_unfair_lock_unlock(&_serverEndpointsLock);
if (haveExisting) {
MTR_LOG_ERROR("Trying to add a server endpoint with endpoint ID %u, which already exists", static_cast<EndpointId>(endpoint.endpointID.unsignedLongLongValue));
}
return !haveExisting;
}
- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint
{
std::lock_guard lock(_serverEndpointsLock);
[_serverEndpoints removeObject:endpoint];
}
- (NSArray<MTRAccessGrant *> *)accessGrantsForFabricIndex:(chip::FabricIndex)fabricIndex clusterPath:(MTRClusterPath *)clusterPath
{
assertChipStackLockedByCurrentThread();
if ([clusterPath.endpoint isEqual:_otaProviderEndpoint.endpointID]) {
return [_otaProviderEndpoint matterAccessGrantsForCluster:clusterPath.cluster];
}
// We do not want to use _serverEndpoints here, because that might contain
// endpoints that are still being set up and whatnot. Ask the controller
// for the relevant fabric index what the relevant access grants are.
// Include controllers that are shutting down, since this may be an accesss
// check for event reports they emit as they shut down.
auto * controller = [self runningControllerForFabricIndex:fabricIndex includeControllerStartingUp:NO includeControllerShuttingDown:YES];
if (controller == nil) {
return @[];
}
return [controller accessGrantsForClusterPath:clusterPath];
}
- (nullable NSNumber *)neededReadPrivilegeForClusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID
{
assertChipStackLockedByCurrentThread();
for (MTRServerCluster * cluster in _otaProviderEndpoint.serverClusters) {
if (![cluster.clusterID isEqual:clusterID]) {
continue;
}
for (MTRServerAttribute * attr in cluster.attributes) {
if (![attr.attributeID isEqual:attributeID]) {
continue;
}
return @(attr.requiredReadPrivilege);
}
}
for (MTRDeviceController * controller in [self getRunningControllers]) {
NSNumber * _Nullable neededPrivilege = [controller neededReadPrivilegeForClusterID:clusterID attributeID:attributeID];
if (neededPrivilege != nil) {
return neededPrivilege;
}
}
return nil;
}
- (void)downloadLogFromNodeWithID:(NSNumber *)nodeID
controller:(MTRDeviceController *)controller
type:(MTRDiagnosticLogType)type
timeout:(NSTimeInterval)timeout
queue:(dispatch_queue_t)queue
completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion
{
assertChipStackLockedByCurrentThread();
if (_diagnosticLogsDownloader == nil) {
_diagnosticLogsDownloader = [[MTRDiagnosticLogsDownloader alloc] init];
auto systemState = _controllerFactory->GetSystemState();
systemState->BDXTransferServer()->SetDelegate([_diagnosticLogsDownloader getBridge]);
}
[_diagnosticLogsDownloader downloadLogFromNodeWithID:nodeID
controller:controller
type:type
timeout:timeout
queue:queue
completion:completion];
}
- (void)operationalInstanceAdded:(chip::PeerId &)operationalID
{
assertChipStackLockedByCurrentThread();
auto * controllersCopy = [self getRunningControllers];
for (MTRDeviceController * controller in controllersCopy) {
auto * compressedFabricId = controller.compressedFabricID;
if (compressedFabricId != nil && compressedFabricId.unsignedLongLongValue == operationalID.GetCompressedFabricId()) {
ChipLogProgress(Controller, "Notifying controller at fabric index %u about new operational node 0x" ChipLogFormatX64,
controller.fabricIndex, ChipLogValueX64(operationalID.GetNodeId()));
[controller operationalInstanceAdded:operationalID.GetNodeId()];
}
// Keep going: more than one controller might match a given compressed
// fabric id, though the chances are low.
}
}
- (MTRDeviceController * _Nullable)initializeController:(MTRDeviceController *)controller
withParameters:(MTRDeviceControllerParameters *)parameters
error:(NSError * __autoreleasing *)error
{
[self _assertCurrentQueueIsNotMatterQueue];
return [self _startDeviceController:controller
startupParams:parameters
fabricChecker:^MTRDeviceControllerStartupParamsInternal *(
FabricTable * fabricTable, MTRDeviceController * controller, CHIP_ERROR & fabricError) {
auto advertiseOperational = self->_advertiseOperational && parameters.shouldAdvertiseOperational;
auto * params =
[[MTRDeviceControllerStartupParamsInternal alloc] initForNewController:controller
fabricTable:fabricTable
keystore:self->_keystore
advertiseOperational:advertiseOperational
params:parameters
error:fabricError];
if (params != nil) {
if (params.productAttestationAuthorityCertificates == nil) {
params.productAttestationAuthorityCertificates = self->_productAttestationAuthorityCertificates;
}
if (params.certificationDeclarationCertificates == nil) {
params.certificationDeclarationCertificates = self->_certificationDeclarationCertificates;
}
}
return params;
}
error:error];
}
- (PersistentStorageDelegate *)storageDelegate
{
return _persistentStorageDelegate;
}
- (Credentials::GroupDataProvider *)groupDataProvider
{
return &_groupDataProvider;
}
@end
@interface MTRDummyStorage : NSObject <MTRStorage>
@end
@implementation MTRDummyStorage
- (nullable NSData *)storageDataForKey:(NSString *)key
{
return nil;
}
- (BOOL)setStorageData:(NSData *)value forKey:(NSString *)key
{
return NO;
}
- (BOOL)removeStorageDataForKey:(NSString *)key
{
return NO;
}
@end
@implementation MTRDeviceControllerFactoryParams
- (instancetype)initWithStorage:(id<MTRStorage>)storage
{
if (!(self = [super init])) {
return nil;
}
_storage = storage;
_hasStorage = YES;
_otaProviderDelegate = nil;
_productAttestationAuthorityCertificates = nil;
_certificationDeclarationCertificates = nil;
_port = nil;
_shouldStartServer = NO;
return self;
}
- (instancetype)initWithoutStorage
{
if (!(self = [super init])) {
return nil;
}
// We promise to have a non-null storage for purposes of our attribute, but
// now we're allowing initialization without storage. Make up a dummy
// storage just so we don't have nil there.
_storage = [[MTRDummyStorage alloc] init];
_hasStorage = NO;
_otaProviderDelegate = nil;
_productAttestationAuthorityCertificates = nil;
_certificationDeclarationCertificates = nil;
_port = nil;
_shouldStartServer = YES;
return self;
}
@end
@implementation MTRControllerFactory
- (BOOL)isRunning
{
return [[MTRDeviceControllerFactory sharedInstance] isRunning];
}
+ (MTRControllerFactory *)sharedInstance
{
// We could try to delegate to MTRDeviceControllerFactory's sharedInstance
// here, but then we would have to add the backwards-compar selectors to
// MTRDeviceControllerFactory, etc. Just forward things along instead.
// This works because we never accept an MTRControllerFactory as an argument
// in any of our public APIs.
static MTRControllerFactory * factory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// initialize the factory.
factory = [[MTRControllerFactory alloc] init];
});
return factory;
}
- (BOOL)startup:(MTRControllerFactoryParams *)startupParams
{
return [[MTRDeviceControllerFactory sharedInstance] startControllerFactory:startupParams error:nil];
}
- (void)shutdown
{
return [[MTRDeviceControllerFactory sharedInstance] stopControllerFactory];
}
- (MTRDeviceController * _Nullable)startControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
{
return [[MTRDeviceControllerFactory sharedInstance] createControllerOnExistingFabric:startupParams error:nil];
}
- (MTRDeviceController * _Nullable)startControllerOnNewFabric:(MTRDeviceControllerStartupParams *)startupParams
{
return [[MTRDeviceControllerFactory sharedInstance] createControllerOnNewFabric:startupParams error:nil];
}
@end
@implementation MTRControllerFactoryParams
- (id<MTRPersistentStorageDelegate>)storageDelegate
{
// Cast is safe, because MTRPersistentStorageDelegate doesn't add
// any selectors to MTRStorage, so anything implementing
// MTRStorage also implements MTRPersistentStorageDelegate.
return static_cast<id<MTRPersistentStorageDelegate>>(self.storage);
}
- (BOOL)startServer
{
return self.shouldStartServer;
}
- (void)setStartServer:(BOOL)startServer
{
self.shouldStartServer = startServer;
}
- (nullable NSArray<NSData *> *)paaCerts
{
return self.productAttestationAuthorityCertificates;
}
- (void)setPaaCerts:(nullable NSArray<NSData *> *)paaCerts
{
self.productAttestationAuthorityCertificates = paaCerts;
}
- (nullable NSArray<NSData *> *)cdCerts
{
return self.certificationDeclarationCertificates;
}
- (void)setCdCerts:(nullable NSArray<NSData *> *)cdCerts
{
self.certificationDeclarationCertificates = cdCerts;
}
@end
void MTRSetMessageReliabilityParameters(NSNumber * _Nullable idleRetransmitMs,
NSNumber * _Nullable activeRetransmitMs,
NSNumber * _Nullable activeThresholdMs,
NSNumber * _Nullable additionalRetransmitDelayMs)
{
bool resetAdvertising = false;
if (idleRetransmitMs == nil && activeRetransmitMs == nil && activeThresholdMs == nil && additionalRetransmitDelayMs == nil) {
Messaging::ReliableMessageMgr::SetAdditionalMRPBackoffTime(NullOptional);
resetAdvertising = ReliableMessageProtocolConfig::SetLocalMRPConfig(NullOptional);
} else {
if (additionalRetransmitDelayMs != nil) {
System::Clock::Timeout additionalBackoff(additionalRetransmitDelayMs.unsignedLongValue);
Messaging::ReliableMessageMgr::SetAdditionalMRPBackoffTime(MakeOptional(additionalBackoff));
}
// Get current MRP parameters, then override the things we were asked to
// override.
ReliableMessageProtocolConfig mrpConfig = GetLocalMRPConfig().ValueOr(GetDefaultMRPConfig());
if (idleRetransmitMs != nil) {
mrpConfig.mIdleRetransTimeout = System::Clock::Milliseconds32(idleRetransmitMs.unsignedLongValue);
}
if (activeRetransmitMs != nil) {
mrpConfig.mActiveRetransTimeout = System::Clock::Milliseconds32(activeRetransmitMs.unsignedLongValue);
}
if (activeThresholdMs != nil) {
mrpConfig.mActiveThresholdTime = System::Clock::Milliseconds32(activeThresholdMs.unsignedLongValue);
}
resetAdvertising = ReliableMessageProtocolConfig::SetLocalMRPConfig(MakeOptional(mrpConfig));
}
if (resetAdvertising) {
[[MTRDeviceControllerFactory sharedInstance] resetOperationalAdvertising];
}
}