Re-add the openCommissioningWindowWithSetupPasscode APIs on MTRDevice and MTRBaseDevice. (#23157)
This is a re-landing of the API addition parts of
https://github.com/project-chip/connectedhomeip/pull/22521, without including
any of the API changes/removals. The code is identical to what was on master
before Darwin framework changes were reverted in
https://github.com/project-chip/connectedhomeip/pull/23155.
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h
index f2b3e01..2c98ab9 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.h
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h
@@ -17,6 +17,8 @@
#import <Foundation/Foundation.h>
+@class MTRSetupPayload;
+
NS_ASSUME_NONNULL_BEGIN
/**
@@ -87,6 +89,11 @@
*/
typedef void (^MTRDeviceResubscriptionScheduledHandler)(NSError * error, NSNumber * resubscriptionDelay);
+/**
+ * Handler for openCommissioningWindowWithSetupPasscode.
+ */
+typedef void (^MTRDeviceOpenCommissioningWindowHandler)(MTRSetupPayload * _Nullable payload, NSError * _Nullable error);
+
extern NSString * const MTRAttributePathKey;
extern NSString * const MTRCommandPathKey;
extern NSString * const MTREventPathKey;
@@ -238,6 +245,26 @@
*/
- (void)deregisterReportHandlersWithClientQueue:(dispatch_queue_t)clientQueue completion:(void (^)(void))completion;
+/**
+ * Open a commissioning window on the device.
+ *
+ * On success, completion will be called on queue with the MTRSetupPayload that
+ * can be used to commission the device.
+ *
+ * @param setupPasscode The setup passcode to use for the commissioning window.
+ * See MTRSetupPayload's generateRandomSetupPasscode for
+ * generating a valid random passcode.
+ * @param discriminator The discriminator to use for the commissionable
+ * advertisement.
+ * @param duration Duration, in seconds, during which the commissioning
+ * window will be open.
+ */
+- (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode
+ discriminator:(NSNumber *)discriminator
+ duration:(NSNumber *)duration
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceOpenCommissioningWindowHandler)completion;
+
@end
@interface MTRAttributePath : NSObject <NSCopying>
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
index 8162452..0a2a008 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
@@ -24,6 +24,7 @@
#import "MTRError_Internal.h"
#import "MTREventTLVValueDecoder_Internal.h"
#import "MTRLogging.h"
+#import "MTRSetupPayload_Internal.h"
#include "app/ConcreteAttributePath.h"
#include "app/ConcreteCommandPath.h"
@@ -36,8 +37,12 @@
#include <app/InteractionModelEngine.h>
#include <app/ReadClient.h>
#include <app/util/error-mapping.h>
+#include <controller/CommissioningWindowOpener.h>
#include <controller/ReadInteraction.h>
#include <controller/WriteInteraction.h>
+#include <crypto/CHIPCryptoPAL.h>
+#include <setup_payload/SetupPayload.h>
+#include <system/SystemClock.h>
#include <memory>
@@ -1256,6 +1261,144 @@
PurgeReadClientContainers(self.nodeID, clientQueue, completion);
}
+namespace {
+class OpenCommissioningWindowHelper {
+ typedef void (^ResultCallback)(CHIP_ERROR status, const SetupPayload &);
+
+public:
+ static CHIP_ERROR OpenCommissioningWindow(Controller::DeviceController * controller, NodeId nodeID,
+ System::Clock::Seconds16 timeout, uint16_t discriminator, uint32_t setupPIN, ResultCallback callback);
+
+private:
+ OpenCommissioningWindowHelper(Controller::DeviceController * controller, ResultCallback callback);
+
+ static void OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload);
+
+ Controller::CommissioningWindowOpener mOpener;
+ Callback::Callback<Controller::OnOpenCommissioningWindow> mOnOpenCommissioningWindowCallback;
+ ResultCallback mResultCallback;
+};
+
+OpenCommissioningWindowHelper::OpenCommissioningWindowHelper(Controller::DeviceController * controller, ResultCallback callback)
+ : mOpener(controller)
+ , mOnOpenCommissioningWindowCallback(OnOpenCommissioningWindowResponse, this)
+ , mResultCallback(callback)
+{
+}
+
+CHIP_ERROR OpenCommissioningWindowHelper::OpenCommissioningWindow(Controller::DeviceController * controller, NodeId nodeID,
+ System::Clock::Seconds16 timeout, uint16_t discriminator, uint32_t setupPIN, ResultCallback callback)
+{
+ auto * self = new (std::nothrow) OpenCommissioningWindowHelper(controller, callback);
+ if (self == nullptr) {
+ return CHIP_ERROR_NO_MEMORY;
+ }
+
+ SetupPayload unused;
+ CHIP_ERROR err = self->mOpener.OpenCommissioningWindow(nodeID, timeout, Crypto::kSpake2p_Min_PBKDF_Iterations, discriminator,
+ MakeOptional(setupPIN), NullOptional, &self->mOnOpenCommissioningWindowCallback, unused);
+ if (err != CHIP_NO_ERROR) {
+ delete self;
+ }
+ // Else will clean up when the callback is called.
+ return err;
+}
+
+void OpenCommissioningWindowHelper::OnOpenCommissioningWindowResponse(
+ void * context, NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload)
+{
+ auto * self = static_cast<OpenCommissioningWindowHelper *>(context);
+ self->mResultCallback(status, payload);
+ delete self;
+}
+
+} // anonymous namespace
+
+- (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode
+ discriminator:(NSNumber *)discriminator
+ duration:(NSNumber *)duration
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceOpenCommissioningWindowHandler)completion
+{
+ if (self.isPASEDevice) {
+ MTR_LOG_ERROR("Can't open a commissioning window over PASE");
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE]);
+ });
+ return;
+ }
+
+ unsigned long long durationVal = [duration unsignedLongLongValue];
+ if (!CanCastTo<uint16_t>(durationVal)) {
+ MTR_LOG_ERROR("Error: Duration %llu is too large.", durationVal);
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_INTEGER_VALUE]);
+ });
+ return;
+ }
+
+ unsigned long long discriminatorVal = [discriminator unsignedLongLongValue];
+
+ if (discriminatorVal > 0xFFF) {
+ MTR_LOG_ERROR("Error: Discriminator %llu is too large. Max value %d", discriminatorVal, 0xFFF);
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_INTEGER_VALUE]);
+ });
+ return;
+ }
+
+ unsigned long long passcodeVal = [setupPasscode unsignedLongLongValue];
+ if (!CanCastTo<uint32_t>(passcodeVal) || !SetupPayload::IsValidSetupPIN(static_cast<uint32_t>(passcodeVal))) {
+ MTR_LOG_ERROR("Error: Setup passcode %llu is not valid", passcodeVal);
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_INTEGER_VALUE]);
+ });
+ return;
+ }
+
+ [self.deviceController
+ asyncDispatchToMatterQueue:^(Controller::DeviceCommissioner * commissioner) {
+ auto resultCallback = ^(CHIP_ERROR status, const SetupPayload & payload) {
+ if (status != CHIP_NO_ERROR) {
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:status]);
+ });
+ return;
+ }
+ auto * payloadObj = [[MTRSetupPayload alloc] initWithSetupPayload:payload];
+ if (payloadObj == nil) {
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_NO_MEMORY]);
+ });
+ return;
+ }
+
+ dispatch_async(queue, ^{
+ completion(payloadObj, nil);
+ });
+ };
+
+ SetupPayload setupPayload;
+ auto errorCode = OpenCommissioningWindowHelper::OpenCommissioningWindow(commissioner, self.nodeID,
+ chip::System::Clock::Seconds16(static_cast<uint16_t>(durationVal)), static_cast<uint16_t>(discriminatorVal),
+ static_cast<uint32_t>(passcodeVal), resultCallback);
+
+ if (errorCode != CHIP_NO_ERROR) {
+ dispatch_async(queue, ^{
+ completion(nil, [MTRError errorForCHIPErrorCode:errorCode]);
+ });
+ return;
+ }
+
+ // resultCallback will handle things now.
+ }
+ errorHandler:^(NSError * error) {
+ dispatch_async(queue, ^{
+ completion(nil, error);
+ });
+ }];
+}
+
#ifdef DEBUG
// This method is for unit testing only
- (void)failSubscribers:(dispatch_queue_t)clientQueue completion:(void (^)(void))completion
diff --git a/src/darwin/Framework/CHIP/MTRDevice.h b/src/darwin/Framework/CHIP/MTRDevice.h
index ea2d047..d946a76 100644
--- a/src/darwin/Framework/CHIP/MTRDevice.h
+++ b/src/darwin/Framework/CHIP/MTRDevice.h
@@ -50,7 +50,7 @@
* The current state of the device.
*
* The three states:
- * MTRDeviceStateUnknkown
+ * MTRDeviceStateUnknown
* Unable to determine the state of the device at the moment.
*
* MTRDeviceStateReachable
@@ -137,6 +137,26 @@
clientQueue:(dispatch_queue_t)clientQueue
completion:(MTRDeviceResponseHandler)completion;
+/**
+ * Open a commissioning window on the device.
+ *
+ * On success, completion will be called on queue with the MTRSetupPayload that
+ * can be used to commission the device.
+ *
+ * @param setupPasscode The setup passcode to use for the commissioning window.
+ * See MTRSetupPayload's generateRandomSetupPasscode for
+ * generating a valid random passcode.
+ * @param discriminator The discriminator to use for the commissionable
+ * advertisement.
+ * @param duration Duration, in seconds, during which the commissioning
+ * window will be open.
+ */
+- (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode
+ discriminator:(NSNumber *)discriminator
+ duration:(NSNumber *)duration
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceOpenCommissioningWindowHandler)completion;
+
@end
@protocol MTRDeviceDelegate <NSObject>
diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm
index 1f5b6f2..9cea81c 100644
--- a/src/darwin/Framework/CHIP/MTRDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRDevice.mm
@@ -474,6 +474,20 @@
[self setExpectedValues:expectedValues expectedValueInterval:expectedValueInterval];
}
+- (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode
+ discriminator:(NSNumber *)discriminator
+ duration:(NSNumber *)duration
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceOpenCommissioningWindowHandler)completion
+{
+ auto * baseDevice = [[MTRBaseDevice alloc] initWithNodeID:self.nodeID controller:self.deviceController];
+ [baseDevice openCommissioningWindowWithSetupPasscode:setupPasscode
+ discriminator:discriminator
+ duration:duration
+ queue:queue
+ completion:completion];
+}
+
#pragma mark - Cache management
// assume lock is held
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm
index 78c01c7..7ff7d59 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm
@@ -886,6 +886,28 @@
return YES;
}
+- (void)asyncDispatchToMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
+ errorHandler:(void (^)(NSError *))errorHandler
+{
+ {
+ NSError * error;
+ if (![self checkIsRunning:&error]) {
+ errorHandler(error);
+ return;
+ }
+ }
+
+ dispatch_async(_chipWorkQueue, ^{
+ NSError * error;
+ if (![self checkIsRunning:&error]) {
+ errorHandler(error);
+ return;
+ }
+
+ block(self.cppCommissioner);
+ });
+}
+
@end
@implementation MTRDeviceController (InternalMethods)
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
index 32edcaf..7ddbb1d 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
@@ -36,6 +36,10 @@
namespace chip {
class FabricTable;
+
+namespace Controller {
+ class DeviceCommissioner;
+}
} // namespace chip
NS_ASSUME_NONNULL_BEGIN
@@ -137,6 +141,19 @@
*/
- (void)invalidateCASESessionForNode:(chip::NodeId)nodeID;
+/**
+ * Try to asynchronously dispatch the given block on the Matter queue. If the
+ * controller is not running either at call time or when the block would be
+ * about to run, the provided error handler will be called with an error. Note
+ * that this means the error handler might be called on an arbitrary queue, and
+ * might be called before this function returns or after it returns.
+ *
+ * The DeviceCommissioner pointer passed to the callback should only be used
+ * synchronously during the callback invocation.
+ */
+- (void)asyncDispatchToMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block
+ errorHandler:(void (^)(NSError *))errorHandler;
+
#pragma mark - Device-specific data and SDK access
// DeviceController will act as a central repository for this opaque dictionary that MTRDevice manages
- (MTRDevice *)deviceForNodeID:(uint64_t)nodeID;
diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m
index 2f0a7c4..a501524 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m
+++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m
@@ -290,6 +290,18 @@
}];
}
+- (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode
+ discriminator:(NSNumber *)discriminator
+ duration:(NSNumber *)duration
+ queue:(dispatch_queue_t)queue
+ completion:(MTRDeviceOpenCommissioningWindowHandler)completion
+{
+ MTR_LOG_ERROR("MTRDevice doesn't support openCommissioningWindowWithSetupPasscode over XPC");
+ dispatch_async(queue, ^{
+ completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]);
+ });
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload.h b/src/darwin/Framework/CHIP/MTRSetupPayload.h
index c60a48e..de50f84 100644
--- a/src/darwin/Framework/CHIP/MTRSetupPayload.h
+++ b/src/darwin/Framework/CHIP/MTRSetupPayload.h
@@ -76,6 +76,11 @@
*/
+ (NSUInteger)generateRandomPIN;
+/**
+ * Generate a random Matter-valid setup passcode.
+ */
++ (NSNumber *)generateRandomSetupPasscode;
+
/** Get 11 digit manual entry code from the setup payload. */
- (nullable NSString *)manualEntryCode;
diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload.mm b/src/darwin/Framework/CHIP/MTRSetupPayload.mm
index 39c18a3..3f05802 100644
--- a/src/darwin/Framework/CHIP/MTRSetupPayload.mm
+++ b/src/darwin/Framework/CHIP/MTRSetupPayload.mm
@@ -121,11 +121,16 @@
+ (NSUInteger)generateRandomPIN
{
+ return [[MTRSetupPayload generateRandomSetupPasscode] unsignedIntValue];
+}
+
++ (NSNumber *)generateRandomSetupPasscode
+{
do {
// Make sure the thing we generate is in the right range.
uint32_t setupPIN = arc4random_uniform(chip::kSetupPINCodeMaximumValue) + 1;
if (chip::SetupPayload::IsValidSetupPIN(setupPIN)) {
- return setupPIN;
+ return @(setupPIN);
}
// We got pretty unlikely with our random number generation. Just try
@@ -135,7 +140,7 @@
} while (1);
// Not reached.
- return chip::kSetupPINCodeUndefinedValue;
+ return @(chip::kSetupPINCodeUndefinedValue);
}
#pragma mark - NSSecureCoding