From b8934f6d158bb7dbffb6c6143395e43246b42a37 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 11 Aug 2022 01:07:36 -0400 Subject: [PATCH] Fix command path checking in Darwin NSObjectCommandCallback. Fixes https://github.com/project-chip/connectedhomeip/issues/21814 --- src/darwin/Framework/CHIP/MTRBaseDevice.mm | 8 +- .../Framework/CHIPTests/MTRDeviceTests.m | 152 +++++++++++++++--- 2 files changed, 137 insertions(+), 23 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 9b3f10521ae465..90c851720a876d 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -1039,6 +1039,7 @@ void OnResponse(app::CommandSender * apCommandSender, const app::ConcreteCommand OnErrorCallbackType mOnError; OnDoneCallbackType mOnDone; chip::ClusterId mClusterId; + // Id of the command we send. chip::CommandId mCommandId; }; @@ -1051,7 +1052,12 @@ void OnResponse(app::CommandSender * apCommandSender, const app::ConcreteCommand // // Validate that the data response we received matches what we expect in terms of its cluster and command IDs. // - VerifyOrExit(aCommandPath.mClusterId == mClusterId && aCommandPath.mCommandId == mCommandId, err = CHIP_ERROR_SCHEMA_MISMATCH); + VerifyOrExit(aCommandPath.mClusterId == mClusterId, err = CHIP_ERROR_SCHEMA_MISMATCH); + + // If aReader is null, we got a status response and the command id in the + // path should match our command id. If aReader is not null, we got a data + // response, which will have its own command id, which we don't know. + VerifyOrExit(aReader != nullptr || aCommandPath.mCommandId == mCommandId, err = CHIP_ERROR_SCHEMA_MISMATCH); if (aReader != nullptr) { err = app::DataModel::Decode(*aReader, response); diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 60e732f125524c..3c6886f2fc1996 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -551,7 +551,6 @@ - (void)test007_WriteAttributeFailure [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kTimeoutInSeconds]; } -#if 0 // Re-enable test if the crash bug in CHIP stack is fixed to handle bad command Id - (void)test008_InvokeCommandFailure { #if MANUAL_INDIVIDUAL_TEST @@ -563,34 +562,31 @@ - (void)test008_InvokeCommandFailure MTRBaseDevice * device = GetConnectedDevice(); dispatch_queue_t queue = dispatch_get_main_queue(); - NSDictionary * fields = @ { -@"type" : - @"Structure", -@"value" : - @[ -@{ @"contextTag" : @0, @"data" : @ { @"type" : @"UnsignedInteger", @"value" : @0 } }, -@{ @"contextTag" : @1, @"data" : @ { @"type" : @"UnsignedInteger", @"value" : @10 } } + NSDictionary * fields = @{ + @"type" : @"Structure", + @"value" : @[ + @{ @"contextTag" : @0, @"data" : @ { @"type" : @"UnsignedInteger", @"value" : @0 } }, + @{ @"contextTag" : @1, @"data" : @ { @"type" : @"UnsignedInteger", @"value" : @10 } } ] }; [device - invokeCommandWithEndpointId:@1 - clusterId:@8 - commandId:@40000 - commandFields:fields - timedInvokeTimeout:nil - clientQueue:queue - completion:^(id _Nullable values, NSError * _Nullable error) { - NSLog(@"invoke command: MoveToLevelWithOnOff values: %@, error: %@", values, error); - - XCTAssertNil(values); - XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND); + invokeCommandWithEndpointId:@1 + clusterId:@8 + commandId:@40000 + commandFields:fields + timedInvokeTimeout:nil + clientQueue:queue + completion:^(id _Nullable values, NSError * _Nullable error) { + NSLog(@"invoke command: MoveToLevelWithOnOff values: %@, error: %@", values, error); - [expectation fulfill]; - }]; + XCTAssertNil(values); + XCTAssertEqual([MTRErrorTestUtils errorToZCLErrorCode:error], EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND); + + [expectation fulfill]; + }]; [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:kTimeoutInSeconds]; } -#endif - (void)test009_SubscribeFailure { @@ -1048,6 +1044,118 @@ - (void)test012_SubscriptionError } #endif +- (void)test013_ReuseChipClusterObject +{ +#if MANUAL_INDIVIDUAL_TEST + [self initStack]; + [self waitForCommissionee]; +#endif + + MTRDeviceController * controller = sController; + XCTAssertNotNil(controller); + + __block MTRBaseDevice * device; + __block XCTestExpectation * connectionExpectation = [self expectationWithDescription:@"CASE established"]; + [controller getBaseDevice:kDeviceId + queue:dispatch_get_main_queue() + completionHandler:^(MTRBaseDevice * _Nullable retrievedDevice, NSError * _Nullable error) { + XCTAssertEqual(error.code, 0); + [connectionExpectation fulfill]; + connectionExpectation = nil; + device = retrievedDevice; + }]; + [self waitForExpectationsWithTimeout:kCASESetupTimeoutInSeconds handler:nil]; + + XCTestExpectation * expectation = [self expectationWithDescription:@"ReuseMTRClusterObjectFirstCall"]; + + dispatch_queue_t queue = dispatch_get_main_queue(); + MTRBaseClusterTestCluster * cluster = [[MTRBaseClusterTestCluster alloc] initWithDevice:device endpoint:1 queue:queue]; + XCTAssertNotNil(cluster); + + [cluster testWithCompletionHandler:^(NSError * err) { + NSLog(@"ReuseMTRClusterObject test Error: %@", err); + XCTAssertEqual(err.code, 0); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; + + expectation = [self expectationWithDescription:@"ReuseMTRClusterObjectSecondCall"]; + + // Reuse the MTRCluster Object for multiple times. + + [cluster testWithCompletionHandler:^(NSError * err) { + NSLog(@"ReuseMTRClusterObject test Error: %@", err); + XCTAssertEqual(err.code, 0); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; +} + +- (void)test014_InvokeCommandWithDifferentIdResponse +{ +#if MANUAL_INDIVIDUAL_TEST + [self initStack]; + [self waitForCommissionee]; +#endif + XCTestExpectation * expectation = [self expectationWithDescription:@"invoke Off command"]; + + MTRBaseDevice * device = GetConnectedDevice(); + dispatch_queue_t queue = dispatch_get_main_queue(); + + NSDictionary * fields = @{ + @"type" : @"Structure", + @"value" : @[], + }; + // KeySetReadAllIndices in the Group Key Management has id 4 and a data response with id 5 + [device + invokeCommandWithEndpointId:@0 + clusterId:@(0x003F) + commandId:@4 + commandFields:fields + timedInvokeTimeout:nil + clientQueue:queue + completion:^(id _Nullable values, NSError * _Nullable error) { + NSLog(@"invoke command: KeySetReadAllIndices values: %@, error: %@", values, error); + + XCTAssertNil(error); + + { + XCTAssertTrue([values isKindOfClass:[NSArray class]]); + NSArray * resultArray = values; + for (NSDictionary * result in resultArray) { + MTRCommandPath * path = result[MTRCommandPathKey]; + XCTAssertEqual([path.endpoint unsignedIntegerValue], 0); + XCTAssertEqual([path.cluster unsignedIntegerValue], 0x003F); + XCTAssertEqual([path.command unsignedIntegerValue], 5); + // We expect a KeySetReadAllIndicesResponse struct, + // which has context tag 0 pointing to a list with one + // item: 0 (the IPK's keyset id). + NSDictionary * expectedResult = @{ + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[ @{ + MTRContextTagKey : @0, + MTRDataKey : @ { + MTRTypeKey : MTRArrayValueType, + MTRValueKey : @[ @{ + MTRDataKey : @ { MTRTypeKey : MTRUnsignedIntegerValueType, MTRValueKey : @0 } + } ] + } + } ], + }; + XCTAssertEqualObjects(result[MTRDataKey], expectedResult); + XCTAssertNil(result[MTRErrorKey]); + } + XCTAssertEqual([resultArray count], 1); + } + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; +} + - (void)test900_SubscribeAllAttributes { #if MANUAL_INDIVIDUAL_TEST