From 8de5e9adb7a2b824567b4d766daee01477a2145f Mon Sep 17 00:00:00 2001 From: Carol Yang Date: Thu, 24 Mar 2022 13:27:56 -0700 Subject: [PATCH] [OTA] Respond with a valid query image response in subsequent requests (#16598) --- examples/ota-provider-app/esp32/main/main.cpp | 15 +- examples/ota-provider-app/linux/main.cpp | 34 +-- .../OTAProviderExample.cpp | 213 ++++++++---------- .../ota-provider-common/OTAProviderExample.h | 38 ++-- 4 files changed, 149 insertions(+), 151 deletions(-) diff --git a/examples/ota-provider-app/esp32/main/main.cpp b/examples/ota-provider-app/esp32/main/main.cpp index 546df355fa285c..fad067f2c9b0ca 100644 --- a/examples/ota-provider-app/esp32/main/main.cpp +++ b/examples/ota-provider-app/esp32/main/main.cpp @@ -36,12 +36,13 @@ #include 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); @@ -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); } diff --git a/examples/ota-provider-app/linux/main.cpp b/examples/ota-provider-app/linux/main.cpp index bc7076da265a1a..47643487cdc31f 100644 --- a/examples/ota-provider-app/linux/main.cpp +++ b/examples/ota-provider-app/linux/main.cpp @@ -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, @@ -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 { @@ -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); @@ -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); } diff --git a/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp b/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp index e583fe0ad4de84..c3f1e2754e2b61 100644 --- a/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp +++ b/examples/ota-provider-app/ota-provider-common/OTAProviderExample.cpp @@ -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(); } @@ -210,107 +215,22 @@ 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. @@ -318,9 +238,9 @@ EmberAfStatus OTAProviderExample::HandleQueryImage(chip::app::CommandHandler * c 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(uri.size()), uri.data()); // Initialize the transfer session in prepartion for a BDX transfer @@ -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); @@ -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; diff --git a/examples/ota-provider-app/ota-provider-common/OTAProviderExample.h b/examples/ota-provider-app/ota-provider-common/OTAProviderExample.h index 19be8e613d49c1..40c414bea31aab 100644 --- a/examples/ota-provider-app/ota-provider-common/OTAProviderExample.h +++ b/examples/ota-provider-app/ota-provider-common/OTAProviderExample.h @@ -32,6 +32,9 @@ class OTAProviderExample : public chip::app::Clusters::OTAProviderDelegate { public: + using OTAQueryStatus = chip::app::Clusters::OtaSoftwareUpdateProvider::OTAQueryStatus; + using OTAApplyUpdateAction = chip::app::Clusters::OtaSoftwareUpdateProvider::OTAApplyUpdateAction; + OTAProviderExample(); void SetOTAFilePath(const char * path); @@ -48,15 +51,10 @@ class OTAProviderExample : public chip::app::Clusters::OTAProviderDelegate chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::DecodableType & commandData) override; - enum QueryImageBehaviorType - { - kRespondWithUnknown, - kRespondWithUpdateAvailable, - kRespondWithBusy, - kRespondWithNotAvailable - }; static constexpr uint16_t SW_VER_STR_MAX_LEN = 64; static constexpr uint16_t OTA_URL_MAX_LEN = 512; + static constexpr size_t kFilepathBufLen = 256; + typedef struct DeviceSoftwareVersionModel { chip::VendorId vendorId; @@ -69,10 +67,11 @@ class OTAProviderExample : public chip::app::Clusters::OTAProviderDelegate uint32_t maxApplicableSoftwareVersion; char otaURL[OTA_URL_MAX_LEN]; } DeviceSoftwareVersionModel; + void SetOTACandidates(std::vector candidates); - void SetQueryImageBehavior(QueryImageBehaviorType behavior) { mQueryImageBehavior = behavior; } void SetIgnoreQueryImageCount(uint32_t count) { mIgnoreQueryImageCount = count; } void SetIgnoreApplyUpdateCount(uint32_t count) { mIgnoreApplyUpdateCount = count; } + void SetQueryImageStatus(OTAQueryStatus status) { mQueryImageStatus = status; } void SetApplyUpdateAction(chip::app::Clusters::OtaSoftwareUpdateProvider::OTAApplyUpdateAction action) { mUpdateAction = action; @@ -94,16 +93,21 @@ class OTAProviderExample : public chip::app::Clusters::OTAProviderDelegate bool ParseOTAHeader(const char * otaFilePath, chip::OTAImageHeader & header); + CHIP_ERROR + SendQueryImageResponse(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImage::DecodableType & commandData); + BdxOtaSender mBdxOtaSender; std::vector mCandidates; - static constexpr size_t kFilepathBufLen = 256; char mOTAFilePath[kFilepathBufLen]; // null-terminated - QueryImageBehaviorType mQueryImageBehavior; - uint32_t mIgnoreQueryImageCount = 0; - uint32_t mIgnoreApplyUpdateCount = 0; - chip::app::Clusters::OtaSoftwareUpdateProvider::OTAApplyUpdateAction mUpdateAction; - uint32_t mDelayedApplyActionTimeSec = 0; - uint32_t mDelayedQueryActionTimeSec = 0; - chip::ota::UserConsentDelegate * mUserConsentDelegate = nullptr; - bool mUserConsentNeeded = false; + OTAQueryStatus mQueryImageStatus; + OTAApplyUpdateAction mUpdateAction; + uint32_t mIgnoreQueryImageCount; + uint32_t mIgnoreApplyUpdateCount; + uint32_t mDelayedQueryActionTimeSec; + uint32_t mDelayedApplyActionTimeSec; + chip::ota::UserConsentDelegate * mUserConsentDelegate; + bool mUserConsentNeeded; + uint32_t mSoftwareVersion; + char mSoftwareVersionString[SW_VER_STR_MAX_LEN]; };