Skip to content

Commit

Permalink
[OTA] Use the version from OTA image header to send QueryImageResponse (
Browse files Browse the repository at this point in the history
#16515)

* [OTA] Use the version from OTA image header to send QueryImageResponse

- Remove command line options for software version and software version string
- Source of truth should be from the image header
- Validate the JSON file when the candidates are set
  • Loading branch information
carol-apple authored Mar 22, 2022
1 parent f97e2c5 commit a9bca9b
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 90 deletions.
6 changes: 4 additions & 2 deletions examples/ota-provider-app/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/debug c
| -t/--delayedQueryActionTimeSec <time> | Value in seconds for the DelayedActionTime field in the first QueryImageResponse. <br> Value will revert back to 0 seconds on subsequent QueryImage Responses. |
| -p/--delayedApplyActionTimeSec <time> | Value in seconds for the DelayedActionTime field in the first ApplyUpdateResponse. <br> Value will revert back to 0 seconds on subsequent ApplyUpdate Responses. |
| -u/--userConsentState <granted \| denied \| deferred> | The user consent state for the first QueryImageResponse. For all subsequent responses, the value of granted state will be used. <br> Current user consent state which results in various values for Status field in QueryImageResponse <br> Note that -q/--queryImageStatus overrides this option <li> granted: Status field in QueryImageResponse is set to updateAvailable <li> denied: Status field in QueryImageResponse is set to updateNotAvailable <li> deferred: Status field in QueryImageResponse is set to busy |
| -s/--softwareVersion <version> | Value for the SoftwareVersion field in the QueryImageResponse <br> Note that -o/--otaImageList overrides this option |
| -S/--softwareVersionStr <version string> | Value for the SoftwareVersionString field in the QueryImageResponse <br> Note that -o/--otaImageList overrides this option |
| -c/--UserConsentNeeded | If provided, and value of RequestorCanConsent field in QueryImage Command is true, <br> then value of UserConsentNeeded field in the QueryImageResponse is set to true. <br> Else, value of UserConsentNeeded is false. |

**Using `--filepath` and `--otaImageList`**
Expand All @@ -39,6 +37,10 @@ scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/debug c
- If `--otaImageList` is supplied, the application will parse the JSON file
and extract all required data. The most recent/valid software version will
be selected and the corresponding OTA file will be sent to the OTA Requestor
- The SoftwareVersion and SoftwareVersionString sent in the QueryImageResponse
is derived from the OTA image header. Please note that if the version in the
`--otaImageList` JSON file does not match that in the header, the
application will terminate.

An example of the `--otaImageList` file contents:

Expand Down
43 changes: 2 additions & 41 deletions examples/ota-provider-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ constexpr uint16_t kOptionUserConsentState = 'u';
constexpr uint16_t kOptionUpdateAction = 'a';
constexpr uint16_t kOptionDelayedQueryActionTimeSec = 't';
constexpr uint16_t kOptionDelayedApplyActionTimeSec = 'p';
constexpr uint16_t kOptionSoftwareVersion = 's';
constexpr uint16_t kOptionSoftwareVersionStr = 'S';
constexpr uint16_t kOptionUserConsentNeeded = 'c';

OTAProviderExample gOtaProvider;
Expand All @@ -68,10 +66,8 @@ static const char * gOtaFilepath = nullptr;
static const char * gOtaImageListFilepath = nullptr;
static chip::ota::UserConsentState gUserConsentState = chip::ota::UserConsentState::kUnknown;
static bool gUserConsentNeeded = false;
static chip::Optional<uint32_t> gSoftwareVersion;
static const char * gSoftwareVersionString = nullptr;
static uint32_t gIgnoreQueryImageCount = 0;
static uint32_t gIgnoreApplyUpdateCount = 0;
static uint32_t gIgnoreQueryImageCount = 0;
static uint32_t gIgnoreApplyUpdateCount = 0;

// Parses the JSON filepath and extracts DeviceSoftwareVersionModel parameters
static bool ParseJsonFileAndPopulateCandidates(const char * filepath,
Expand Down Expand Up @@ -252,28 +248,9 @@ bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier,
retval = false;
}
break;
case kOptionSoftwareVersion:
gSoftwareVersion.SetValue(static_cast<uint32_t>(strtoul(aValue, NULL, 0)));
break;
case kOptionUserConsentNeeded:
gUserConsentNeeded = true;
break;
case kOptionSoftwareVersionStr:
if (aValue == NULL)
{
PrintArgError("%s: ERROR: NULL SoftwareVersionStr parameter\n", aProgram);
retval = false;
}
else if ((strlen(aValue) < 1 || strlen(aValue) > 64))
{
PrintArgError("%s: ERROR: SoftwareVersionStr parameter length is out of range \n", aProgram);
retval = false;
}
else
{
gSoftwareVersionString = aValue;
}
break;
default:
PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName);
retval = false;
Expand All @@ -293,8 +270,6 @@ OptionDef cmdLineOptionsDef[] = {
{ "delayedQueryActionTimeSec", chip::ArgParser::kArgumentRequired, kOptionDelayedQueryActionTimeSec },
{ "delayedApplyActionTimeSec", chip::ArgParser::kArgumentRequired, kOptionDelayedApplyActionTimeSec },
{ "userConsentState", chip::ArgParser::kArgumentRequired, kOptionUserConsentState },
{ "softwareVersion", chip::ArgParser::kArgumentRequired, kOptionSoftwareVersion },
{ "softwareVersionStr", chip::ArgParser::kArgumentRequired, kOptionSoftwareVersionStr },
{ "UserConsentNeeded", chip::ArgParser::kNoArgument, kOptionUserConsentNeeded },
{},
};
Expand Down Expand Up @@ -327,12 +302,6 @@ OptionSet cmdLineOptions = { HandleOptions, cmdLineOptionsDef, "PROGRAM OPTIONS"
" denied: Status field in QueryImageResponse is set to updateNotAvailable\n"
" deferred: Status field in QueryImageResponse is set to busy\n"
" -q/--queryImageStatus overrides this option\n"
" -s/--softwareVersion <version>\n"
" Value for the SoftwareVersion field in the QueryImageResponse\n"
" -o/--otaImageList overrides this option\n"
" -S/--softwareVersionStr <version string>\n"
" Value for the SoftwareVersionString field in the QueryImageResponse\n"
" -o/--otaImageList overrides this option\n"
" -c/--UserConsentNeeded\n"
" If provided, and value of RequestorCanConsent field in QueryImage Command is true,\n"
" then value of UserConsentNeeded field in the QueryImageResponse is set to true.\n"
Expand Down Expand Up @@ -367,14 +336,6 @@ void ApplicationInit()
gOtaProvider.SetApplyUpdateAction(gOptionUpdateAction);
gOtaProvider.SetDelayedQueryActionTimeSec(gDelayedQueryActionTimeSec);
gOtaProvider.SetDelayedApplyActionTimeSec(gDelayedApplyActionTimeSec);
if (gSoftwareVersion.HasValue())
{
gOtaProvider.SetSoftwareVersion(gSoftwareVersion.Value());
}
if (gSoftwareVersionString)
{
gOtaProvider.SetSoftwareVersionString(gSoftwareVersionString);
}

if (gUserConsentState != chip::ota::UserConsentState::kUnknown)
{
Expand Down
119 changes: 88 additions & 31 deletions examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <lib/support/CHIPMemString.h>
#include <protocols/bdx/BdxUri.h>

#include <fstream>
#include <string.h>

using chip::BitFlags;
Expand All @@ -51,6 +52,7 @@ using namespace chip::app::Clusters::OtaSoftwareUpdateProvider::Commands;
constexpr uint8_t kUpdateTokenLen = 32; // must be between 8 and 32
constexpr uint8_t kUpdateTokenStrLen = kUpdateTokenLen * 2 + 1; // Hex string needs 2 hex chars for every byte
constexpr size_t kUriMaxLen = 256;
constexpr size_t kOtaHeaderMaxSize = 1024;

// Arbitrary BDX Transfer Params
constexpr uint32_t kMaxBdxBlockSize = 1024;
Expand Down Expand Up @@ -99,6 +101,29 @@ void OTAProviderExample::SetOTAFilePath(const char * path)
void OTAProviderExample::SetOTACandidates(std::vector<OTAProviderExample::DeviceSoftwareVersionModel> candidates)
{
mCandidates = std::move(candidates);

// Validate that each candidate matches the info in the image header
for (auto candidate : mCandidates)
{
OTAImageHeader header;
ParseOTAHeader(candidate.otaURL, header);

ChipLogDetail(SoftwareUpdate, "Validating image list candidate %s: ", candidate.otaURL);
VerifyOrDie(candidate.vendorId == header.mVendorId);
VerifyOrDie(candidate.productId == header.mProductId);
VerifyOrDie(candidate.softwareVersion == header.mSoftwareVersion);
VerifyOrDie(strlen(candidate.softwareVersionString) == header.mSoftwareVersionString.size());
VerifyOrDie(memcmp(candidate.softwareVersionString, header.mSoftwareVersionString.data(),
header.mSoftwareVersionString.size()) == 0);
if (header.mMinApplicableVersion.HasValue())
{
VerifyOrDie(candidate.minApplicableSoftwareVersion == header.mMinApplicableVersion.Value());
}
if (header.mMaxApplicableVersion.HasValue())
{
VerifyOrDie(candidate.maxApplicableSoftwareVersion == header.mMaxApplicableVersion.Value());
}
}
}

static bool CompareSoftwareVersions(const OTAProviderExample::DeviceSoftwareVersionModel & a,
Expand Down Expand Up @@ -147,20 +172,58 @@ UserConsentSubject OTAProviderExample::GetUserConsentSubject(const app::CommandH
return subject;
}

bool OTAProviderExample::ParseOTAHeader(const char * otaFilePath, OTAImageHeader & header)
{
OTAImageHeaderParser parser;
uint8_t otaFileContent[kOtaHeaderMaxSize];
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<char *>(otaFileContent), kOtaHeaderMaxSize);
if (!otaFile.good())
{
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;
}

parser.Clear();

return true;
}

EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath,
const QueryImage::DecodableType & commandData)
{
OTAQueryStatus queryStatus = OTAQueryStatus::kNotAvailable;
OTAProviderExample::DeviceSoftwareVersionModel candidate;
uint32_t newSoftwareVersion = commandData.softwareVersion + 1;
const char * newSoftwareVersionString = "Example-Image-V0.1";
const char * otaFilePath = mOTAFilePath;
uint8_t updateToken[kUpdateTokenLen] = { 0 };
char strBuf[kUpdateTokenStrLen] = { 0 };
char uriBuf[kUriMaxLen] = { 0 };
uint32_t delayedQueryActionTimeSec = mDelayedQueryActionTimeSec;
bool requestorCanConsent = commandData.requestorCanConsent.ValueOr(false);
uint32_t newSoftwareVersion = 0;
char newSoftwareVersionString[SW_VER_STR_MAX_LEN] = { 0 };
const char * otaFilePath = mOTAFilePath;
uint8_t updateToken[kUpdateTokenLen] = { 0 };
char strBuf[kUpdateTokenStrLen] = { 0 };
char uriBuf[kUriMaxLen] = { 0 };
uint32_t delayedQueryActionTimeSec = mDelayedQueryActionTimeSec;
bool requestorCanConsent = commandData.requestorCanConsent.ValueOr(false);
QueryImageResponse::Type response;

if (mIgnoreQueryImageCount > 0)
Expand All @@ -177,30 +240,26 @@ EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * c
// Can be removed once all other platforms start using the ota-candidates-file method.
if (strlen(mOTAFilePath) > 0) // If OTA file is directly provided
{
// TODO: Following details shall be read from the OTA file

// If software version is provided using command line then use it
if (mSoftwareVersion.HasValue())
{
newSoftwareVersion = mSoftwareVersion.Value();
}

// If software version string is provided using command line then use it
if (mSoftwareVersionString)
{
newSoftwareVersionString = mSoftwareVersionString;
}
// Parse the header and set version info based on the header
OTAImageHeader header;
VerifyOrDie(ParseOTAHeader(mOTAFilePath, header) == true);
VerifyOrDie(sizeof(newSoftwareVersionString) > header.mSoftwareVersionString.size());
newSoftwareVersion = header.mSoftwareVersion;
memcpy(newSoftwareVersionString, header.mSoftwareVersionString.data(), header.mSoftwareVersionString.size());

queryStatus = OTAQueryStatus::kUpdateAvailable;
}
else if (!mCandidates.empty()) // If list of OTA candidates is supplied instead
{
if (SelectOTACandidate(commandData.vendorId, commandData.productId, commandData.softwareVersion, candidate))
{
newSoftwareVersion = candidate.softwareVersion;
newSoftwareVersionString = candidate.softwareVersionString;
otaFilePath = candidate.otaURL;
queryStatus = OTAQueryStatus::kUpdateAvailable;
VerifyOrDie(sizeof(newSoftwareVersionString) > strlen(candidate.softwareVersionString));

// This assumes all candidates have passed verification so the values are safe to use
newSoftwareVersion = candidate.softwareVersion;
memcpy(newSoftwareVersionString, candidate.softwareVersionString, strlen(candidate.softwareVersionString));
otaFilePath = candidate.otaURL;
queryStatus = OTAQueryStatus::kUpdateAvailable;
}
}

Expand Down Expand Up @@ -247,9 +306,6 @@ EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * c
break;
}

// Reset with default success behavior
mQueryImageBehavior = OTAProviderExample::kRespondWithUpdateAvailable;

if (queryStatus == OTAQueryStatus::kUpdateAvailable)
{
GenerateUpdateToken(updateToken, kUpdateTokenLen);
Expand Down Expand Up @@ -309,11 +365,12 @@ EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * c
response.metadataForRequestor.Emplace(chip::ByteSpan());
}

// Reset delay back to 0 for subsequent uses
mDelayedQueryActionTimeSec = 0;

VerifyOrReturnError(commandObj->AddResponseData(commandPath, response) == CHIP_NO_ERROR, EMBER_ZCL_STATUS_FAILURE);

// After the first response is sent back, default to these values
mQueryImageBehavior = OTAProviderExample::kRespondWithUpdateAvailable;
mDelayedQueryActionTimeSec = 0;

return EMBER_ZCL_STATUS_SUCCESS;
}

Expand Down
Loading

0 comments on commit a9bca9b

Please sign in to comment.