Skip to content

Commit

Permalink
[OTA] Respond with a valid query image response in subsequent requests (
Browse files Browse the repository at this point in the history
  • Loading branch information
carol-apple authored Mar 24, 2022
1 parent 710b9d8 commit 8de5e9a
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 151 deletions.
15 changes: 8 additions & 7 deletions examples/ota-provider-app/esp32/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@
#include <shell_extension/launch.h>

using chip::Callback::Callback;
using namespace ::chip;
using namespace ::chip::Shell;
using namespace ::chip::System;
using namespace ::chip::Credentials;
using namespace ::chip::DeviceManager;
using namespace ::chip::DeviceLayer;
using namespace chip;
using namespace chip::Shell;
using namespace chip::System;
using namespace chip::Credentials;
using namespace chip::DeviceManager;
using namespace chip::DeviceLayer;
using namespace chip::app::Clusters::OtaSoftwareUpdateProvider;

CHIP_ERROR OnBlockQuery(void * context, chip::System::PacketBufferHandle & blockBuf, size_t & size, bool & isEof, uint32_t offset);
void OnTransferComplete(void * context);
Expand Down Expand Up @@ -124,7 +125,7 @@ static void InitServer(intptr_t context)
ESP_LOGI(TAG, "The OTA image size: %d", otaImageLen);
if (otaImageLen > 0)
{
otaProvider.SetQueryImageBehavior(OTAProviderExample::kRespondWithUpdateAvailable);
otaProvider.SetQueryImageStatus(OTAQueryStatus::kUpdateAvailable);
otaProvider.SetOTAFilePath(otaFilename);
}

Expand Down
34 changes: 20 additions & 14 deletions examples/ota-provider-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ OTAProviderExample gOtaProvider;
chip::ota::DefaultUserConsentProvider gUserConsentProvider;

// Global variables used for passing the CLI arguments to the OTAProviderExample object
static OTAProviderExample::QueryImageBehaviorType gQueryImageBehavior = OTAProviderExample::kRespondWithUnknown;
static OTAApplyUpdateAction gOptionUpdateAction = OTAApplyUpdateAction::kProceed;
static uint32_t gDelayedQueryActionTimeSec = 0;
static uint32_t gDelayedApplyActionTimeSec = 0;
static const char * gOtaFilepath = nullptr;
static const char * gOtaImageListFilepath = nullptr;
static chip::ota::UserConsentState gUserConsentState = chip::ota::UserConsentState::kUnknown;
static bool gUserConsentNeeded = false;
static uint32_t gIgnoreQueryImageCount = 0;
static uint32_t gIgnoreApplyUpdateCount = 0;
static OTAQueryStatus gQueryImageStatus = OTAQueryStatus::kUpdateAvailable;
static OTAApplyUpdateAction gOptionUpdateAction = OTAApplyUpdateAction::kProceed;
static uint32_t gDelayedQueryActionTimeSec = 0;
static uint32_t gDelayedApplyActionTimeSec = 0;
static const char * gOtaFilepath = nullptr;
static const char * gOtaImageListFilepath = nullptr;
static chip::ota::UserConsentState gUserConsentState = chip::ota::UserConsentState::kUnknown;
static bool gUserConsentNeeded = false;
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 @@ -172,15 +172,15 @@ bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier,
}
else if (strcmp(aValue, "updateAvailable") == 0)
{
gQueryImageBehavior = OTAProviderExample::kRespondWithUpdateAvailable;
gQueryImageStatus = OTAQueryStatus::kUpdateAvailable;
}
else if (strcmp(aValue, "busy") == 0)
{
gQueryImageBehavior = OTAProviderExample::kRespondWithBusy;
gQueryImageStatus = OTAQueryStatus::kBusy;
}
else if (strcmp(aValue, "updateNotAvailable") == 0)
{
gQueryImageBehavior = OTAProviderExample::kRespondWithNotAvailable;
gQueryImageStatus = OTAQueryStatus::kNotAvailable;
}
else
{
Expand Down Expand Up @@ -330,9 +330,9 @@ void ApplicationInit()
gOtaProvider.SetOTAFilePath(gOtaFilepath);
}

