diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h new file mode 100644 index 00000000000000..eaae1fcf7d4c6a --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.h @@ -0,0 +1,343 @@ +/** + * + * 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 + +#import +#import +#import + +@class MTRBaseDevice; +@class MTRServerEndpoint; // Defined in MTRServerEndpoint.h, which imports MTRAccessGrant.h, which imports MTRBaseClusters.h, which imports this file, so we can't import it. + +@class MTRDeviceControllerAbstractParameters; + +NS_ASSUME_NONNULL_BEGIN + +MTR_DEPRECATED("Please use MTRBaseDevice deviceWithNodeID", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)) +typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NSError * _Nullable error); + +@class MTRCommissioningParameters; +@class MTRCommissionableBrowserResult; +@class MTRSetupPayload; +@protocol MTRDevicePairingDelegate; +@protocol MTRDeviceControllerDelegate; + +MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) +@interface MTRDeviceController : NSObject + +/** + * Controllers are created via the MTRDeviceControllerFactory object or + * initialized via initWithParameters:error:. + */ +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * Initialize a device controller with the provided parameters. This will: + * + * 1) Auto-start the MTRDeviceControllerFactory in storage-per-controller mode + * if it has not already been started. + * 2) Return nil or a running controller. + * + * Once this returns non-nil, it's the caller's responsibility to call shutdown + * on the controller to avoid leaking it. + */ +- (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters + error:(NSError * __autoreleasing *)error MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); + +/** + * If true, the controller has not been shut down yet. + */ +@property (readonly, nonatomic, getter=isRunning) BOOL running; + +/** + * The ID assigned to this controller at creation time. + */ +@property (readonly, nonatomic) NSUUID * uniqueIdentifier MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); + +/** + * Return the Node ID assigned to the controller. Will return nil if the + * controller is not running (and hence does not know its node id). + */ +@property (readonly, nonatomic, nullable) + NSNumber * controllerNodeID MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Set up a commissioning session for a device, using the provided setup payload + * to discover it and connect to it. + * + * @param payload a setup payload (probably created from a QR code or numeric + * code onboarding payload). + * @param newNodeID the planned node id for the node. + * @error error indication if discovery can't start at all (e.g. because the + * setup payload is invalid). + * + * The IP and port for the device will be discovered automatically based on the + * provided discriminator. + * + * Then a PASE session will be established with the device, unless an error + * occurs. MTRDeviceControllerDelegate will be notified as follows: + * + * * Discovery fails: onStatusUpdate with MTRCommissioningStatusFailed. + * + * * Discovery succeeds but commissioning session setup fails: onPairingComplete + * with an error. + * + * * Commissioning session setup succeeds: onPairingComplete with no error. + * + * Once a commissioning session is set up, getDeviceBeingCommissioned + * can be used to get an MTRBaseDevice and discover what sort of network + * credentials the device might need, and commissionDevice can be used to + * commission the device. + */ +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error + MTR_AVAILABLE(ios(16.2), macos(13.1), watchos(9.2), tvos(16.2)); + +/** + * Set up a commissioning session for a device, using the provided discovered + * result to connect to it. + * + * @param discoveredDevice a previously discovered device. + * @param payload a setup payload (probably created from a QR code or numeric + * code onboarding payload). + * @param newNodeID the planned node id for the node. + * @error error indication if the commissioning session establishment can't start at all. + * + * The connection information for the device will be retrieved from the discovered device. + * A device discovered over DNS-SD will use the discovered IPs/ports, while a device discovered + * over BLE will use the underlying CBPeripheral. + * + * Then a PASE session will be established with the device, unless an error + * occurs. MTRDeviceControllerDelegate will be notified as follows: + * + * * Invalid connection information: onStatusUpdate with MTRCommissioningStatusFailed. + * + * * Commissioning session setup fails: onPairingComplete with an error. + * + * * Commissioning session setup succeeds: onPairingComplete with no error. + * + * Once a commissioning session is set up, getDeviceBeingCommissioned + * can be used to get an MTRBaseDevice and discover what sort of network + * credentials the device might need, and commissionDevice can be used to + * commission the device. + */ +- (BOOL)setupCommissioningSessionWithDiscoveredDevice:(MTRCommissionableBrowserResult *)discoveredDevice + payload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error + MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); + +/** + * Commission the node with the given node ID. The node ID must match the node + * ID that was used to set up the commissioning session. + */ +- (BOOL)commissionNodeWithID:(NSNumber *)nodeID + commissioningParams:(MTRCommissioningParameters *)commissioningParams + error:(NSError * __autoreleasing *)error MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Call this method after MTRDeviceAttestationDelegate + * deviceAttestationFailedForController:opaqueDeviceHandle:error: or + * deviceAttestationCompletedForController:opaqueDeviceHandle:attestationDeviceInfo:error: + * is called to continue commissioning the device. + */ +- (BOOL)continueCommissioningDevice:(void *)opaqueDeviceHandle + ignoreAttestationFailure:(BOOL)ignoreAttestationFailure + error:(NSError * __autoreleasing *)error; + +/** + * Cancel commissioning for the given node id. This will shut down any existing + * commissioning session for that node id. + */ +- (BOOL)cancelCommissioningForNodeID:(NSNumber *)nodeID + error:(NSError * __autoreleasing *)error + MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Get an MTRBaseDevice for a commissioning session that was set up for the + * given node ID. Returns nil if no such commissioning session is available. + */ +- (nullable MTRBaseDevice *)deviceBeingCommissionedWithNodeID:(NSNumber *)nodeID + error:(NSError * __autoreleasing *)error + MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +- (void)preWarmCommissioningSession MTR_DEPRECATED("-[MTRDeviceControllerFactory preWarmCommissioningSession]", ios(16.4, 17.6), macos(13.3, 14.6), watchos(9.4, 10.6), tvos(16.4, 17.6)); + +/** + * Set the Delegate for the device controller as well as the Queue on which the Delegate callbacks will be triggered + * + * @param[in] delegate The delegate the commissioning process should use + * + * @param[in] queue The queue on which the callbacks will be delivered + */ +- (void)setDeviceControllerDelegate:(id)delegate + queue:(dispatch_queue_t)queue MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Start scanning for commissionable devices. + * + * This method will fail if the controller factory is not running or the browse has already been started. + */ +- (BOOL)startBrowseForCommissionables:(id)delegate + queue:(dispatch_queue_t)queue MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); + +/** + * Stop scanning for commissionable devices. + * + * This method will fail if the controller factory is not running or the browse has not been started. + */ +- (BOOL)stopBrowseForCommissionables MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); + +/** + * Return the attestation challenge for the secure session of the device being commissioned. + * + * Attempts to retrieve the attestation challenge for a commissionee with the given Device ID. + * Returns nil if given Device ID does not match an active commissionee, or if a Secure Session is not availale. + */ +- (NSData * _Nullable)attestationChallengeForDeviceID:(NSNumber *)deviceID + MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Add a server endpoint for this controller. The endpoint starts off enabled. + * + * Will fail in the following cases: + * + * 1) There is already an endpoint defined with the given endpoint id. + * 2) There are too many endpoints defined already. + */ +- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); + +/** + * Remove the given server endpoint from this controller. If the endpoint is + * not attached to this controller, will just call the completion and do nothing + * else. + */ +- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint queue:(dispatch_queue_t)queue completion:(dispatch_block_t)completion MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); + +/** + * Remove the given server endpoint without being notified when the removal + * completes. + */ +- (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); + +/** + * Compute a PASE verifier for the desired setup passcode. + * + * @param[in] setupPasscode The desired passcode to use. + * @param[in] iterations The number of iterations to use when generating the verifier. + * @param[in] salt The 16-byte salt for verifier computation. + * + * Returns nil on errors (e.g. salt has the wrong size), otherwise the computed + * verifier bytes. + */ ++ (nullable NSData *)computePASEVerifierForSetupPasscode:(NSNumber *)setupPasscode + iterations:(NSNumber *)iterations + salt:(NSData *)salt + error:(NSError * __autoreleasing *)error + MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Shut down the controller. Calls to shutdown after the first one are NO-OPs. + * This must be called, either directly or via shutting down the + * MTRDeviceControllerFactory, to avoid leaking the controller. + */ +- (void)shutdown; + +@end + +@interface MTRDeviceController (Deprecated) + +@property (readonly, nonatomic, nullable) NSNumber * controllerNodeId MTR_DEPRECATED( + "Please use controllerNodeID", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (nullable NSData *)fetchAttestationChallengeForDeviceId:(uint64_t)deviceId + MTR_DEPRECATED( + "Please use attestationChallengeForDeviceID", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (BOOL)getBaseDevice:(uint64_t)deviceID + queue:(dispatch_queue_t)queue + completionHandler:(MTRDeviceConnectionCallback)completionHandler + MTR_DEPRECATED("Please use [MTRBaseDevice deviceWithNodeID:controller:]", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), + tvos(16.1, 16.4)); + +- (BOOL)pairDevice:(uint64_t)deviceID + discriminator:(uint16_t)discriminator + setupPINCode:(uint32_t)setupPINCode + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use setupCommissioningSessionWithPayload:newNodeID:error:", ios(16.1, 16.4), macos(13.0, 13.3), + watchos(9.1, 9.4), tvos(16.1, 16.4)); +- (BOOL)pairDevice:(uint64_t)deviceID + address:(NSString *)address + port:(uint16_t)port + setupPINCode:(uint32_t)setupPINCode + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use setupCommissioningSessionWithPayload:newNodeID:error:", ios(16.1, 16.4), macos(13.0, 13.3), + watchos(9.1, 9.4), tvos(16.1, 16.4)); +- (BOOL)pairDevice:(uint64_t)deviceID + onboardingPayload:(NSString *)onboardingPayload + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use setupCommissioningSessionWithPayload:newNodeID:error:", ios(16.1, 16.4), macos(13.0, 13.3), + watchos(9.1, 9.4), tvos(16.1, 16.4)); +- (BOOL)commissionDevice:(uint64_t)deviceId + commissioningParams:(MTRCommissioningParameters *)commissioningParams + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use commissionNodeWithID:commissioningParams:error:", ios(16.1, 16.4), macos(13.0, 13.3), + watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (BOOL)stopDevicePairing:(uint64_t)deviceID + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED( + "Please use cancelCommissioningForNodeID:error:", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (nullable MTRBaseDevice *)getDeviceBeingCommissioned:(uint64_t)deviceId + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use deviceBeingCommissionedWithNodeID:error:", ios(16.1, 16.4), macos(13.0, 13.3), watchos(9.1, 9.4), + tvos(16.1, 16.4)); + +- (BOOL)openPairingWindow:(uint64_t)deviceID + duration:(NSUInteger)duration + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use MTRDevice or MTRBaseDevice openCommissioningWindowWithSetupPasscode", ios(16.1, 16.4), + macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); +- (nullable NSString *)openPairingWindowWithPIN:(uint64_t)deviceID + duration:(NSUInteger)duration + discriminator:(NSUInteger)discriminator + setupPIN:(NSUInteger)setupPIN + error:(NSError * __autoreleasing *)error + MTR_DEPRECATED("Please use MTRDevice or MTRBaseDevice openCommissioningWindowWithSetupPasscode", ios(16.1, 16.4), + macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (nullable NSData *)computePaseVerifier:(uint32_t)setupPincode + iterations:(uint32_t)iterations + salt:(NSData *)salt + MTR_DEPRECATED("Please use computePASEVerifierForSetupPasscode:iterations:salt:error:", ios(16.1, 16.4), macos(13.0, 13.3), + watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (void)setPairingDelegate:(id)delegate + queue:(dispatch_queue_t)queue MTR_DEPRECATED("Please use setDeviceControllerDelegate:", ios(16.1, 16.4), + macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); + +- (void)setNocChainIssuer:(id)nocChainIssuer + queue:(dispatch_queue_t)queue + MTR_DEPRECATED("Please set the operationalCertificateIssuer in the MTRDeviceControllerStartupParams instead.", ios(16.1, 16.4), + macos(13.0, 13.3), watchos(9.1, 9.4), tvos(16.1, 16.4)); +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm new file mode 100644 index 00000000000000..6c312d510a93a7 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm @@ -0,0 +1,1988 @@ +/** + * + * 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 +#import + +#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 "MTRDeviceControllerDelegateBridge.h" +#import "MTRDeviceControllerFactory_Internal.h" +#import "MTRDeviceControllerLocalTestStorage.h" +#import "MTRDeviceControllerStartupParams.h" +#import "MTRDeviceControllerStartupParams_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 "NSDataSpanConversion.h" +#import "NSStringSpanConversion.h" +#import +#import +#import + +#import "MTRDeviceAttestationDelegateBridge.h" +#import "MTRDeviceConnectionBridge.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#import + +static NSString * const kErrorCommissionerInit = @"Init failure while initializing a commissioner"; +static NSString * const kErrorIPKInit = @"Init failure while initializing IPK"; +static NSString * const kErrorSigningKeypairInit = @"Init failure while creating signing keypair bridge"; +static NSString * const kErrorOperationalCredentialsInit = @"Init failure while creating operational credentials delegate"; +static NSString * const kErrorOperationalKeypairInit = @"Init failure while creating operational keypair bridge"; +static NSString * const kErrorPairingInit = @"Init failure while creating a pairing delegate"; +static NSString * const kErrorPartialDacVerifierInit = @"Init failure while creating a partial DAC verifier"; +static NSString * const kErrorPairDevice = @"Failure while pairing the device"; +static NSString * const kErrorStopPairing = @"Failure while trying to stop the pairing process"; +static NSString * const kErrorOpenPairingWindow = @"Open Pairing Window failed"; +static NSString * const kErrorNotRunning = @"Controller is not running. Call startup first."; +static NSString * const kErrorSetupCodeGen = @"Generating Manual Pairing Code failed"; +static NSString * const kErrorGenerateNOC = @"Generating operational certificate failed"; +static NSString * const kErrorKeyAllocation = @"Generating new operational key failed"; +static NSString * const kErrorCSRValidation = @"Extracting public key from CSR failed"; +static NSString * const kErrorGetCommissionee = @"Failure obtaining device being commissioned"; +static NSString * const kErrorGetAttestationChallenge = @"Failure getting attestation challenge"; +static NSString * const kErrorSpake2pVerifierGenerationFailed = @"PASE verifier generation failed"; +static NSString * const kErrorSpake2pVerifierSerializationFailed = @"PASE verifier serialization failed"; +static NSString * const kErrorCDCertStoreInit = @"Init failure while initializing Certificate Declaration Signing Keys store"; + +typedef void (^SyncWorkQueueBlock)(void); +typedef id (^SyncWorkQueueBlockWithReturnValue)(void); +typedef BOOL (^SyncWorkQueueBlockWithBoolReturnValue)(void); + +using namespace chip::Tracing::DarwinFramework; + +@implementation MTRDeviceController { + // Atomic because it can be touched from multiple threads. + std::atomic _storedFabricIndex; + + // queue used to serialize all work performed by the MTRDeviceController + dispatch_queue_t _chipWorkQueue; + + chip::Controller::DeviceCommissioner * _cppCommissioner; + chip::Credentials::PartialDACVerifier * _partialDACVerifier; + chip::Credentials::DefaultDACVerifier * _defaultDACVerifier; + MTRDeviceControllerDelegateBridge * _deviceControllerDelegateBridge; + MTROperationalCredentialsDelegate * _operationalCredentialsDelegate; + MTRP256KeypairBridge _signingKeypairBridge; + MTRP256KeypairBridge _operationalKeypairBridge; + MTRDeviceAttestationDelegateBridge * _deviceAttestationDelegateBridge; + MTRDeviceControllerFactory * _factory; + NSMapTable * _nodeIDToDeviceMap; + os_unfair_lock _deviceMapLock; // protects nodeIDToDeviceMap + MTRCommissionableBrowser * _commissionableBrowser; + MTRAttestationTrustStoreBridge * _attestationTrustStoreBridge; + + // _serverEndpoints is only touched on the Matter queue. + NSMutableArray * _serverEndpoints; + + MTRDeviceStorageBehaviorConfiguration * _storageBehaviorConfiguration; +} + +- (nullable instancetype)initWithParameters:(MTRDeviceControllerAbstractParameters *)parameters error:(NSError * __autoreleasing *)error +{ + if (![parameters isKindOfClass:MTRDeviceControllerParameters.class]) { + MTR_LOG_ERROR("Unsupported type of MTRDeviceControllerAbstractParameters: %@", parameters); + if (error) { + *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]; + } + return nil; + } + auto * controllerParameters = static_cast(parameters); + + // MTRDeviceControllerFactory will auto-start in per-controller-storage mode if necessary + return [MTRDeviceControllerFactory.sharedInstance initializeController:self withParameters:controllerParameters error:error]; +} + +- (instancetype)initWithFactory:(MTRDeviceControllerFactory *)factory + queue:(dispatch_queue_t)queue + storageDelegate:(id _Nullable)storageDelegate + storageDelegateQueue:(dispatch_queue_t _Nullable)storageDelegateQueue + otaProviderDelegate:(id _Nullable)otaProviderDelegate + otaProviderDelegateQueue:(dispatch_queue_t _Nullable)otaProviderDelegateQueue + uniqueIdentifier:(NSUUID *)uniqueIdentifier + concurrentSubscriptionPoolSize:(NSUInteger)concurrentSubscriptionPoolSize + storageBehaviorConfiguration:(MTRDeviceStorageBehaviorConfiguration *)storageBehaviorConfiguration +{ + if (self = [super init]) { + // Make sure our storage is all set up to work as early as possible, + // before we start doing anything else with the controller. + _uniqueIdentifier = uniqueIdentifier; + if (storageDelegate != nil) { + if (storageDelegateQueue == nil) { + MTR_LOG_ERROR("storageDelegate provided without storageDelegateQueue"); + return nil; + } + + id storageDelegateToUse = storageDelegate; + if (MTRDeviceControllerLocalTestStorage.localTestStorageEnabled) { + storageDelegateToUse = [[MTRDeviceControllerLocalTestStorage alloc] initWithPassThroughStorage:storageDelegate]; + } + _controllerDataStore = [[MTRDeviceControllerDataStore alloc] initWithController:self + storageDelegate:storageDelegateToUse + storageDelegateQueue:storageDelegateQueue]; + if (_controllerDataStore == nil) { + return nil; + } + } else { + if (MTRDeviceControllerLocalTestStorage.localTestStorageEnabled) { + dispatch_queue_t localTestStorageQueue = dispatch_queue_create("org.csa-iot.matter.framework.devicecontroller.localteststorage", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + MTRDeviceControllerLocalTestStorage * localTestStorage = [[MTRDeviceControllerLocalTestStorage alloc] initWithPassThroughStorage:nil]; + _controllerDataStore = [[MTRDeviceControllerDataStore alloc] initWithController:self + storageDelegate:localTestStorage + storageDelegateQueue:localTestStorageQueue]; + if (_controllerDataStore == nil) { + return nil; + } + } + } + + // Ensure the otaProviderDelegate, if any, is valid. + if (otaProviderDelegate == nil && otaProviderDelegateQueue != nil) { + MTR_LOG_ERROR("Must have otaProviderDelegate when we have otaProviderDelegateQueue"); + return nil; + } + + if (otaProviderDelegate != nil && otaProviderDelegateQueue == nil) { + MTR_LOG_ERROR("Must have otaProviderDelegateQueue when we have otaProviderDelegate"); + return nil; + } + + if (otaProviderDelegate != nil) { + if (![otaProviderDelegate respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completion:)] + && ![otaProviderDelegate respondsToSelector:@selector(handleQueryImageForNodeID:controller:params:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleQueryImageForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completion:)] + && ![otaProviderDelegate respondsToSelector:@selector(handleApplyUpdateRequestForNodeID:controller:params:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleApplyUpdateRequestForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completion:)] + && ![otaProviderDelegate + respondsToSelector:@selector(handleNotifyUpdateAppliedForNodeID:controller:params:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleNotifyUpdateAppliedForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector(handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completion:)] + && ![otaProviderDelegate respondsToSelector:@selector(handleBDXTransferSessionBeginForNodeID:controller:fileDesignator:offset:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleBDXTransferSessionBeginForNodeID"); + return nil; + } + if (![otaProviderDelegate respondsToSelector:@selector(handleBDXQueryForNodeID:controller:blockSize:blockIndex:bytesToSkip:completion:)] + && ![otaProviderDelegate respondsToSelector:@selector(handleBDXQueryForNodeID:controller:blockSize:blockIndex:bytesToSkip:completionHandler:)]) { + MTR_LOG_ERROR("Error: MTROTAProviderDelegate does not support handleBDXQueryForNodeID"); + return nil; + } + } + + _otaProviderDelegate = otaProviderDelegate; + _otaProviderDelegateQueue = otaProviderDelegateQueue; + + _chipWorkQueue = queue; + _factory = factory; + _deviceMapLock = OS_UNFAIR_LOCK_INIT; + _nodeIDToDeviceMap = [NSMapTable strongToWeakObjectsMapTable]; + _serverEndpoints = [[NSMutableArray alloc] init]; + _commissionableBrowser = nil; + + _deviceControllerDelegateBridge = new MTRDeviceControllerDelegateBridge(); + if ([self checkForInitError:(_deviceControllerDelegateBridge != nullptr) logMsg:kErrorPairingInit]) { + return nil; + } + + _partialDACVerifier = new chip::Credentials::PartialDACVerifier(); + if ([self checkForInitError:(_partialDACVerifier != nullptr) logMsg:kErrorPartialDacVerifierInit]) { + return nil; + } + + _operationalCredentialsDelegate = new MTROperationalCredentialsDelegate(self); + if ([self checkForInitError:(_operationalCredentialsDelegate != nullptr) logMsg:kErrorOperationalCredentialsInit]) { + return nil; + } + + // Provide a way to test different subscription pool sizes without code change + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + if ([defaults objectForKey:kDefaultSubscriptionPoolSizeOverrideKey]) { + NSInteger subscriptionPoolSizeOverride = [defaults integerForKey:kDefaultSubscriptionPoolSizeOverrideKey]; + if (subscriptionPoolSizeOverride < 1) { + concurrentSubscriptionPoolSize = 1; + } else { + concurrentSubscriptionPoolSize = static_cast(subscriptionPoolSizeOverride); + } + + MTR_LOG(" *** Overriding pool size of MTRDeviceController with: %lu", static_cast(concurrentSubscriptionPoolSize)); + } + + if (!concurrentSubscriptionPoolSize) { + concurrentSubscriptionPoolSize = 1; + } + + MTR_LOG("%@ Setting up pool size of MTRDeviceController with: %lu", self, static_cast(concurrentSubscriptionPoolSize)); + + _concurrentSubscriptionPool = [[MTRAsyncWorkQueue alloc] initWithContext:self width:concurrentSubscriptionPoolSize]; + + _storedFabricIndex = chip::kUndefinedFabricIndex; + + _storageBehaviorConfiguration = storageBehaviorConfiguration; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", self, _uniqueIdentifier]; +} + +- (BOOL)isRunning +{ + return _cppCommissioner != nullptr; +} + +- (void)shutdown +{ + MTR_LOG("%@ shutdown called", self); + if (_cppCommissioner == nullptr) { + // Already shut down. + return; + } + + MTR_LOG("Shutting down MTRDeviceController: %@", self); + [self cleanupAfterStartup]; +} + +// Clean up from a state where startup was called. +- (void)cleanupAfterStartup +{ + // Invalidate our MTRDevice instances before we shut down our secure + // sessions and whatnot, so they don't start trying to resubscribe when we + // do the secure session shutdowns. Since we don't want to hold the lock + // while calling out into arbitrary invalidation code, snapshot the list of + // devices before we start invalidating. + MTR_LOG("cleanupAfterStartup MTRDeviceController: %@", self); + os_unfair_lock_lock(&_deviceMapLock); + NSEnumerator * devices = [_nodeIDToDeviceMap objectEnumerator]; + [_nodeIDToDeviceMap removeAllObjects]; + os_unfair_lock_unlock(&_deviceMapLock); + + for (MTRDevice * device in devices) { + [device invalidate]; + } + [self stopBrowseForCommissionables]; + + [_factory controllerShuttingDown:self]; +} + +// Part of cleanupAfterStartup that has to interact with the Matter work queue +// in a very specific way that only MTRDeviceControllerFactory knows about. +- (void)shutDownCppController +{ + MTR_LOG("shutDownCppController MTRDeviceController: %p", self); + assertChipStackLockedByCurrentThread(); + + // Shut down all our endpoints. + for (MTRServerEndpoint * endpoint in [_serverEndpoints copy]) { + [self removeServerEndpointOnMatterQueue:endpoint]; + } + + if (_cppCommissioner) { + auto * commissionerToShutDown = _cppCommissioner; + // Flag ourselves as not running before we start shutting down + // _cppCommissioner, so we're not in a state where we claim to be + // running but are actually partially shut down. + _cppCommissioner = nullptr; + commissionerToShutDown->Shutdown(); + // Don't clear out our fabric index association until controller + // shutdown completes, in case it wants to write to storage as it + // shuts down. + _storedFabricIndex = chip::kUndefinedFabricIndex; + delete commissionerToShutDown; + if (_operationalCredentialsDelegate != nil) { + _operationalCredentialsDelegate->SetDeviceCommissioner(nullptr); + } + } +} + +- (void)deinitFromFactory +{ + [self cleanup]; +} + +// Clean up any members we might have allocated. +- (void)cleanup +{ + VerifyOrDie(_cppCommissioner == nullptr); + + if (_defaultDACVerifier) { + delete _defaultDACVerifier; + _defaultDACVerifier = nullptr; + } + + if (_attestationTrustStoreBridge) { + delete _attestationTrustStoreBridge; + _attestationTrustStoreBridge = nullptr; + } + + [self clearDeviceAttestationDelegateBridge]; + + if (_operationalCredentialsDelegate) { + delete _operationalCredentialsDelegate; + _operationalCredentialsDelegate = nullptr; + } + + if (_partialDACVerifier) { + delete _partialDACVerifier; + _partialDACVerifier = nullptr; + } + + if (_deviceControllerDelegateBridge) { + delete _deviceControllerDelegateBridge; + _deviceControllerDelegateBridge = nullptr; + } +} + +- (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams +{ + __block BOOL commissionerInitialized = NO; + if ([self isRunning]) { + MTR_LOG_ERROR("%@ Unexpected duplicate call to startup", self); + return NO; + } + + dispatch_sync(_chipWorkQueue, ^{ + if ([self isRunning]) { + return; + } + + if (startupParams.vendorID == nil || [startupParams.vendorID unsignedShortValue] == chip::VendorId::Common) { + // Shouldn't be using the "standard" vendor ID for actual devices. + MTR_LOG_ERROR("%@ %@ is not a valid vendorID to initialize a device controller with", self, startupParams.vendorID); + return; + } + + if (startupParams.operationalCertificate == nil && startupParams.nodeID == nil) { + MTR_LOG_ERROR("%@ Can't start a controller if we don't know what node id it is", self); + return; + } + + if ([startupParams keypairsMatchCertificates] == NO) { + MTR_LOG_ERROR("%@ Provided keypairs do not match certificates", self); + return; + } + + if (startupParams.operationalCertificate != nil && startupParams.operationalKeypair == nil + && (!startupParams.fabricIndex.HasValue() + || !startupParams.keystore->HasOpKeypairForFabric(startupParams.fabricIndex.Value()))) { + MTR_LOG_ERROR("%@ Have no operational keypair for our operational certificate", self); + return; + } + + CHIP_ERROR errorCode = CHIP_ERROR_INCORRECT_STATE; + + // create a MTRP256KeypairBridge here and pass it to the operationalCredentialsDelegate + chip::Crypto::P256Keypair * signingKeypair = nullptr; + if (startupParams.nocSigner) { + errorCode = _signingKeypairBridge.Init(startupParams.nocSigner); + if ([self checkForStartError:errorCode logMsg:kErrorSigningKeypairInit]) { + return; + } + signingKeypair = &_signingKeypairBridge; + } + errorCode = _operationalCredentialsDelegate->Init( + signingKeypair, startupParams.ipk, startupParams.rootCertificate, startupParams.intermediateCertificate); + if ([self checkForStartError:errorCode logMsg:kErrorOperationalCredentialsInit]) { + return; + } + + _cppCommissioner = new chip::Controller::DeviceCommissioner(); + + // nocBuffer might not be used, but if it is it needs to live + // long enough (until after we are done using + // commissionerParams). + uint8_t nocBuffer[chip::Controller::kMaxCHIPDERCertLength]; + + chip::Controller::SetupParams commissionerParams; + + commissionerParams.pairingDelegate = _deviceControllerDelegateBridge; + + _operationalCredentialsDelegate->SetDeviceCommissioner(_cppCommissioner); + + commissionerParams.operationalCredentialsDelegate = _operationalCredentialsDelegate; + + commissionerParams.controllerRCAC = _operationalCredentialsDelegate->RootCertSpan(); + commissionerParams.controllerICAC = _operationalCredentialsDelegate->IntermediateCertSpan(); + + if (startupParams.operationalKeypair != nil) { + errorCode = _operationalKeypairBridge.Init(startupParams.operationalKeypair); + if ([self checkForStartError:errorCode logMsg:kErrorOperationalKeypairInit]) { + return; + } + commissionerParams.operationalKeypair = &_operationalKeypairBridge; + commissionerParams.hasExternallyOwnedOperationalKeypair = true; + } + + if (startupParams.operationalCertificate) { + commissionerParams.controllerNOC = AsByteSpan(startupParams.operationalCertificate); + } else { + chip::MutableByteSpan noc(nocBuffer); + + chip::CATValues cats = chip::kUndefinedCATs; + if (startupParams.caseAuthenticatedTags != nil) { + errorCode = SetToCATValues(startupParams.caseAuthenticatedTags, cats); + if (errorCode != CHIP_NO_ERROR) { + // SetToCATValues already handles logging. + return; + } + } + + if (commissionerParams.operationalKeypair != nullptr) { + errorCode = _operationalCredentialsDelegate->GenerateNOC(startupParams.nodeID.unsignedLongLongValue, + startupParams.fabricID.unsignedLongLongValue, cats, commissionerParams.operationalKeypair->Pubkey(), noc); + + if ([self checkForStartError:errorCode logMsg:kErrorGenerateNOC]) { + return; + } + } else { + // Generate a new random keypair. + uint8_t csrBuffer[chip::Crypto::kMIN_CSR_Buffer_Size]; + chip::MutableByteSpan csr(csrBuffer); + errorCode = startupParams.fabricTable->AllocatePendingOperationalKey(startupParams.fabricIndex, csr); + if ([self checkForStartError:errorCode logMsg:kErrorKeyAllocation]) { + return; + } + + chip::Crypto::P256PublicKey pubKey; + errorCode = VerifyCertificateSigningRequest(csr.data(), csr.size(), pubKey); + if ([self checkForStartError:errorCode logMsg:kErrorCSRValidation]) { + return; + } + + errorCode = _operationalCredentialsDelegate->GenerateNOC( + startupParams.nodeID.unsignedLongLongValue, startupParams.fabricID.unsignedLongLongValue, cats, pubKey, noc); + + if ([self checkForStartError:errorCode logMsg:kErrorGenerateNOC]) { + return; + } + } + commissionerParams.controllerNOC = noc; + } + commissionerParams.controllerVendorId = static_cast([startupParams.vendorID unsignedShortValue]); + commissionerParams.enableServerInteractions = startupParams.advertiseOperational; + + // We never want plain "removal" from the fabric table since this leaves + // the in-memory state out of sync with what's in storage. In per-controller + // storage mode, have the controller delete itself from the fabric table on shutdown. + // In factory storage mode we need to keep fabric information around so we can + // start another controller on that existing fabric at a later time. + commissionerParams.removeFromFabricTableOnShutdown = false; + commissionerParams.deleteFromFabricTableOnShutdown = (startupParams.storageDelegate != nil); + + commissionerParams.permitMultiControllerFabrics = startupParams.allowMultipleControllersPerFabric; + + // Set up our attestation verifier. Assume we want to use the default + // one, until something tells us otherwise. + const chip::Credentials::AttestationTrustStore * trustStore; + if (startupParams.productAttestationAuthorityCertificates) { + _attestationTrustStoreBridge + = new MTRAttestationTrustStoreBridge(startupParams.productAttestationAuthorityCertificates); + trustStore = _attestationTrustStoreBridge; + } else { + // TODO: Replace testingRootStore with a AttestationTrustStore that has the necessary official PAA roots available + trustStore = chip::Credentials::GetTestAttestationTrustStore(); + } + + _defaultDACVerifier = new chip::Credentials::DefaultDACVerifier(trustStore); + + if (startupParams.certificationDeclarationCertificates) { + auto cdTrustStore = _defaultDACVerifier->GetCertificationDeclarationTrustStore(); + if (cdTrustStore == nullptr) { + errorCode = CHIP_ERROR_INCORRECT_STATE; + } + if ([self checkForStartError:errorCode logMsg:kErrorCDCertStoreInit]) { + return; + } + + for (NSData * cdSigningCert in startupParams.certificationDeclarationCertificates) { + errorCode = cdTrustStore->AddTrustedKey(AsByteSpan(cdSigningCert)); + if ([self checkForStartError:errorCode logMsg:kErrorCDCertStoreInit]) { + return; + } + } + } + + commissionerParams.deviceAttestationVerifier = _defaultDACVerifier; + + auto & factory = chip::Controller::DeviceControllerFactory::GetInstance(); + + errorCode = factory.SetupCommissioner(commissionerParams, *_cppCommissioner); + if ([self checkForStartError:errorCode logMsg:kErrorCommissionerInit]) { + return; + } + + chip::FabricIndex fabricIdx = _cppCommissioner->GetFabricIndex(); + + uint8_t compressedIdBuffer[sizeof(uint64_t)]; + chip::MutableByteSpan compressedId(compressedIdBuffer); + errorCode = _cppCommissioner->GetCompressedFabricIdBytes(compressedId); + if ([self checkForStartError:errorCode logMsg:kErrorIPKInit]) { + return; + } + + errorCode = chip::Credentials::SetSingleIpkEpochKey( + _factory.groupDataProvider, fabricIdx, _operationalCredentialsDelegate->GetIPK(), compressedId); + if ([self checkForStartError:errorCode logMsg:kErrorIPKInit]) { + return; + } + + self->_storedFabricIndex = fabricIdx; + commissionerInitialized = YES; + + MTR_LOG("%@ startup succeeded for nodeID 0x%016llX", self, self->_cppCommissioner->GetNodeId()); + }); + + if (commissionerInitialized == NO) { + MTR_LOG_ERROR("%@ startup failed", self); + [self cleanupAfterStartup]; + return NO; + } + + // TODO: Once setNocChainIssuer no longer needs to be supported, + // we can just move the internals of + // setOperationalCertificateIssuer into the sync-dispatched block + // above. + if (![self setOperationalCertificateIssuer:startupParams.operationalCertificateIssuer + queue:startupParams.operationalCertificateIssuerQueue]) { + MTR_LOG_ERROR("%@ operationalCertificateIssuer and operationalCertificateIssuerQueue must both be nil or both be non-nil", self); + [self cleanupAfterStartup]; + return NO; + } + + if (_controllerDataStore) { + // If the storage delegate supports the bulk read API, then a dictionary of nodeID => cluster data dictionary would be passed to the handler. Otherwise this would be a no-op, and stored attributes for MTRDevice objects will be loaded lazily in -deviceForNodeID:. + [_controllerDataStore fetchAttributeDataForAllDevices:^(NSDictionary *> * _Nonnull clusterDataByNode) { + MTR_LOG("%@ Loaded attribute values for %lu nodes from storage for controller uuid %@", self, static_cast(clusterDataByNode.count), self->_uniqueIdentifier); + + std::lock_guard lock(self->_deviceMapLock); + NSMutableArray * deviceList = [NSMutableArray array]; + for (NSNumber * nodeID in clusterDataByNode) { + NSDictionary * clusterData = clusterDataByNode[nodeID]; + MTRDevice * device = [self _setupDeviceForNodeID:nodeID prefetchedClusterData:clusterData]; + MTR_LOG("%@ Loaded %lu cluster data from storage for %@", self, static_cast(clusterData.count), device); + + [deviceList addObject:device]; + } + +#define kSecondsToWaitBeforeAPIClientRetainsMTRDevice 60 + // Keep the devices retained for a while, in case API client doesn't immediately retain them. + // + // Note that this is just an optimization to avoid throwing the information away and immediately + // re-reading it from storage. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (kSecondsToWaitBeforeAPIClientRetainsMTRDevice * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + MTR_LOG("%@ un-retain devices loaded at startup %lu", self, static_cast(deviceList.count)); + }); + }]; + } + MTR_LOG("MTRDeviceController startup: %@", self); + + return YES; +} + +- (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; +} + +static inline void emitMetricForSetupPayload(MTRSetupPayload * payload) +{ + MATTER_LOG_METRIC(kMetricDeviceVendorID, [payload.vendorID unsignedIntValue]); + MATTER_LOG_METRIC(kMetricDeviceProductID, [payload.productID unsignedIntValue]); +} + +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error +{ + MTR_LOG("Setting up commissioning session for device ID 0x%016llX with setup payload %@", newNodeID.unsignedLongLongValue, payload); + + [[MTRMetricsCollector sharedInstance] resetMetrics]; + + // Track overall commissioning + MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissioning); + emitMetricForSetupPayload(payload); + + // Capture in a block variable to avoid losing granularity for metrics, + // when translating CHIP_ERROR to NSError + __block CHIP_ERROR errorCode = CHIP_NO_ERROR; + + auto block = ^BOOL { + // Track work until end of scope + MATTER_LOG_METRIC_SCOPE(kMetricSetupWithPayload, errorCode); + + // Try to get a QR code if possible (because it has a better + // discriminator, etc), then fall back to manual code if that fails. + NSString * pairingCode = [payload qrCodeString:nil]; + if (pairingCode == nil) { + pairingCode = [payload manualEntryCode]; + } + if (pairingCode == nil) { + errorCode = CHIP_ERROR_INVALID_ARGUMENT; + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorSetupCodeGen error:error]; + } + + chip::NodeId nodeId = [newNodeID unsignedLongLongValue]; + self->_operationalCredentialsDelegate->SetDeviceID(nodeId); + + MATTER_LOG_METRIC_BEGIN(kMetricSetupPASESession); + errorCode = self->_cppCommissioner->EstablishPASEConnection(nodeId, [pairingCode UTF8String]); + if (CHIP_NO_ERROR == errorCode) { + self->_deviceControllerDelegateBridge->SetDeviceNodeID(nodeId); + } else { + MATTER_LOG_METRIC_END(kMetricSetupPASESession, errorCode); + } + + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + auto success = [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; + if (!success) { + MATTER_LOG_METRIC_END(kMetricDeviceCommissioning, errorCode); + } + return success; +} + +- (BOOL)setupCommissioningSessionWithDiscoveredDevice:(MTRCommissionableBrowserResult *)discoveredDevice + payload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error +{ + MTR_LOG("%@ Setting up commissioning session for already-discovered device %@ and device ID 0x%016llX with setup payload %@", self, discoveredDevice, newNodeID.unsignedLongLongValue, payload); + + [[MTRMetricsCollector sharedInstance] resetMetrics]; + + // Track overall commissioning + MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissioning); + emitMetricForSetupPayload(payload); + + // Capture in a block variable to avoid losing granularity for metrics, + // when translating CHIP_ERROR to NSError + __block CHIP_ERROR errorCode = CHIP_NO_ERROR; + + auto block = ^BOOL { + // Track work until end of scope + MATTER_LOG_METRIC_SCOPE(kMetricSetupWithDiscovered, errorCode); + + chip::NodeId nodeId = [newNodeID unsignedLongLongValue]; + self->_operationalCredentialsDelegate->SetDeviceID(nodeId); + + errorCode = CHIP_ERROR_INVALID_ARGUMENT; + chip::Optional params = discoveredDevice.params; + if (params.HasValue()) { + auto pinCode = static_cast(payload.setupPasscode.unsignedLongValue); + params.Value().SetSetupPINCode(pinCode); + + MATTER_LOG_METRIC_BEGIN(kMetricSetupPASESession); + errorCode = self->_cppCommissioner->EstablishPASEConnection(nodeId, params.Value()); + if (CHIP_NO_ERROR == errorCode) { + self->_deviceControllerDelegateBridge->SetDeviceNodeID(nodeId); + } else { + MATTER_LOG_METRIC_END(kMetricSetupPASESession, errorCode); + } + } else { + // Try to get a QR code if possible (because it has a better + // discriminator, etc), then fall back to manual code if that fails. + NSString * pairingCode = [payload qrCodeString:nil]; + if (pairingCode == nil) { + pairingCode = [payload manualEntryCode]; + } + if (pairingCode == nil) { + errorCode = CHIP_ERROR_INVALID_ARGUMENT; + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorSetupCodeGen error:error]; + } + + for (id key in discoveredDevice.interfaces) { + auto resolutionData = discoveredDevice.interfaces[key].resolutionData; + if (!resolutionData.HasValue()) { + continue; + } + + MATTER_LOG_METRIC_BEGIN(kMetricSetupPASESession); + errorCode = self->_cppCommissioner->EstablishPASEConnection( + nodeId, [pairingCode UTF8String], chip::Controller::DiscoveryType::kDiscoveryNetworkOnly, resolutionData); + if (CHIP_NO_ERROR == errorCode) { + self->_deviceControllerDelegateBridge->SetDeviceNodeID(nodeId); + } else { + MATTER_LOG_METRIC_END(kMetricSetupPASESession, errorCode); + break; + } + } + } + + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + auto success = [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; + if (!success) { + MATTER_LOG_METRIC_END(kMetricDeviceCommissioning, errorCode); + } + return success; +} + +- (BOOL)commissionNodeWithID:(NSNumber *)nodeID + commissioningParams:(MTRCommissioningParameters *)commissioningParams + error:(NSError * __autoreleasing *)error +{ + auto block = ^BOOL { + chip::Controller::CommissioningParameters params; + if (commissioningParams.csrNonce) { + params.SetCSRNonce(AsByteSpan(commissioningParams.csrNonce)); + } + if (commissioningParams.attestationNonce) { + params.SetAttestationNonce(AsByteSpan(commissioningParams.attestationNonce)); + } + if (commissioningParams.threadOperationalDataset) { + params.SetThreadOperationalDataset(AsByteSpan(commissioningParams.threadOperationalDataset)); + } + params.SetSkipCommissioningComplete(commissioningParams.skipCommissioningComplete); + if (commissioningParams.wifiSSID) { + chip::ByteSpan ssid = AsByteSpan(commissioningParams.wifiSSID); + chip::ByteSpan credentials; + if (commissioningParams.wifiCredentials != nil) { + credentials = AsByteSpan(commissioningParams.wifiCredentials); + } + chip::Controller::WiFiCredentials wifiCreds(ssid, credentials); + params.SetWiFiCredentials(wifiCreds); + } + if (commissioningParams.deviceAttestationDelegate) { + [self clearDeviceAttestationDelegateBridge]; + + chip::Optional timeoutSecs; + if (commissioningParams.failSafeTimeout) { + timeoutSecs = chip::MakeOptional(static_cast([commissioningParams.failSafeTimeout unsignedIntValue])); + } + BOOL shouldWaitAfterDeviceAttestation = NO; + if ([commissioningParams.deviceAttestationDelegate + respondsToSelector:@selector(deviceAttestationCompletedForController: + opaqueDeviceHandle:attestationDeviceInfo:error:)] + || [commissioningParams.deviceAttestationDelegate + respondsToSelector:@selector(deviceAttestation:completedForDevice:attestationDeviceInfo:error:)]) { + shouldWaitAfterDeviceAttestation = YES; + } + self->_deviceAttestationDelegateBridge = new MTRDeviceAttestationDelegateBridge( + self, commissioningParams.deviceAttestationDelegate, timeoutSecs, shouldWaitAfterDeviceAttestation); + params.SetDeviceAttestationDelegate(self->_deviceAttestationDelegateBridge); + } + if (commissioningParams.countryCode != nil) { + params.SetCountryCode(AsCharSpan(commissioningParams.countryCode)); + } + + // Set up the right timezone and DST information. For timezone, just + // use our current timezone and don't schedule any sort of timezone + // change. + auto * tz = [NSTimeZone localTimeZone]; + using TimeZoneType = chip::app::Clusters::TimeSynchronization::Structs::TimeZoneStruct::Type; + TimeZoneType timeZone; + timeZone.validAt = 0; + timeZone.offset = static_cast(tz.secondsFromGMT - tz.daylightSavingTimeOffset); + timeZone.name.Emplace(AsCharSpan(tz.name)); + + params.SetTimeZone(chip::app::DataModel::List(&timeZone, 1)); + + // For DST, there is no limit to the number of transitions we could try + // to add, but in practice devices likely support only 2 and + // AutoCommissioner caps the list at 10. Let's do up to 4 transitions + // for now. + constexpr size_t dstOffsetMaxCount = 4; + using DSTOffsetType = chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type; + // dstOffsets needs to live long enough, so its existence is not + // conditional on having offsets. + DSTOffsetType dstOffsets[dstOffsetMaxCount]; + + auto * offsets = MTRComputeDSTOffsets(dstOffsetMaxCount); + if (offsets != nil) { + size_t dstOffsetCount = 0; + for (MTRTimeSynchronizationClusterDSTOffsetStruct * offset in offsets) { + if (dstOffsetCount >= dstOffsetMaxCount) { + // Really shouldn't happen, but let's be extra careful about + // buffer overruns. + break; + } + auto & targetOffset = dstOffsets[dstOffsetCount]; + targetOffset.offset = offset.offset.intValue; + targetOffset.validStarting = offset.validStarting.unsignedLongLongValue; + if (offset.validUntil == nil) { + targetOffset.validUntil.SetNull(); + } else { + targetOffset.validUntil.SetNonNull(offset.validUntil.unsignedLongLongValue); + } + ++dstOffsetCount; + } + + params.SetDSTOffsets(chip::app::DataModel::List(dstOffsets, dstOffsetCount)); + } + + chip::NodeId deviceId = [nodeID unsignedLongLongValue]; + self->_operationalCredentialsDelegate->SetDeviceID(deviceId); + auto errorCode = self->_cppCommissioner->Commission(deviceId, params); + MATTER_LOG_METRIC(kMetricCommissionNode, errorCode); + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; +} + +- (BOOL)continueCommissioningDevice:(void *)device + ignoreAttestationFailure:(BOOL)ignoreAttestationFailure + error:(NSError * __autoreleasing *)error +{ + auto block = ^BOOL { + auto lastAttestationResult = self->_deviceAttestationDelegateBridge + ? self->_deviceAttestationDelegateBridge->attestationVerificationResult() + : chip::Credentials::AttestationVerificationResult::kSuccess; + + auto deviceProxy = static_cast(device); + auto errorCode = self->_cppCommissioner->ContinueCommissioningAfterDeviceAttestation(deviceProxy, + ignoreAttestationFailure ? chip::Credentials::AttestationVerificationResult::kSuccess : lastAttestationResult); + // Emit metric on stage after continuing post attestation + MATTER_LOG_METRIC(kMetricContinueCommissioningAfterAttestation, errorCode); + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; +} + +- (BOOL)cancelCommissioningForNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error +{ + auto block = ^BOOL { + self->_operationalCredentialsDelegate->ResetDeviceID(); + auto errorCode = self->_cppCommissioner->StopPairing([nodeID unsignedLongLongValue]); + // Emit metric on status of cancel + MATTER_LOG_METRIC(kMetricCancelCommissioning, errorCode); + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorStopPairing error:error]; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; +} + +- (BOOL)startBrowseForCommissionables:(id)delegate queue:(dispatch_queue_t)queue +{ + auto block = ^BOOL { + VerifyOrReturnValueWithMetric(kMetricStartBrowseForCommissionables, self->_commissionableBrowser == nil, NO); + + auto commissionableBrowser = [[MTRCommissionableBrowser alloc] initWithDelegate:delegate controller:self queue:queue]; + VerifyOrReturnValueWithMetric(kMetricStartBrowseForCommissionables, [commissionableBrowser start], NO); + + self->_commissionableBrowser = commissionableBrowser; + return YES; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:nil]; +} + +- (BOOL)stopBrowseForCommissionables +{ + auto block = ^BOOL { + VerifyOrReturnValueWithMetric(kMetricStopBrowseForCommissionables, self->_commissionableBrowser != nil, NO); + + auto commissionableBrowser = self->_commissionableBrowser; + VerifyOrReturnValueWithMetric(kMetricStopBrowseForCommissionables, [commissionableBrowser stop], NO); + + self->_commissionableBrowser = nil; + return YES; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:nil]; +} + +- (void)preWarmCommissioningSession +{ + [_factory preWarmCommissioningSession]; +} + +- (MTRBaseDevice *)deviceBeingCommissionedWithNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error +{ + auto block = ^MTRBaseDevice * + { + chip::CommissioneeDeviceProxy * deviceProxy; + + auto errorCode = self->_cppCommissioner->GetDeviceBeingCommissioned(nodeID.unsignedLongLongValue, &deviceProxy); + MATTER_LOG_METRIC(kMetricDeviceBeingCommissioned, errorCode); + + VerifyOrReturnValue(![MTRDeviceController checkForError:errorCode logMsg:kErrorGetCommissionee error:error], nil); + + return [[MTRBaseDevice alloc] initWithPASEDevice:deviceProxy controller:self]; + }; + + MTRBaseDevice * device = [self syncRunOnWorkQueueWithReturnValue:block error:error]; + MTR_LOG("%@ Getting device being commissioned with node ID 0x%016llX: %@ (error: %@)", self, nodeID.unsignedLongLongValue, device, (error ? *error : nil)); + return device; +} + +- (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID +{ + return [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:self]; +} + +// If prefetchedClusterData is not provided, load attributes individually from controller data store +- (MTRDevice *)_setupDeviceForNodeID:(NSNumber *)nodeID prefetchedClusterData:(NSDictionary *)prefetchedClusterData +{ + os_unfair_lock_assert_owner(&_deviceMapLock); + + MTRDevice * deviceToReturn = [[MTRDevice_Concrete alloc] initWithNodeID:nodeID controller:self]; + // If we're not running, don't add the device to our map. That would + // create a cycle that nothing would break. Just return the device, + // which will be in exactly the state it would be in if it were created + // while we were running and then we got shut down. + if ([self isRunning]) { + [_nodeIDToDeviceMap setObject:deviceToReturn forKey:nodeID]; + } + + if (prefetchedClusterData) { + if (prefetchedClusterData.count) { + [deviceToReturn setPersistedClusterData:prefetchedClusterData]; + } + } else if (_controllerDataStore) { + // Load persisted cluster data if they exist. + NSDictionary * clusterData = [_controllerDataStore getStoredClusterDataForNodeID:nodeID]; + MTR_LOG("%@ Loaded %lu cluster data from storage for %@", self, static_cast(clusterData.count), deviceToReturn); + if (clusterData.count) { + [deviceToReturn setPersistedClusterData:clusterData]; + } + } + + // TODO: Figure out how to get the device data as part of our bulk-read bits. + if (_controllerDataStore) { + auto * deviceData = [_controllerDataStore getStoredDeviceDataForNodeID:nodeID]; + if (deviceData.count) { + [deviceToReturn setPersistedDeviceData:deviceData]; + } + } + + [deviceToReturn setStorageBehaviorConfiguration:_storageBehaviorConfiguration]; + + return deviceToReturn; +} + +- (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID +{ + std::lock_guard lock(_deviceMapLock); + MTRDevice * deviceToReturn = [_nodeIDToDeviceMap objectForKey:nodeID]; + if (!deviceToReturn) { + deviceToReturn = [self _setupDeviceForNodeID:nodeID prefetchedClusterData:nil]; + } + + return deviceToReturn; +} + +- (void)removeDevice:(MTRDevice *)device +{ + std::lock_guard lock(_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 *)unitTestGetDeviceAttributeCounts +{ + std::lock_guard lock(_deviceMapLock); + NSMutableDictionary * deviceAttributeCounts = [NSMutableDictionary dictionary]; + for (NSNumber * nodeID in _nodeIDToDeviceMap) { + deviceAttributeCounts[nodeID] = @([[_nodeIDToDeviceMap objectForKey:nodeID] unitTestAttributeCount]); + } + return deviceAttributeCounts; +} +#endif + +- (void)setDeviceControllerDelegate:(id)delegate queue:(dispatch_queue_t)queue +{ + [self + asyncDispatchToMatterQueue:^() { + self->_deviceControllerDelegateBridge->setDelegate(self, delegate, queue); + } + errorHandler:nil]; +} + +- (BOOL)setOperationalCertificateIssuer:(nullable id)operationalCertificateIssuer + queue:(nullable dispatch_queue_t)queue +{ + if ((operationalCertificateIssuer != nil && queue == nil) || (operationalCertificateIssuer == nil && queue != nil)) { + return NO; + } + + auto block = ^{ + BOOL usePartialDACVerifier = NO; + if (operationalCertificateIssuer != nil) { + self->_operationalCredentialsDelegate->SetOperationalCertificateIssuer(operationalCertificateIssuer, queue); + usePartialDACVerifier = operationalCertificateIssuer.shouldSkipAttestationCertificateValidation; + } + if (usePartialDACVerifier) { + self->_cppCommissioner->SetDeviceAttestationVerifier(self->_partialDACVerifier); + } else { + // TODO: Once we are not supporting setNocChainIssuer this + // branch can just go away. + self->_cppCommissioner->SetDeviceAttestationVerifier(self->_defaultDACVerifier); + } + return YES; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:nil]; +} + ++ (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 +{ + auto block = ^NSData * + { + chip::CommissioneeDeviceProxy * deviceProxy; + + auto errorCode = CHIP_NO_ERROR; + MATTER_LOG_METRIC_SCOPE(kMetricAttestationChallengeForDevice, errorCode); + + errorCode = self->_cppCommissioner->GetDeviceBeingCommissioned([deviceID unsignedLongLongValue], &deviceProxy); + VerifyOrReturnValue(![MTRDeviceController checkForError:errorCode logMsg:kErrorGetCommissionee error:nil], nil); + + uint8_t challengeBuffer[chip::Crypto::kAES_CCM128_Key_Length]; + chip::ByteSpan challenge(challengeBuffer); + + errorCode = deviceProxy->GetAttestationChallenge(challenge); + VerifyOrReturnValue(![MTRDeviceController checkForError:errorCode logMsg:kErrorGetAttestationChallenge error:nil], nil); + + return AsData(challenge); + }; + + return [self syncRunOnWorkQueueWithReturnValue:block error:nil]; +} + +- (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint +{ + VerifyOrReturnValue([self checkIsRunning], NO); + + if (![_factory addServerEndpoint:endpoint]) { + return NO; + } + + if (![endpoint associateWithController:self]) { + MTR_LOG_ERROR("%@ Failed to associate MTRServerEndpoint with MTRDeviceController", self); + [_factory removeServerEndpoint:endpoint]; + return NO; + } + + [self asyncDispatchToMatterQueue:^() { + [self->_serverEndpoints addObject:endpoint]; + [endpoint registerMatterEndpoint]; + MTR_LOG("%@ Added server endpoint %u to controller %@", self, static_cast(endpoint.endpointID.unsignedLongLongValue), + self->_uniqueIdentifier); + } + errorHandler:^(NSError * error) { + MTR_LOG_ERROR("%@ Unexpected failure dispatching to Matter queue on running controller in addServerEndpoint, adding endpoint %u", self, + static_cast(endpoint.endpointID.unsignedLongLongValue)); + }]; + return YES; +} + +- (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 +{ + VerifyOrReturn([self checkIsRunning]); + + // We need to unhook the endpoint from the Matter side before we can start + // tearing it down. + [self asyncDispatchToMatterQueue:^() { + [self removeServerEndpointOnMatterQueue:endpoint]; + MTR_LOG("%@ Removed server endpoint %u from controller %@", self, static_cast(endpoint.endpointID.unsignedLongLongValue), + self->_uniqueIdentifier); + if (queue != nil && completion != nil) { + dispatch_async(queue, completion); + } + } + errorHandler:^(NSError * error) { + // Error means we got shut down, so the endpoint is removed now. + MTR_LOG("%@ controller already shut down, so endpoint %u has already been removed", self, + static_cast(endpoint.endpointID.unsignedLongLongValue)); + if (queue != nil && completion != nil) { + dispatch_async(queue, completion); + } + }]; +} + +- (void)removeServerEndpointOnMatterQueue:(MTRServerEndpoint *)endpoint +{ + assertChipStackLockedByCurrentThread(); + + [endpoint unregisterMatterEndpoint]; + [_serverEndpoints removeObject:endpoint]; + [endpoint invalidate]; + + [_factory removeServerEndpoint:endpoint]; +} + +- (BOOL)checkForInitError:(BOOL)condition logMsg:(NSString *)logMsg +{ + if (condition) { + return NO; + } + + MTR_LOG_ERROR("%@ Error: %@", self, logMsg); + + [self cleanup]; + + return YES; +} + +- (void)clearDeviceAttestationDelegateBridge +{ + if (_deviceAttestationDelegateBridge) { + delete _deviceAttestationDelegateBridge; + _deviceAttestationDelegateBridge = nullptr; + } +} + +- (BOOL)checkForStartError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg +{ + if (CHIP_NO_ERROR == errorCode) { + return NO; + } + + MTR_LOG_ERROR("Error(%" CHIP_ERROR_FORMAT "): %@ %@", errorCode.Format(), self, logMsg); + + return YES; +} + ++ (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("MTRDeviceController: %@ Error: %s", 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 & 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 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 +{ + assertChipStackLockedByCurrentThread(); + + if (!_cppCommissioner) { + return nil; + } + + return @(_cppCommissioner->GetCompressedFabricId()); +} + +- (NSNumber * _Nullable)syncGetCompressedFabricID +{ + return [self syncRunOnWorkQueueWithReturnValue:^NSNumber * { + return [self compressedFabricID]; + } error:nil]; +} + +- (CHIP_ERROR)isRunningOnFabric:(chip::FabricTable *)fabricTable + fabricIndex:(chip::FabricIndex)fabricIndex + isRunning:(BOOL *)isRunning +{ + assertChipStackLockedByCurrentThread(); + + if (![self isRunning]) { + *isRunning = NO; + return CHIP_NO_ERROR; + } + + const chip::FabricInfo * otherFabric = fabricTable->FindFabricWithIndex(fabricIndex); + if (!otherFabric) { + // Should not happen... + return CHIP_ERROR_INCORRECT_STATE; + } + + if (_cppCommissioner->GetFabricId() != otherFabric->GetFabricId()) { + *isRunning = NO; + return CHIP_NO_ERROR; + } + + chip::Crypto::P256PublicKey ourRootPublicKey, otherRootPublicKey; + ReturnErrorOnFailure(_cppCommissioner->GetRootPublicKey(ourRootPublicKey)); + ReturnErrorOnFailure(fabricTable->FetchRootPubkey(otherFabric->GetFabricIndex(), otherRootPublicKey)); + + *isRunning = (ourRootPublicKey.Matches(otherRootPublicKey)); + return CHIP_NO_ERROR; +} + +- (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(&_deviceMapLock); + MTRDevice * device = [_nodeIDToDeviceMap objectForKey:@(nodeID)]; + os_unfair_lock_unlock(&_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 *)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 + +@end + +/** + * Shim to allow us to treat an MTRDevicePairingDelegate as an + * MTRDeviceControllerDelegate. + */ +@interface MTRDevicePairingDelegateShim : NSObject +@property (nonatomic, readonly) id delegate; +- (instancetype)initWithDelegate:(id)delegate; +@end + +@implementation MTRDevicePairingDelegateShim +- (instancetype)initWithDelegate:(id)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(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. + */ +@interface MTROperationalCertificateChainIssuerShim : NSObject +@property (nonatomic, readonly) id nocChainIssuer; +@property (nonatomic, readonly) BOOL shouldSkipAttestationCertificateValidation; +- (instancetype)initWithIssuer:(id)nocChainIssuer; +@end + +@implementation MTROperationalCertificateChainIssuerShim +- (instancetype)initWithIssuer:(id)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 +{ + NSError * error; + if (![self checkIsRunning:&error]) { + dispatch_async(queue, ^{ + completion(nil, error); + }); + return NO; + } + + // We know getSessionForNode will return YES here, since we already checked + // that we are running. + [self getSessionForNode:deviceID + completion:^(chip::Messaging::ExchangeManager * _Nullable exchangeManager, + const chip::Optional & session, NSError * _Nullable error, NSNumber * _Nullable retryDelay) { + // Create an MTRBaseDevice for the node id involved, now that our + // CASE session is primed. We don't actually care about the session + // information here. + dispatch_async(queue, ^{ + MTRBaseDevice * device; + if (error == nil) { + device = [[MTRBaseDevice alloc] initWithNodeID:@(deviceID) controller:self]; + } else { + device = nil; + } + completion(device, error); + }); + }]; + + return YES; +} + +- (BOOL)pairDevice:(uint64_t)deviceID + discriminator:(uint16_t)discriminator + setupPINCode:(uint32_t)setupPINCode + error:(NSError * __autoreleasing *)error +{ + [[MTRMetricsCollector sharedInstance] resetMetrics]; + + // Track overall commissioning + MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissioning); + + // Capture in a block variable to avoid losing granularity for metrics, + // when translating CHIP_ERROR to NSError + __block CHIP_ERROR errorCode = CHIP_NO_ERROR; + + auto block = ^BOOL { + // Track work until end of scope + MATTER_LOG_METRIC_SCOPE(kMetricPairDevice, errorCode); + + std::string manualPairingCode; + chip::SetupPayload payload; + payload.discriminator.SetLongValue(discriminator); + payload.setUpPINCode = setupPINCode; + + errorCode = chip::ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(manualPairingCode); + VerifyOrReturnValue(![MTRDeviceController checkForError:errorCode logMsg:kErrorSetupCodeGen error:error], NO); + + self->_operationalCredentialsDelegate->SetDeviceID(deviceID); + + MATTER_LOG_METRIC_BEGIN(kMetricSetupPASESession); + errorCode = self->_cppCommissioner->EstablishPASEConnection(deviceID, manualPairingCode.c_str()); + if (CHIP_NO_ERROR == errorCode) { + self->_deviceControllerDelegateBridge->SetDeviceNodeID(deviceID); + } else { + MATTER_LOG_METRIC_END(kMetricSetupPASESession, errorCode); + } + + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + auto success = [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; + if (!success) { + MATTER_LOG_METRIC_END(kMetricDeviceCommissioning, errorCode); + } + return success; +} + +- (BOOL)pairDevice:(uint64_t)deviceID + address:(NSString *)address + port:(uint16_t)port + setupPINCode:(uint32_t)setupPINCode + error:(NSError * __autoreleasing *)error +{ + [[MTRMetricsCollector sharedInstance] resetMetrics]; + + // Track overall commissioning + MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissioning); + + // Capture in a block variable to avoid losing granularity for metrics, + // when translating CHIP_ERROR to NSError + __block CHIP_ERROR errorCode = CHIP_NO_ERROR; + + auto block = ^BOOL { + // Track work until end of scope + MATTER_LOG_METRIC_SCOPE(kMetricPairDevice, errorCode); + + chip::Inet::IPAddress addr; + chip::Inet::IPAddress::FromString([address UTF8String], addr); + chip::Transport::PeerAddress peerAddress = chip::Transport::PeerAddress::UDP(addr, port); + + self->_operationalCredentialsDelegate->SetDeviceID(deviceID); + + auto params = chip::RendezvousParameters().SetSetupPINCode(setupPINCode).SetPeerAddress(peerAddress); + + MATTER_LOG_METRIC_BEGIN(kMetricSetupPASESession); + errorCode = self->_cppCommissioner->EstablishPASEConnection(deviceID, params); + if (CHIP_NO_ERROR == errorCode) { + self->_deviceControllerDelegateBridge->SetDeviceNodeID(deviceID); + } else { + MATTER_LOG_METRIC_END(kMetricSetupPASESession, errorCode); + } + + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + auto success = [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; + if (!success) { + MATTER_LOG_METRIC_END(kMetricDeviceCommissioning, errorCode); + } + return success; +} + +- (BOOL)pairDevice:(uint64_t)deviceID onboardingPayload:(NSString *)onboardingPayload error:(NSError * __autoreleasing *)error +{ + [[MTRMetricsCollector sharedInstance] resetMetrics]; + + // Track overall commissioning + MATTER_LOG_METRIC_BEGIN(kMetricDeviceCommissioning); + emitMetricForSetupPayload([MTRSetupPayload setupPayloadWithOnboardingPayload:onboardingPayload error:nil]); + + // Capture in a block variable to avoid losing granularity for metrics, + // when translating CHIP_ERROR to NSError + __block CHIP_ERROR errorCode = CHIP_NO_ERROR; + + auto block = ^BOOL { + // Track work until end of scope + MATTER_LOG_METRIC_SCOPE(kMetricPairDevice, errorCode); + + self->_operationalCredentialsDelegate->SetDeviceID(deviceID); + + MATTER_LOG_METRIC_BEGIN(kMetricSetupPASESession); + errorCode = self->_cppCommissioner->EstablishPASEConnection(deviceID, [onboardingPayload UTF8String]); + if (CHIP_NO_ERROR == errorCode) { + self->_deviceControllerDelegateBridge->SetDeviceNodeID(deviceID); + } else { + MATTER_LOG_METRIC_END(kMetricSetupPASESession, errorCode); + } + + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }; + + auto success = [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; + if (!success) { + MATTER_LOG_METRIC_END(kMetricDeviceCommissioning, errorCode); + } + return success; +} + +- (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 +{ + if (duration > UINT16_MAX) { + MTR_LOG_ERROR("%@ Error: Duration %lu is too large. Max value %d", self, static_cast(duration), UINT16_MAX); + if (error) { + *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_INTEGER_VALUE]; + } + return NO; + } + + auto block = ^BOOL { + CHIP_ERROR errorCode = CHIP_NO_ERROR; + MATTER_LOG_METRIC_SCOPE(kMetricOpenPairingWindow, errorCode); + + errorCode = chip::Controller::AutoCommissioningWindowOpener::OpenBasicCommissioningWindow( + self->_cppCommissioner, deviceID, chip::System::Clock::Seconds16(static_cast(duration))); + return ![MTRDeviceController checkForError:errorCode logMsg:kErrorOpenPairingWindow error:error]; + }; + + return [self syncRunOnWorkQueueWithBoolReturnValue:block error:error]; +} + +- (NSString *)openPairingWindowWithPIN:(uint64_t)deviceID + duration:(NSUInteger)duration + discriminator:(NSUInteger)discriminator + setupPIN:(NSUInteger)setupPIN + error:(NSError * __autoreleasing *)error +{ + if (duration > UINT16_MAX) { + MTR_LOG_ERROR("%@ Error: Duration %lu is too large. Max value %d", self, static_cast(duration), UINT16_MAX); + if (error) { + *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_INTEGER_VALUE]; + } + return nil; + } + + if (discriminator > 0xfff) { + MTR_LOG_ERROR("%@ Error: Discriminator %lu is too large. Max value %d", self, static_cast(discriminator), 0xfff); + if (error) { + *error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_INTEGER_VALUE]; + } + return nil; + } + + __block CHIP_ERROR errorCode = CHIP_NO_ERROR; + MATTER_LOG_METRIC_SCOPE(kMetricOpenPairingWindow, errorCode); + + if (!chip::CanCastTo(setupPIN) || !chip::SetupPayload::IsValidSetupPIN(static_cast(setupPIN))) { + MTR_LOG_ERROR("%@ Error: Setup pin %lu is not valid", self, static_cast(setupPIN)); + errorCode = CHIP_ERROR_INVALID_INTEGER_VALUE; + if (error) { + *error = [MTRError errorForCHIPErrorCode:errorCode]; + } + return nil; + } + + auto block = ^NSString * + { + chip::SetupPayload setupPayload; + errorCode = chip::Controller::AutoCommissioningWindowOpener::OpenCommissioningWindow(self->_cppCommissioner, deviceID, + chip::System::Clock::Seconds16(static_cast(duration)), chip::Crypto::kSpake2p_Min_PBKDF_Iterations, + static_cast(discriminator), chip::MakeOptional(static_cast(setupPIN)), chip::NullOptional, + setupPayload); + + VerifyOrReturnValue(![MTRDeviceController checkForError:errorCode logMsg:kErrorOpenPairingWindow error:error], nil); + + chip::ManualSetupPayloadGenerator generator(setupPayload); + std::string outCode; + + if (CHIP_NO_ERROR != (errorCode = generator.payloadDecimalStringRepresentation(outCode))) { + MTR_LOG_ERROR("%@ Failed to get decimal setup code", self); + return nil; + } + + MTR_LOG_ERROR("%@ Setup code is %s", self, outCode.c_str()); + return [NSString stringWithCString:outCode.c_str() encoding:[NSString defaultCStringEncoding]]; + }; + + return [self syncRunOnWorkQueueWithReturnValue:block error:error]; +} + +- (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)delegate queue:(dispatch_queue_t)queue +{ + auto * delegateShim = [[MTRDevicePairingDelegateShim alloc] initWithDelegate:delegate]; + [self setDeviceControllerDelegate:delegateShim queue:queue]; +} + +- (void)setNocChainIssuer:(id)nocChainIssuer queue:(dispatch_queue_t)queue +{ + [self setOperationalCertificateIssuer:[[MTROperationalCertificateChainIssuerShim alloc] initWithIssuer:nocChainIssuer] + queue:queue]; +} +@end