| /* |
| * |
| * Copyright (c) 2022 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "OTAProviderDelegate.h" |
| #import <Matter/Matter.h> |
| #include <fstream> |
| |
| constexpr uint8_t kUpdateTokenLen = 32; |
| |
| @interface OTAProviderDelegate () |
| @property NSString * mOTAFilePath; |
| @property NSFileHandle * mFileHandle; |
| @property NSNumber * mFileOffset; |
| @property NSNumber * mFileEndOffset; |
| @property DeviceSoftwareVersionModel * candidate; |
| @end |
| |
| @implementation OTAProviderDelegate |
| |
| - (instancetype)init |
| { |
| if (self = [super init]) { |
| _selectedCandidate = [[DeviceSoftwareVersionModel alloc] init]; |
| _userConsentState = OTAProviderUserUnknown; |
| } |
| return self; |
| } |
| |
| - (void)handleQueryImage:(MTROtaSoftwareUpdateProviderClusterQueryImageParams * _Nonnull)params |
| completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, |
| NSError * _Nullable error))completionHandler |
| { |
| NSError * error; |
| |
| auto isBDXProtocolSupported = |
| [params.protocolsSupported containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)]; |
| if (!isBDXProtocolSupported) { |
| _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusDownloadProtocolNotSupported); |
| error = |
| [[NSError alloc] initWithDomain:@"OTAProviderDomain" |
| code:MTRErrorCodeGeneralError |
| userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Protocol is not supported.", nil) }]; |
| completionHandler(_selectedCandidate, error); |
| return; |
| } |
| |
| auto hasCandidate = [self SelectOTACandidate:params.vendorId rPID:params.productId rSV:params.softwareVersion]; |
| if (!hasCandidate) { |
| NSLog(@"Unable to select OTA Image."); |
| _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable); |
| error = [[NSError alloc] |
| initWithDomain:@"OTAProviderDomain" |
| code:MTRErrorCodeInvalidState |
| userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Unable to select Candidate.", nil) }]; |
| return; |
| } |
| |
| _selectedCandidate.updateToken = [self generateUpdateToken]; |
| |
| if (params.requestorCanConsent.integerValue == 1) { |
| _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable); |
| _selectedCandidate.userConsentNeeded |
| = (_userConsentState == OTAProviderUserUnknown || _userConsentState == OTAProviderUserDenied) ? @(1) : @(0); |
| NSLog(@"User Consent Needed: %@", _selectedCandidate.userConsentNeeded); |
| completionHandler(_selectedCandidate, error); |
| return; |
| } |
| |
| NSLog(@"Requestor cannot obtain user consent. Our State: %hhu", _userConsentState); |
| switch (_userConsentState) { |
| case OTAProviderUserGranted: |
| NSLog(@"User Consent Granted"); |
| _queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable; |
| break; |
| |
| case OTAProviderUserObtaining: |
| NSLog(@"User Consent Obtaining"); |
| _queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusBusy; |
| break; |
| |
| case OTAProviderUserDenied: |
| case OTAProviderUserUnknown: |
| NSLog(@"User Consent Denied or Uknown"); |
| _queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable; |
| break; |
| } |
| _selectedCandidate.status = @(_queryImageStatus); |
| completionHandler(_selectedCandidate, error); |
| } |
| |
| - (void)handleApplyUpdateRequest:(MTROtaSoftwareUpdateProviderClusterApplyUpdateRequestParams * _Nonnull)params |
| completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, |
| NSError * _Nullable error))completionHandler |
| { |
| MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * applyUpdateResponseParams = |
| [[MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams alloc] init]; |
| applyUpdateResponseParams.action = @(MTROtaSoftwareUpdateProviderOTAApplyUpdateActionProceed); |
| completionHandler(applyUpdateResponseParams, nil); |
| } |
| |
| - (void)handleNotifyUpdateApplied:(MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams * _Nonnull)params |
| completionHandler:(StatusCompletion _Nonnull)completionHandler |
| { |
| completionHandler(nil); |
| } |
| |
| - (void)handleBDXTransferSessionBegin:(NSString * _Nonnull)fileDesignator |
| offset:(NSNumber * _Nonnull)offset |
| completionHandler:(void (^)(NSError * error))completionHandler |
| { |
| NSLog(@"BDX TransferSession begin with %@ (offset: %@)", fileDesignator, offset); |
| |
| auto * handle = [NSFileHandle fileHandleForReadingAtPath:fileDesignator]; |
| if (handle == nil) { |
| auto errorString = [NSString stringWithFormat:@"Error accessing file at at %@", fileDesignator]; |
| auto error = [[NSError alloc] initWithDomain:@"OTAProviderDomain" |
| code:MTRErrorCodeGeneralError |
| userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(errorString, nil) }]; |
| completionHandler(error); |
| return; |
| } |
| |
| NSError * seekError = nil; |
| [handle seekToOffset:[offset unsignedLongValue] error:&seekError]; |
| if (seekError != nil) { |
| auto errorString = [NSString stringWithFormat:@"Error seeking file (%@) to offset %@", fileDesignator, offset]; |
| auto error = [[NSError alloc] initWithDomain:@"OTAProviderDomain" |
| code:MTRErrorCodeGeneralError |
| userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(errorString, nil) }]; |
| completionHandler(error); |
| return; |
| } |
| |
| uint64_t endOffset; |
| if (![handle seekToEndReturningOffset:&endOffset error:&seekError]) { |
| auto errorString = [NSString stringWithFormat:@"Error seeking file (%@) to end offset", fileDesignator]; |
| auto error = [[NSError alloc] initWithDomain:@"OTAProviderDomain" |
| code:MTRErrorCodeGeneralError |
| userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(errorString, nil) }]; |
| completionHandler(error); |
| return; |
| } |
| |
| _mFileHandle = handle; |
| _mFileOffset = offset; |
| _mFileEndOffset = @(endOffset); |
| completionHandler(nil); |
| } |
| |
| - (void)handleBDXTransferSessionEnd:(NSError * _Nullable)error |
| { |
| NSLog(@"BDX TransferSession end with error: %@", error); |
| _mFileHandle = nil; |
| _mFileOffset = nil; |
| _mFileEndOffset = nil; |
| } |
| |
| - (void)handleBDXQuery:(NSNumber * _Nonnull)blockSize |
| blockIndex:(NSNumber * _Nonnull)blockIndex |
| bytesToSkip:(NSNumber * _Nonnull)bytesToSkip |
| completionHandler:(void (^)(NSData * _Nullable data, BOOL isEOF))completionHandler |
| { |
| NSLog(@"BDX Query received blockSize: %@, blockIndex: %@", blockSize, blockIndex); |
| |
| NSError * error = nil; |
| auto offset = [_mFileOffset unsignedLongValue] + [bytesToSkip unsignedLongLongValue] |
| + ([blockSize unsignedLongValue] * [blockIndex unsignedLongValue]); |
| [_mFileHandle seekToOffset:offset error:&error]; |
| if (error != nil) { |
| NSLog(@"Error seeking to offset %@", @(offset)); |
| completionHandler(nil, NO); |
| return; |
| } |
| |
| NSData * data = [_mFileHandle readDataUpToLength:[blockSize unsignedLongValue] error:&error]; |
| if (error != nil) { |
| NSLog(@"Error reading file %@", _mFileHandle); |
| completionHandler(nil, NO); |
| return; |
| } |
| |
| BOOL isEOF = offset + [blockSize unsignedLongValue] >= [_mFileEndOffset unsignedLongLongValue]; |
| completionHandler(data, isEOF); |
| } |
| |
| - (void)SetOTAFilePath:(const char *)path |
| { |
| _mOTAFilePath = [NSString stringWithUTF8String:path]; |
| } |
| |
| - (NSData *)generateUpdateToken |
| { |
| NSMutableData * updateTokenData = [NSMutableData dataWithCapacity:kUpdateTokenLen]; |
| for (unsigned int i = 0; i < kUpdateTokenLen / 4; ++i) { |
| u_int32_t randomBits = arc4random(); |
| [updateTokenData appendBytes:(void *) &randomBits length:4]; |
| } |
| |
| return [NSData dataWithData:updateTokenData]; |
| } |
| |
| - (bool)SelectOTACandidate:(NSNumber *)requestorVendorID |
| rPID:(NSNumber *)requestorProductID |
| rSV:(NSNumber *)requestorSoftwareVersion |
| { |
| auto vendorId = [requestorVendorID unsignedIntValue]; |
| auto productId = [requestorProductID unsignedIntValue]; |
| auto softwareVersion = [requestorSoftwareVersion unsignedLongValue]; |
| |
| bool candidateFound = false; |
| NSArray * sortedArray = [_candidates sortedArrayUsingSelector:@selector(CompareSoftwareVersions:)]; |
| for (DeviceSoftwareVersionModel * candidate : sortedArray) { |
| auto candidateSoftwareVersionValid = candidate.deviceModelData.softwareVersionValid; |
| auto candidateSoftwareVersion = [candidate.softwareVersion unsignedLongValue]; |
| auto candidateMinApplicableSoftwareVersion = [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue]; |
| auto candidateMaxApplicableSoftwareVersion = [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue]; |
| auto candidateVendorId = [candidate.deviceModelData.vendorId unsignedIntValue]; |
| auto candidateProductId = [candidate.deviceModelData.productId unsignedIntValue]; |
| |
| if (candidateSoftwareVersionValid && (softwareVersion < candidateSoftwareVersion) |
| && (softwareVersion >= candidateMinApplicableSoftwareVersion) |
| && (softwareVersion <= candidateMaxApplicableSoftwareVersion) && (vendorId == candidateVendorId) |
| && (productId == candidateProductId)) { |
| _selectedCandidate = candidate; |
| _selectedCandidate.imageURI = candidate.deviceModelData.otaURL; |
| candidateFound = true; |
| } |
| } |
| |
| return candidateFound; |
| } |
| |
| @end |
| |
| @implementation DeviceSoftwareVersionModelData |
| - (instancetype)init |
| { |
| if (self = [super init]) { |
| _cDVersionNumber = nil; |
| |
| _minApplicableSoftwareVersion = nil; |
| |
| _maxApplicableSoftwareVersion = nil; |
| |
| _otaURL = nil; |
| } |
| return self; |
| } |
| |
| @end |
| |
| @implementation DeviceSoftwareVersionModel |
| - (instancetype)init |
| { |
| if (self = [super init]) { |
| _deviceModelData = [[DeviceSoftwareVersionModelData alloc] init]; |
| } |
| return self; |
| } |
| - (NSComparisonResult)CompareSoftwareVersions:(DeviceSoftwareVersionModel * _Nullable)otherObject |
| { |
| return [self.deviceModelData.softwareVersion compare:otherObject.deviceModelData.softwareVersion]; |
| } |
| @end |