gOtaProvider.SetQueryImageBehavior(gQueryImageBehavior);
gOtaProvider.SetIgnoreQueryImageCount(gIgnoreQueryImageCount);
gOtaProvider.SetIgnoreApplyUpdateCount(gIgnoreApplyUpdateCount);
gOtaProvider.SetQueryImageStatus(gQueryImageStatus);
gOtaProvider.SetApplyUpdateAction(gOptionUpdateAction);
gOtaProvider.SetDelayedQueryActionTimeSec(gDelayedQueryActionTimeSec);
gOtaProvider.SetDelayedApplyActionTimeSec(gDelayedApplyActionTimeSec);
Expand All @@ -358,6 +358,12 @@ void ApplicationInit()
gOtaProvider.SetOTACandidates(candidates);
}

if ((gOtaFilepath == nullptr) && (gOtaImageListFilepath == nullptr))
{
ChipLogError(SoftwareUpdate, "Either an OTA file or image list file must be specified");
chipDie();
}

chip::app::Clusters::OTAProvider::SetDelegate(kOtaProviderEndpoint, &gOtaProvider);
}

Expand Down
213 changes: 100 additions & 113 deletions examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,14 @@ void GenerateUpdateToken(uint8_t * buf, size_t bufSize)
OTAProviderExample::OTAProviderExample()
{
memset(mOTAFilePath, 0, kFilepathBufLen);
mQueryImageBehavior = kRespondWithNotAvailable;
mIgnoreQueryImageCount = 0;
mIgnoreApplyUpdateCount = 0;
mQueryImageStatus = OTAQueryStatus::kNotAvailable;
mUpdateAction = OTAApplyUpdateAction::kDiscontinue;
mDelayedQueryActionTimeSec = 0;
mDelayedApplyActionTimeSec = 0;
mUserConsentDelegate = nullptr;
mUserConsentNeeded = false;
mCandidates.clear();
}

Expand Down Expand Up @@ -210,117 +215,32 @@ bool OTAProviderExample::ParseOTAHeader(const char * otaFilePath, OTAImageHeader
return true;
}

EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath,
const QueryImage::DecodableType & commandData)
CHIP_ERROR OTAProviderExample::SendQueryImageResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath,
const QueryImage::DecodableType & commandData)
{
OTAQueryStatus queryStatus = OTAQueryStatus::kNotAvailable;
OTAProviderExample::DeviceSoftwareVersionModel candidate;
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;
bool requestorCanConsent = commandData.requestorCanConsent.ValueOr(false);
uint8_t updateToken[kUpdateTokenLen] = { 0 };
char strBuf[kUpdateTokenStrLen] = { 0 };
char uriBuf[kUriMaxLen] = { 0 };

if (mIgnoreQueryImageCount > 0)
{
ChipLogDetail(SoftwareUpdate, "Skip HandleQueryImage response. mIgnoreQueryImageCount %" PRIu32, mIgnoreQueryImageCount);
mIgnoreQueryImageCount--;
return EMBER_ZCL_STATUS_SUCCESS;
}

switch (mQueryImageBehavior)
{
case kRespondWithUnknown:
// This use-case is a subset of the ota-candidates-file option.
// Can be removed once all other platforms start using the ota-candidates-file method.
if (strlen(mOTAFilePath) > 0) // If OTA file is directly provided
{
// 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))
{
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;
}
}

// If mUserConsentNeeded (set by the CLI) is true and requestor is capable of taking user consent
// then delegate obtaining user consent to the requestor
if (mUserConsentDelegate && queryStatus == OTAQueryStatus::kUpdateAvailable &&
(requestorCanConsent && mUserConsentNeeded) == false)
{
UserConsentState state = mUserConsentDelegate->GetUserConsentState(
GetUserConsentSubject(commandObj, commandPath, commandData, newSoftwareVersion));
ChipLogProgress(SoftwareUpdate, "User Consent state: %s", mUserConsentDelegate->UserConsentStateToString(state));
switch (state)
{
case UserConsentState::kGranted:
queryStatus = OTAQueryStatus::kUpdateAvailable;
break;

case UserConsentState::kObtaining:
queryStatus = OTAQueryStatus::kBusy;
break;

case UserConsentState::kDenied:
case UserConsentState::kUnknown:
queryStatus = OTAQueryStatus::kNotAvailable;
break;
}
}
break;

case kRespondWithUpdateAvailable:
queryStatus = OTAQueryStatus::kUpdateAvailable;
break;

case kRespondWithBusy:
queryStatus = OTAQueryStatus::kBusy;
break;

case kRespondWithNotAvailable:
queryStatus = OTAQueryStatus::kNotAvailable;
break;

default:
queryStatus = OTAQueryStatus::kNotAvailable;
break;
}

if (queryStatus == OTAQueryStatus::kUpdateAvailable)
// Set fields specific for an available status response
if (mQueryImageStatus == OTAQueryStatus::kUpdateAvailable)
{
GenerateUpdateToken(updateToken, kUpdateTokenLen);
GetUpdateTokenString(ByteSpan(updateToken), strBuf, kUpdateTokenStrLen);
ChipLogDetail(SoftwareUpdate, "generated updateToken: %s", strBuf);
ChipLogDetail(SoftwareUpdate, "Generated updateToken: %s", strBuf);

// TODO: This uses the current node as the provider to supply the OTA image. This can be configurable such that the
// provider supplying the response is not the provider supplying the OTA image.
FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex();
FabricInfo * fabricInfo = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex);
NodeId nodeId = fabricInfo->GetPeerId().GetNodeId();

// Only doing BDX transport for now
// Only supporting BDX protocol for now
MutableCharSpan uri(uriBuf, kUriMaxLen);
chip::bdx::MakeURI(nodeId, CharSpan::fromCharString(otaFilePath), uri);
chip::bdx::MakeURI(nodeId, CharSpan::fromCharString(mOTAFilePath), uri);
ChipLogDetail(SoftwareUpdate, "Generated URI: %.*s", static_cast<int>(uri.size()), uri.data());

// Initialize the transfer session in prepartion for a BDX transfer
Expand All @@ -329,28 +249,25 @@ EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * c
if (mBdxOtaSender.InitializeTransfer(commandObj->GetSubjectDescriptor().fabricIndex,
commandObj->GetSubjectDescriptor().subject) == CHIP_NO_ERROR)
{
CHIP_ERROR err = mBdxOtaSender.PrepareForTransfer(&chip::DeviceLayer::SystemLayer(), chip::bdx::TransferRole::kSender,
bdxFlags, kMaxBdxBlockSize, kBdxTimeout, kBdxPollFreq);
if (err != CHIP_NO_ERROR)
{
ChipLogError(BDX, "Failed to initialize BDX transfer session: %s", chip::ErrorStr(err));
return EMBER_ZCL_STATUS_FAILURE;
}
ReturnErrorOnFailure(mBdxOtaSender.PrepareForTransfer(&chip::DeviceLayer::SystemLayer(),
chip::bdx::TransferRole::kSender, bdxFlags, kMaxBdxBlockSize,
kBdxTimeout, kBdxPollFreq));

response.imageURI.Emplace(chip::CharSpan::fromCharString(uriBuf));
response.softwareVersion.Emplace(newSoftwareVersion);
response.softwareVersionString.Emplace(chip::CharSpan::fromCharString(newSoftwareVersionString));
response.softwareVersion.Emplace(mSoftwareVersion);
response.softwareVersionString.Emplace(chip::CharSpan::fromCharString(mSoftwareVersionString));
response.updateToken.Emplace(chip::ByteSpan(updateToken));
}
else
{
// Another BDX transfer in progress
queryStatus = OTAQueryStatus::kBusy;
mQueryImageStatus = OTAQueryStatus::kBusy;
}
}

