diff --git a/examples/darwin-framework-tool/commands/pairing/Commands.h b/examples/darwin-framework-tool/commands/pairing/Commands.h index 164c952ffc2f4f..ffda27b9e3d348 100644 --- a/examples/darwin-framework-tool/commands/pairing/Commands.h +++ b/examples/darwin-framework-tool/commands/pairing/Commands.h @@ -41,12 +41,6 @@ class PairCodeThread : public PairingCommandBridge PairCodeThread() : PairingCommandBridge("code-thread", PairingMode::Code, PairingNetworkType::Thread) {} }; -class PairWithIPAddress : public PairingCommandBridge -{ -public: - PairWithIPAddress() : PairingCommandBridge("ethernet", PairingMode::Ethernet, PairingNetworkType::Ethernet) {} -}; - class PairBleWiFi : public PairingCommandBridge { public: @@ -70,10 +64,13 @@ void registerCommandsPairing(Commands & commands) const char * clusterName = "Pairing"; commands_list clusterCommands = { - make_unique(), make_unique(), - make_unique(), make_unique(), - make_unique(), make_unique(), - make_unique(), make_unique(), + make_unique(), + make_unique(), + make_unique(), + make_unique(), + make_unique(), + make_unique(), + make_unique(), }; commands.Register(clusterName, clusterCommands); diff --git a/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.h b/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.h index cf736e0e193387..4885b328c8ad9a 100644 --- a/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.h +++ b/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.h @@ -24,7 +24,6 @@ enum class PairingMode { None, Code, - Ethernet, Ble, }; @@ -64,12 +63,6 @@ class PairingCommandBridge : public CHIPCommandBridge case PairingMode::Code: AddArgument("payload", &mOnboardingPayload); break; - case PairingMode::Ethernet: - AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); - AddArgument("discriminator", 0, 4096, &mDiscriminator); - AddArgument("device-remote-ip", &ipAddress); - AddArgument("device-remote-port", 0, UINT16_MAX, &mRemotePort); - break; case PairingMode::Ble: AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); AddArgument("discriminator", 0, 4096, &mDiscriminator); @@ -84,7 +77,6 @@ class PairingCommandBridge : public CHIPCommandBridge private: void PairWithCode(NSError * __autoreleasing * error); void PairWithPayload(NSError * __autoreleasing * error); - void PairWithIPAddress(NSError * __autoreleasing * error); void Unpair(); void SetUpPairingDelegate(); @@ -94,9 +86,7 @@ class PairingCommandBridge : public CHIPCommandBridge chip::ByteSpan mSSID; chip::ByteSpan mPassword; chip::NodeId mNodeId; - uint16_t mRemotePort; uint16_t mDiscriminator; uint32_t mSetupPINCode; char * mOnboardingPayload; - char * ipAddress; }; diff --git a/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.mm b/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.mm index e8fca6ab8771f5..a0292b367c75f9 100644 --- a/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/pairing/PairingCommandBridge.mm @@ -66,9 +66,6 @@ case PairingMode::Code: PairWithPayload(&error); break; - case PairingMode::Ethernet: - PairWithIPAddress(&error); - break; case PairingMode::Ble: PairWithCode(&error); break; @@ -83,28 +80,21 @@ void PairingCommandBridge::PairWithCode(NSError * __autoreleasing * error) { SetUpPairingDelegate(); + auto * payload = [[MTRSetupPayload alloc] initWithSetupPasscode:@(mSetupPINCode) discriminator:@(mDiscriminator)]; MTRDeviceController * commissioner = CurrentCommissioner(); - [commissioner pairDevice:mNodeId discriminator:mDiscriminator setupPINCode:mSetupPINCode error:error]; + [commissioner setupCommissioningSessionWithPayload:payload newNodeID:@(mNodeId) error:error]; } void PairingCommandBridge::PairWithPayload(NSError * __autoreleasing * error) { - NSString * payload = [NSString stringWithUTF8String:mOnboardingPayload]; - - SetUpPairingDelegate(); - MTRDeviceController * commissioner = CurrentCommissioner(); - [commissioner pairDevice:mNodeId onboardingPayload:payload error:error]; -} - -void PairingCommandBridge::PairWithIPAddress(NSError * __autoreleasing * error) -{ + NSString * onboardingPayload = [NSString stringWithUTF8String:mOnboardingPayload]; SetUpPairingDelegate(); + auto * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:onboardingPayload error:error]; + if (payload == nil) { + return; + } MTRDeviceController * commissioner = CurrentCommissioner(); - [commissioner pairDevice:mNodeId - address:[NSString stringWithUTF8String:ipAddress] - port:mRemotePort - setupPINCode:mSetupPINCode - error:error]; + [commissioner setupCommissioningSessionWithPayload:payload newNodeID:@(mNodeId) error:error]; } void PairingCommandBridge::Unpair() diff --git a/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h b/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h index 14cd1eef4d4b94..20bf5ea53fdee5 100644 --- a/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h +++ b/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h @@ -197,7 +197,11 @@ class TestCommandBridge : public CHIPCommandBridge, length:value.payload.size() encoding:NSUTF8StringEncoding]; NSError * err; - BOOL ok = [controller pairDevice:value.nodeId onboardingPayload:payloadStr error:&err]; + auto * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payloadStr error:&err]; + if (err != nil) { + return MTRErrorToCHIPErrorCode(err); + } + BOOL ok = [controller setupCommissioningSessionWithPayload:payload newNodeID:@(value.nodeId) error:&err]; if (ok == YES) { return CHIP_NO_ERROR; } diff --git a/scripts/tests/chiptest/lsan-mac-suppressions.txt b/scripts/tests/chiptest/lsan-mac-suppressions.txt index fad4a1e0971248..0b7f0d0a3c8a08 100644 --- a/scripts/tests/chiptest/lsan-mac-suppressions.txt +++ b/scripts/tests/chiptest/lsan-mac-suppressions.txt @@ -19,17 +19,6 @@ leak:drbg_bytes # TODO: OpenSSL ERR_get_state seems to leak. leak:ERR_get_state -# TODO: BLE initialization allocates some UUIDs and strings that seem to leak. -leak:[BleConnection initWithDiscriminator:] -leak:[CBXpcConnection initWithDelegate:queue:options:sessionType:] - -# TODO: Figure out how we are managing to leak NSData while using ARC! -leak:[CHIPToolKeypair signMessageECDSA_RAW:] - -# TODO: Figure out how we are managing to leak NSData while using ARC, though -# this may just be a leak deep inside the CFPreferences stuff somewhere. -leak:[CHIPToolPersistentStorageDelegate storageDataForKey:] - # TODO: https://github.com/project-chip/connectedhomeip/issues/22333 leak:[MTRBaseCluster* subscribeAttribute*WithMinInterval:maxInterval:params:subscriptionEstablished:reportHandler:] diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.h b/src/darwin/Framework/CHIP/MTRDeviceController.h index a7467520083af9..de46330af677b8 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController.h @@ -39,6 +39,38 @@ typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NS */ @property (readonly, nonatomic, nullable) NSNumber * controllerNodeId; +/** + * 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. MTRDevicePairingDelegate will be notified as follows: + * + * * Discovery fails: onStatusUpdate with MTRPairingStatusFailed. + * + * * 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; + /** * Start pairing for a device with the given ID, using the provided setup PIN * to establish a PASE connection. diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 7ff7d59a80072d..81da7ed56c1fc4 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -361,6 +361,36 @@ - (NSNumber *)controllerNodeId return nodeID; } +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error +{ + VerifyOrReturnValue([self checkIsRunning:error], NO); + + __block BOOL success = NO; + dispatch_sync(_chipWorkQueue, ^{ + VerifyOrReturn([self checkIsRunning:error]); + + // 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]; + if (pairingCode == nil) { + pairingCode = [payload manualEntryCode]; + } + if (pairingCode == nil) { + success = ![self checkForError:CHIP_ERROR_INVALID_ARGUMENT logMsg:kErrorSetupCodeGen error:error]; + return; + } + + chip::NodeId nodeId = [newNodeID unsignedLongLongValue]; + _operationalCredentialsDelegate->SetDeviceID(nodeId); + CHIP_ERROR errorCode = self.cppCommissioner->EstablishPASEConnection(nodeId, [pairingCode UTF8String]); + success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; + }); + + return success; +} + - (BOOL)pairDevice:(uint64_t)deviceID discriminator:(uint16_t)discriminator setupPINCode:(uint32_t)setupPINCode diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m b/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m index 54463a59f99a65..d8c2df5290514b 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m @@ -46,6 +46,17 @@ + (MTRDeviceControllerOverXPC *)sharedControllerWithId:(id _Nullable) connectBlock:connectBlock]; } +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error +{ + MTR_LOG_ERROR("MTRDeviceController doesn't support setupCommissioningSessionWithPayload over XPC"); + if (error != nil) { + *error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; + } + return NO; +} + - (BOOL)pairDevice:(uint64_t)deviceID discriminator:(uint16_t)discriminator setupPINCode:(uint32_t)setupPINCode diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload.h b/src/darwin/Framework/CHIP/MTRSetupPayload.h index de50f84211eeae..c281f3020009a4 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload.h +++ b/src/darwin/Framework/CHIP/MTRSetupPayload.h @@ -81,9 +81,31 @@ typedef NS_ENUM(NSUInteger, MTROptionalQRCodeInfoType) { */ + (NSNumber *)generateRandomSetupPasscode; +/** + * Create an MTRSetupPayload with the given onboarding payload. + * + * Will return nil on errors (e.g. if the onboarding payload cannot be parsed). + */ ++ (MTRSetupPayload * _Nullable)setupPayloadWithOnboardingPayload:(NSString *)onboardingPayload + error:(NSError * __autoreleasing *)error; + +/** + * Initialize an MTRSetupPayload with the given passcode and discriminator. + * This will pre-set version, product id, and vendor id to 0. + */ +- (instancetype)initWithSetupPasscode:(NSNumber *)setupPasscode discriminator:(NSNumber *)discriminator; + /** Get 11 digit manual entry code from the setup payload. */ - (nullable NSString *)manualEntryCode; +/** + * Get a QR code from the setup payload. + * + * Returns nil on failure (e.g. if the setup payload does not have all the + * information a QR code needs). + */ +- (NSString * _Nullable)qrCodeString; + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload.mm b/src/darwin/Framework/CHIP/MTRSetupPayload.mm index 3f058023bc45eb..5aa74002eef819 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload.mm +++ b/src/darwin/Framework/CHIP/MTRSetupPayload.mm @@ -17,8 +17,10 @@ #import "MTRError.h" #import "MTRError_Internal.h" +#import "MTROnboardingPayloadParser.h" #import "MTRSetupPayload_Internal.h" #import "setup_payload/ManualSetupPayloadGenerator.h" +#import "setup_payload/QRCodeSetupPayloadGenerator.h" #import @implementation MTROptionalQRCodeInfo @@ -47,6 +49,27 @@ - (NSNumber *)convertRendezvousFlags:(const chip::Optional)unconvertRendezvousFlags:(nullable NSNumber *)nullableValue +{ + if (nullableValue == nil) { + return chip::NullOptional; + } + + MTRDiscoveryCapabilities value = static_cast([nullableValue unsignedLongValue]); + + chip::RendezvousInformationFlags flags; + if (value & MTRDiscoveryCapabilitiesBLE) { + flags.Set(chip::RendezvousInformationFlag::kBLE); + } + if (value & MTRDiscoveryCapabilitiesSoftAP) { + flags.Set(chip::RendezvousInformationFlag::kSoftAP); + } + if (value & MTRDiscoveryCapabilitiesOnNetwork) { + flags.Set(chip::RendezvousInformationFlag::kOnNetwork); + } + return chip::MakeOptional(flags); +} + - (MTRCommissioningFlow)convertCommissioningFlow:(chip::CommissioningFlow)value { if (value == chip::CommissioningFlow::kStandard) { @@ -61,7 +84,23 @@ - (MTRCommissioningFlow)convertCommissioningFlow:(chip::CommissioningFlow)value return MTRCommissioningFlowInvalid; } -- (id)initWithSetupPayload:(chip::SetupPayload)setupPayload ++ (chip::CommissioningFlow)unconvertCommissioningFlow:(MTRCommissioningFlow)value +{ + if (value == MTRCommissioningFlowStandard) { + return chip::CommissioningFlow::kStandard; + } + if (value == MTRCommissioningFlowUserActionRequired) { + return chip::CommissioningFlow::kUserActionRequired; + } + if (value == MTRCommissioningFlowCustom) { + return chip::CommissioningFlow::kCustom; + } + // It's MTRCommissioningFlowInvalid ... now what? But in practice + // this is not called when we have MTRCommissioningFlowInvalid. + return chip::CommissioningFlow::kStandard; +} + +- (instancetype)initWithSetupPayload:(chip::SetupPayload)setupPayload { if (self = [super init]) { _chipSetupPayload = setupPayload; @@ -83,6 +122,22 @@ - (id)initWithSetupPayload:(chip::SetupPayload)setupPayload return self; } +- (instancetype)initWithSetupPasscode:(NSNumber *)setupPasscode discriminator:(NSNumber *)discriminator +{ + if (self = [super init]) { + _version = @(0); // Only supported Matter version so far. + _vendorID = @(0); // Not available. + _productID = @(0); // Not available. + _commissioningFlow = MTRCommissioningFlowStandard; + _rendezvousInformation = nil; + _hasShortDiscriminator = NO; + _discriminator = discriminator; + _setUpPINCode = setupPasscode; + _serialNumber = nil; + } + return self; +} + - (void)getSerialNumber:(chip::SetupPayload)setupPayload { std::string serialNumberC; @@ -143,6 +198,13 @@ + (NSNumber *)generateRandomSetupPasscode return @(chip::kSetupPINCodeUndefinedValue); } ++ (MTRSetupPayload * _Nullable)setupPayloadWithOnboardingPayload:(NSString *)onboardingPayload + error:(NSError * __autoreleasing *)error +{ + // TODO: Do we actually need the MTROnboardingPayloadParser abstraction? + return [MTROnboardingPayloadParser setupPayloadForOnboardingPayload:onboardingPayload error:error]; +} + #pragma mark - NSSecureCoding static NSString * const MTRSetupPayloadCodingKeyVersion = @"MTRSP.ck.version"; @@ -227,4 +289,41 @@ - (nullable NSString *)manualEntryCode return [NSString stringWithUTF8String:outDecimalString.c_str()]; } +- (NSString * _Nullable)qrCodeString +{ + if (self.commissioningFlow == MTRCommissioningFlowInvalid) { + // No idea how to map this to the standard codes. + return nil; + } + + if (self.hasShortDiscriminator) { + // Can't create a QR code with a short discriminator. + return nil; + } + + if (self.rendezvousInformation == nil) { + // Can't create a QR code if we don't know the discovery capabilities. + return nil; + } + + chip::SetupPayload payload; + + payload.version = [self.version unsignedCharValue]; + payload.vendorID = [self.vendorID unsignedShortValue]; + payload.productID = [self.productID unsignedShortValue]; + payload.commissioningFlow = [MTRSetupPayload unconvertCommissioningFlow:self.commissioningFlow]; + payload.rendezvousInformation = [MTRSetupPayload unconvertRendezvousFlags:self.rendezvousInformation]; + payload.discriminator.SetLongValue([self.discriminator unsignedShortValue]); + payload.setUpPINCode = [self.setUpPINCode unsignedIntValue]; + + std::string outDecimalString; + CHIP_ERROR err = chip::QRCodeSetupPayloadGenerator(payload).payloadBase38Representation(outDecimalString); + + if (err != CHIP_NO_ERROR) { + return nil; + } + + return [NSString stringWithUTF8String:outDecimalString.c_str()]; +} + @end diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload_Internal.h b/src/darwin/Framework/CHIP/MTRSetupPayload_Internal.h index 4abd9d9e8a5b05..1436decc816392 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload_Internal.h +++ b/src/darwin/Framework/CHIP/MTRSetupPayload_Internal.h @@ -17,7 +17,7 @@ @interface MTRSetupPayload () #ifdef __cplusplus -- (id)initWithSetupPayload:(chip::SetupPayload)setupPayload; +- (instancetype)initWithSetupPayload:(chip::SetupPayload)setupPayload; - (NSNumber *)convertRendezvousFlags:(const chip::Optional &)value; - (MTRCommissioningFlow)convertCommissioningFlow:(chip::CommissioningFlow)value; #endif diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index e2480742783907..c5a0643a6db9ff 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -42,11 +42,9 @@ static const uint16_t kCASESetupTimeoutInSeconds = 30; static const uint16_t kTimeoutInSeconds = 3; static const uint64_t kDeviceId = 0x12344321; -static const uint32_t kSetupPINCode = 20202021; -static const uint16_t kRemotePort = 5540; +static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00"; static const uint16_t kLocalPort = 5541; -static NSString * kAddress = @"::1"; -static uint16_t kTestVendorId = 0xFFF1u; +static const uint16_t kTestVendorId = 0xFFF1u; // This test suite reuses a device object to speed up the test process for CI. // The following global variable holds the reference to the device object. @@ -178,8 +176,12 @@ - (void)initStack [controller setPairingDelegate:pairing queue:callbackQueue]; NSError * error; - [controller pairDevice:kDeviceId address:kAddress port:kRemotePort setupPINCode:kSetupPINCode error:&error]; - XCTAssertEqual(error.code, 0); + __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error]; + XCTAssertNotNil(payload); + XCTAssertNil(error); + + [controller setupCommissioningSessionWithPayload:payload newNodeID:@(kDeviceId) error:&error]; + XCTAssertNil(error); [self waitForExpectationsWithTimeout:kPairingTimeoutInSeconds handler:nil]; diff --git a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m index e4ae881b3c45bb..ec592d75f20bad 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m @@ -436,10 +436,8 @@ - (void)readAttributeCacheWithController:(id _Nullable)controller static const uint16_t kCASESetupTimeoutInSeconds = 30; static const uint16_t kTimeoutInSeconds = 3; static const uint64_t kDeviceId = 0x12344321; -static const uint32_t kSetupPINCode = 20202021; -static const uint16_t kRemotePort = 5540; +static NSString * kOnboardingPayload = @"MT:-24J0AFN00KA0648G00"; static const uint16_t kLocalPort = 5541; -static NSString * kAddress = @"::1"; // This test suite reuses a device object to speed up the test process for CI. // The following global variable holds the reference to the device object. @@ -544,8 +542,12 @@ - (void)initStack [controller setPairingDelegate:pairing queue:callbackQueue]; NSError * error; - [controller pairDevice:kDeviceId address:kAddress port:kRemotePort setupPINCode:kSetupPINCode error:&error]; - XCTAssertEqual(error.code, 0); + __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error]; + XCTAssertNotNil(payload); + XCTAssertNil(error); + + [controller setupCommissioningSessionWithPayload:payload newNodeID:@(kDeviceId) error:&error]; + XCTAssertNil(error); [self waitForExpectationsWithTimeout:kPairingTimeoutInSeconds handler:nil];