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..2b3e2d0ff96800 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,8 @@ 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]; + NextTest(); return CHIP_NO_ERROR; } @@ -197,7 +189,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 +230,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/examples/darwin-framework-tool/main.mm b/examples/darwin-framework-tool/main.mm index 09885d08eb3178..f0aeab45f78342 100644 --- a/examples/darwin-framework-tool/main.mm +++ b/examples/darwin-framework-tool/main.mm @@ -30,13 +30,15 @@ int main(int argc, const char * argv[]) { - Commands commands; - registerCommandsPairing(commands); - registerCommandsInteractive(commands); - registerCommandsPayload(commands); - registerClusterOtaSoftwareUpdateProviderInteractive(commands); - registerCommandsStorage(commands); - registerCommandsTests(commands); - registerClusters(commands); - return commands.Run(argc, (char **) argv); + @autoreleasepool { + Commands commands; + registerCommandsPairing(commands); + registerCommandsInteractive(commands); + registerCommandsPayload(commands); + registerClusterOtaSoftwareUpdateProviderInteractive(commands); + registerCommandsStorage(commands); + registerCommandsTests(commands); + registerClusters(commands); + return commands.Run(argc, (char **) argv); + } } diff --git a/scripts/tests/chiptest/lsan-mac-suppressions.txt b/scripts/tests/chiptest/lsan-mac-suppressions.txt index 0ff9c0e4ee2acd..0de63d72b73a11 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*WithMinInterval:maxInterval:params:subscriptionEstablished:reportHandler:] diff --git a/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.h b/src/darwin/CHIPTool/CHIPTool/Framework Helpers/DefaultsUtils.h index e4f114bc4ab869..dc70e2ba649fd2 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 49578370a62f63..7a066d660b2138 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 ea98603f196f81..5db2b26773b0c3 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 3447e7351260e2..6e8fc146551d78 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 f7451454a561db..4e50fc7c06aa9a 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h @@ -53,12 +53,17 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign, readonly) chip::NodeId nodeID; -/** - * Controllers are created via the MTRControllerFactory object. - */ - (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 87dea50b3d1476..eed299d591dcde 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) BOOL isRunning; /** @@ -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 MTRControllerFactory object. @@ -133,19 +134,21 @@ 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 04c8441fd7b91a..d8eb04b4c90fa7 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 () @@ -357,10 +359,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); @@ -368,67 +369,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); @@ -473,9 +436,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; } @@ -497,12 +461,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); @@ -511,14 +475,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); @@ -528,42 +491,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 @@ -614,21 +552,21 @@ - (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)); - return nil; + 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)); - return nil; + if ([MTRDeviceController checkForError:err logMsg:kErrorSpake2pVerifierSerializationFailed error:error]) { + return nil; } return AsData(serializedBytes); @@ -644,14 +582,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); @@ -692,7 +630,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..efb6d2615ed242 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,20 @@ - (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] initWithControllerID:self.controllerID + controller: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 b6c4d01fa57b20..95219c10a5896a 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -131,6 +131,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 942429a352dd76..f71fc4bcdb1f6d 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 @@ -31,9 +33,10 @@ NS_ASSUME_NONNULL_BEGIN reportHandler:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))reportHandler subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablishedHandler NS_UNAVAILABLE; -- (instancetype)initWithController:(id)controller - deviceID:(NSNumber *)deviceID - xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection; +- (instancetype)initWithControllerID:(id _Nullable)controllerID + controller:(MTRDeviceControllerOverXPC *)controller + deviceID:(NSNumber *)deviceID + xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection; @end diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.m index f6166f93b30b30..b528340c7293e3 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,10 +38,12 @@ @interface MTRDeviceOverXPC () @implementation MTRDeviceOverXPC -- (instancetype)initWithController:(id)controller - deviceID:(NSNumber *)deviceID - xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection +- (instancetype)initWithControllerID:(id _Nullable)controllerID + controller:(MTRDeviceControllerOverXPC *)controller + deviceID:(NSNumber *)deviceID + xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection { + _controllerID = controllerID; _controller = controller; _nodeID = deviceID; _xpcConnection = xpcConnection; @@ -59,13 +63,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 minInterval:minInterval maxInterval:maxInterval @@ -89,6 +94,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 @@ -99,10 +121,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 @@ -125,6 +148,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 @@ -136,10 +176,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 @@ -163,6 +204,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 @@ -174,10 +232,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 @@ -201,6 +260,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 @@ -214,80 +290,119 @@ - (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 + minInterval:minInterval + maxInterval:maxInterval + 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 - minInterval:minInterval - maxInterval:maxInterval - 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 9a0a551ab34eb5..55b85518969652 100644 --- a/src/darwin/Framework/CHIPTests/MTRControllerTests.m +++ b/src/darwin/Framework/CHIPTests/MTRControllerTests.m @@ -250,11 +250,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 shutdown]; XCTAssertFalse([factory isRunning]); diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 3ab80284620fd9..37e0917a03c3dc 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 @@ -173,20 +171,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 @@ -204,8 +196,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]; } @@ -1106,17 +1097,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 9a3539c3b1aa91..3972842f923f26 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 @@ -256,7 +256,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 @@ -290,7 +290,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"); @@ -313,7 +313,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() @@ -375,13 +375,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. @@ -413,7 +410,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 @@ -488,20 +487,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]; mSampleListener = [[MTRXPCListenerSample alloc] init]; [mSampleListener start]; @@ -525,9 +518,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 { @@ -537,13 +527,6 @@ - (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]; mDeviceController = remoteController; } diff --git a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m index d55a52a548e6a6..3da33ca682a514 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 established = [NSMutableArray arrayWithCapacity:1]; - [established addObject:@NO]; - [device subscribeWithQueue:queue - minInterval:@(1) - maxInterval:@(43200) - params:params - 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 established = [NSMutableArray arrayWithCapacity:1]; + [established addObject:@NO]; + [device subscribeWithQueue:queue + minInterval:@(1) + maxInterval:@(43200) + params:params + 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 @@ -366,27 +356,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]; @@ -428,27 +412,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]; @@ -481,26 +459,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]; @@ -540,28 +512,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]; @@ -603,28 +569,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]; @@ -659,27 +619,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]; @@ -719,28 +673,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]; @@ -782,28 +730,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]; @@ -841,27 +783,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]; @@ -906,31 +842,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -970,15 +900,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]; @@ -1026,31 +953,25 @@ - (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 - minInterval:myMinInterval - maxInterval:myMaxInterval - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + 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]; @@ -1090,15 +1011,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]; @@ -1141,31 +1059,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1203,15 +1115,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]; @@ -1255,31 +1164,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1319,15 +1222,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]; @@ -1371,31 +1271,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1435,15 +1329,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]; @@ -1487,31 +1378,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1551,15 +1436,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]; @@ -1603,31 +1485,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1667,15 +1543,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]; @@ -1718,31 +1591,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:nil - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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:nil + clusterID:myClusterId + attributeID:myAttributeId + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1782,15 +1649,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]; @@ -1833,31 +1697,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:nil - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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:nil + attributeID:myAttributeId + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -1897,15 +1755,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]; @@ -1948,31 +1803,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:nil - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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:nil + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; @@ -2012,15 +1861,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]; @@ -2074,31 +1920,25 @@ - (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..."); - [device subscribeAttributePathWithEndpointID:myEndpointId - clusterID:myClusterId - attributeID:myAttributeId - minInterval:myMinInterval - maxInterval:myMaxInterval - params:nil - 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 * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Subscribing..."); + [device subscribeAttributePathWithEndpointID:myEndpointId + clusterID:myClusterId + attributeID:myAttributeId + minInterval:myMinInterval + maxInterval:myMaxInterval + params:nil + 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]; } @@ -2149,16 +1989,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]; @@ -2207,16 +2043,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 ] @@ -2262,6 +2095,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 @@ -2276,17 +2134,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 @@ -2537,24 +2408,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]; }