diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index 8f6e387896f82b..f724c0134de616 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -132,7 +132,6 @@ jobs: timeout-minutes: 10 run: | scripts/examples/gn_build_example.sh examples/ota-requestor-app/linux out/debug chip_config_network_layer_ble=false non_spec_compliant_ota_action_delay_floor=0 chip_device_config_device_software_version=5 chip_device_config_device_software_version_string='"5.0"' - src/app/ota_image_tool.py create -v 0xFF01 -p 0x8001 -vn 5 -vs "5.0" -da sha256 out/debug/chip-ota-requestor-app /tmp/ota-test005-image cp out/debug/chip-ota-requestor-app /tmp/ota-test005-raw-image - name: Build example OTA Requestor timeout-minutes: 10 diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m index 7a7b943e739d6a..0b84c225c44c28 100644 --- a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -35,7 +35,7 @@ static const uint16_t kPairingTimeoutInSeconds = 10; static const uint16_t kTimeoutInSeconds = 3; -static const uint16_t kTimeoutWithUpdateInSeconds = 10; +static const uint16_t kTimeoutWithUpdateInSeconds = 60; 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 @@ -255,18 +255,11 @@ - (void)respondWithErrorToApplyUpdateRequestWithCompletion:(ApplyUpdateRequestCo }]; } -- (void)respondWithDiscontinueToApplyUpdateRequestWithCompletion:(ApplyUpdateRequestCompletion)completion +- (void)respondToApplyUpdateRequestWithAction:(MTROTASoftwareUpdateProviderOTAApplyUpdateAction)action + completion:(ApplyUpdateRequestCompletion)completion { __auto_type * params = [[MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams alloc] init]; - params.action = @(MTROTASoftwareUpdateProviderOTAApplyUpdateActionDiscontinue); - params.delayedActionTime = @(0); - completion(params, nil); -} - -- (void)respondWithProceedToApplyUpdateRequestWithCompletion:(ApplyUpdateRequestCompletion)completion -{ - __auto_type * params = [[MTROTASoftwareUpdateProviderClusterApplyUpdateResponseParams alloc] init]; - params.action = @(MTROTASoftwareUpdateProviderOTAApplyUpdateActionProceed); + params.action = @(action); params.delayedActionTime = @(0); completion(params, nil); } @@ -308,6 +301,172 @@ - (NSData *)generateUpdateToken static MTROTAProviderDelegateImpl * sOTAProviderDelegate; +/** + * Helper that, given a raw image, creates an image-with-header from it, drives + * a BDX transfer, etc. + */ +@interface MTROTAProviderTransferChecker : NSObject + +- (instancetype)initWithRawImagePath:(NSString *)rawImagePath + nodeID:(NSNumber *)nodeID + applyUpdateAction:(MTROTASoftwareUpdateProviderOTAApplyUpdateAction)applyUpdateAction + testcase:(XCTestCase *)testcase; + +@property (nonatomic, readonly) XCTestExpectation * queryExpectation; +@property (nonatomic, readonly) XCTestExpectation * bdxBeginExpectation; +@property (nonatomic, readonly) XCTestExpectation * bdxQueryExpectation; +@property (nonatomic, readonly) XCTestExpectation * bdxEndExpectation; +@property (nonatomic, readonly) XCTestExpectation * applyUpdateRequestExpectation; +@property (nonatomic, readonly) XCTestExpectation * notifyUpdateAppliedExpectation; +@end + +@implementation MTROTAProviderTransferChecker + +- (instancetype)initWithRawImagePath:(NSString *)rawImagePath + nodeID:(NSNumber *)nodeID + applyUpdateAction:(MTROTASoftwareUpdateProviderOTAApplyUpdateAction)applyUpdateAction + testcase:(XCTestCase *)testcase +{ + if (!(self = [super init])) { + return nil; + } + + _queryExpectation = [testcase expectationWithDescription:@"handleQueryImageForNodeID called"]; + _bdxBeginExpectation = [testcase expectationWithDescription:@"handleBDXTransferSessionBeginForNodeID called"]; + _bdxQueryExpectation = [testcase expectationWithDescription:@"handleBDXQueryForNodeID called"]; + _bdxEndExpectation = [testcase expectationWithDescription:@"handleBDXTransferSessionEndForNodeID called"]; + _applyUpdateRequestExpectation = [testcase expectationWithDescription:@"handleApplyUpdateRequestForNodeID called"]; + _notifyUpdateAppliedExpectation = [testcase expectationWithDescription:@"handleNotifyUpdateAppliedForNodeID called"]; + + NSString * imagePath = [rawImagePath stringByReplacingOccurrencesOfString:@"raw-image" withString:@"image"]; + + // Find the right absolute path to our ota_image_tool.py script. PWD should + // point to our src/darwin/Framework, while the script is in + // src/app/ota_image_tool.py. + NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; + NSString * imageToolPath = [NSString + pathWithComponents:@[ [pwd substringToIndex:(pwd.length - @"darwin/Framework".length)], @"app", @"ota_image_tool.py" ]]; + + NSTask * task = [[NSTask alloc] init]; + [task setLaunchPath:imageToolPath]; + [task setArguments:@[ + @"create", @"-v", @"0xFFF1", @"-p", @"0x8001", @"-vn", [kUpdatedSoftwareVersion stringValue], @"-vs", + kUpdatedSoftwareVersionString, @"-da", @"sha256", rawImagePath, imagePath + ]]; + NSError * launchError = nil; + [task launchAndReturnError:&launchError]; + XCTAssertNil(launchError); + [task waitUntilExit]; + XCTAssertEqual([task terminationStatus], 0); + + NSData * updateToken = [sOTAProviderDelegate generateUpdateToken]; + + __block NSFileHandle * readHandle; + __block uint64_t imageSize; + __block uint32_t lastBlockIndex = UINT32_MAX; + + sOTAProviderDelegate.queryImageHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterQueryImageParams * params, QueryImageCompletion completion) { + XCTAssertEqualObjects(nodeID, nodeID); + XCTAssertEqual(controller, sController); + + sOTAProviderDelegate.queryImageHandler = nil; + [sOTAProviderDelegate respondAvailableWithDelay:@(0) uri:imagePath updateToken:updateToken completion:completion]; + [self.queryExpectation fulfill]; + }; + sOTAProviderDelegate.transferBeginHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSString * fileDesignator, + NSNumber * offset, MTRStatusCompletion completion) { + XCTAssertEqualObjects(nodeID, nodeID); + XCTAssertEqual(controller, sController); + XCTAssertEqualObjects(fileDesignator, imagePath); + XCTAssertEqualObjects(offset, @(0)); + + readHandle = [NSFileHandle fileHandleForReadingAtPath:fileDesignator]; + XCTAssertNotNil(readHandle); + + NSError * endSeekError; + XCTAssertTrue([readHandle seekToEndReturningOffset:&imageSize error:&endSeekError]); + XCTAssertNil(endSeekError); + + sOTAProviderDelegate.transferBeginHandler = nil; + [sOTAProviderDelegate respondSuccess:completion]; + [self.bdxBeginExpectation fulfill]; + }; + sOTAProviderDelegate.blockQueryHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSNumber * blockSize, + NSNumber * blockIndex, NSNumber * bytesToSkip, BlockQueryCompletion completion) { + XCTAssertEqualObjects(nodeID, nodeID); + XCTAssertEqual(controller, sController); + XCTAssertEqualObjects(blockSize, @(1024)); // Seems to always be 1024. + XCTAssertEqualObjects(blockIndex, @(lastBlockIndex + 1)); + XCTAssertEqualObjects(bytesToSkip, @(0)); // Don't expect to see skips here. + // Make sure we actually end up with multiple blocks. + XCTAssertTrue(blockSize.unsignedLongLongValue < imageSize); + + XCTAssertNotNil(readHandle); + uint64_t offset = blockSize.unsignedLongLongValue * blockIndex.unsignedLongLongValue; + NSError * seekError = nil; + [readHandle seekToOffset:offset error:&seekError]; + XCTAssertNil(seekError); + + NSError * readError = nil; + NSData * data = [readHandle readDataUpToLength:blockSize.unsignedLongValue error:&readError]; + XCTAssertNil(readError); + XCTAssertNotNil(data); + + BOOL isEOF = offset + blockSize.unsignedLongValue >= imageSize; + + ++lastBlockIndex; + + if (isEOF) { + sOTAProviderDelegate.blockQueryHandler = nil; + } + + completion(data, isEOF); + + if (isEOF) { + [self.bdxQueryExpectation fulfill]; + } + }; + sOTAProviderDelegate.transferEndHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSError * _Nullable error) { + XCTAssertEqualObjects(nodeID, nodeID); + XCTAssertEqual(controller, sController); + XCTAssertNil(error); + + sOTAProviderDelegate.transferEndHandler = nil; + [self.bdxEndExpectation fulfill]; + }; + sOTAProviderDelegate.applyUpdateRequestHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams * params, ApplyUpdateRequestCompletion completion) { + XCTAssertEqualObjects(nodeID, nodeID); + XCTAssertEqual(controller, sController); + XCTAssertEqualObjects(params.updateToken, updateToken); + XCTAssertEqualObjects(params.newVersion, kUpdatedSoftwareVersion); // TODO: Factor this out better! + + XCTAssertTrue([[NSFileManager defaultManager] contentsEqualAtPath:rawImagePath andPath:kOtaDownloadedFilePath1]); + + sOTAProviderDelegate.applyUpdateRequestHandler = nil; + [sOTAProviderDelegate respondToApplyUpdateRequestWithAction:applyUpdateAction completion:completion]; + [self.applyUpdateRequestExpectation fulfill]; + }; + + if (applyUpdateAction == MTROTASoftwareUpdateProviderOTAApplyUpdateActionProceed) { + sOTAProviderDelegate.notifyUpdateAppliedHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, + MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * params, MTRStatusCompletion completion) { + XCTAssertEqualObjects(nodeID, nodeID); + XCTAssertEqual(controller, sController); + XCTAssertEqualObjects(params.updateToken, updateToken); + XCTAssertEqualObjects(params.softwareVersion, kUpdatedSoftwareVersion); + + sOTAProviderDelegate.notifyUpdateAppliedHandler = nil; + [sOTAProviderDelegate respondSuccess:completion]; + [self.notifyUpdateAppliedExpectation fulfill]; + }; + } + + return self; +} +@end + @interface MTROTAProviderTests : XCTestCase @end @@ -625,15 +784,6 @@ - (void)test004_DoBDXTransferDenyUpdateRequest // that the update does not actually proceed. __auto_type * device = sConnectedDevice1; - XCTestExpectation * queryExpectation = [self expectationWithDescription:@"handleQueryImageForNodeID called"]; - XCTestExpectation * bdxBeginExpectation = [self expectationWithDescription:@"handleBDXTransferSessionBeginForNodeID called"]; - XCTestExpectation * bdxQueryExpectation = [self expectationWithDescription:@"handleBDXQueryForNodeID called"]; - XCTestExpectation * bdxEndExpectation = [self expectationWithDescription:@"handleBDXTransferSessionEndForNodeID called"]; - XCTestExpectation * applyUpdateRequestExpectation = - [self expectationWithDescription:@"handleApplyUpdateRequestForNodeID called"]; - - NSData * updateToken = [sOTAProviderDelegate generateUpdateToken]; - // First, create an image. Make it at least 4096 bytes long, so we get // multiple BDX blocks going. const size_t rawImageSize = 4112; @@ -644,142 +794,37 @@ - (void)test004_DoBDXTransferDenyUpdateRequest [fakeImage appendData:rawImagePiece]; } NSString * rawImagePath = @"/tmp/ota-test004-raw-image"; - NSString * imagePath = @"/tmp/ota-test004-image"; [[NSFileManager defaultManager] createFileAtPath:rawImagePath contents:fakeImage attributes:nil]; - // Find the right absolute path to our ota_image_tool.py script. PWD should - // point to our src/darwin/Framework, while the script is in - // src/app/ota_image_tool.py. - NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; - NSString * imageToolPath = [NSString - pathWithComponents:@[ [pwd substringToIndex:(pwd.length - @"darwin/Framework".length)], @"app", @"ota_image_tool.py" ]]; - - NSTask * task = [[NSTask alloc] init]; - [task setLaunchPath:imageToolPath]; - [task setArguments:@[ - @"create", @"-v", @"0xFFF1", @"-p", @"0x8001", @"-vn", [kUpdatedSoftwareVersion stringValue], @"-vs", - kUpdatedSoftwareVersionString, @"-da", @"sha256", rawImagePath, imagePath - ]]; - NSError * launchError = nil; - [task launchAndReturnError:&launchError]; - XCTAssertNil(launchError); - [task waitUntilExit]; - XCTAssertEqual([task terminationStatus], 0); - - __block NSFileHandle * readHandle; - __block uint64_t imageSize; - __block uint32_t lastBlockIndex = UINT32_MAX; - - // TODO: Maybe we should move more of this logic into sOTAProviderDelegate - // or some other helper, once we have multiple tests sending images? For - // example, we could have something where you can do one of two things: - // - // 1) register a "raw image" with it, and it generates the - // image-with header. - // 2) register a pre-generated image with it and it uses "ota_image_tool.py - // extract" to extract the raw image. - // - // Once that's done the helper could track the transfer state for a - // particular image, etc, with us just forwarding our notifications to it. - sOTAProviderDelegate.queryImageHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, - MTROTASoftwareUpdateProviderClusterQueryImageParams * params, QueryImageCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - - sOTAProviderDelegate.queryImageHandler = nil; - [sOTAProviderDelegate respondAvailableWithDelay:@(0) uri:imagePath updateToken:updateToken completion:completion]; - [queryExpectation fulfill]; - }; - sOTAProviderDelegate.transferBeginHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSString * fileDesignator, - NSNumber * offset, MTRStatusCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(fileDesignator, imagePath); - XCTAssertEqualObjects(offset, @(0)); - - readHandle = [NSFileHandle fileHandleForReadingAtPath:fileDesignator]; - XCTAssertNotNil(readHandle); - - NSError * endSeekError; - XCTAssertTrue([readHandle seekToEndReturningOffset:&imageSize error:&endSeekError]); - XCTAssertNil(endSeekError); - - sOTAProviderDelegate.transferBeginHandler = nil; - [sOTAProviderDelegate respondSuccess:completion]; - [bdxBeginExpectation fulfill]; - }; - sOTAProviderDelegate.blockQueryHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSNumber * blockSize, - NSNumber * blockIndex, NSNumber * bytesToSkip, BlockQueryCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(blockSize, @(1024)); // Seems to always be 1024. - XCTAssertEqualObjects(blockIndex, @(lastBlockIndex + 1)); - XCTAssertEqualObjects(bytesToSkip, @(0)); // Don't expect to see skips here. - // Make sure we actually end up with multiple blocks. - XCTAssertTrue(blockSize.unsignedLongLongValue < rawImageSize); - - XCTAssertNotNil(readHandle); - uint64_t offset = blockSize.unsignedLongLongValue * blockIndex.unsignedLongLongValue; - NSError * seekError = nil; - [readHandle seekToOffset:offset error:&seekError]; - XCTAssertNil(seekError); - - NSError * readError = nil; - NSData * data = [readHandle readDataUpToLength:blockSize.unsignedLongValue error:&readError]; - XCTAssertNil(readError); - XCTAssertNotNil(data); - - BOOL isEOF = offset + blockSize.unsignedLongValue >= imageSize; - - ++lastBlockIndex; - - if (isEOF) { - sOTAProviderDelegate.blockQueryHandler = nil; - } - - completion(data, isEOF); - - if (isEOF) { - [bdxQueryExpectation fulfill]; - } - }; - sOTAProviderDelegate.transferEndHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSError * _Nullable error) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertNil(error); - - sOTAProviderDelegate.transferEndHandler = nil; - [bdxEndExpectation fulfill]; - }; - sOTAProviderDelegate.applyUpdateRequestHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, - MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams * params, ApplyUpdateRequestCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(params.updateToken, updateToken); - XCTAssertEqualObjects(params.newVersion, kUpdatedSoftwareVersion); // TODO: Factor this out better! - - XCTAssertTrue([[NSFileManager defaultManager] contentsEqualAtPath:rawImagePath andPath:kOtaDownloadedFilePath1]); - - sOTAProviderDelegate.applyUpdateRequestHandler = nil; - [sOTAProviderDelegate respondWithDiscontinueToApplyUpdateRequestWithCompletion:completion]; - [applyUpdateRequestExpectation fulfill]; - }; + __auto_type * checker = + [[MTROTAProviderTransferChecker alloc] initWithRawImagePath:rawImagePath + nodeID:@(kDeviceId1) + applyUpdateAction:MTROTASoftwareUpdateProviderOTAApplyUpdateActionDiscontinue + testcase:self]; + // We do not expect the update to actually be applied here. + checker.notifyUpdateAppliedExpectation.inverted = YES; // Advertise ourselves as an OTA provider. XCTestExpectation * announceResponseExpectation = [self announceProviderToDevice:device]; // Make sure we get our callbacks in order. Give it a bit more time, because // we want to allow time for the BDX download. - [self waitForExpectations:@[ queryExpectation, bdxBeginExpectation, bdxQueryExpectation ] + [self waitForExpectations:@[ checker.queryExpectation, checker.bdxBeginExpectation, checker.bdxQueryExpectation ] timeout:(kTimeoutWithUpdateInSeconds) enforceOrder:YES]; // Nothing really defines the ordering of bdxEndExpectation and // applyUpdateRequestExpectation with respect to each other, and nothing // defines the ordering of announceResponseExpectation with respect to _any_ // of the above expectations. - [self waitForExpectations:@[ bdxEndExpectation, applyUpdateRequestExpectation, announceResponseExpectation ] + [self waitForExpectations:@[ checker.bdxEndExpectation, checker.applyUpdateRequestExpectation, announceResponseExpectation ] timeout:kTimeoutInSeconds]; + + // We are not expecting checker.notifyUpdateAppliedExpectation to actually + // be called fulfilled, but we still need to wait on it here. Since we set + // inverted = YES, on it, this is basically a no-op, except for making + // XCTest not complain about unwaited expectations. + [self waitForExpectations:@[ checker.notifyUpdateAppliedExpectation ] timeout:kTimeoutInSeconds]; } // TODO: Enable this test when PR #26040 is merged. Currently the poll interval causes delays in the BDX transfer and @@ -799,149 +844,33 @@ - (void)test005_DoBDXTransferAllowUpdateRequest // 7) When device invokes ApplyUpdateRequest, respond with Proceed so that the update proceeds // 8) Wait for the app to restart and wait for the NotifyUpdateApplied message to confirm the app has updated to the new version - // These are the paths where the raw ota image file and the raw image file will be generated - // as a pre-requisite to running the test. + // This test expects a pre-generated raw image at otaRawImagePath. NSString * otaRawImagePath = @"/tmp/ota-test005-raw-image"; - NSString * otaImagePath = @"/tmp/ota-test005-image"; // Check if the ota raw image exists at kOtaRawImagePath XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:otaRawImagePath]); - // Check if the ota image file is created at kOtaImagePath. - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:otaImagePath]); - __auto_type * device = sConnectedDevice1; - XCTestExpectation * queryExpectation = [self expectationWithDescription:@"handleQueryImageForNodeID called"]; - XCTestExpectation * bdxBeginExpectation = [self expectationWithDescription:@"handleBDXTransferSessionBeginForNodeID called"]; - XCTestExpectation * bdxQueryExpectation = [self expectationWithDescription:@"handleBDXQueryForNodeID called"]; - XCTestExpectation * bdxEndExpectation = [self expectationWithDescription:@"handleBDXTransferSessionEndForNodeID called"]; - XCTestExpectation * applyUpdateRequestExpectation = - [self expectationWithDescription:@"handleApplyUpdateRequestForNodeID called"]; - XCTestExpectation * notifyUpdateAppliedExpectation = - [self expectationWithDescription:@"handleNotifyUpdateAppliedForNodeID called"]; - - NSData * updateToken = [sOTAProviderDelegate generateUpdateToken]; - - __block NSFileHandle * readHandle; - __block uint64_t imageSize; - __block uint32_t lastBlockIndex = UINT32_MAX; - - // TODO: Maybe we should move more of this logic into sOTAProviderDelegate - // or some other helper, once we have multiple tests sending images? For - // example, we could have something where you can do one of two things: - // - // 1) register a "raw image" with it, and it generates the - // image-with header. - // 2) register a pre-generated image with it and it uses "ota_image_tool.py - // extract" to extract the raw image. - // - // Once that's done the helper could track the transfer state for a - // particular image, etc, with us just forwarding our notifications to it. - sOTAProviderDelegate.queryImageHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, - MTROTASoftwareUpdateProviderClusterQueryImageParams * params, QueryImageCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - - sOTAProviderDelegate.queryImageHandler = nil; - [sOTAProviderDelegate respondAvailableWithDelay:@(0) uri:otaImagePath updateToken:updateToken completion:completion]; - [queryExpectation fulfill]; - }; - sOTAProviderDelegate.transferBeginHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSString * fileDesignator, - NSNumber * offset, MTRStatusCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(fileDesignator, otaImagePath); - XCTAssertEqualObjects(offset, @(0)); - - readHandle = [NSFileHandle fileHandleForReadingAtPath:fileDesignator]; - XCTAssertNotNil(readHandle); - - NSError * endSeekError; - XCTAssertTrue([readHandle seekToEndReturningOffset:&imageSize error:&endSeekError]); - XCTAssertNil(endSeekError); - - sOTAProviderDelegate.transferBeginHandler = nil; - [sOTAProviderDelegate respondSuccess:completion]; - [bdxBeginExpectation fulfill]; - }; - sOTAProviderDelegate.blockQueryHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSNumber * blockSize, - NSNumber * blockIndex, NSNumber * bytesToSkip, BlockQueryCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(blockSize, @(1024)); // Seems to always be 1024. - XCTAssertEqualObjects(blockIndex, @(lastBlockIndex + 1)); - XCTAssertEqualObjects(bytesToSkip, @(0)); // Don't expect to see skips here. - - XCTAssertNotNil(readHandle); - uint64_t offset = blockSize.unsignedLongLongValue * blockIndex.unsignedLongLongValue; - NSError * seekError = nil; - [readHandle seekToOffset:offset error:&seekError]; - XCTAssertNil(seekError); - - NSError * readError = nil; - NSData * data = [readHandle readDataUpToLength:blockSize.unsignedLongValue error:&readError]; - XCTAssertNil(readError); - XCTAssertNotNil(data); - - BOOL isEOF = offset + blockSize.unsignedLongValue >= imageSize; - - ++lastBlockIndex; - - if (isEOF) { - sOTAProviderDelegate.blockQueryHandler = nil; - } - - completion(data, isEOF); - - if (isEOF) { - [bdxQueryExpectation fulfill]; - } - }; - sOTAProviderDelegate.transferEndHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, NSError * _Nullable error) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertNil(error); - - sOTAProviderDelegate.transferEndHandler = nil; - [bdxEndExpectation fulfill]; - }; - sOTAProviderDelegate.applyUpdateRequestHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, - MTROTASoftwareUpdateProviderClusterApplyUpdateRequestParams * params, ApplyUpdateRequestCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(params.updateToken, updateToken); - XCTAssertEqualObjects(params.newVersion, kUpdatedSoftwareVersion); // TODO: Factor this out better! - - XCTAssertTrue([[NSFileManager defaultManager] contentsEqualAtPath:otaRawImagePath andPath:kOtaDownloadedFilePath1]); - - sOTAProviderDelegate.applyUpdateRequestHandler = nil; - [sOTAProviderDelegate respondWithProceedToApplyUpdateRequestWithCompletion:completion]; - [applyUpdateRequestExpectation fulfill]; - }; - sOTAProviderDelegate.notifyUpdateAppliedHandler = ^(NSNumber * nodeID, MTRDeviceController * controller, - MTROTASoftwareUpdateProviderClusterNotifyUpdateAppliedParams * params, MTRStatusCompletion completion) { - XCTAssertEqualObjects(nodeID, @(kDeviceId1)); - XCTAssertEqual(controller, sController); - XCTAssertEqualObjects(params.updateToken, updateToken); - XCTAssertEqualObjects(params.softwareVersion, kUpdatedSoftwareVersion); - - sOTAProviderDelegate.notifyUpdateAppliedHandler = nil; - [sOTAProviderDelegate respondSuccess:completion]; - [notifyUpdateAppliedExpectation fulfill]; - }; + __auto_type * checker = + [[MTROTAProviderTransferChecker alloc] initWithRawImagePath:otaRawImagePath + nodeID:@(kDeviceId1) + applyUpdateAction:MTROTASoftwareUpdateProviderOTAApplyUpdateActionProceed + testcase:self]; // Advertise ourselves as an OTA provider. XCTestExpectation * announceResponseExpectation = [self announceProviderToDevice:device]; // Make sure we get our callbacks in order. Give it a bit more time, because // we want to allow time for the BDX download. - [self waitForExpectations:@[ queryExpectation, bdxBeginExpectation, bdxQueryExpectation, bdxEndExpectation ] + [self waitForExpectations:@[ + checker.queryExpectation, checker.bdxBeginExpectation, checker.bdxQueryExpectation, checker.bdxEndExpectation + ] timeout:(kTimeoutWithUpdateInSeconds) enforceOrder:YES]; // Nothing really defines the ordering of bdxEndExpectation and // applyUpdateRequestExpectation with respect to each other. - [self waitForExpectations:@[ applyUpdateRequestExpectation, notifyUpdateAppliedExpectation ] + [self waitForExpectations:@[ checker.applyUpdateRequestExpectation, checker.notifyUpdateAppliedExpectation ] timeout:kTimeoutInSeconds enforceOrder:YES];