response.status = queryStatus;
response.delayedActionTime.Emplace(delayedQueryActionTimeSec);
// Set remaining fields common to all status types
response.status = mQueryImageStatus;
response.delayedActionTime.Emplace(mDelayedQueryActionTimeSec);
if (mUserConsentNeeded && requestorCanConsent)
{
response.userConsentNeeded.Emplace(true);
Expand All @@ -365,10 +282,80 @@ EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * c
response.metadataForRequestor.Emplace(chip::ByteSpan());
}

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

return CHIP_NO_ERROR;
}

EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath,
const QueryImage::DecodableType & commandData)
{
bool requestorCanConsent = commandData.requestorCanConsent.ValueOr(false);

if (mIgnoreQueryImageCount > 0)
{
ChipLogDetail(SoftwareUpdate, "Skip HandleQueryImage response. mIgnoreQueryImageCount %" PRIu32, mIgnoreQueryImageCount);
mIgnoreQueryImageCount--;
return EMBER_ZCL_STATUS_SUCCESS;
}

if (mQueryImageStatus == OTAQueryStatus::kUpdateAvailable)
{
memset(mSoftwareVersionString, 0, SW_VER_STR_MAX_LEN);

if (!mCandidates.empty()) // If list of OTA candidates is supplied
{
OTAProviderExample::DeviceSoftwareVersionModel candidate;
if (SelectOTACandidate(commandData.vendorId, commandData.productId, commandData.softwareVersion, candidate))
{
VerifyOrDie(sizeof(mSoftwareVersionString) > strlen(candidate.softwareVersionString));

// This assumes all candidates have passed verification so the values are safe to use
mSoftwareVersion = candidate.softwareVersion;
memcpy(mSoftwareVersionString, candidate.softwareVersionString, strlen(candidate.softwareVersionString));
SetOTAFilePath(candidate.otaURL);
}
}
else if (strlen(mOTAFilePath) > 0) // If OTA file is directly provided
{
// Parse the header and set version info based on the header
OTAImageHeader header;
VerifyOrDie(ParseOTAHeader(mOTAFilePath, header) == true);
VerifyOrDie(sizeof(mSoftwareVersionString) > header.mSoftwareVersionString.size());
mSoftwareVersion = header.mSoftwareVersion;
memcpy(mSoftwareVersionString, header.mSoftwareVersionString.data(), header.mSoftwareVersionString.size());
}

// If mUserConsentNeeded (set by the CLI) is true and requestor is capable of taking user consent
// then delegate obtaining user consent to the requestor
if (mUserConsentDelegate && (requestorCanConsent && mUserConsentNeeded) == false)
{
UserConsentState state = mUserConsentDelegate->GetUserConsentState(
GetUserConsentSubject(commandObj, commandPath, commandData, mSoftwareVersion));
ChipLogProgress(SoftwareUpdate, "User Consent state: %s", mUserConsentDelegate->UserConsentStateToString(state));
switch (state)
{
case UserConsentState::kGranted:
mQueryImageStatus = OTAQueryStatus::kUpdateAvailable;
break;

case UserConsentState::kObtaining:
mQueryImageStatus = OTAQueryStatus::kBusy;
break;

case UserConsentState::kDenied:
case UserConsentState::kUnknown:
mQueryImageStatus = OTAQueryStatus::kNotAvailable;
break;
}
}
}

VerifyOrReturnError(SendQueryImageResponse(commandObj, commandPath, commandData) == CHIP_NO_ERROR, EMBER_ZCL_STATUS_FAILURE);

// After the first response is sent back, default to these values
mQueryImageBehavior = OTAProviderExample::kRespondWithUpdateAvailable;
// After the first response is sent, default to these values for subsequent queries
mQueryImageStatus = OTAQueryStatus::kUpdateAvailable;
mDelayedQueryActionTimeSec = 0;

return EMBER_ZCL_STATUS_SUCCESS;
Expand Down
Loading

0 comments on commit 8de5e9a

Please sign in to comment.