diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index bd8af5cd75fd1f..cce408df280b02 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -139,9 +139,12 @@ jobs: run: | mkdir -p /tmp/darwin/framework-tests ../../../out/debug/chip-all-clusters-app --interface-id -1 > >(tee /tmp/darwin/framework-tests/all-cluster-app.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-err.log >&2) & - # Make sure ota-requestor is using a different port, discriminator, and KVS from all-clusters-app. - # And a different one from the test harness too; the test harness uses port 5541. - ../../../out/debug/chip-ota-requestor-app --interface-id -1 --secured-device-port 5542 --discriminator 1111 --KVS /tmp/chip-ota-requestor-kvs > >(tee /tmp/darwin/framework-tests/ota-requestor-app.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err.log >&2) & + # Make each ota-requestor is using a different port, discriminator, and KVS from + # all-clusters-app and from other requestors. + # + # And a different port from the test harness too; the test harness uses port 5541. + ../../../out/debug/chip-ota-requestor-app --interface-id -1 --secured-device-port 5542 --discriminator 1111 --KVS /tmp/chip-ota-requestor-kvs1 > >(tee /tmp/darwin/framework-tests/ota-requestor-app-1.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err-1.log >&2) & + ../../../out/debug/chip-ota-requestor-app --interface-id -1 --secured-device-port 5543 --discriminator 1112 --KVS /tmp/chip-ota-requestor-kvs2 > >(tee /tmp/darwin/framework-tests/ota-requestor-app-2.log) 2> >(tee /tmp/darwin/framework-tests/ota-requestor-app-err-2.log >&2) & xcodebuild test -target "Matter" -scheme "Matter Framework Tests" -sdk macosx OTHER_CFLAGS='${inherited} -Werror -Wconversion -Wno-incomplete-umbrella -Wno-unguarded-availability-new' > >(tee /tmp/darwin/framework-tests/darwin-tests.log) 2> >(tee /tmp/darwin/framework-tests/darwin-tests-err.log >&2) working-directory: src/darwin/Framework - name: Uploading log files diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m index 83c1fb677bc72a..d5e00e78cac4dc 100644 --- a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -31,15 +31,18 @@ static const uint16_t kPairingTimeoutInSeconds = 10; static const uint16_t kTimeoutInSeconds = 3; -static const uint64_t kDeviceId = 0x12341234; -// NOTE: This onboarding payload is for the chip-ota-requestor-app, not chip-all-clusters-app -static NSString * kOnboardingPayload = @"MT:-24J0SO527K10648G00"; +static const uint64_t kDeviceId1 = 0x12341234; +static const uint64_t kDeviceId2 = 0x12341235; +// NOTE: These onboarding payloads are for the chip-ota-requestor-app, not chip-all-clusters-app +static NSString * kOnboardingPayload1 = @"MT:-24J0SO527K10648G00"; // Discriminator: 1111 +static NSString * kOnboardingPayload2 = @"MT:-24J0AFN00L10648G00"; // Discriminator: 1112 static const uint16_t kLocalPort = 5541; static const uint16_t kTestVendorId = 0xFFF1u; static const uint16_t kOTAProviderEndpointId = 0; -static MTRDevice * sConnectedDevice; +static MTRDevice * sConnectedDevice1; +static MTRDevice * sConnectedDevice2; // Singleton controller we use. static MTRDeviceController * sController = nil; @@ -48,15 +51,17 @@ static MTRTestKeys * sTestKeys = nil; @interface MTROTAProviderTestControllerDelegate : NSObject -@property (nonatomic, strong) XCTestExpectation * expectation; +@property (nonatomic, readonly) XCTestExpectation * expectation; +@property (nonatomic, readonly) NSNumber * commissioneeNodeID; @end @implementation MTROTAProviderTestControllerDelegate -- (id)initWithExpectation:(XCTestExpectation *)expectation +- (id)initWithExpectation:(XCTestExpectation *)expectation commissioneeNodeID:(NSNumber *)nodeID { self = [super init]; if (self) { _expectation = expectation; + _commissioneeNodeID = nodeID; } return self; } @@ -66,7 +71,7 @@ - (void)controller:(MTRDeviceController *)controller commissioningSessionEstabli XCTAssertEqual(error.code, 0); NSError * commissionError = nil; - [sController commissionNodeWithID:@(kDeviceId) + [sController commissionNodeWithID:self.commissioneeNodeID commissioningParams:[[MTRCommissioningParameters alloc] init] error:&commissionError]; XCTAssertNil(commissionError); @@ -83,38 +88,60 @@ - (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSEr @end +typedef void (^QueryImageCompletion)( + MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, NSError * _Nullable error); +typedef void (^ApplyUpdateRequestCompletion)( + MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, NSError * _Nullable error); +typedef void (^BlockQueryCompletion)(NSData * _Nullable data, BOOL isEOF); + +typedef void (^QueryImageHandler)(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterQueryImageParams * params, QueryImageCompletion completion); +typedef void (^ApplyUpdateRequestHandler)(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams * params, ApplyUpdateRequestCompletion completion); +typedef void (^NotifyUpdateAppliedHandler)(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * params, MTRStatusCompletion completion); +typedef void (^BDXTransferBeginHandler)(NSNumber * nodeID, MTRDeviceController * controller, NSString * fileDesignator, + NSNumber * offset, MTRStatusCompletion completion); +typedef void (^BDXQueryHandler)(NSNumber * nodeID, MTRDeviceController * controller, NSNumber * blockSize, NSNumber * blockIndex, + NSNumber * bytesToSkip, BlockQueryCompletion completion); +typedef void (^BDXTransferEndHandler)(NSNumber * nodeID, MTRDeviceController * controller, NSError * _Nullable error); + @interface MTROTAProviderDelegateImpl : NSObject -@property (nonatomic) XCTestExpectation * handleQueryImageExpectation; +@property (nonatomic, nullable) QueryImageHandler queryImageHandler; +@property (nonatomic, nullable) ApplyUpdateRequestHandler applyUpdateRequestHandler; +@property (nonatomic, nullable) NotifyUpdateAppliedHandler notifyUpdateAppliedHandler; +@property (nonatomic, nullable) BDXTransferBeginHandler transferBeginHandler; +@property (nonatomic, nullable) BDXQueryHandler blockQueryHandler; +@property (nonatomic, nullable) BDXTransferEndHandler transferEndHandler; @end @implementation MTROTAProviderDelegateImpl - (void)handleQueryImageForNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller params:(MTROTASoftwareUpdateProviderClusterQueryImageParams *)params - completion:(void (^)(MTROTASoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, - NSError * _Nullable error))completion + completion:(QueryImageCompletion)completion { - XCTAssertEqualObjects(nodeID, @(kDeviceId)); XCTAssertEqual(controller, sController); - // TODO: Anything we can test here about the params? - // TODO: Make it possible to configure our responses. - __auto_type * responseParams = [[MTROTASoftwareUpdateProviderClusterQueryImageResponseParams alloc] init]; - responseParams.status = @(MTROTASoftwareUpdateProviderOTAQueryStatusNotAvailable); - completion(responseParams, nil); - - if (self.handleQueryImageExpectation != nil) { - [self.handleQueryImageExpectation fulfill]; + if (self.queryImageHandler) { + self.queryImageHandler(nodeID, controller, params, completion); + } else { + [self respondNotAvailableWithCompletion:completion]; } } - (void)handleApplyUpdateRequestForNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller params:(MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams *)params - completion:(void (^)(MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, - NSError * _Nullable error))completion + completion:(ApplyUpdateRequestCompletion)completion { - completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); + XCTAssertEqual(controller, sController); + + if (self.applyUpdateRequestHandler) { + self.applyUpdateRequestHandler(nodeID, controller, params, completion); + } else { + [self respondWithErrorToApplyUpdateRequestWithCompletion:completion]; + } } - (void)handleNotifyUpdateAppliedForNodeID:(NSNumber *)nodeID @@ -122,7 +149,13 @@ - (void)handleNotifyUpdateAppliedForNodeID:(NSNumber *)nodeID params:(MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams *)params completion:(MTRStatusCompletion)completion { - completion([NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); + XCTAssertEqual(controller, sController); + + if (self.notifyUpdateAppliedHandler) { + self.notifyUpdateAppliedHandler(nodeID, controller, params, completion); + } else { + [self respondErrorWithCompletion:completion]; + } } - (void)handleBDXTransferSessionBeginForNodeID:(NSNumber *)nodeID @@ -131,7 +164,13 @@ - (void)handleBDXTransferSessionBeginForNodeID:(NSNumber *)nodeID offset:(NSNumber *)offset completion:(MTRStatusCompletion)completion { - completion([NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); + XCTAssertEqual(controller, sController); + + if (self.transferBeginHandler) { + self.transferBeginHandler(nodeID, controller, fileDesignator, offset, completion); + } else { + [self respondErrorWithCompletion:completion]; + } } - (void)handleBDXQueryForNodeID:(NSNumber *)nodeID @@ -139,15 +178,66 @@ - (void)handleBDXQueryForNodeID:(NSNumber *)nodeID blockSize:(NSNumber *)blockSize blockIndex:(NSNumber *)blockIndex bytesToSkip:(NSNumber *)bytesToSkip - completion:(void (^)(NSData * _Nullable data, BOOL isEOF))completion + completion:(BlockQueryCompletion)completion { - completion(nil, YES); + XCTAssertEqual(controller, sController); + + if (self.blockQueryHandler) { + self.blockQueryHandler(nodeID, controller, blockSize, blockIndex, bytesToSkip, completion); + } else { + completion(nil, YES); + } } - (void)handleBDXTransferSessionEndForNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller error:(NSError * _Nullable)error { + if (self.transferEndHandler) { + self.transferEndHandler(nodeID, controller, error); + } +} + +- (void)respondNotAvailableWithCompletion:(QueryImageCompletion)completion +{ + __auto_type * responseParams = [[MTROTASoftwareUpdateProviderClusterQueryImageResponseParams alloc] init]; + responseParams.status = @(MTROTASoftwareUpdateProviderOTAQueryStatusNotAvailable); + completion(responseParams, nil); +} + +- (void)respondBusyWithDelay:(NSNumber *)delay completion:(QueryImageCompletion)completion +{ + __auto_type * responseParams = [[MTROTASoftwareUpdateProviderClusterQueryImageResponseParams alloc] init]; + responseParams.status = @(MTROTASoftwareUpdateProviderOTAQueryStatusBusy); + responseParams.delayedActionTime = delay; + completion(responseParams, nil); +} + +- (void)respondWithErrorToApplyUpdateRequestWithCompletion:(ApplyUpdateRequestCompletion)completion +{ + [self respondErrorWithCompletion:^(NSError * _Nullable error) { + completion(nil, error); + }]; +} + +- (void)respondErrorWithCompletion:(MTRStatusCompletion)completion +{ + [self respondErrorWithCode:MTRErrorCodeGeneralError completion:completion]; +} + +- (void)respondErrorWithCode:(MTRErrorCode)code completion:(MTRStatusCompletion)completion +{ + [self respondError:[NSError errorWithDomain:MTRErrorDomain code:code userInfo:nil] completion:completion]; +} + +- (void)respondError:(NSError *)error completion:(MTRStatusCompletion)completion +{ + completion(error); +} + +- (void)respondSuccess:(MTRStatusCompletion)completion +{ + completion(nil); } @end @@ -173,6 +263,29 @@ - (void)tearDown [super tearDown]; } +- (MTRDevice *)commissionDeviceWithPayload:(NSString *)payloadString nodeID:(NSNumber *)nodeID +{ + XCTestExpectation * expectation = + [self expectationWithDescription:[NSString stringWithFormat:@"Commissioning Complete for %@", nodeID]]; + __auto_type * deviceControllerDelegate = [[MTROTAProviderTestControllerDelegate alloc] initWithExpectation:expectation + commissioneeNodeID:nodeID]; + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.device_controller_delegate", DISPATCH_QUEUE_SERIAL); + + [sController setDeviceControllerDelegate:deviceControllerDelegate queue:callbackQueue]; + + NSError * error; + __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:payloadString error:&error]; + XCTAssertNotNil(payload); + XCTAssertNil(error); + + [sController setupCommissioningSessionWithPayload:payload newNodeID:nodeID error:&error]; + XCTAssertNil(error); + + [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds]; + + return [MTRDevice deviceWithNodeID:nodeID controller:sController]; +} + - (void)initStack { __auto_type * factory = [MTRDeviceControllerFactory sharedInstance]; @@ -204,23 +317,8 @@ - (void)initStack sController = controller; - XCTestExpectation * expectation = [self expectationWithDescription:@"Commissioning Complete"]; - __auto_type * deviceControllerDelegate = [[MTROTAProviderTestControllerDelegate alloc] initWithExpectation:expectation]; - dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.device_controller_delegate", DISPATCH_QUEUE_SERIAL); - - [controller setDeviceControllerDelegate:deviceControllerDelegate queue:callbackQueue]; - - NSError * error; - __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error]; - XCTAssertNotNil(payload); - XCTAssertNil(error); - - [controller setupCommissioningSessionWithPayload:payload newNodeID:@(kDeviceId) error:&error]; - XCTAssertNil(error); - - [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds]; - - sConnectedDevice = [MTRDevice deviceWithNodeID:@(kDeviceId) controller:controller]; + sConnectedDevice1 = [self commissionDeviceWithPayload:kOnboardingPayload1 nodeID:@(kDeviceId1)]; + sConnectedDevice2 = [self commissionDeviceWithPayload:kOnboardingPayload2 nodeID:@(kDeviceId2)]; } - (void)shutdownStack @@ -241,20 +339,13 @@ - (void)test000_SetUp } #endif -- (void)test001_ReceiveOTAQuery +- (XCTestExpectation *)announceProviderToDevice:(MTRDevice *)device { -#if MANUAL_INDIVIDUAL_TEST - [self initStack]; -#endif - - __auto_type * device = sConnectedDevice; dispatch_queue_t queue = dispatch_get_main_queue(); - XCTestExpectation * queryExpectation = [self expectationWithDescription:@"handleQueryImageForNodeID called"]; - XCTestExpectation * responseExpectation = [self expectationWithDescription:@"AnnounceOTAProvider succeeded"]; - sOTAProviderDelegate.handleQueryImageExpectation = queryExpectation; + XCTestExpectation * responseExpectation = + [self expectationWithDescription:[NSString stringWithFormat:@"AnnounceOTAProvider to %@ succeeded", device]]; - // Advertise ourselves as an OTA provider. __auto_type * params = [[MTROTASoftwareUpdateRequestorClusterAnnounceOTAProviderParams alloc] init]; params.providerNodeID = [sController controllerNodeID]; params.vendorID = @(kTestVendorId); @@ -270,14 +361,90 @@ - (void)test001_ReceiveOTAQuery [responseExpectation fulfill]; }]; - [self waitForExpectations:@[ queryExpectation, responseExpectation ] timeout:kTimeoutInSeconds]; - sOTAProviderDelegate.handleQueryImageExpectation = nil; + return responseExpectation; +} + +- (void)test001_ReceiveOTAQuery +{ +#if MANUAL_INDIVIDUAL_TEST + [self initStack]; +#endif + + __auto_type * device = sConnectedDevice1; + + XCTestExpectation * queryExpectation = [self expectationWithDescription:@"handleQueryImageForNodeID called"]; + sOTAProviderDelegate.queryImageHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterQueryImageParams * params, QueryImageCompletion completion) { + XCTAssertEqualObjects(nodeID, @(kDeviceId1)); + XCTAssertEqual(controller, sController); + [sOTAProviderDelegate respondNotAvailableWithCompletion:completion]; + [queryExpectation fulfill]; + }; + + // Advertise ourselves as an OTA provider. + XCTestExpectation * announceResponseExpectation = [self announceProviderToDevice:device]; + + [self waitForExpectations:@[ queryExpectation, announceResponseExpectation ] timeout:kTimeoutInSeconds]; + + sOTAProviderDelegate.queryImageHandler = nil; +} + +- (void)test002_ReceiveTwoQueriesExplicitBusy +{ +#if MANUAL_INDIVIDUAL_TEST + [self initStack]; +#endif + + // TODO: This test fails so far because the OTA requestor is following the + // spec and clamping the delay to a minimum of 120 seconds. We don't really + // want to spend 2 minutes waiting in this test, so just disable it for + // now. See https://github.com/project-chip/connectedhomeip/issues/25922 + // for a proposal to address this. +#if 0 + __auto_type * device = sConnectedDevice1; + + XCTestExpectation * queryExpectation1 = [self expectationWithDescription:@"handleQueryImageForNodeID called first time"]; + XCTestExpectation * queryExpectation2 = [self expectationWithDescription:@"handleQueryImageForNodeID called second time"]; + const uint16_t busyDelay = 1; // Second + + __block QueryImageHandler handleSecondQuery; + sOTAProviderDelegate.queryImageHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, MTROTASoftwareUpdateProviderClusterQueryImageParams * params, + QueryImageCompletion completion) { + sOTAProviderDelegate.queryImageHandler = handleSecondQuery; + XCTAssertEqualObjects(nodeID, @(kDeviceId1)); + XCTAssertEqual(controller, sController); + [sOTAProviderDelegate respondBusyWithDelay:@(busyDelay) completion:completion]; + [queryExpectation1 fulfill]; + }; + + handleSecondQuery = ^(NSNumber * nodeID, MTRDeviceController * controller, MTROTASoftwareUpdateProviderClusterQueryImageParams * params, + QueryImageCompletion completion) { + XCTAssertEqualObjects(nodeID, @(kDeviceId1)); + XCTAssertEqual(controller, sController); + [sOTAProviderDelegate respondNotAvailableWithCompletion:completion]; + [queryExpectation2 fulfill]; + }; + + // Advertise ourselves as an OTA provider. + XCTestExpectation * announceResponseExpectation = [self announceProviderToDevice:device]; + + // Make sure we get our queries in order. Give it a bit more time, because + // there will be a delay between the two queries. + [self waitForExpectations:@[ queryExpectation1, queryExpectation2 ] timeout:(kTimeoutInSeconds+busyDelay) enforceOrder:YES]; + + [self waitForExpectations:@[ announceResponseExpectation ] timeout:kTimeoutInSeconds]; + + sOTAProviderDelegate.queryImageHandler = nil; +#endif } #if !MANUAL_INDIVIDUAL_TEST - (void)test999_TearDown { - __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId) controller:sController]; + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId1) controller:sController]; + ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); + + device = [MTRBaseDevice deviceWithNodeID:@(kDeviceId2) controller:sController]; ResetCommissionee(device, dispatch_get_main_queue(), self, kTimeoutInSeconds); [self shutdownStack]; }