From 0ad5fabf925fac41d04f83e1b2831ec194a8052d Mon Sep 17 00:00:00 2001 From: krypton36 Date: Mon, 18 Jul 2022 13:26:11 -0700 Subject: [PATCH] Add OTA Software Update in Darwin Tool Framework (#20767) --- .github/.wordlist.txt | 1 + examples/darwin-framework-tool/BUILD.gn | 3 + examples/darwin-framework-tool/README.md | 45 +++++ .../commands/common/CHIPCommandBridge.h | 5 + .../commands/common/CHIPCommandBridge.mm | 8 + .../commands/provider/Commands.h | 15 ++ .../commands/provider/OTAProviderDelegate.h | 58 ++++++ .../commands/provider/OTAProviderDelegate.mm | 184 ++++++++++++++++++ .../provider/OTASoftwareUpdateInteractive.h | 72 +++++++ .../provider/OTASoftwareUpdateInteractive.mm | 177 +++++++++++++++++ examples/darwin-framework-tool/main.mm | 2 + 11 files changed, 570 insertions(+) create mode 100644 examples/darwin-framework-tool/commands/provider/Commands.h create mode 100644 examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.h create mode 100644 examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm create mode 100644 examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.h create mode 100644 examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 4042327049362d..d27721c7561ae9 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -946,6 +946,7 @@ OTARequestorDriver OTARequestorInterface OTARequestorSerialPort OTARequestorStorage +otasoftwareupdateapp OtaSoftwareUpdateProvider otasoftwareupdaterequestor otatesting diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn index 469763c91f5f2a..2bb38888cf2ab8 100644 --- a/examples/darwin-framework-tool/BUILD.gn +++ b/examples/darwin-framework-tool/BUILD.gn @@ -93,6 +93,9 @@ executable("darwin-framework-tool") { "commands/pairing/PairingCommandBridge.mm", "commands/pairing/PairingDelegateBridge.mm", "commands/payload/SetupPayloadParseCommand.mm", + "commands/provider/Commands.h", + "commands/provider/OTAProviderDelegate.mm", + "commands/provider/OTASoftwareUpdateInteractive.mm", "commands/storage/Commands.h", "commands/storage/StorageManagementCommand.mm", "main.mm", diff --git a/examples/darwin-framework-tool/README.md b/examples/darwin-framework-tool/README.md index 833749bea25a99..7da06958ae5ac1 100644 --- a/examples/darwin-framework-tool/README.md +++ b/examples/darwin-framework-tool/README.md @@ -114,3 +114,48 @@ To get the list of parameters for a specific command, run the built executable with the target cluster name and the target command name $ darwin-framework-tool onoff on + +## Using Interactive mode + +To start the interactive mode run the following command: + + $ darwin-framework-tool interactive start + +Once in interactive mode, 'help' will display commands available + +## Using the OTA Software Update app + +OTA SW app will only work in interactive mode. In interactive mode there will be +an additional command 'otasoftwareupdateapp'. Running the following command in +interactive will display available commands. + + $ otasoftwareupdateapp + +The following json is an example of a list of candidates to set in interactive +mode with `otasoftwareupdateapp candidate-file-path`: + +```json +{ + "deviceSoftwareVersionModel": [ + { + "vendorId": 65521, + "productId": 32769, + "softwareVersion": 10, + "softwareVersionString": "1.0.0", + "cDVersionNumber": 18, + "softwareVersionValid": true, + "minApplicableSoftwareVersion": 0, + "maxApplicableSoftwareVersion": 100, + "otaURL": "/Users/josh/Desktop/OTACandidates/ota_v10.bin" + } + ] +} +``` + +darwin-framework-tool allows to set the consent status on the Provider side with +the following command: + + $ otasoftwareupdateapp set-consent-status [granted, obtaining, denied] + +By default, the consent will be set to unknown and the requestor will have to +consent. If the requestor cannot consent, the update will be denied. diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h index 5892d21ec8f079..a16eb224db52e6 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h @@ -23,6 +23,8 @@ #include #include +#include "../provider/OTAProviderDelegate.h" + #pragma once constexpr const char kIdentityAlpha[] = "alpha"; @@ -48,6 +50,8 @@ class CHIPCommandBridge : public Command StopWaiting(); } + static OTAProviderDelegate * mOTADelegate; + protected: // Will be called in a setting in which it's safe to touch the CHIP // stack. The rules for Run() are as follows: @@ -115,4 +119,5 @@ class CHIPCommandBridge : public Command std::mutex cvWaitingForResponseMutex; chip::Optional mCommissionerName; bool mWaitingForResponse{ true }; + static dispatch_queue_t mOTAProviderCallbackQueue; }; diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm index caebb41c20de2a..490eec1e7421b9 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm @@ -30,12 +30,17 @@ static CHIPToolPersistentStorageDelegate * storage = nil; std::set CHIPCommandBridge::sDeferredCleanups; std::map CHIPCommandBridge::mControllers; +dispatch_queue_t CHIPCommandBridge::mOTAProviderCallbackQueue; +OTAProviderDelegate * CHIPCommandBridge::mOTADelegate; CHIP_ERROR CHIPCommandBridge::Run() { ChipLogProgress(chipTool, "Running Command"); ReturnErrorOnFailure(MaybeSetUpStack()); SetIdentity(mCommissionerName.HasValue() ? mCommissionerName.Value() : kIdentityAlpha); + ChipLogDetail(chipTool, "Setting OTA Provider Delegate:"); + mOTADelegate.nodeID = [CurrentCommissioner() controllerNodeId]; + [CurrentCommissioner() setOTAProviderDelegate:mOTADelegate queue:mOTAProviderCallbackQueue]; ReturnLogErrorOnFailure(RunCommand()); ReturnLogErrorOnFailure(StartWaiting(GetWaitDuration())); @@ -62,6 +67,9 @@ CHIPToolKeypair * nocSigner = [[CHIPToolKeypair alloc] init]; storage = [[CHIPToolPersistentStorageDelegate alloc] init]; + mOTAProviderCallbackQueue = dispatch_queue_create("com.darwin-framework-tool.command", DISPATCH_QUEUE_SERIAL); + mOTADelegate = [[OTAProviderDelegate alloc] init]; + auto factory = [MTRControllerFactory sharedInstance]; if (factory == nil) { ChipLogError(chipTool, "Controller factory is nil"); diff --git a/examples/darwin-framework-tool/commands/provider/Commands.h b/examples/darwin-framework-tool/commands/provider/Commands.h new file mode 100644 index 00000000000000..a762ec7862efef --- /dev/null +++ b/examples/darwin-framework-tool/commands/provider/Commands.h @@ -0,0 +1,15 @@ +#include "OTASoftwareUpdateInteractive.h" + +void registerClusterOtaSoftwareUpdateProviderInteractive(Commands & commands) +{ + + const char * clusterName = "OtaSoftwareUpdateApp"; + + commands_list clusterCommands = { + make_unique(), // + make_unique(), // + + }; + + commands.Register(clusterName, clusterCommands); +} diff --git a/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.h b/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.h new file mode 100644 index 00000000000000..05ba9e7960a084 --- /dev/null +++ b/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.h @@ -0,0 +1,58 @@ +/* + * + * 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. + */ +#pragma once +#import + +typedef NS_ENUM(uint8_t, UserConsentState) { + OTAProviderUserGranted = 0x00, + OTAProviderUserObtaining = 0x01, + OTAProviderUserDenied = 0x02, + OTAProviderUserUnknown = 0x03, +}; + +@interface DeviceSoftwareVersionModelData : MTROtaSoftwareUpdateProviderClusterQueryImageParams +@property BOOL softwareVersionValid; +@property (strong, nonatomic, nullable) NSNumber * cDVersionNumber; +@property (strong, nonatomic, nullable) NSNumber * minApplicableSoftwareVersion; +@property (strong, nonatomic, nullable) NSNumber * maxApplicableSoftwareVersion; +@property (strong, nonatomic, nullable) NSString * otaURL; +@end + +@interface DeviceSoftwareVersionModel : MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams +@property (strong, nonatomic, nullable) DeviceSoftwareVersionModelData * deviceModelData; +- (NSComparisonResult)CompareSoftwareVersions:(DeviceSoftwareVersionModel * _Nullable)otherObject; +@end + +@interface OTAProviderDelegate : NSObject +- (void)handleQueryImage:(MTROtaSoftwareUpdateProviderClusterQueryImageParams * _Nonnull)params + completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, + NSError * _Nullable error))completionHandler; + +- (void)handleApplyUpdateRequest:(MTROtaSoftwareUpdateProviderClusterApplyUpdateRequestParams * _Nonnull)params + completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, + NSError * _Nullable error))completionHandler; + +- (void)handleNotifyUpdateApplied:(MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams * _Nonnull)params + completionHandler:(StatusCompletion _Nonnull)completionHandler; + +@property (strong, nonatomic, nullable) NSArray * candidates; +@property (strong, nonatomic, nullable) DeviceSoftwareVersionModel * selectedCandidate; +@property (strong, nonatomic, nullable) NSNumber * nodeID; +@property (nonatomic, readwrite) MTROtaSoftwareUpdateProviderOTAQueryStatus queryImageStatus; +@property (nonatomic, readwrite) UserConsentState userConsentState; + +@end diff --git a/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm b/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm new file mode 100644 index 00000000000000..1ee29bf5da13b6 --- /dev/null +++ b/examples/darwin-framework-tool/commands/provider/OTAProviderDelegate.mm @@ -0,0 +1,184 @@ +/* + * + * 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 + +constexpr uint8_t kUpdateTokenLen = 32; + +@interface OTAProviderDelegate () +@property NSString * mOTAFilePath; +@property DeviceSoftwareVersionModel * candidate; +@end + +@implementation OTAProviderDelegate + +- (instancetype)init +{ + if (self = [super init]) { + _nodeID = @(0); + _selectedCandidate = [[DeviceSoftwareVersionModel alloc] init]; + _userConsentState = OTAProviderUserUnknown; + } + return self; +} + +// TODO: When BDX is added to Matter.framework, update to initialize +// it when there is an update available. +- (void)handleQueryImage:(MTROtaSoftwareUpdateProviderClusterQueryImageParams * _Nonnull)params + completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data, + NSError * _Nullable error))completionHandler +{ + NSError * error; + _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable); + if (![params.protocolsSupported containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)]) { + _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusDownloadProtocolNotSupported); + error = + [[NSError alloc] initWithDomain:@"OTAProviderDomain" + code:MTRErrorCodeGeneralError + userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Protocol is not supported.", nil) }]; + completionHandler(_selectedCandidate, error); + return; + } + + if ([self SelectOTACandidate:params.vendorId rPID:params.productId rSV:params.softwareVersion]) { + _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable); + _selectedCandidate.updateToken = [self generateUpdateToken]; + if (params.requestorCanConsent.integerValue == 1) { + _selectedCandidate.userConsentNeeded + = (_userConsentState == OTAProviderUserUnknown || _userConsentState == OTAProviderUserDenied) ? @(1) : @(0); + NSLog(@"User Consent Needed: %@", _selectedCandidate.userConsentNeeded); + } else { + 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); + } + } else { + NSLog(@"Unable to select OTA Image."); + _selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable); + error = [[NSError alloc] + initWithDomain:@"OTAProviderDomain" + code:MTRErrorCodeInvalidState + userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Unable to select Candidate.", nil) }]; + } + completionHandler(_selectedCandidate, error); +} + +- (void)handleApplyUpdateRequest:(MTROtaSoftwareUpdateProviderClusterApplyUpdateRequestParams * _Nonnull)params + completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * _Nullable data, + NSError * _Nullable error))completionHandler +{ + MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams * applyUpdateResponsePrams = + [[MTROtaSoftwareUpdateProviderClusterApplyUpdateResponseParams alloc] init]; + applyUpdateResponsePrams.action = @(MTROtaSoftwareUpdateProviderOTAApplyUpdateActionProceed); + completionHandler(applyUpdateResponsePrams, nil); +} + +- (void)handleNotifyUpdateApplied:(MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams * _Nonnull)params + completionHandler:(StatusCompletion _Nonnull)completionHandler +{ + completionHandler(nil); +} + +- (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 +{ + bool candidateFound = false; + NSArray * sortedArray = [_candidates sortedArrayUsingSelector:@selector(CompareSoftwareVersions:)]; + for (DeviceSoftwareVersionModel * candidate : sortedArray) { + if (candidate.deviceModelData.softwareVersionValid + && ([requestorSoftwareVersion unsignedLongValue] < [candidate.softwareVersion unsignedLongValue]) + && ([requestorSoftwareVersion unsignedLongValue] >= + [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue]) + && ([requestorSoftwareVersion unsignedLongValue] <= + [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue]) + && ([requestorVendorID unsignedIntValue] == [candidate.deviceModelData.vendorId unsignedIntValue]) + && ([requestorProductID unsignedIntValue] == [candidate.deviceModelData.productId unsignedIntValue])) { + candidateFound = true; + _selectedCandidate = candidate; + _selectedCandidate.imageURI = [NSString + stringWithFormat:@"bdx://%016llX/%@", [_nodeID unsignedLongLongValue], _selectedCandidate.deviceModelData.otaURL]; + } + } + 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 diff --git a/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.h b/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.h new file mode 100644 index 00000000000000..64af48f8bf5364 --- /dev/null +++ b/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.h @@ -0,0 +1,72 @@ +/* + * + * 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. + */ +#pragma once +#include "../common/CHIPCommandBridge.h" +#include "OTAProviderDelegate.h" + +static constexpr uint16_t SW_VER_STR_MAX_LEN = 64; +static constexpr uint16_t OTA_URL_MAX_LEN = 512; + +class OTASoftwareUpdateBase : public CHIPCommandBridge { +public: + OTASoftwareUpdateBase(const char * _Nonnull commandName) + : CHIPCommandBridge(commandName) + { + } + void SetCandidatesFromFilePath(char * _Nonnull filePath); + CHIP_ERROR SetUserConsentStatus(char * _Nonnull status); + static constexpr size_t kFilepathBufLen = 256; + + CHIP_ERROR Run() override; + + /////////// CHIPCommandBridge Interface ///////// + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(120); } + +private: + NSString * _Nullable mOTAFilePath; + void SetOTAFilePath(const char * _Nonnull path); +}; + +class OTASoftwareUpdateSetFilePath : public OTASoftwareUpdateBase { +public: + OTASoftwareUpdateSetFilePath() + : OTASoftwareUpdateBase("candidate-file-path") + { + AddArgument("path", &mOTACandidatesFilePath); + } + + /////////// CHIPCommandBridge Interface ///////// + CHIP_ERROR RunCommand() override; + +private: + char * _Nonnull mOTACandidatesFilePath; +}; + +class OTASoftwareUpdateSetStatus : public OTASoftwareUpdateBase { +public: + OTASoftwareUpdateSetStatus() + : OTASoftwareUpdateBase("set-consent-status") + { + AddArgument("status", &mUserConsentStatus); + } + + /////////// CHIPCommandBridge Interface ///////// + CHIP_ERROR RunCommand() override; + +private: + char * _Nonnull mUserConsentStatus; +}; diff --git a/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm b/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm new file mode 100644 index 00000000000000..fae950f04a91a6 --- /dev/null +++ b/examples/darwin-framework-tool/commands/provider/OTASoftwareUpdateInteractive.mm @@ -0,0 +1,177 @@ +/* + * + * 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 "OTASoftwareUpdateInteractive.h" + +#include +#include + +// TODO: Objective-C Matter.framework needs to expose this. +#include + +constexpr size_t kOtaHeaderMaxSize = 1024; + +bool ParseOTAHeader(chip::OTAImageHeaderParser & parser, const char * otaFilePath, chip::OTAImageHeader & header) +{ + uint8_t otaFileContent[kOtaHeaderMaxSize]; + chip::ByteSpan buffer(otaFileContent); + + std::ifstream otaFile(otaFilePath, std::ifstream::in); + if (!otaFile.is_open() || !otaFile.good()) { + ChipLogError(SoftwareUpdate, "Error opening OTA image file: %s", otaFilePath); + return false; + } + + otaFile.read(reinterpret_cast(otaFileContent), kOtaHeaderMaxSize); + if (otaFile.bad()) { + ChipLogError(SoftwareUpdate, "Error reading OTA image file: %s", otaFilePath); + return false; + } + + parser.Init(); + if (!parser.IsInitialized()) { + return false; + } + + CHIP_ERROR error = parser.AccumulateAndDecode(buffer, header); + if (error != CHIP_NO_ERROR) { + ChipLogError(SoftwareUpdate, "Error parsing OTA image header: %" CHIP_ERROR_FORMAT, error.Format()); + return false; + } + + return true; +} + +// Parses the JSON filepath and extracts DeviceSoftwareVersionModel parameters +static bool ParseJsonFileAndPopulateCandidates( + const char * filepath, NSMutableArray ** _Nonnull candidates) +{ + bool ret = false; + Json::Value root; + Json::CharReaderBuilder builder; + JSONCPP_STRING errs; + std::ifstream ifs; + + builder["collectComments"] = true; // allow C/C++ type comments in JSON file + ifs.open(filepath); + + if (!ifs.good()) { + ChipLogError(SoftwareUpdate, "Error opening ifstream with file: \"%s\"", filepath); + return ret; + } + + if (!parseFromStream(builder, ifs, &root, &errs)) { + ChipLogError(SoftwareUpdate, "Error parsing JSON from file: \"%s\"", filepath); + return ret; + } + + const Json::Value devSofVerModValue = root["deviceSoftwareVersionModel"]; + if (!devSofVerModValue || !devSofVerModValue.isArray()) { + ChipLogError(SoftwareUpdate, "Error: Key deviceSoftwareVersionModel not found or its value is not of type Array"); + } else { + *candidates = [[NSMutableArray alloc] init]; + for (auto iter : devSofVerModValue) { + DeviceSoftwareVersionModel * candidate = [[DeviceSoftwareVersionModel alloc] init]; + candidate.deviceModelData.vendorId = [NSNumber numberWithUnsignedInt:iter.get("vendorId", 1).asUInt()]; + candidate.deviceModelData.productId = [NSNumber numberWithUnsignedInt:iter.get("productId", 1).asUInt()]; + candidate.softwareVersion = [NSNumber numberWithUnsignedLong:iter.get("softwareVersion", 10).asUInt64()]; + candidate.softwareVersionString = + [NSString stringWithUTF8String:iter.get("softwareVersionString", "1.0.0").asCString()]; + candidate.deviceModelData.cDVersionNumber = [NSNumber numberWithUnsignedInt:iter.get("cDVersionNumber", 0).asUInt()]; + candidate.deviceModelData.softwareVersionValid = iter.get("softwareVersionValid", true).asBool() ? YES : NO; + candidate.deviceModelData.minApplicableSoftwareVersion = + [NSNumber numberWithUnsignedLong:iter.get("minApplicableSoftwareVersion", 0).asUInt64()]; + candidate.deviceModelData.maxApplicableSoftwareVersion = + [NSNumber numberWithUnsignedLong:iter.get("maxApplicableSoftwareVersion", 1000).asUInt64()]; + candidate.deviceModelData.otaURL = [NSString stringWithUTF8String:iter.get("otaURL", "https://test.com").asCString()]; + [*candidates addObject:candidate]; + ret = true; + } + } + return ret; +} + +CHIP_ERROR OTASoftwareUpdateSetFilePath::RunCommand() +{ + SetCandidatesFromFilePath(mOTACandidatesFilePath); + SetCommandExitStatus(nil); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTASoftwareUpdateSetStatus::RunCommand() +{ + CHIP_ERROR error = CHIP_NO_ERROR; + error = SetUserConsentStatus(mUserConsentStatus); + SetCommandExitStatus(nil); + + return error; +} + +void OTASoftwareUpdateBase::SetCandidatesFromFilePath(char * _Nonnull filePath) +{ + NSMutableArray * candidates; + ChipLogDetail(chipTool, "Setting candidates from file path: %s", filePath); + ParseJsonFileAndPopulateCandidates(filePath, &candidates); + for (DeviceSoftwareVersionModel * candidate : candidates) { + chip::OTAImageHeaderParser parser; + chip::OTAImageHeader header; + ParseOTAHeader(parser, [candidate.deviceModelData.otaURL UTF8String], header); + ChipLogDetail(chipTool, "Validating image list candidate %s: ", [candidate.deviceModelData.otaURL UTF8String]); + VerifyOrDie([candidate.deviceModelData.vendorId unsignedIntValue] == header.mVendorId); + VerifyOrDie([candidate.deviceModelData.productId unsignedIntValue] == header.mProductId); + VerifyOrDie([candidate.softwareVersion unsignedLongValue] == header.mSoftwareVersion); + VerifyOrDie([candidate.softwareVersionString length] == header.mSoftwareVersionString.size()); + VerifyOrDie(memcmp([candidate.softwareVersionString UTF8String], header.mSoftwareVersionString.data(), + header.mSoftwareVersionString.size()) + == 0); + if (header.mMinApplicableVersion.HasValue()) { + VerifyOrDie( + [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue] == header.mMinApplicableVersion.Value()); + } + if (header.mMaxApplicableVersion.HasValue()) { + VerifyOrDie( + [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue] == header.mMaxApplicableVersion.Value()); + } + parser.Clear(); + } + mOTADelegate.candidates = candidates; +} + +CHIP_ERROR OTASoftwareUpdateBase::SetUserConsentStatus(char * _Nonnull otaSTatus) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + if (strcmp(otaSTatus, "granted") == 0) { + mOTADelegate.userConsentState = OTAProviderUserGranted; + } else if (strcmp(otaSTatus, "obtaining") == 0) { + mOTADelegate.userConsentState = OTAProviderUserObtaining; + } else if (strcmp(otaSTatus, "denied") == 0) { + mOTADelegate.userConsentState = OTAProviderUserDenied; + } else { + ChipLogError(chipTool, "Only accepts the following: granted, obtaining, and denied."); + error = CHIP_ERROR_INTERNAL; + } + return error; +} + +CHIP_ERROR OTASoftwareUpdateBase::Run() +{ + if (!IsInteractive()) { + ChipLogError(chipTool, "OTA software update commands can only be ran in interactive mode."); + return CHIP_ERROR_INTERNAL; + } + return CHIPCommandBridge::Run(); +} diff --git a/examples/darwin-framework-tool/main.mm b/examples/darwin-framework-tool/main.mm index 8159f91df78ac0..09885d08eb3178 100644 --- a/examples/darwin-framework-tool/main.mm +++ b/examples/darwin-framework-tool/main.mm @@ -22,6 +22,7 @@ #include "commands/interactive/Commands.h" #include "commands/pairing/Commands.h" #include "commands/payload/Commands.h" +#include "commands/provider/Commands.h" #include "commands/storage/Commands.h" #include @@ -33,6 +34,7 @@ int main(int argc, const char * argv[]) registerCommandsPairing(commands); registerCommandsInteractive(commands); registerCommandsPayload(commands); + registerClusterOtaSoftwareUpdateProviderInteractive(commands); registerCommandsStorage(commands); registerCommandsTests(commands); registerClusters(commands);