diff --git a/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm b/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm index 2f3eb9e8d2fc41..4c25000ed91b01 100644 --- a/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm +++ b/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm @@ -17,47 +17,59 @@ #import +#import "MTRError_Utils.h" + #include "OpenCommissioningWindowCommand.h" CHIP_ERROR OpenCommissioningWindowCommand::RunCommand() { + mWorkQueue = dispatch_queue_create("com.chip.open_commissioning_window", DISPATCH_QUEUE_SERIAL); auto * controller = CurrentCommissioner(); - NSError * error; - __block NSString * pairingCode; + auto * device = [MTRDevice deviceWithNodeID:mNodeId deviceController:controller]; + + auto * self = this; if (mCommissioningWindowOption == 0) { - [controller openPairingWindow:mNodeId duration:mCommissioningWindowTimeoutMs error:&error]; + auto * cluster = [[MTRClusterAdministratorCommissioning alloc] initWithDevice:device endpoint:0 queue:mWorkQueue]; + auto * params = [[MTRAdministratorCommissioningClusterOpenBasicCommissioningWindowParams alloc] init]; + params.commissioningTimeout = @(mCommissioningWindowTimeoutMs); + params.timedInvokeTimeoutMs = @(10000); + [cluster openBasicCommissioningWindowWithParams:params + expectedValues:nil + expectedValueInterval:nil + completionHandler:^(NSError * _Nullable error) { + if (error == nil) { + self->SetCommandExitStatus(CHIP_NO_ERROR); + } else { + self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(error)); + } + }]; } else { - pairingCode = [controller openPairingWindowWithPIN:mNodeId - duration:mCommissioningWindowTimeoutMs - discriminator:mDiscriminator - setupPIN:[MTRSetupPayload generateRandomPIN] - error:&error]; - } + [device + openCommissioningWindowWithSetupPasscode:[MTRSetupPayload generateRandomSetupPasscode] + discriminator:@(mDiscriminator) + duration:@(mCommissioningWindowTimeoutMs) + queue:mWorkQueue + completion:^(MTRSetupPayload * _Nullable payload, NSError * error) { + if (error != nil) { + self->SetCommandExitStatus(MTRErrorToCHIPErrorCode(error)); + return; + } - if (error != nil) { - SetCommandExitStatus(error); - return CHIP_NO_ERROR; - } + if (payload == nil) { + self->SetCommandExitStatus(CHIP_ERROR_INVALID_ARGUMENT); + return; + } - // TODO: Those should be async operations and we should not claim to - // be done until they complete. As things stand, we have no idea - // how to tell when we're done, so just set a timer for slightly - // less than our command timeout to call SetCommandExitStatus. - mWorkQueue = dispatch_queue_create("com.chip.open_commissioning_window", DISPATCH_QUEUE_SERIAL); - mTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, mWorkQueue); - auto * self = this; - dispatch_source_set_event_handler(mTimer, ^{ - dispatch_source_cancel(mTimer); - mTimer = nil; - mWorkQueue = nil; - if (pairingCode != nil) { - ChipLogProgress(chipTool, "Setup code: %s\n", [pairingCode UTF8String]); - } - self->SetCommandExitStatus(CHIP_NO_ERROR); - }); - dispatch_source_set_timer( - mTimer, dispatch_time(DISPATCH_TIME_NOW, (GetWaitDuration().count() - 2000) * NSEC_PER_MSEC), DISPATCH_TIME_FOREVER, 0); - dispatch_resume(mTimer); + auto * pairingCode = [payload manualEntryCode]; + if (pairingCode == nil) { + self->SetCommandExitStatus(CHIP_ERROR_INVALID_ARGUMENT); + return; + } + + ChipLogProgress(chipTool, "Setup code: %s\n", [[payload manualEntryCode] UTF8String]); + self->SetCommandExitStatus(CHIP_NO_ERROR); + }]; + } return CHIP_NO_ERROR; } diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index f2b3e01a7e6fcf..2c98ab9e968777 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -17,6 +17,8 @@ #import +@class MTRSetupPayload; + NS_ASSUME_NONNULL_BEGIN /** @@ -87,6 +89,11 @@ typedef void (^MTRDeviceErrorHandler)(NSError * error); */ 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 @@ extern NSString * const MTRArrayValueType; */ - (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 diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 8162452fbdb957..0a2a0083d0f34f 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 #include #include +#include #include #include +#include +#include +#include #include @@ -1256,6 +1261,144 @@ - (void)deregisterReportHandlersWithClientQueue:(dispatch_queue_t)clientQueue co 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 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(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(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(passcodeVal) || !SetupPayload::IsValidSetupPIN(static_cast(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(durationVal)), static_cast(discriminatorVal), + static_cast(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 ea2d047bc44b09..d946a76fd2f2b5 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.h +++ b/src/darwin/Framework/CHIP/MTRDevice.h @@ -50,7 +50,7 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) { * 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 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) { 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 diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 1f5b6f21803476..9cea81ca5120a6 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -474,6 +474,20 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID [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 78c01c7bb40212..7ff7d59a80072d 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -886,6 +886,28 @@ - (BOOL)getSessionForCommissioneeDevice:(chip::NodeId)deviceID completion:(MTRIn 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 32edcafd2d9761..7ddbb1df310753 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 @@ NS_ASSUME_NONNULL_BEGIN */ - (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 2f0a7c47918652..a501524ddcaaf2 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m +++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m @@ -290,6 +290,18 @@ - (void)deregisterReportHandlersWithClientQueue:(dispatch_queue_t)clientQueue co }]; } +- (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 c60a48ec8fc8b9..de50f84211eeae 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload.h +++ b/src/darwin/Framework/CHIP/MTRSetupPayload.h @@ -76,6 +76,11 @@ typedef NS_ENUM(NSUInteger, MTROptionalQRCodeInfoType) { */ + (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 39c18a33d1a66c..3f058023bc45eb 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload.mm +++ b/src/darwin/Framework/CHIP/MTRSetupPayload.mm @@ -120,12 +120,17 @@ - (void)getSerialNumber:(chip::SetupPayload)setupPayload } + (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 @@ + (NSUInteger)generateRandomPIN } while (1); // Not reached. - return chip::kSetupPINCodeUndefinedValue; + return @(chip::kSetupPINCodeUndefinedValue); } #pragma mark - NSSecureCoding