From 8a5e2a57740de860318c1bc1b260e1b5805e8efe Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 14 Sep 2022 14:56:13 -0400 Subject: [PATCH] Address API review issues in MTRDeviceController. (#22596) * Address API review issues in MTRDeviceController. * Make MTRBaseDevice creation synchronous. This requires updates to MTRBaseDeviceOverXPC to do the possible async getting of the controller id it needs during its async operations, not when getting the device object. * Rename "pairDevice" to "setupCommissioningSessionWithPayload", fix its signature, improve documentation. * Rename "commissionDevice" to "commissionNodeWithID". * Rename "stopDevicePairing" to "cancelCommissioningForNodeID" and document. * Rename "getDeviceBeingCommissioned" to "getDeviceBeingCommissionedWithNodeID". * Various documentation improvements. * Add a way to generate a QR code from an MTRSetupPayload to allow correct recovery of long discriminators in setupCommissioningSessionWithPayload. * Fix signature of computePaseVerifier. * Fix a leak when we failed to start a controller because it wanted a fabric that does not exist, or wanted a new fabric and a matching one existed. This used to not show up in LSan before, maybe because we did not have an autoreleasepool in place. Fixes https://github.com/project-chip/connectedhomeip/issues/22533 Addresses part of https://github.com/project-chip/connectedhomeip/issues/22420 * Address review comments. --- .../commands/clusters/ModelCommandBridge.mm | 28 +- .../commands/pairing/Commands.h | 17 +- .../pairing/OpenCommissioningWindowCommand.mm | 2 +- .../commands/pairing/PairingCommandBridge.h | 10 - .../commands/pairing/PairingCommandBridge.mm | 97 +- .../commands/pairing/PairingDelegateBridge.mm | 2 +- .../commands/tests/TestCommandBridge.h | 32 +- .../tests/chiptest/lsan-mac-suppressions.txt | 11 - .../Framework Helpers/DefaultsUtils.h | 6 +- .../Framework Helpers/DefaultsUtils.m | 18 +- .../QRCode/QRCodeViewController.m | 15 +- src/darwin/Framework/CHIP/MTRBaseDevice.h | 4 +- src/darwin/Framework/CHIP/MTRBaseDevice.mm | 7 + .../Framework/CHIP/MTRBaseDevice_Internal.h | 8 + .../Framework/CHIP/MTRDeviceController.h | 104 +- .../Framework/CHIP/MTRDeviceController.mm | 153 +-- .../CHIP/MTRDeviceControllerOverXPC.m | 74 +- .../MTRDeviceControllerOverXPC_Internal.h | 5 + .../CHIP/MTRDeviceController_Internal.h | 7 + src/darwin/Framework/CHIP/MTRDeviceOverXPC.h | 8 +- src/darwin/Framework/CHIP/MTRDeviceOverXPC.m | 285 +++-- src/darwin/Framework/CHIP/MTRSetupPayload.h | 18 + src/darwin/Framework/CHIP/MTRSetupPayload.mm | 91 +- .../Framework/CHIP/MTRSetupPayload_Internal.h | 2 +- .../Framework/CHIPTests/MTRControllerTests.m | 5 - .../Framework/CHIPTests/MTRDeviceTests.m | 59 +- .../CHIPTests/MTRXPCListenerSampleTests.m | 55 +- .../Framework/CHIPTests/MTRXPCProtocolTests.m | 1128 ++++++++--------- 28 files changed, 1110 insertions(+), 1141 deletions(-) diff --git a/examples/darwin-framework-tool/commands/clusters/ModelCommandBridge.mm b/examples/darwin-framework-tool/commands/clusters/ModelCommandBridge.mm index a21e6e201a2419..97b7c09806d200 100644 --- a/examples/darwin-framework-tool/commands/clusters/ModelCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/clusters/ModelCommandBridge.mm @@ -25,31 +25,15 @@ CHIP_ERROR ModelCommand::RunCommand() { - dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip-tool.command", DISPATCH_QUEUE_SERIAL); - MTRDeviceController * commissioner = CurrentCommissioner(); ChipLogProgress(chipTool, "Sending command to node 0x" ChipLogFormatX64, ChipLogValueX64(mNodeId)); - [commissioner getBaseDevice:mNodeId - queue:callbackQueue - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - if (error != nil) { - SetCommandExitStatus(error, "Error getting connected device"); - return; - } - - CHIP_ERROR err; - if (device == nil) { - err = CHIP_ERROR_INTERNAL; - } else { - err = SendCommand(device, mEndPointId); - } + auto * device = [MTRBaseDevice deviceWithNodeID:@(mNodeId) controller:commissioner]; + CHIP_ERROR err = SendCommand(device, mEndPointId); - if (err != CHIP_NO_ERROR) { - ChipLogError(chipTool, "Error: %s", chip::ErrorStr(err)); - SetCommandExitStatus(err); - return; - } - }]; + if (err != CHIP_NO_ERROR) { + ChipLogError(chipTool, "Error: %s", chip::ErrorStr(err)); + return err; + } return CHIP_NO_ERROR; } 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/OpenCommissioningWindowCommand.mm b/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm index ca8ef3c307dbfc..621010c1d16e8b 100644 --- a/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm +++ b/examples/darwin-framework-tool/commands/pairing/OpenCommissioningWindowCommand.mm @@ -25,7 +25,7 @@ { mWorkQueue = dispatch_queue_create("com.chip.open_commissioning_window", DISPATCH_QUEUE_SERIAL); auto * controller = CurrentCommissioner(); - auto * device = [[MTRBaseDevice alloc] initWithNodeID:@(mNodeId) controller:controller]; + auto * device = [MTRBaseDevice deviceWithNodeID:@(mNodeId) controller:controller]; auto * self = this; if (mCommissioningWindowOption == 0) { 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 8e04ac33926186..a06c40a5265d35 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,75 +80,53 @@ 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() { dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip-tool.command", DISPATCH_QUEUE_SERIAL); MTRDeviceController * commissioner = CurrentCommissioner(); - [commissioner - getBaseDevice:mNodeId - queue:callbackQueue - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - CHIP_ERROR err = CHIP_NO_ERROR; - if (error) { - err = MTRErrorToCHIPErrorCode(error); - LogNSError("Error: ", error); - SetCommandExitStatus(err); - } else if (device == nil) { - ChipLogError(chipTool, "Error: %s", chip::ErrorStr(CHIP_ERROR_INTERNAL)); - SetCommandExitStatus(CHIP_ERROR_INTERNAL); - } else { - ChipLogProgress(chipTool, "Attempting to unpair device %llu", mNodeId); - MTRBaseClusterOperationalCredentials * opCredsCluster = - [[MTRBaseClusterOperationalCredentials alloc] initWithDevice:device endpoint:@(0) queue:callbackQueue]; - [opCredsCluster - readAttributeCurrentFabricIndexWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable readError) { - if (readError) { - CHIP_ERROR readErr = MTRErrorToCHIPErrorCode(readError); - LogNSError("Failed to get current fabric: ", readError); - SetCommandExitStatus(readErr); - return; - } - MTROperationalCredentialsClusterRemoveFabricParams * params = - [[MTROperationalCredentialsClusterRemoveFabricParams alloc] init]; - params.fabricIndex = value; - [opCredsCluster - removeFabricWithParams:params - completion:^(MTROperationalCredentialsClusterNOCResponseParams * _Nullable data, - NSError * _Nullable removeError) { - CHIP_ERROR removeErr = CHIP_NO_ERROR; - if (removeError) { - removeErr = MTRErrorToCHIPErrorCode(removeError); - LogNSError("Failed to remove current fabric: ", removeError); - } else { - ChipLogProgress(chipTool, "Successfully unpaired deviceId %llu", mNodeId); - } - SetCommandExitStatus(removeErr); - }]; - }]; - } - }]; + auto * device = [MTRBaseDevice deviceWithNodeID:@(mNodeId) controller:commissioner]; + + ChipLogProgress(chipTool, "Attempting to unpair device %llu", mNodeId); + MTRBaseClusterOperationalCredentials * opCredsCluster = + [[MTRBaseClusterOperationalCredentials alloc] initWithDevice:device endpoint:@(0) queue:callbackQueue]; + [opCredsCluster readAttributeCurrentFabricIndexWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable readError) { + if (readError) { + CHIP_ERROR readErr = MTRErrorToCHIPErrorCode(readError); + LogNSError("Failed to get current fabric: ", readError); + SetCommandExitStatus(readErr); + return; + } + MTROperationalCredentialsClusterRemoveFabricParams * params = + [[MTROperationalCredentialsClusterRemoveFabricParams alloc] init]; + params.fabricIndex = value; + [opCredsCluster removeFabricWithParams:params + completion:^(MTROperationalCredentialsClusterNOCResponseParams * _Nullable data, + NSError * _Nullable removeError) { + CHIP_ERROR removeErr = CHIP_NO_ERROR; + if (removeError) { + removeErr = MTRErrorToCHIPErrorCode(removeError); + LogNSError("Failed to remove current fabric: ", removeError); + } else { + ChipLogProgress(chipTool, "Successfully unpaired deviceId %llu", mNodeId); + } + SetCommandExitStatus(removeErr); + }]; + }]; } diff --git a/examples/darwin-framework-tool/commands/pairing/PairingDelegateBridge.mm b/examples/darwin-framework-tool/commands/pairing/PairingDelegateBridge.mm index b2dae799513433..da87dc349b8eff 100644 --- a/examples/darwin-framework-tool/commands/pairing/PairingDelegateBridge.mm +++ b/examples/darwin-framework-tool/commands/pairing/PairingDelegateBridge.mm @@ -54,7 +54,7 @@ - (void)onPairingComplete:(NSError *)error ChipLogProgress(chipTool, "Pairing Success"); ChipLogProgress(chipTool, "PASE establishment successful"); NSError * commissionError; - [_commissioner commissionDevice:_deviceID commissioningParams:_params error:&commissionError]; + [_commissioner commissionNodeWithID:@(_deviceID) commissioningParams:_params error:&commissionError]; if (commissionError != nil) { _commandBridge->SetCommandExitStatus(commissionError); return; diff --git a/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h b/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h index 6983787c56833f..03f6b70aae719b 100644 --- a/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h +++ b/examples/darwin-framework-tool/commands/tests/TestCommandBridge.h @@ -156,9 +156,10 @@ class TestCommandBridge : public CHIPCommandBridge, SetIdentity(identity); - // Invalidate our existing CASE session; otherwise getConnectedDevice - // will just hand it right back to us without establishing a new CASE - // session when a reboot is done on the server. + // Invalidate our existing CASE session; otherwise trying to work with + // our device will just reuse it without establishing a new CASE + // session when a reboot is done on the server, and then our next + // interaction will time out. if (value.expireExistingSession.ValueOr(true)) { if (GetDevice(identity) != nil) { [GetDevice(identity) invalidateCASESession]; @@ -166,17 +167,10 @@ class TestCommandBridge : public CHIPCommandBridge, } } - [controller getBaseDevice:value.nodeId - queue:mCallbackQueue - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - if (error != nil) { - SetCommandExitStatus(error); - return; - } - - mConnectedDevices[identity] = device; - NextTest(); - }]; + mConnectedDevices[identity] = [MTRBaseDevice deviceWithNodeID:@(value.nodeId) controller:controller]; + dispatch_async(mCallbackQueue, ^{ + NextTest(); + }); return CHIP_NO_ERROR; } @@ -197,7 +191,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; } @@ -234,7 +232,9 @@ class TestCommandBridge : public CHIPCommandBridge, VerifyOrReturn(commissioner != nil, Exit("No current commissioner")); NSError * commissionError = nil; - [commissioner commissionDevice:nodeId commissioningParams:[[MTRCommissioningParameters alloc] init] error:&commissionError]; + [commissioner commissionNodeWithID:@(nodeId) + commissioningParams:[[MTRCommissioningParameters alloc] init] + error:&commissionError]; CHIP_ERROR err = MTRErrorToCHIPErrorCode(commissionError); if (err != CHIP_NO_ERROR) { Exit("Failed to kick off commissioning", err); diff --git a/scripts/tests/chiptest/lsan-mac-suppressions.txt b/scripts/tests/chiptest/lsan-mac-suppressions.txt index 1c5c7136a1ce42..859d82cebf6e62 100644 --- a/scripts/tests/chiptest/lsan-mac-suppressions.txt +++ b/scripts/tests/chiptest/lsan-mac-suppressions.txt @@ -19,16 +19,5 @@ 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*WithParams:subscriptionEstablished:reportHandler:] diff --git a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.h b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.h index 36436bd76a0154..fbdcff4df3c79e 100644 --- a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.h +++ b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.h @@ -25,6 +25,8 @@ extern NSString * const kNetworkSSIDDefaultsKey; extern NSString * const kNetworkPasswordDefaultsKey; extern NSString * const kFabricIdKey; +typedef void (^DeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NSError * _Nullable error); + MTRDeviceController * _Nullable InitializeMTR(void); MTRDeviceController * _Nullable MTRRestartController(MTRDeviceController * controller); id _Nullable MTRGetDomainValueForKey(NSString * domain, NSString * key); @@ -36,8 +38,8 @@ uint64_t MTRGetLastPairedDeviceId(void); void MTRSetNextAvailableDeviceID(uint64_t id); void MTRSetDevicePaired(uint64_t id, BOOL paired); BOOL MTRIsDevicePaired(uint64_t id); -BOOL MTRGetConnectedDevice(MTRDeviceConnectionCallback completionHandler); -BOOL MTRGetConnectedDeviceWithID(uint64_t deviceId, MTRDeviceConnectionCallback completionHandler); +BOOL MTRGetConnectedDevice(DeviceConnectionCallback completionHandler); +BOOL MTRGetConnectedDeviceWithID(uint64_t deviceId, DeviceConnectionCallback completionHandler); void MTRUnpairDeviceWithID(uint64_t deviceId); MTRBaseDevice * _Nullable MTRGetDeviceBeingCommissioned(void); diff --git a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m index 18511c34a76976..8ecbda4a4b884e 100644 --- a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m +++ b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.m @@ -129,20 +129,21 @@ uint64_t MTRGetLastPairedDeviceId(void) return deviceId; } -BOOL MTRGetConnectedDevice(MTRDeviceConnectionCallback completionHandler) +BOOL MTRGetConnectedDevice(DeviceConnectionCallback completionHandler) { - MTRDeviceController * controller = InitializeMTR(); + InitializeMTR(); // Let's use the last device that was paired uint64_t deviceId = MTRGetLastPairedDeviceId(); - return [controller getBaseDevice:deviceId queue:dispatch_get_main_queue() completion:completionHandler]; + + return MTRGetConnectedDeviceWithID(deviceId, completionHandler); } MTRBaseDevice * MTRGetDeviceBeingCommissioned(void) { NSError * error; MTRDeviceController * controller = InitializeMTR(); - MTRBaseDevice * device = [controller getDeviceBeingCommissioned:MTRGetLastPairedDeviceId() error:&error]; + MTRBaseDevice * device = [controller deviceBeingCommissionedWithNodeID:@(MTRGetLastPairedDeviceId()) error:&error]; if (error) { NSLog(@"Error retrieving device being commissioned for deviceId %llu", MTRGetLastPairedDeviceId()); return nil; @@ -150,11 +151,16 @@ BOOL MTRGetConnectedDevice(MTRDeviceConnectionCallback completionHandler) return device; } -BOOL MTRGetConnectedDeviceWithID(uint64_t deviceId, MTRDeviceConnectionCallback completionHandler) +BOOL MTRGetConnectedDeviceWithID(uint64_t deviceId, DeviceConnectionCallback completionHandler) { MTRDeviceController * controller = InitializeMTR(); - return [controller getBaseDevice:deviceId queue:dispatch_get_main_queue() completion:completionHandler]; + // We can simplify this now that devices can be gotten sync, but for now just do the async dispatch. + dispatch_async(dispatch_get_main_queue(), ^{ + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(deviceId) controller:controller]; + completionHandler(device, nil); + }); + return YES; } BOOL MTRIsDevicePaired(uint64_t deviceId) diff --git a/src/darwin/CHIPTool/CHIPTool/View Controllers/QRCode/QRCodeViewController.m b/src/darwin/CHIPTool/CHIPTool/View Controllers/QRCode/QRCodeViewController.m index 094ae11559b273..adffc96973fafa 100644 --- a/src/darwin/CHIPTool/CHIPTool/View Controllers/QRCode/QRCodeViewController.m +++ b/src/darwin/CHIPTool/CHIPTool/View Controllers/QRCode/QRCodeViewController.m @@ -508,7 +508,7 @@ - (void)onPairingComplete:(NSError * _Nullable)error params.deviceAttestationDelegate = [[CHIPToolDeviceAttestationDelegate alloc] initWithViewController:self]; params.failSafeExpiryTimeout = @600; NSError * error; - if (![controller commissionDevice:deviceId commissioningParams:params error:&error]) { + if (![controller commissionNodeWithID:@(deviceId) commissioningParams:params error:&error]) { NSLog(@"Failed to commission Device %llu, with error %@", deviceId, error); } } @@ -678,7 +678,7 @@ - (void)commissionWithSSID:(NSString *)ssid password:(NSString *)password uint64_t deviceId = MTRGetNextAvailableDeviceID() - 1; - if (![controller commissionDevice:deviceId commissioningParams:params error:&error]) { + if (![controller commissionNodeWithID:@(deviceId) commissioningParams:params error:&error]) { NSLog(@"Failed to commission Device %llu, with error %@", deviceId, error); } } @@ -814,9 +814,18 @@ - (void)handleRendezVousDefault:(NSString *)payload // restart the Matter Stack before pairing (for reliability + testing restarts) [self _restartMatterStack]; - if ([self.chipController pairDevice:deviceID onboardingPayload:payload error:&error]) { + __auto_type * setupPayload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payload error:&error]; + if (setupPayload == nil) { + NSLog(@"Could not parse setup payload: %@", [error localizedDescription]); + return; + } + + ; + if ([self.chipController setupCommissioningSessionWithPayload:setupPayload newNodeID:@(deviceID) error:&error]) { deviceID++; MTRSetNextAvailableDeviceID(deviceID); + } else { + NSLog(@"Could not start commissioning session setup: %@", [error localizedDescription]); } } diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index 03561adea0c9fe..0f775df57ec95f 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -126,12 +126,12 @@ extern NSString * const MTRArrayValueType; + (instancetype)new NS_UNAVAILABLE; /** - * Initialize the device object with the given node id and controller. This + * Create a device object with the given node id and controller. This * will always succeed, even if there is no such node id on the controller's * fabric, but attempts to actually use the MTRBaseDevice will fail * (asynchronously) in that case. */ -- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller; ++ (instancetype)deviceWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller; /** * Subscribe to receive attribute reports for everything (all endpoints, all diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index c5626db5e54c2c..c01234a7ffd5e7 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -251,6 +251,13 @@ - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControlle return self; } ++ (instancetype)deviceWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller +{ + // Indirect through the controller to give it a chance to create an + // MTRBaseDeviceOverXPC instead of just an MTRBaseDevice. + return [controller baseDeviceForNodeID:nodeID]; +} + - (chip::DeviceProxy * _Nullable)paseDevice { return _cppPASEDevice; diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h index 1b902565727e0b..3dc8cb9bf875bf 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h @@ -59,6 +59,14 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; +/** + * Initialize the device object with the given node id and controller. This + * will always succeed, even if there is no such node id on the controller's + * fabric, but attempts to actually use the MTRBaseDevice will fail + * (asynchronously) in that case. + */ +- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller; + @end @interface MTRAttributePath () diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.h b/src/darwin/Framework/CHIP/MTRDeviceController.h index 605fd385d461b3..66486c84e621fd 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController.h @@ -23,13 +23,15 @@ NS_ASSUME_NONNULL_BEGIN -typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NSError * _Nullable error); - @class MTRCommissioningParameters; @protocol MTRDevicePairingDelegate; +@class MTRSetupPayload; @interface MTRDeviceController : NSObject +/** + * If true, the controller has not been shut down yet. + */ @property (readonly, nonatomic, getter=isRunning) BOOL running; /** @@ -39,61 +41,60 @@ typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NS @property (readonly, nonatomic, nullable) NSNumber * controllerNodeID; /** - * Start pairing for a device with the given ID, using the provided setup PIN - * to establish a PASE connection. + * 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. * - * The pairing process will proceed until a PASE session is established or an - * error occurs, then notify onPairingComplete on the MTRDevicePairingDelegate - * for this controller. That delegate is expected to call commissionDevice - * after that point if it wants to commission the device. - */ -- (BOOL)pairDevice:(uint64_t)deviceID - discriminator:(uint16_t)discriminator - setupPINCode:(uint32_t)setupPINCode - error:(NSError * __autoreleasing *)error; - -/** - * Start pairing for a device with the given ID, using the provided IP address - * and port to connect to the device and the provided setup PIN to establish a - * PASE connection. - * - * The pairing process will proceed until a PASE session is established or an - * error occurs, then notify onPairingComplete on the MTRDevicePairingDelegate - * for this controller. That delegate is expected to call commissionDevice - * after that point if it wants to commission the device. + * 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, deviceBeingCommissionedWithNodeID + * can be used to get an MTRBaseDevice and discover what sort of network + * credentials the device might need, and commissionNodeWithID can be used to + * commission the device. */ -- (BOOL)pairDevice:(uint64_t)deviceID - address:(NSString *)address - port:(uint16_t)port - setupPINCode:(uint32_t)setupPINCode - error:(NSError * __autoreleasing *)error; +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error; /** - * Start pairing for a device with the given ID and onboarding payload (QR code - * or manual setup code). The payload will be used to discover the device and - * establish a PASE connection. - * - * The pairing process will proceed until a PASE session is established or an - * error occurs, then notify onPairingComplete on the MTRDevicePairingDelegate - * for this controller. That delegate is expected to call commissionDevice - * after that point if it wants to commission the device. + * 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)pairDevice:(uint64_t)deviceID onboardingPayload:(NSString *)onboardingPayload error:(NSError * __autoreleasing *)error; -- (BOOL)commissionDevice:(uint64_t)deviceID - commissioningParams:(MTRCommissioningParameters *)commissioningParams - error:(NSError * __autoreleasing *)error; +- (BOOL)commissionNodeWithID:(NSNumber *)nodeID + commissioningParams:(MTRCommissioningParameters *)commissioningParams + error:(NSError * __autoreleasing *)error; - (BOOL)continueCommissioningDevice:(void *)device ignoreAttestationFailure:(BOOL)ignoreAttestationFailure error:(NSError * __autoreleasing *)error; -- (BOOL)stopDevicePairing:(uint64_t)deviceID 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; -- (nullable MTRBaseDevice *)getDeviceBeingCommissioned:(uint64_t)deviceID error:(NSError * __autoreleasing *)error; -- (BOOL)getBaseDevice:(uint64_t)deviceID queue:(dispatch_queue_t)queue completion:(MTRDeviceConnectionCallback)completion; +/** + * 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; /** * Controllers are created via the MTRDeviceControllerFactory object. @@ -133,19 +134,24 @@ typedef void (^MTRDeviceConnectionCallback)(MTRBaseDevice * _Nullable device, NS - (NSData * _Nullable)fetchAttestationChallengeForDeviceID:(NSNumber *)deviceID; /** - * Compute a PASE verifier and passcode ID for the desired setup pincode. + * Compute a PASE verifier for the desired setup passcode. * - * @param[in] setupPincode The desired PIN code 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 + * @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 *)computePaseVerifier:(uint32_t)setupPincode iterations:(uint32_t)iterations salt:(NSData *)salt; ++ (nullable NSData *)computePASEVerifierForSetupPasscode:(NSNumber *)setupPasscode + iterations:(NSNumber *)iterations + salt:(NSData *)salt + error:(NSError * __autoreleasing *)error; /** - * Shutdown the controller. Calls to shutdown after the first one are NO-OPs. + * 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; diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 965c5f3ddcd2cf..9b623624a65fdf 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -73,6 +73,8 @@ 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"; @interface MTRDeviceController () @@ -361,10 +363,9 @@ - (NSNumber *)controllerNodeID return nodeID; } -- (BOOL)pairDevice:(uint64_t)deviceID - discriminator:(uint16_t)discriminator - setupPINCode:(uint32_t)setupPINCode - error:(NSError * __autoreleasing *)error +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error; { VerifyOrReturnValue([self checkIsRunning:error], NO); @@ -372,67 +373,29 @@ - (BOOL)pairDevice:(uint64_t)deviceID dispatch_sync(_chipWorkQueue, ^{ VerifyOrReturn([self checkIsRunning:error]); - std::string manualPairingCode; - chip::SetupPayload payload; - payload.discriminator.SetLongValue(discriminator); - payload.setUpPINCode = setupPINCode; - - auto errorCode = chip::ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(manualPairingCode); - success = ![self checkForError:errorCode logMsg:kErrorSetupCodeGen error:error]; - VerifyOrReturn(success); - - _operationalCredentialsDelegate->SetDeviceID(deviceID); - errorCode = self.cppCommissioner->EstablishPASEConnection(deviceID, manualPairingCode.c_str()); - success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; - }); - - return success; -} - -- (BOOL)pairDevice:(uint64_t)deviceID - address:(NSString *)address - port:(uint16_t)port - setupPINCode:(uint32_t)setupPINCode - error:(NSError * __autoreleasing *)error -{ - VerifyOrReturnValue([self checkIsRunning:error], NO); - - __block BOOL success = NO; - dispatch_sync(_chipWorkQueue, ^{ - VerifyOrReturn([self checkIsRunning:error]); - - chip::Inet::IPAddress addr; - chip::Inet::IPAddress::FromString([address UTF8String], addr); - chip::Transport::PeerAddress peerAddress = chip::Transport::PeerAddress::UDP(addr, port); - - _operationalCredentialsDelegate->SetDeviceID(deviceID); + // 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 = ![MTRDeviceController checkForError:CHIP_ERROR_INVALID_ARGUMENT logMsg:kErrorSetupCodeGen error:error]; + return; + } - auto params = chip::RendezvousParameters().SetSetupPINCode(setupPINCode).SetPeerAddress(peerAddress); - auto errorCode = self.cppCommissioner->EstablishPASEConnection(deviceID, params); - success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; + chip::NodeId nodeId = [newNodeID unsignedLongLongValue]; + _operationalCredentialsDelegate->SetDeviceID(nodeId); + CHIP_ERROR errorCode = self.cppCommissioner->EstablishPASEConnection(nodeId, [pairingCode UTF8String]); + success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; }); return success; } -- (BOOL)pairDevice:(uint64_t)deviceID onboardingPayload:(NSString *)onboardingPayload error:(NSError * __autoreleasing *)error -{ - VerifyOrReturnValue([self checkIsRunning:error], NO); - - __block BOOL success = NO; - dispatch_sync(_chipWorkQueue, ^{ - VerifyOrReturn([self checkIsRunning:error]); - - _operationalCredentialsDelegate->SetDeviceID(deviceID); - auto errorCode = self.cppCommissioner->EstablishPASEConnection(deviceID, [onboardingPayload UTF8String]); - success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; - }); - return success; -} - -- (BOOL)commissionDevice:(uint64_t)deviceID - commissioningParams:(MTRCommissioningParameters *)commissioningParams - error:(NSError * __autoreleasing *)error +- (BOOL)commissionNodeWithID:(NSNumber *)nodeID + commissioningParams:(MTRCommissioningParameters *)commissioningParams + error:(NSError * __autoreleasing *)error { VerifyOrReturnValue([self checkIsRunning:error], NO); @@ -477,9 +440,10 @@ - (BOOL)commissionDevice:(uint64_t)deviceID params.SetDeviceAttestationDelegate(_deviceAttestationDelegateBridge); } - _operationalCredentialsDelegate->SetDeviceID(deviceID); - auto errorCode = self.cppCommissioner->Commission(deviceID, params); - success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; + chip::NodeId deviceId = [nodeID unsignedLongLongValue]; + _operationalCredentialsDelegate->SetDeviceID(deviceId); + auto errorCode = self.cppCommissioner->Commission(deviceId, params); + success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; }); return success; } @@ -501,12 +465,12 @@ - (BOOL)continueCommissioningDevice:(void *)device auto deviceProxy = static_cast(device); auto errorCode = self.cppCommissioner->ContinueCommissioningAfterDeviceAttestation(deviceProxy, ignoreAttestationFailure ? chip::Credentials::AttestationVerificationResult::kSuccess : lastAttestationResult); - success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; + success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorPairDevice error:error]; }); return success; } -- (BOOL)stopDevicePairing:(uint64_t)deviceID error:(NSError * __autoreleasing *)error +- (BOOL)cancelCommissioningForNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error { VerifyOrReturnValue([self checkIsRunning:error], NO); @@ -515,14 +479,13 @@ - (BOOL)stopDevicePairing:(uint64_t)deviceID error:(NSError * __autoreleasing *) VerifyOrReturn([self checkIsRunning:error]); _operationalCredentialsDelegate->ResetDeviceID(); - auto errorCode = self.cppCommissioner->StopPairing(deviceID); - success = ![self checkForError:errorCode logMsg:kErrorStopPairing error:error]; + auto errorCode = self.cppCommissioner->StopPairing([nodeID unsignedLongLongValue]); + success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorStopPairing error:error]; }); - return success; } -- (MTRBaseDevice *)getDeviceBeingCommissioned:(uint64_t)deviceID error:(NSError * __autoreleasing *)error +- (MTRBaseDevice *)deviceBeingCommissionedWithNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error { VerifyOrReturnValue([self checkIsRunning:error], nil); @@ -532,42 +495,17 @@ - (MTRBaseDevice *)getDeviceBeingCommissioned:(uint64_t)deviceID error:(NSError dispatch_sync(_chipWorkQueue, ^{ VerifyOrReturn([self checkIsRunning:error]); - auto errorCode = self->_cppCommissioner->GetDeviceBeingCommissioned(deviceID, &deviceProxy); - success = ![self checkForError:errorCode logMsg:kErrorStopPairing error:error]; + auto errorCode = self->_cppCommissioner->GetDeviceBeingCommissioned([nodeID unsignedLongLongValue], &deviceProxy); + success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorStopPairing error:error]; }); VerifyOrReturnValue(success, nil); return [[MTRBaseDevice alloc] initWithPASEDevice:deviceProxy controller:self]; } -- (BOOL)getBaseDevice:(uint64_t)deviceID queue:(dispatch_queue_t)queue completion:(MTRDeviceConnectionCallback)completion +- (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID { - 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. - return [self getSessionForNode:deviceID - completion:^(chip::Messaging::ExchangeManager * _Nullable exchangeManager, - const chip::Optional & session, NSError * _Nullable error) { - // 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 [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:self]; } - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID @@ -618,20 +556,23 @@ - (void)setNocChainIssuer:(id)nocChainIssuer queue:(dispatch_ }); } -+ (nullable NSData *)computePaseVerifier:(uint32_t)setupPincode iterations:(uint32_t)iterations salt:(NSData *)salt ++ (nullable NSData *)computePASEVerifierForSetupPasscode:(NSNumber *)setupPasscode + iterations:(NSNumber *)iterations + salt:(NSData *)salt + error:(NSError * __autoreleasing *)error { + // Spake2pVerifier::Generate takes the passcode by non-const reference for some reason. + uint32_t unboxedSetupPasscode = [setupPasscode unsignedIntValue]; chip::Spake2pVerifier verifier; - CHIP_ERROR err = verifier.Generate(iterations, AsByteSpan(salt), setupPincode); - if (err != CHIP_NO_ERROR) { - MTR_LOG_ERROR("computePaseVerifier generation failed: %s", chip::ErrorStr(err)); + CHIP_ERROR err = verifier.Generate([iterations unsignedIntValue], AsByteSpan(salt), unboxedSetupPasscode); + 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 (err != CHIP_NO_ERROR) { - MTR_LOG_ERROR("computePaseVerifier serialization failed: %s", chip::ErrorStr(err)); + if ([MTRDeviceController checkForError:err logMsg:kErrorSpake2pVerifierSerializationFailed error:error]) { return nil; } @@ -648,14 +589,14 @@ - (NSData * _Nullable)fetchAttestationChallengeForDeviceID:(NSNumber *)deviceID chip::CommissioneeDeviceProxy * deviceProxy; auto errorCode = self.cppCommissioner->GetDeviceBeingCommissioned([deviceID unsignedLongLongValue], &deviceProxy); - auto success = ![self checkForError:errorCode logMsg:kErrorGetCommissionee error:nil]; + auto success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorGetCommissionee error:nil]; VerifyOrReturn(success); uint8_t challengeBuffer[chip::Crypto::kAES_CCM128_Key_Length]; chip::ByteSpan challenge(challengeBuffer); errorCode = deviceProxy->GetAttestationChallenge(challenge); - success = ![self checkForError:errorCode logMsg:kErrorGetAttestationChallenge error:nil]; + success = ![MTRDeviceController checkForError:errorCode logMsg:kErrorGetAttestationChallenge error:nil]; VerifyOrReturn(success); attestationChallenge = AsData(challenge); @@ -696,7 +637,7 @@ - (BOOL)checkForStartError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg return YES; } -- (BOOL)checkForError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg error:(NSError * __autoreleasing *)error ++ (BOOL)checkForError:(CHIP_ERROR)errorCode logMsg:(NSString *)logMsg error:(NSError * __autoreleasing *)error { if (CHIP_NO_ERROR == errorCode) { return NO; diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m b/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m index f782c3210aa12e..52cfb2244caba1 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC.m @@ -46,65 +46,54 @@ + (MTRDeviceControllerOverXPC *)sharedControllerWithID:(id _Nullable) connectBlock:xpcConnectBlock]; } -- (BOOL)pairDevice:(uint64_t)deviceID - discriminator:(uint16_t)discriminator - setupPINCode:(uint32_t)setupPINCode - error:(NSError * __autoreleasing *)error +- (BOOL)setupCommissioningSessionWithPayload:(MTRSetupPayload *)payload + newNodeID:(NSNumber *)newNodeID + error:(NSError * __autoreleasing *)error { - MTR_LOG_ERROR("MTRDevice doesn't support pairDevice over XPC"); + 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 - address:(NSString *)address - port:(uint16_t)port - discriminator:(uint16_t)discriminator - setupPINCode:(uint32_t)setupPINCode - error:(NSError * __autoreleasing *)error +- (BOOL)commissionNodeWithID:(NSNumber *)nodeID + commissioningParams:(MTRCommissioningParameters *)commissioningParams + error:(NSError * __autoreleasing *)error; { - MTR_LOG_ERROR("MTRDevice doesn't support pairDevice over XPC"); + MTR_LOG_ERROR("MTRDeviceController doesn't support commissionNodeWithID over XPC"); + if (error != nil) { + *error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; + } return NO; } -- (BOOL)pairDevice:(uint64_t)deviceID onboardingPayload:(NSString *)onboardingPayload error:(NSError * __autoreleasing *)error +- (BOOL)cancelCommissioningForNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error { - MTR_LOG_ERROR("MTRDevice doesn't support pairDevice over XPC"); + MTR_LOG_ERROR("MTRDeviceController doesn't support cancelCommissioningForNodeID over XPC"); + if (error != nil) { + *error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; + } return NO; } -- (BOOL)commissionDevice:(uint64_t)deviceID - commissioningParams:(MTRCommissioningParameters *)commissioningParams - error:(NSError * __autoreleasing *)error +- (nullable MTRBaseDevice *)deviceBeingCommissionedWithNodeID:(NSNumber *)nodeID error:(NSError * __autoreleasing *)error { - MTR_LOG_ERROR("MTRDevice doesn't support pairDevice over XPC"); - return NO; -} - -- (void)setListenPort:(uint16_t)port -{ - MTR_LOG_ERROR("MTRDevice doesn't support setListenPort over XPC"); -} - -- (BOOL)stopDevicePairing:(uint64_t)deviceID error:(NSError * __autoreleasing *)error -{ - MTR_LOG_ERROR("MTRDevice doesn't support stopDevicePairing over XPC"); - return NO; -} - -- (nullable MTRBaseDevice *)getDeviceBeingCommissioned:(uint64_t)deviceID error:(NSError * __autoreleasing *)error -{ - MTR_LOG_ERROR("MTRDevice doesn't support getDeviceBeingCommissioned over XPC"); + MTR_LOG_ERROR("MTRDeviceController doesn't support deviceBeingCommissionedWithNodeID over XPC"); + if (error != nil) { + *error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]; + } return nil; } -- (BOOL)getBaseDevice:(uint64_t)deviceID queue:(dispatch_queue_t)queue completion:(MTRDeviceConnectionCallback)completion +- (void)fetchControllerIdWithQueue:(dispatch_queue_t)queue completion:(MTRFetchControllerIDCompletion)completion { dispatch_async(_workQueue, ^{ dispatch_group_t group = dispatch_group_create(); if (!self.controllerID) { dispatch_group_enter(group); [self.xpcConnection getProxyHandleWithCompletion:^( - dispatch_queue_t _Nonnull queue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { + dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { if (handle) { [handle.proxy getAnyDeviceControllerWithCompletion:^(id _Nullable controller, NSError * _Nullable error) { if (error) { @@ -124,16 +113,17 @@ - (BOOL)getBaseDevice:(uint64_t)deviceID queue:(dispatch_queue_t)queue completio } dispatch_group_notify(group, queue, ^{ if (self.controllerID) { - MTRDeviceOverXPC * device = [[MTRDeviceOverXPC alloc] initWithController:self.controllerID - deviceID:@(deviceID) - xpcConnection:self.xpcConnection]; - completion(device, nil); + completion(self.controllerID, nil); } else { completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); } }); }); - return YES; +} + +- (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID +{ + return [[MTRDeviceOverXPC alloc] initWithControllerOverXPC:self deviceID:nodeID xpcConnection:self.xpcConnection]; } - (instancetype)initWithControllerID:(id)controllerID diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC_Internal.h index 43d236e10b975e..d772dab879f077 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerOverXPC_Internal.h @@ -20,12 +20,17 @@ NS_ASSUME_NONNULL_BEGIN +typedef void (^MTRFetchControllerIDCompletion)(id _Nullable controllerID, NSError * _Nullable error); + @interface MTRDeviceControllerOverXPC () @property (nonatomic, readwrite, strong) id _Nullable controllerID; @property (nonatomic, readonly, strong) dispatch_queue_t workQueue; @property (nonatomic, readonly, strong) MTRDeviceControllerXPCConnection * xpcConnection; +// Guarantees that completion is called with either a non-nil controllerID or a +// non-nil error. +- (void)fetchControllerIdWithQueue:(dispatch_queue_t)queue completion:(MTRFetchControllerIDCompletion)completion; @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index f61a1b81a4eb53..6b69b4a579077e 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -140,6 +140,13 @@ NS_ASSUME_NONNULL_BEGIN - (void)asyncDispatchToMatterQueue:(void (^)(chip::Controller::DeviceCommissioner *))block errorHandler:(void (^)(NSError *))erroHandler; +/** + * Get an MTRBaseDevice for the given node id. This exists to allow subclasses + * of MTRDeviceController (e.g. MTRDeviceControllerOverXPC) to override what + * sort of MTRBaseDevice they return. + */ +- (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID; + #pragma mark - Device-specific data and SDK access // DeviceController will act as a central repository for this opaque dictionary that MTRDevice manages - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID; diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.h b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.h index 2f7915014e2782..9718c3ee34a3f7 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.h +++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.h @@ -19,6 +19,8 @@ #import "MTRCluster.h" // For MTRSubscriptionEstablishedHandler #import "MTRDeviceControllerXPCConnection.h" +@class MTRDeviceControllerOverXPC; + NS_ASSUME_NONNULL_BEGIN @interface MTRDeviceOverXPC : MTRBaseDevice @@ -26,9 +28,9 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; -- (instancetype)initWithController:(id)controller - deviceID:(NSNumber *)deviceID - xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection; +- (instancetype)initWithControllerOverXPC:(MTRDeviceControllerOverXPC *)controllerOverXPC + deviceID:(NSNumber *)deviceID + xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection; @end diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m index 0033b0f2131d77..b34c78ed747654 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m +++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m @@ -20,6 +20,7 @@ #import "MTRAttributeCacheContainer+XPC.h" #import "MTRCluster.h" #import "MTRDeviceController+XPC.h" +#import "MTRDeviceControllerOverXPC_Internal.h" #import "MTRDeviceControllerXPCConnection.h" #import "MTRError.h" #import "MTRLogging.h" @@ -28,7 +29,8 @@ @interface MTRDeviceOverXPC () -@property (nonatomic, strong, readonly) id controller; +@property (nonatomic, strong, readonly, nullable) id controllerID; +@property (nonatomic, strong, readonly) MTRDeviceControllerOverXPC * controller; @property (nonatomic, readonly) NSNumber * nodeID; @property (nonatomic, strong, readonly) MTRDeviceControllerXPCConnection * xpcConnection; @@ -36,11 +38,12 @@ @interface MTRDeviceOverXPC () @implementation MTRDeviceOverXPC -- (instancetype)initWithController:(id)controller - deviceID:(NSNumber *)deviceID - xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection +- (instancetype)initWithControllerOverXPC:(MTRDeviceControllerOverXPC *)controllerOverXPC + deviceID:(NSNumber *)deviceID + xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection { - _controller = controller; + _controllerID = controllerOverXPC.controllerID; + _controller = controllerOverXPC; _nodeID = deviceID; _xpcConnection = xpcConnection; return self; @@ -57,13 +60,14 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue { MTR_LOG_DEBUG("Subscribing all attributes... Note that attributeReportHandler, eventReportHandler, and resubscriptionScheduled " "are not supported."); - if (attributeCacheContainer) { - [attributeCacheContainer setXPCConnection:_xpcConnection controllerID:self.controller deviceID:self.nodeID]; - } - [_xpcConnection - getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { + __auto_type workBlock = ^{ + if (attributeCacheContainer) { + [attributeCacheContainer setXPCConnection:self->_xpcConnection controllerID:self.controllerID deviceID:self.nodeID]; + } + [self->_xpcConnection getProxyHandleWithCompletion:^( + dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { if (handle) { - [handle.proxy subscribeWithController:self.controller + [handle.proxy subscribeWithController:self.controllerID nodeID:self.nodeID params:[MTRDeviceController encodeXPCSubscribeParams:params] shouldCache:(attributeCacheContainer != nil) @@ -85,6 +89,23 @@ - (void)subscribeWithQueue:(dispatch_queue_t)queue }); } }]; + }; + + if (self.controllerID != nil) { + workBlock(); + } else { + [self.controller fetchControllerIdWithQueue:queue + completion:^(id _Nullable controllerID, NSError * _Nullable error) { + if (error != nil) { + // We're already running on the right queue. + errorHandler(error); + return; + } + + self->_controllerID = controllerID; + workBlock(); + }]; + } } - (void)readAttributePathWithEndpointID:(NSNumber * _Nullable)endpointID @@ -95,10 +116,11 @@ - (void)readAttributePathWithEndpointID:(NSNumber * _Nullable)endpointID completion:(MTRDeviceResponseHandler)completion { MTR_LOG_DEBUG("Reading attribute ..."); - [_xpcConnection - getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { + __auto_type workBlock = ^{ + [self->_xpcConnection getProxyHandleWithCompletion:^( + dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { if (handle) { - [handle.proxy readAttributeWithController:self.controller + [handle.proxy readAttributeWithController:self.controllerID nodeID:self.nodeID endpointID:endpointID clusterID:clusterID @@ -121,6 +143,23 @@ - (void)readAttributePathWithEndpointID:(NSNumber * _Nullable)endpointID }); } }]; + }; + + if (self.controllerID != nil) { + workBlock(); + } else { + [self.controller fetchControllerIdWithQueue:queue + completion:^(id _Nullable controllerID, NSError * _Nullable error) { + if (error != nil) { + // We're already running on the right queue. + completion(nil, error); + return; + } + + self->_controllerID = controllerID; + workBlock(); + }]; + } } - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID @@ -132,10 +171,11 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID completion:(MTRDeviceResponseHandler)completion { MTR_LOG_DEBUG("Writing attribute ..."); - [_xpcConnection - getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { + __auto_type workBlock = ^{ + [self->_xpcConnection getProxyHandleWithCompletion:^( + dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { if (handle) { - [handle.proxy writeAttributeWithController:self.controller + [handle.proxy writeAttributeWithController:self.controllerID nodeID:self.nodeID endpointID:endpointID clusterID:clusterID @@ -159,6 +199,23 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID }); } }]; + }; + + if (self.controllerID != nil) { + workBlock(); + } else { + [self.controller fetchControllerIdWithQueue:queue + completion:^(id _Nullable controllerID, NSError * _Nullable error) { + if (error != nil) { + // We're already running on the right queue. + completion(nil, error); + return; + } + + self->_controllerID = controllerID; + workBlock(); + }]; + } } - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID @@ -170,10 +227,11 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID completion:(MTRDeviceResponseHandler)completion { MTR_LOG_DEBUG("Invoking command ..."); - [_xpcConnection - getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { + __auto_type workBlock = ^{ + [self->_xpcConnection getProxyHandleWithCompletion:^( + dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { if (handle) { - [handle.proxy invokeCommandWithController:self.controller + [handle.proxy invokeCommandWithController:self.controllerID nodeID:self.nodeID endpointID:endpointID clusterID:clusterID @@ -197,6 +255,23 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID }); } }]; + }; + + if (self.controllerID != nil) { + workBlock(); + } else { + [self.controller fetchControllerIdWithQueue:queue + completion:^(id _Nullable controllerID, NSError * _Nullable error) { + if (error != nil) { + // We're already running on the right queue. + completion(nil, error); + return; + } + + self->_controllerID = controllerID; + workBlock(); + }]; + } } - (void)subscribeAttributePathWithEndpointID:(NSNumber * _Nullable)endpointID @@ -208,78 +283,118 @@ - (void)subscribeAttributePathWithEndpointID:(NSNumber * _Nullable)endpointID subscriptionEstablished:(void (^_Nullable)(void))subscriptionEstablishedHandler { MTR_LOG_DEBUG("Subscribing attribute ..."); - [_xpcConnection getProxyHandleWithCompletion:^( - dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { - if (handle) { - MTR_LOG_DEBUG("Setup report handler"); - [self.xpcConnection - registerReportHandlerWithController:self.controller - nodeID:self.nodeID - handler:^(id _Nullable values, NSError * _Nullable error) { - if (values && ![values isKindOfClass:[NSArray class]]) { - MTR_LOG_ERROR("Unsupported report format"); - return; - } - if (!values) { - MTR_LOG_DEBUG("Error report received"); - dispatch_async(queue, ^{ - reportHandler(values, error); - }); - return; - } - __auto_type decodedValues = [MTRDeviceController decodeXPCResponseValues:values]; - NSMutableArray *> * filteredValues = - [NSMutableArray arrayWithCapacity:[decodedValues count]]; - for (NSDictionary * decodedValue in decodedValues) { - MTRAttributePath * attributePath = decodedValue[MTRAttributePathKey]; - if ((endpointID == nil || [attributePath.endpoint isEqualToNumber:endpointID]) - && (clusterID == nil || [attributePath.cluster isEqualToNumber:clusterID]) - && (attributeID == nil || - [attributePath.attribute isEqualToNumber:attributeID])) { - [filteredValues addObject:decodedValue]; + __auto_type workBlock = ^{ + [self->_xpcConnection getProxyHandleWithCompletion:^( + dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { + if (handle) { + MTR_LOG_DEBUG("Setup report handler"); + [self.xpcConnection + registerReportHandlerWithController:self.controllerID + nodeID:self.nodeID + handler:^(id _Nullable values, NSError * _Nullable error) { + if (values && ![values isKindOfClass:[NSArray class]]) { + MTR_LOG_ERROR("Unsupported report format"); + return; } - } - if ([filteredValues count] > 0) { - MTR_LOG_DEBUG("Report received"); - dispatch_async(queue, ^{ - reportHandler(filteredValues, error); - }); - } + if (!values) { + MTR_LOG_DEBUG("Error report received"); + dispatch_async(queue, ^{ + reportHandler(values, error); + }); + return; + } + __auto_type decodedValues = + [MTRDeviceController decodeXPCResponseValues:values]; + NSMutableArray *> * filteredValues = + [NSMutableArray arrayWithCapacity:[decodedValues count]]; + for (NSDictionary * decodedValue in decodedValues) { + MTRAttributePath * attributePath = decodedValue[MTRAttributePathKey]; + if ((endpointID == nil || + [attributePath.endpoint isEqualToNumber:endpointID]) + && (clusterID == nil || + [attributePath.cluster isEqualToNumber:clusterID]) + && (attributeID == nil || + [attributePath.attribute isEqualToNumber:attributeID])) { + [filteredValues addObject:decodedValue]; + } + } + if ([filteredValues count] > 0) { + MTR_LOG_DEBUG("Report received"); + dispatch_async(queue, ^{ + reportHandler(filteredValues, error); + }); + } + }]; + + [handle.proxy subscribeAttributeWithController:self.controllerID + nodeID:self.nodeID + endpointID:endpointID + clusterID:clusterID + attributeID:attributeID + params:[MTRDeviceController encodeXPCSubscribeParams:params] + establishedHandler:^{ + dispatch_async(queue, ^{ + MTR_LOG_DEBUG("Subscription established"); + subscriptionEstablishedHandler(); + // The following captures the proxy handle in the closure so that the handle + // won't be released prior to block call. + __auto_type handleRetainer = handle; + (void) handleRetainer; + }); }]; - [handle.proxy subscribeAttributeWithController:self.controller - nodeID:self.nodeID - endpointID:endpointID - clusterID:clusterID - attributeID:attributeID - params:[MTRDeviceController encodeXPCSubscribeParams:params] - establishedHandler:^{ - dispatch_async(queue, ^{ - MTR_LOG_DEBUG("Subscription established"); - subscriptionEstablishedHandler(); - // The following captures the proxy handle in the closure so that the handle - // won't be released prior to block call. - __auto_type handleRetainer = handle; - (void) handleRetainer; - }); - }]; - } else { - dispatch_async(queue, ^{ - MTR_LOG_ERROR("Failed to obtain XPC connection to subscribe to attribute"); - subscriptionEstablishedHandler(); - reportHandler(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); - }); - } - }]; + } else { + dispatch_async(queue, ^{ + MTR_LOG_ERROR("Failed to obtain XPC connection to subscribe to attribute"); + subscriptionEstablishedHandler(); + reportHandler(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); + }); + } + }]; + }; + + if (self.controllerID != nil) { + workBlock(); + } else { + [self.controller fetchControllerIdWithQueue:queue + completion:^(id _Nullable controllerID, NSError * _Nullable error) { + if (error != nil) { + // We're already running on the right queue. + reportHandler(nil, error); + return; + } + + self->_controllerID = controllerID; + workBlock(); + }]; + } } - (void)deregisterReportHandlersWithQueue:(dispatch_queue_t)queue completion:(void (^)(void))completion { MTR_LOG_DEBUG("Deregistering report handlers"); - [_xpcConnection deregisterReportHandlersWithController:self.controller - nodeID:self.nodeID - completion:^{ - dispatch_async(queue, completion); - }]; + __auto_type workBlock = ^{ + [self->_xpcConnection deregisterReportHandlersWithController:self.controllerID + nodeID:self.nodeID + completion:^{ + dispatch_async(queue, completion); + }]; + }; + + if (self.controllerID != nil) { + workBlock(); + } else { + [self.controller fetchControllerIdWithQueue:queue + completion:^(id _Nullable controllerID, NSError * _Nullable error) { + if (error != nil) { + // We're already running on the right queue. + completion(); + return; + } + + self->_controllerID = controllerID; + workBlock(); + }]; + } } - (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload.h b/src/darwin/Framework/CHIP/MTRSetupPayload.h index a3351648e5e2c6..721ccdd67a5781 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload.h +++ b/src/darwin/Framework/CHIP/MTRSetupPayload.h @@ -92,9 +92,27 @@ typedef NS_ENUM(NSUInteger, MTROptionalQRCodeInfoType) { MTROptionalQRCodeInfoTy + (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. */ - (NSString * _Nullable)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; + +// A setup payload must have a passcode and a discriminator at the very least. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRSetupPayload.mm b/src/darwin/Framework/CHIP/MTRSetupPayload.mm index b607a320dea6bc..2069cd06e6ebb8 100644 --- a/src/darwin/Framework/CHIP/MTRSetupPayload.mm +++ b/src/darwin/Framework/CHIP/MTRSetupPayload.mm @@ -20,6 +20,7 @@ #import "MTROnboardingPayloadParser.h" #import "MTRSetupPayload_Internal.h" #import "setup_payload/ManualSetupPayloadGenerator.h" +#import "setup_payload/QRCodeSetupPayloadGenerator.h" #import @implementation MTROptionalQRCodeInfo @@ -52,6 +53,25 @@ - (MTRDiscoveryCapabilities)convertRendezvousFlags:(const chip::Optional)convertDiscoveryCapabilities:(MTRDiscoveryCapabilities)value +{ + if (value == MTRDiscoveryCapabilitiesUnknown) { + return chip::NullOptional; + } + + 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) { @@ -66,7 +86,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; @@ -88,6 +124,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; + _discoveryCapabilities = MTRDiscoveryCapabilitiesUnknown; + _hasShortDiscriminator = NO; + _discriminator = discriminator; + _setupPasscode = setupPasscode; + _serialNumber = nil; + } + return self; +} + - (void)getSerialNumber:(chip::SetupPayload)setupPayload { std::string serialNumberC; @@ -233,4 +285,41 @@ - (NSString * _Nullable)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.discoveryCapabilities == MTRDiscoveryCapabilitiesUnknown) { + // 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 convertDiscoveryCapabilities:self.discoveryCapabilities]; + payload.discriminator.SetLongValue([self.discriminator unsignedShortValue]); + payload.setUpPINCode = [self.setupPasscode 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 7950d029c84790..6cac43e7d5ea4b 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; - (MTRDiscoveryCapabilities)convertRendezvousFlags:(const chip::Optional &)value; - (MTRCommissioningFlow)convertCommissioningFlow:(chip::CommissioningFlow)value; #endif diff --git a/src/darwin/Framework/CHIPTests/MTRControllerTests.m b/src/darwin/Framework/CHIPTests/MTRControllerTests.m index 6f6984403b0429..f118d429ca8ac2 100644 --- a/src/darwin/Framework/CHIPTests/MTRControllerTests.m +++ b/src/darwin/Framework/CHIPTests/MTRControllerTests.m @@ -238,11 +238,6 @@ - (void)testControllerInvalidAccess [controller shutdown]; XCTAssertFalse([controller isRunning]); - XCTAssertFalse([controller getBaseDevice:1234 - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable chipDevice, NSError * _Nullable error) { - XCTAssertEqual(error.code, MTRErrorCodeInvalidState); - }]); [factory stopControllerFactory]; XCTAssertFalse([factory isRunning]); diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index abbb56442912e3..0e548f872a308a 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -39,14 +39,11 @@ #define MANUAL_INDIVIDUAL_TEST 0 static const uint16_t kPairingTimeoutInSeconds = 10; -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. @@ -55,18 +52,17 @@ // Singleton controller we use. static MTRDeviceController * sController = nil; -static void WaitForCommissionee(XCTestExpectation * expectation, dispatch_queue_t queue) +static void WaitForCommissionee(XCTestExpectation * expectation) { MTRDeviceController * controller = sController; XCTAssertNotNil(controller); - [controller getBaseDevice:kDeviceId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertEqual(error.code, 0); - [expectation fulfill]; - mConnectedDevice = device; - }]; + // For now keep the async dispatch, but could we just + // synchronously fulfill the expectation here? + dispatch_async(dispatch_get_main_queue(), ^{ + [expectation fulfill]; + mConnectedDevice = [MTRBaseDevice deviceWithNodeID:@(kDeviceId) controller:controller]; + }); } static MTRBaseDevice * GetConnectedDevice(void) @@ -100,7 +96,9 @@ - (void)onPairingComplete:(NSError *)error XCTAssertEqual(error.code, 0); NSError * commissionError = nil; - [sController commissionDevice:kDeviceId commissioningParams:[[MTRCommissioningParameters alloc] init] error:&commissionError]; + [sController commissionNodeWithID:@(kDeviceId) + commissioningParams:[[MTRCommissioningParameters alloc] init] + error:&commissionError]; XCTAssertNil(commissionError); // Keep waiting for onCommissioningComplete @@ -171,20 +169,14 @@ - (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); - [self waitForExpectationsWithTimeout:kPairingTimeoutInSeconds handler:nil]; + [controller setupCommissioningSessionWithPayload:payload newNodeID:@(kDeviceId) error:&error]; + XCTAssertNil(error); - __block XCTestExpectation * connectionExpectation = [self expectationWithDescription:@"CASE established"]; - [controller getBaseDevice:kDeviceId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertEqual(error.code, 0); - [connectionExpectation fulfill]; - connectionExpectation = nil; - }]; - [self waitForExpectationsWithTimeout:kCASESetupTimeoutInSeconds handler:nil]; + [self waitForExpectationsWithTimeout:kPairingTimeoutInSeconds handler:nil]; } - (void)shutdownStack @@ -202,8 +194,7 @@ - (void)waitForCommissionee { XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for the commissioned device to be retrieved"]; - dispatch_queue_t queue = dispatch_get_main_queue(); - WaitForCommissionee(expectation, queue); + WaitForCommissionee(expectation); [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; } @@ -1097,17 +1088,7 @@ - (void)test013_ReuseChipClusterObject MTRDeviceController * controller = sController; XCTAssertNotNil(controller); - __block MTRBaseDevice * device; - __block XCTestExpectation * connectionExpectation = [self expectationWithDescription:@"CASE established"]; - [controller getBaseDevice:kDeviceId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable retrievedDevice, NSError * _Nullable error) { - XCTAssertEqual(error.code, 0); - [connectionExpectation fulfill]; - connectionExpectation = nil; - device = retrievedDevice; - }]; - [self waitForExpectationsWithTimeout:kCASESetupTimeoutInSeconds handler:nil]; + MTRBaseDevice * device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId) controller:controller]; XCTestExpectation * expectation = [self expectationWithDescription:@"ReuseMTRClusterObjectFirstCall"]; diff --git a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m index 593035a6830b18..92ff0ec9133dfd 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m @@ -170,7 +170,7 @@ - (void)readAttributeWithController:(id)controller (void) controller; __auto_type sharedController = sController; if (sharedController) { - __auto_type device = [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:sharedController]; + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sharedController]; [device readAttributePathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID @@ -198,7 +198,7 @@ - (void)writeAttributeWithController:(id)controller (void) controller; __auto_type sharedController = sController; if (sharedController) { - __auto_type device = [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:sharedController]; + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sharedController]; [device writeAttributeWithEndpointID:endpointID clusterID:clusterID @@ -227,7 +227,7 @@ - (void)invokeCommandWithController:(id)controller (void) controller; __auto_type sharedController = sController; if (sharedController) { - __auto_type device = [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:sharedController]; + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sharedController]; [device invokeCommandWithEndpointID:endpointID clusterID:clusterID @@ -254,7 +254,7 @@ - (void)subscribeAttributeWithController:(id)controller { __auto_type sharedController = sController; if (sharedController) { - __auto_type device = [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:sharedController]; + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sharedController]; [device subscribeAttributePathWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID @@ -286,7 +286,7 @@ - (void)stopReportsWithController:(id _Nullable)controller nodeID:(NSNumber *)no { __auto_type sharedController = sController; if (sharedController) { - __auto_type device = [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:sharedController]; + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sharedController]; [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() completion:completion]; } else { NSLog(@"Failed to get shared controller"); @@ -307,7 +307,7 @@ - (void)subscribeWithController:(id _Nullable)controller attributeCacheContainer = [[MTRAttributeCacheContainer alloc] init]; } - __auto_type device = [[MTRBaseDevice alloc] initWithNodeID:nodeID controller:sharedController]; + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeID controller:sharedController]; NSMutableArray * established = [NSMutableArray arrayWithCapacity:1]; [established addObject:@NO]; [device subscribeWithQueue:dispatch_get_main_queue() @@ -367,13 +367,10 @@ - (void)readAttributeCacheWithController:(id _Nullable)controller @end static const uint16_t kPairingTimeoutInSeconds = 10; -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. @@ -405,7 +402,9 @@ - (void)onPairingComplete:(NSError *)error { XCTAssertEqual(error.code, 0); NSError * commissionError = nil; - [sController commissionDevice:kDeviceId commissioningParams:[[MTRCommissioningParameters alloc] init] error:&commissionError]; + [sController commissionNodeWithID:@(kDeviceId) + commissioningParams:[[MTRCommissioningParameters alloc] init] + error:&commissionError]; XCTAssertNil(commissionError); // Keep waiting for onCommissioningComplete @@ -480,20 +479,14 @@ - (void)initStack [controller setPairingDelegate:pairing queue:callbackQueue]; - [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); - [self waitForExpectationsWithTimeout:kPairingTimeoutInSeconds handler:nil]; + [controller setupCommissioningSessionWithPayload:payload newNodeID:@(kDeviceId) error:&error]; + XCTAssertNil(error); - __block XCTestExpectation * connectionExpectation = [self expectationWithDescription:@"CASE established"]; - [controller getBaseDevice:kDeviceId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertEqual(error.code, 0); - [connectionExpectation fulfill]; - connectionExpectation = nil; - }]; - [self waitForExpectationsWithTimeout:kCASESetupTimeoutInSeconds handler:nil]; + [self waitForExpectationsWithTimeout:kPairingTimeoutInSeconds handler:nil]; mSampleListener = [[MTRXPCListenerSample alloc] init]; [mSampleListener start]; @@ -517,9 +510,6 @@ - (void)shutdownStack - (void)waitForCommissionee { - XCTestExpectation * expectation = [self expectationWithDescription:@"Wait for the commissioned device to be retrieved"]; - - dispatch_queue_t queue = dispatch_get_main_queue(); __auto_type remoteController = [MTRDeviceController sharedControllerWithID:MTRDeviceControllerId xpcConnectBlock:^NSXPCConnection * _Nonnull { @@ -529,13 +519,7 @@ - (void)waitForCommissionee NSLog(@"Listener is not active"); return nil; }]; - [remoteController getBaseDevice:kDeviceId - queue:queue - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - mConnectedDevice = device; - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; + mConnectedDevice = [MTRBaseDevice deviceWithNodeID:@(kDeviceId) controller:remoteController]; mDeviceController = remoteController; } @@ -890,11 +874,13 @@ - (void)test008_SubscribeFailure // Set up expectation for report XCTestExpectation * errorReportExpectation = [self expectationWithDescription:@"receive OnOff attribute report"]; globalReportHandler = ^(id _Nullable values, NSError * _Nullable error) { + // Because our subscription has no existent paths, it gets an + // InvalidAction response, which is EMBER_ZCL_STATUS_MALFORMED_COMMAND. XCTAssertNil(values); // Error is copied over XPC and hence cannot use MTRErrorTestUtils utility which checks against a local domain string // object. XCTAssertTrue([error.domain isEqualToString:MTRInteractionErrorDomain]); - XCTAssertEqual(error.code, EMBER_ZCL_STATUS_UNSUPPORTED_ENDPOINT); + XCTAssertEqual(error.code, EMBER_ZCL_STATUS_MALFORMED_COMMAND); [errorReportExpectation fulfill]; }; @@ -902,6 +888,7 @@ - (void)test008_SubscribeFailure dispatch_queue_t queue = dispatch_get_main_queue(); __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(2) maxInterval:@(10)]; + params.autoResubscribe = NO; [device subscribeAttributePathWithEndpointID:@10000 clusterID:@6 attributeID:@0 diff --git a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m index 30711ebf2f8914..25a9ec9cf68fbe 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m @@ -92,49 +92,39 @@ - (void)subscribeWithDeviceController:(MTRDeviceController *)deviceController queue:queue completion:(void (^)(NSError * _Nullable error))completion { - __auto_type workQueue = dispatch_get_main_queue(); __auto_type completionHandler = ^(NSError * _Nullable error) { dispatch_async(queue, ^{ completion(error); }); }; - [deviceController - getBaseDevice:[deviceID unsignedLongLongValue] - queue:workQueue - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - if (error) { - NSLog(@"Error: Failed to get connected device (%llu) for attribute cache: %@", [deviceID unsignedLongLongValue], - error); - completionHandler(error); - return; - } - __auto_type * subscriptionParams - = (params == nil) ? [[MTRSubscribeParams alloc] initWithMinInterval:@(1) maxInterval:@(43200)] : params; - __auto_type established = [NSMutableArray arrayWithCapacity:1]; - [established addObject:@NO]; - [device subscribeWithQueue:queue - params:subscriptionParams - attributeCacheContainer:self - attributeReportHandler:^(NSArray * value) { - NSLog(@"Report received for attribute cache: %@", value); - } - eventReportHandler:nil - errorHandler:^(NSError * error) { - NSLog(@"Report error received for attribute cache: %@", error); - if (![established[0] boolValue]) { - established[0] = @YES; - completionHandler(error); - } - } - subscriptionEstablished:^() { - NSLog(@"Attribute cache subscription succeeded for device %llu", [deviceID unsignedLongLongValue]); - if (![established[0] boolValue]) { - established[0] = @YES; - completionHandler(nil); - } - } - resubscriptionScheduled:nil]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:deviceID controller:deviceController]; + + __auto_type * subscriptionParams + = (params == nil) ? [[MTRSubscribeParams alloc] initWithMinInterval:@(1) maxInterval:@(43200)] : params; + __auto_type established = [NSMutableArray arrayWithCapacity:1]; + [established addObject:@NO]; + [device subscribeWithQueue:queue + params:subscriptionParams + attributeCacheContainer:self + attributeReportHandler:^(NSArray * value) { + NSLog(@"Report received for attribute cache: %@", value); + } + eventReportHandler:nil + errorHandler:^(NSError * error) { + NSLog(@"Report error received for attribute cache: %@", error); + if (![established[0] boolValue]) { + established[0] = @YES; + completionHandler(error); + } + } + subscriptionEstablished:^() { + NSLog(@"Attribute cache subscription succeeded for device %llu", [deviceID unsignedLongLongValue]); + if (![established[0] boolValue]) { + established[0] = @YES; + completionHandler(nil); + } + } + resubscriptionScheduled:nil]; } @end @@ -361,27 +351,21 @@ - (void)testReadAttributeSuccess completion([MTRDeviceController encodeXPCResponseValues:myValues], nil); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Reading..."); - [device readAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Read value: %@", value); - XCTAssertNotNil(value); - XCTAssertNil(error); - XCTAssertTrue([myValues isEqual:value]); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Reading..."); + [device readAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Read value: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myValues isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -423,27 +407,21 @@ - (void)testReadAttributeWithParamsSuccess completion([MTRDeviceController encodeXPCResponseValues:myValues], nil); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Reading..."); - [device readAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:myParams - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Read value: %@", value); - XCTAssertNotNil(value); - XCTAssertNil(error); - XCTAssertTrue([myValues isEqual:value]); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Reading..."); + [device readAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:myParams + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Read value: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myValues isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -476,26 +454,20 @@ - (void)testReadAttributeFailure completion(nil, myError); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Reading..."); - [device readAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Read value: %@", value); - XCTAssertNil(value); - XCTAssertNotNil(error); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Reading..."); + [device readAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Read value: %@", value); + XCTAssertNil(value); + XCTAssertNotNil(error); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -535,28 +507,22 @@ - (void)testWriteAttributeSuccess completion([MTRDeviceController encodeXPCResponseValues:myResults], nil); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Writing..."); - [device writeAttributeWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - value:myValue - timedWriteTimeout:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Write response: %@", value); - XCTAssertNotNil(value); - XCTAssertNil(error); - XCTAssertTrue([myResults isEqual:value]); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Writing..."); + [device writeAttributeWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + value:myValue + timedWriteTimeout:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Write response: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myResults isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -598,28 +564,22 @@ - (void)testTimedWriteAttributeSuccess completion([MTRDeviceController encodeXPCResponseValues:myResults], nil); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Writing..."); - [device writeAttributeWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - value:myValue - timedWriteTimeout:myTimedWriteTimeout - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Write response: %@", value); - XCTAssertNotNil(value); - XCTAssertNil(error); - XCTAssertTrue([myResults isEqual:value]); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Writing..."); + [device writeAttributeWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + value:myValue + timedWriteTimeout:myTimedWriteTimeout + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Write response: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myResults isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -654,27 +614,21 @@ - (void)testWriteAttributeFailure completion(nil, myError); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Writing..."); - [device writeAttributeWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - value:myValue - timedWriteTimeout:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Write response: %@", value); - XCTAssertNil(value); - XCTAssertNotNil(error); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Writing..."); + [device writeAttributeWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + value:myValue + timedWriteTimeout:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Write response: %@", value); + XCTAssertNil(value); + XCTAssertNotNil(error); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -714,28 +668,22 @@ - (void)testInvokeCommandSuccess completion([MTRDeviceController encodeXPCResponseValues:myResults], nil); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Invoking command..."); - [device invokeCommandWithEndpointID:myEndpointId - clusterID:myClusterId - commandID:myCommandId - commandFields:myFields - timedInvokeTimeout:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Command response: %@", value); - XCTAssertNotNil(value); - XCTAssertNil(error); - XCTAssertTrue([myResults isEqual:value]); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Invoking command..."); + [device invokeCommandWithEndpointID:myEndpointId + clusterID:myClusterId + commandID:myCommandId + commandFields:myFields + timedInvokeTimeout:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Command response: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myResults isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -777,28 +725,22 @@ - (void)testTimedInvokeCommandSuccess completion([MTRDeviceController encodeXPCResponseValues:myResults], nil); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Invoking command..."); - [device invokeCommandWithEndpointID:myEndpointId - clusterID:myClusterId - commandID:myCommandId - commandFields:myFields - timedInvokeTimeout:myTimedInvokeTimeout - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Command response: %@", value); - XCTAssertNotNil(value); - XCTAssertNil(error); - XCTAssertTrue([myResults isEqual:value]); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Invoking command..."); + [device invokeCommandWithEndpointID:myEndpointId + clusterID:myClusterId + commandID:myCommandId + commandFields:myFields + timedInvokeTimeout:myTimedInvokeTimeout + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Command response: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myResults isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -836,27 +778,21 @@ - (void)testInvokeCommandFailure completion(nil, myError); }; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Invoking command..."); - [device invokeCommandWithEndpointID:myEndpointId - clusterID:myClusterId - commandID:myCommandId - commandFields:myFields - timedInvokeTimeout:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Command response: %@", value); - XCTAssertNil(value); - XCTAssertNotNil(error); - [responseExpectation fulfill]; - self.xpcDisconnectExpectation = - [self expectationWithDescription:@"XPC Disconnected"]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Invoking command..."); + [device invokeCommandWithEndpointID:myEndpointId + clusterID:myClusterId + commandID:myCommandId + commandFields:myFields + timedInvokeTimeout:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Command response: %@", value); + XCTAssertNil(value); + XCTAssertNotNil(error); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; @@ -899,30 +835,24 @@ - (void)testSubscribeAttributeSuccess _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -962,15 +892,12 @@ - (void)testSubscribeAttributeSuccess }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1016,29 +943,23 @@ - (void)testSubscribeAttributeWithParamsSuccess _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:myParams - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:myParams + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1078,15 +999,12 @@ - (void)testSubscribeAttributeWithParamsSuccess }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1127,30 +1045,24 @@ - (void)testBadlyFormattedReport _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1188,15 +1100,12 @@ - (void)testBadlyFormattedReport // Deregister report handler _xpcDisconnectExpectation.inverted = NO; - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1238,30 +1147,24 @@ - (void)testReportWithUnrelatedEndpointId _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1301,15 +1204,12 @@ - (void)testReportWithUnrelatedEndpointId }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1351,30 +1251,24 @@ - (void)testReportWithUnrelatedClusterId _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1414,15 +1308,12 @@ - (void)testReportWithUnrelatedClusterId }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1464,30 +1355,24 @@ - (void)testReportWithUnrelatedAttributeId _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1527,15 +1412,12 @@ - (void)testReportWithUnrelatedAttributeId }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1577,30 +1459,24 @@ - (void)testReportWithUnrelatedNode _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1640,15 +1516,12 @@ - (void)testReportWithUnrelatedNode }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1689,30 +1562,24 @@ - (void)testSubscribeMultiEndpoints _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:nil - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:nil + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1752,15 +1619,12 @@ - (void)testSubscribeMultiEndpoints }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1801,30 +1665,24 @@ - (void)testSubscribeMultiClusters _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:nil - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:nil + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1864,15 +1722,12 @@ - (void)testSubscribeMultiClusters }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -1913,30 +1768,24 @@ - (void)testSubscribeMultiAttributes _xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:nil - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Report value: %@", values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReport isEqual:values]); - [reportExpectation fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:nil + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Report value: %@", values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReport isEqual:values]); + [reportExpectation fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; @@ -1976,15 +1825,12 @@ - (void)testSubscribeMultiAttributes }; // Deregister report handler - [_remoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + }]; // Wait for disconnection [self waitForExpectations:@[ _xpcDisconnectExpectation, stopExpectation ] timeout:kTimeoutInSeconds]; @@ -2036,30 +1882,24 @@ - (void)testMutiSubscriptions myMaxInterval = maxIntervals[i]; callExpectation = [self expectationWithDescription:[NSString stringWithFormat:@"XPC call (%u) received", i]]; establishExpectation = [self expectationWithDescription:[NSString stringWithFormat:@"Established (%u) called", i]]; - [_remoteDeviceController - getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Subscribing..."); - __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:params - queue:dispatch_get_main_queue() - reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { - NSLog(@"Subscriber [%d] report value: %@", i, values); - XCTAssertNotNil(values); - XCTAssertNil(error); - XCTAssertTrue([myReports[i] isEqual:values]); - [reportExpectations[i] fulfill]; - } - subscriptionEstablished:^{ - [establishExpectation fulfill]; - }]; - }]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:myMinInterval maxInterval:myMaxInterval]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:params + queue:dispatch_get_main_queue() + reportHandler:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + NSLog(@"Subscriber [%d] report value: %@", i, values); + XCTAssertNotNil(values); + XCTAssertNil(error); + XCTAssertTrue([myReports[i] isEqual:values]); + [reportExpectations[i] fulfill]; + } + subscriptionEstablished:^{ + [establishExpectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, establishExpectation, nil] timeout:kTimeoutInSeconds]; } @@ -2110,16 +1950,12 @@ - (void)testMutiSubscriptions // Deregister report handler for first subscriber __auto_type deregisterExpectation = [self expectationWithDescription:@"First subscriber deregistered"]; - [_remoteDeviceController getBaseDevice:nodeToStop - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - [deregisterExpectation fulfill]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(nodeToStop) controller:_remoteDeviceController]; + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + [deregisterExpectation fulfill]; + }]; [self waitForExpectations:@[ stopExpectation, deregisterExpectation ] timeout:kTimeoutInSeconds]; @@ -2168,16 +2004,13 @@ - (void)testMutiSubscriptions // Deregister report handler for second subscriber __auto_type secondDeregisterExpectation = [self expectationWithDescription:@"Second subscriber deregistered"]; - [_remoteDeviceController getBaseDevice:nodeToStop - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - NSLog(@"Device acquired. Deregistering..."); - [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() - completion:^{ - NSLog(@"Deregistered"); - [secondDeregisterExpectation fulfill]; - }]; - }]; + device = [MTRBaseDevice deviceWithNodeID:@(nodeToStop) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Deregistering..."); + [device deregisterReportHandlersWithQueue:dispatch_get_main_queue() + completion:^{ + NSLog(@"Deregistered"); + [secondDeregisterExpectation fulfill]; + }]; // Wait for deregistration and disconnection [self waitForExpectations:@[ secondDeregisterExpectation, _xpcDisconnectExpectation, stopExpectation ] @@ -2223,6 +2056,31 @@ - (void)testAnySharedRemoteController { NSString * myUUID = [[NSUUID UUID] UUIDString]; uint64_t myNodeId = 9876543210; + NSNumber * myEndpointId = @100; + NSNumber * myClusterId = @200; + NSNumber * myAttributeId = @300; + NSArray * myValues = @[ @{ + @"attributePath" : [MTRAttributePath attributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId], + @"data" : @ { @"type" : @"SignedInteger", @"value" : @123456 } + } ]; + + XCTestExpectation * callExpectation = [self expectationWithDescription:@"XPC call received"]; + XCTestExpectation * responseExpectation = [self expectationWithDescription:@"XPC response received"]; + + _handleReadAttribute = ^(id controller, NSNumber * nodeId, NSNumber * _Nullable endpointId, NSNumber * _Nullable clusterId, + NSNumber * _Nullable attributeId, MTRReadParams * _Nullable params, + void (^completion)(id _Nullable values, NSError * _Nullable error)) { + XCTAssertTrue([controller isEqualToString:myUUID]); + XCTAssertEqual([nodeId unsignedLongLongValue], myNodeId); + XCTAssertEqual([endpointId unsignedShortValue], [myEndpointId unsignedShortValue]); + XCTAssertEqual([clusterId unsignedLongValue], [myClusterId unsignedLongValue]); + XCTAssertEqual([attributeId unsignedLongValue], [myAttributeId unsignedLongValue]); + XCTAssertNil(params); + [callExpectation fulfill]; + completion([MTRDeviceController encodeXPCResponseValues:myValues], nil); + }; __auto_type unspecifiedRemoteDeviceController = [MTRDeviceController sharedControllerWithID:nil @@ -2237,17 +2095,30 @@ - (void)testAnySharedRemoteController [anySharedRemoteControllerCallExpectation fulfill]; }; - __auto_type deviceAcquired = [self expectationWithDescription:@"Connected device was acquired"]; - [unspecifiedRemoteDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - [deviceAcquired fulfill]; - }]; - - [self waitForExpectations:[NSArray arrayWithObjects:anySharedRemoteControllerCallExpectation, deviceAcquired, nil] + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:unspecifiedRemoteDeviceController]; + // Do a read to exercise the device. + NSLog(@"Device acquired. Reading..."); + [device readAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Read value: %@", value); + XCTAssertNotNil(value); + XCTAssertNil(error); + XCTAssertTrue([myValues isEqual:value]); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; + + [self waitForExpectations:[NSArray arrayWithObjects:anySharedRemoteControllerCallExpectation, callExpectation, + responseExpectation, nil] timeout:kTimeoutInSeconds]; + + // When read is done, connection should have been released + [self waitForExpectations:[NSArray arrayWithObject:_xpcDisconnectExpectation] timeout:kTimeoutInSeconds]; + XCTAssertNil(_xpcConnection); } - (void)testSubscribeAttributeCacheSuccess @@ -2493,24 +2364,19 @@ - (void)testXPCConnectionFailure return nil; }]; - [failingDeviceController getBaseDevice:myNodeId - queue:dispatch_get_main_queue() - completion:^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { - XCTAssertNotNil(device); - XCTAssertNil(error); - NSLog(@"Device acquired. Reading..."); - [device readAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - params:nil - queue:dispatch_get_main_queue() - completion:^(id _Nullable value, NSError * _Nullable error) { - NSLog(@"Read value: %@", value); - XCTAssertNil(value); - XCTAssertNotNil(error); - [responseExpectation fulfill]; - }]; - }]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:failingDeviceController]; + NSLog(@"Device acquired. Reading..."); + [device readAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + params:nil + queue:dispatch_get_main_queue() + completion:^(id _Nullable value, NSError * _Nullable error) { + NSLog(@"Read value: %@", value); + XCTAssertNil(value); + XCTAssertNotNil(error); + [responseExpectation fulfill]; + }]; [self waitForExpectations:@[ responseExpectation ] timeout:kTimeoutInSeconds]; }