Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OTA] Respond with a valid query image response in subsequent requests #16598

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Damian-Nordic marked this conversation as resolved.
Show resolved Hide resolved
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