blob: 3bc3192b743b6919f60825656ff82856efcdf03f [file] [log] [blame]
/*
*
* 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 <fstream>
#include <json/json.h>
constexpr size_t kOtaHeaderMaxSize = 1024;
MTROTAHeader * ParseOTAHeader(const char * otaFilePath)
{
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 nil;
}
otaFile.read(reinterpret_cast<char *>(otaFileContent), kOtaHeaderMaxSize);
if (otaFile.bad()) {
ChipLogError(SoftwareUpdate, "Error reading OTA image file: %s", otaFilePath);
return nil;
}
NSError * error;
return [MTROTAHeaderParser headerFromData:[NSData dataWithBytes:buffer.data() length:buffer.size()] error:&error];
}
// Parses the JSON filepath and extracts DeviceSoftwareVersionModel parameters
static bool ParseJsonFileAndPopulateCandidates(
const char * filepath, NSMutableArray<DeviceSoftwareVersionModel *> ** _Nonnull candidates)
{
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 false;
}
if (!parseFromStream(builder, ifs, &root, &errs)) {
ChipLogError(SoftwareUpdate, "Error parsing JSON from file: \"%s\"", filepath);
return false;
}
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");
return false;
}
*candidates = [[NSMutableArray alloc] init];
bool ret = false;
for (auto iter : devSofVerModValue) {
DeviceSoftwareVersionModel * candidate = [[DeviceSoftwareVersionModel alloc] init];
auto vendorId = [NSNumber numberWithUnsignedInt:iter.get("vendorId", 1).asUInt()];
auto productId = [NSNumber numberWithUnsignedInt:iter.get("productId", 1).asUInt()];
auto softwareVersion = [NSNumber numberWithUnsignedLong:iter.get("softwareVersion", 10).asUInt64()];
auto softwareVersionString = [NSString stringWithUTF8String:iter.get("softwareVersionString", "1.0.0").asCString()];
auto cDVersionNumber = [NSNumber numberWithUnsignedInt:iter.get("cDVersionNumber", 0).asUInt()];
auto softwareVersionValid = iter.get("softwareVersionValid", true).asBool() ? YES : NO;
auto minApplicableSoftwareVersion =
[NSNumber numberWithUnsignedLong:iter.get("minApplicableSoftwareVersion", 0).asUInt64()];
auto maxApplicableSoftwareVersion =
[NSNumber numberWithUnsignedLong:iter.get("maxApplicableSoftwareVersion", 1000).asUInt64()];
auto otaURL = [NSString stringWithUTF8String:iter.get("otaURL", "https://test.com").asCString()];
candidate.deviceModelData.vendorId = vendorId;
candidate.deviceModelData.productId = productId;
candidate.softwareVersion = softwareVersion;
candidate.softwareVersionString = softwareVersionString;
candidate.deviceModelData.cDVersionNumber = cDVersionNumber;
candidate.deviceModelData.softwareVersionValid = softwareVersionValid;
candidate.deviceModelData.minApplicableSoftwareVersion = minApplicableSoftwareVersion;
candidate.deviceModelData.maxApplicableSoftwareVersion = maxApplicableSoftwareVersion;
candidate.deviceModelData.otaURL = otaURL;
[*candidates addObject:candidate];
ret = true;
}
return ret;
}
CHIP_ERROR OTASoftwareUpdateSetFilePath::RunCommand()
{
auto error = SetCandidatesFromFilePath(mOTACandidatesFilePath);
SetCommandExitStatus(nil);
return error;
}
CHIP_ERROR OTASoftwareUpdateSetStatus::RunCommand()
{
auto error = SetUserConsentStatus(mUserConsentStatus);
SetCommandExitStatus(nil);
return error;
}
CHIP_ERROR OTASoftwareUpdateBase::SetCandidatesFromFilePath(char * _Nonnull filePath)
{
NSMutableArray<DeviceSoftwareVersionModel *> * candidates;
ChipLogDetail(chipTool, "Setting candidates from file path: %s", filePath);
VerifyOrReturnError(ParseJsonFileAndPopulateCandidates(filePath, &candidates), CHIP_ERROR_INTERNAL);
for (DeviceSoftwareVersionModel * candidate : candidates) {
auto otaURL = [candidate.deviceModelData.otaURL UTF8String];
auto header = ParseOTAHeader(otaURL);
VerifyOrReturnError(header != nil, CHIP_ERROR_INVALID_ARGUMENT);
ChipLogDetail(chipTool, "Validating image list candidate %s: ", [candidate.deviceModelData.otaURL UTF8String]);
auto vendorId = [candidate.deviceModelData.vendorId unsignedIntValue];
auto productId = [candidate.deviceModelData.productId unsignedIntValue];
auto softwareVersion = [candidate.softwareVersion unsignedLongValue];
auto softwareVersionString = [candidate.softwareVersionString UTF8String];
auto softwareVersionStringLength = [candidate.softwareVersionString length];
auto minApplicableSoftwareVersion = [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue];
auto maxApplicableSoftwareVersion = [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue];
auto headerVendorId = [header.vendorID unsignedIntValue];
auto headerProductId = [header.productID unsignedIntValue];
auto headerSoftwareVersion = [header.softwareVersion unsignedLongValue];
auto headerSoftwareVersionString = [header.softwareVersionString UTF8String];
auto headerSoftwareVersionStringLength = [header.softwareVersionString length];
VerifyOrReturnError(vendorId == headerVendorId, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(productId == headerProductId, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(softwareVersion == headerSoftwareVersion, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(softwareVersionStringLength == headerSoftwareVersionStringLength, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(memcmp(softwareVersionString, headerSoftwareVersionString, headerSoftwareVersionStringLength) == 0,
CHIP_ERROR_INVALID_ARGUMENT);
if (header.minApplicableVersion) {
auto version = [header.minApplicableVersion unsignedLongValue];
VerifyOrReturnError(minApplicableSoftwareVersion == version, CHIP_ERROR_INVALID_ARGUMENT);
}
if (header.maxApplicableVersion) {
auto version = [header.maxApplicableVersion unsignedLongValue];
VerifyOrReturnError(maxApplicableSoftwareVersion == version, CHIP_ERROR_INVALID_ARGUMENT);
}
}
mOTADelegate.candidates = candidates;
return CHIP_NO_ERROR;
}
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();
}