From 13999608f2394973bd3aadc578ff0d1c1d287c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Kr=C3=B3lik?= <66667989+Damian-Nordic@users.noreply.github.com> Date: Sat, 11 Dec 2021 08:20:02 +0100 Subject: [PATCH] [ota-requestor] Add ApplyUpdate command support to OTARequestor class (#12796) * [ota-requestor] Move interfaces to include/platform * [ota-requestor] Add ApplyUpdate command support to OTARequestor class 1. Make OTARequestor class able to send ApplyUpdate command. 2. Add Apply() method OTAImageProcessor so that an image can be applied on ApplyUpdateResponse. * Use node ID as Update Token if the latter is unknown 1. Validate QueryImageResponse to make sure that all fields for the Update Available case are present. 2. Use OTA Requestor node ID as Update Token if the original update token is lost. --- .../linux/LinuxOTAImageProcessor.cpp | 5 + .../linux/LinuxOTAImageProcessor.h | 3 +- .../linux/LinuxOTARequestorDriver.h | 2 +- examples/ota-requestor-app/linux/main.cpp | 2 +- .../ota-requestor/ClusterInterface.cpp | 2 +- .../clusters/ota-requestor/OTADownloader.h | 4 +- .../clusters/ota-requestor/OTARequestor.cpp | 99 +++++++++++++++++-- src/app/clusters/ota-requestor/OTARequestor.h | 43 ++++++-- .../platform}/OTAImageProcessor.h | 5 + .../platform}/OTARequestorDriver.h | 0 .../platform}/OTARequestorInterface.h | 0 11 files changed, 143 insertions(+), 22 deletions(-) rename src/{app/clusters/ota-requestor => include/platform}/OTAImageProcessor.h (96%) rename src/{app/clusters/ota-requestor => include/platform}/OTARequestorDriver.h (100%) rename src/{app/clusters/ota-requestor => include/platform}/OTARequestorInterface.h (100%) diff --git a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp index 41f717bfa3be03..0465a271409930 100644 --- a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp +++ b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp @@ -40,6 +40,11 @@ CHIP_ERROR LinuxOTAImageProcessor::Finalize() return CHIP_NO_ERROR; } +CHIP_ERROR LinuxOTAImageProcessor::Apply() +{ + return CHIP_NO_ERROR; +} + CHIP_ERROR LinuxOTAImageProcessor::Abort() { if (mParams.imageFile.empty()) diff --git a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h index e36b78dc78abac..ed66a4c091700f 100644 --- a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h +++ b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h @@ -19,8 +19,8 @@ #pragma once #include -#include #include +#include #include @@ -32,6 +32,7 @@ class LinuxOTAImageProcessor : public OTAImageProcessorInterface //////////// OTAImageProcessorInterface Implementation /////////////// CHIP_ERROR PrepareDownload() override; CHIP_ERROR Finalize() override; + CHIP_ERROR Apply() override; CHIP_ERROR Abort() override; CHIP_ERROR ProcessBlock(ByteSpan & block) override; diff --git a/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h b/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h index d3c00709ea794b..f039d32c1ae980 100644 --- a/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h +++ b/examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h @@ -19,7 +19,7 @@ /* This file contains the decalarions for the Linux implementation of the * the OTARequestorDriver interface class */ -#include "app/clusters/ota-requestor/OTARequestorDriver.h" +#include namespace chip { diff --git a/examples/ota-requestor-app/linux/main.cpp b/examples/ota-requestor-app/linux/main.cpp index 679bbb8434bb52..89cf37d2cc8dd2 100644 --- a/examples/ota-requestor-app/linux/main.cpp +++ b/examples/ota-requestor-app/linux/main.cpp @@ -231,7 +231,7 @@ int main(int argc, char * argv[]) if (delayQueryTimeInSec > 0) { // In this mode Provider node ID and fabric idx must be supplied explicitly from program args - gRequestorCore.TestModeSetProviderParameters(providerNodeId, providerFabricIndex); + gRequestorCore.TestModeSetProviderParameters(providerNodeId, providerFabricIndex, chip::kRootEndpointId); chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(delayQueryTimeInSec * 1000), OnStartDelayTimerHandler, nullptr); diff --git a/src/app/clusters/ota-requestor/ClusterInterface.cpp b/src/app/clusters/ota-requestor/ClusterInterface.cpp index 599524aae87864..03bee4216f9fe3 100644 --- a/src/app/clusters/ota-requestor/ClusterInterface.cpp +++ b/src/app/clusters/ota-requestor/ClusterInterface.cpp @@ -20,7 +20,7 @@ * to the OTA Requestor object that handles them */ -#include "OTARequestorInterface.h" +#include // OTA Software Update Requestor Cluster AnnounceOtaProvider Command callback bool emberAfOtaSoftwareUpdateRequestorClusterAnnounceOtaProviderCallback( diff --git a/src/app/clusters/ota-requestor/OTADownloader.h b/src/app/clusters/ota-requestor/OTADownloader.h index 5ae41e91603dc6..b77514c35b53b2 100644 --- a/src/app/clusters/ota-requestor/OTADownloader.h +++ b/src/app/clusters/ota-requestor/OTADownloader.h @@ -25,9 +25,8 @@ #pragma once -#include "OTAImageProcessor.h" - #include +#include namespace chip { @@ -69,6 +68,7 @@ class OTADownloader // A setter for the delegate class pointer void SetImageProcessorDelegate(OTAImageProcessorInterface * delegate) { mImageProcessor = delegate; } + OTAImageProcessorInterface * GetImageProcessorDelegate() const { return mImageProcessor; } State GetState() const { return mState; } diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp index 61d365ae923105..4fb512a4c45698 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.cpp +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -104,16 +105,16 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse { LogQueryImageResponse(response); - VerifyOrReturn(context != nullptr, ChipLogError(SoftwareUpdate, "Received QueryImageResponse with invalid context")); - OTARequestor * requestorCore = static_cast(context); + VerifyOrReturn(requestorCore != nullptr, ChipLogError(SoftwareUpdate, "Received QueryImageResponse with invalid context")); + // TODO: Add a method to OTARequestorDriver used to report error condictions + VerifyOrReturn(requestorCore->ValidateQueryImageResponse(response), + ChipLogError(SoftwareUpdate, "Received invalid QueryImageResponse")); + switch (response.status) { case EMBER_ZCL_OTA_QUERY_STATUS_UPDATE_AVAILABLE: { - // TODO: Add a method to OTARequestorDriver used to report error condictions - VerifyOrReturn(response.imageURI.HasValue(), ChipLogError(SoftwareUpdate, "Update is available but no image URI present")); - // Parse out the provider node ID and file designator from the image URI NodeId nodeId = kUndefinedNodeId; CharSpan fileDesignator; @@ -124,6 +125,11 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse err.Format())); requestorCore->mProviderNodeId = nodeId; + MutableByteSpan updateToken(requestorCore->mUpdateTokenBuffer); + CopySpanToMutableSpan(response.updateToken.Value(), updateToken); + requestorCore->mUpdateVersion = response.softwareVersion.Value(); + requestorCore->mUpdateToken = updateToken; + // CSM should already be created for sending QueryImage command so use the same CSM since the // provider node ID that will supply the OTA image must be on the same fabric as the sender of the QueryImageResponse requestorCore->ConnectToProvider(kStartBDX); @@ -145,6 +151,32 @@ void OTARequestor::OnQueryImageFailure(void * context, EmberAfStatus status) ChipLogDetail(SoftwareUpdate, "QueryImage failure response %" PRIu8, status); } +void OTARequestor::OnApplyUpdateResponse(void * context, const ApplyUpdateResponse::DecodableType & response) +{ + VerifyOrReturn(context != nullptr, ChipLogError(SoftwareUpdate, "Received ApplyUpdateResponse with invalid context")); + + OTARequestor * requestorCore = static_cast(context); + + switch (response.action) + { + case EMBER_ZCL_OTA_APPLY_UPDATE_ACTION_PROCEED: { + // TODO: Call OTARequestorDriver to schedule the image application. + VerifyOrReturn(requestorCore->mBdxDownloader != nullptr, ChipLogError(SoftwareUpdate, "Downloader is not set")); + OTAImageProcessorInterface * imageProcessor = requestorCore->mBdxDownloader->GetImageProcessorDelegate(); + VerifyOrReturn(imageProcessor != nullptr, ChipLogError(SoftwareUpdate, "Image processor is not set")); + imageProcessor->Apply(); + break; + } + default: + break; + } +} + +void OTARequestor::OnApplyUpdateFailure(void * context, EmberAfStatus status) +{ + ChipLogDetail(SoftwareUpdate, "ApplyUpdate failure response %" PRIu8, status); +} + EmberAfStatus OTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const AnnounceOtaProvider::DecodableType & commandData) @@ -261,15 +293,13 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr switch (requestorCore->mOnConnectedAction) { case kQueryImage: { - constexpr EndpointId kOtaProviderEndpoint = 0; - QueryImageRequest request; CHIP_ERROR err = requestorCore->BuildQueryImageRequest(request); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(SoftwareUpdate, "Failed to build QueryImage command: %" CHIP_ERROR_FORMAT, err.Format())); Controller::OtaSoftwareUpdateProviderCluster cluster; - cluster.Associate(deviceProxy, kOtaProviderEndpoint); + cluster.Associate(deviceProxy, requestorCore->mProviderEndpointId); err = cluster.InvokeCommand(request.args, requestorCore, OnQueryImageResponse, OnQueryImageFailure); VerifyOrReturn(err == CHIP_NO_ERROR, @@ -325,6 +355,21 @@ void OTARequestor::OnConnected(void * context, OperationalDeviceProxy * devicePr ChipLogError(SoftwareUpdate, "Cannot begin prepare download: %" CHIP_ERROR_FORMAT, err.Format())); break; } + case kApplyUpdate: { + ApplyUpdateRequest::Type args; + CHIP_ERROR err = requestorCore->BuildApplyUpdateRequest(args); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(SoftwareUpdate, "Failed to build ApplyUpdate command: %" CHIP_ERROR_FORMAT, err.Format())); + + Controller::OtaSoftwareUpdateProviderCluster cluster; + cluster.Associate(deviceProxy, requestorCore->mProviderEndpointId); + + err = cluster.InvokeCommand(args, requestorCore, OnApplyUpdateResponse, OnApplyUpdateFailure); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(SoftwareUpdate, "Failed to send ApplyUpdate command: %" CHIP_ERROR_FORMAT, err.Format())); + + break; + } default: break; } @@ -351,6 +396,11 @@ void OTARequestor::OnConnectionFailure(void * context, NodeId deviceId, CHIP_ERR ChipLogError(SoftwareUpdate, "Failed to connect to node 0x%" PRIX64 ": %" CHIP_ERROR_FORMAT, deviceId, error.Format()); } +void OTARequestor::ApplyUpdate() +{ + ConnectToProvider(kApplyUpdate); +} + CHIP_ERROR OTARequestor::BuildQueryImageRequest(QueryImageRequest & request) { constexpr EmberAfOTADownloadProtocol kProtocolsSupported[] = { EMBER_ZCL_OTA_DOWNLOAD_PROTOCOL_BDX_SYNCHRONOUS }; @@ -385,4 +435,37 @@ CHIP_ERROR OTARequestor::BuildQueryImageRequest(QueryImageRequest & request) return CHIP_NO_ERROR; } +bool OTARequestor::ValidateQueryImageResponse(const QueryImageResponse::DecodableType & response) const +{ + if (response.status == EMBER_ZCL_OTA_QUERY_STATUS_UPDATE_AVAILABLE) + { + VerifyOrReturnError(response.imageURI.HasValue(), false); + VerifyOrReturnError(response.softwareVersion.HasValue() && response.softwareVersionString.HasValue(), false); + VerifyOrReturnError(response.updateToken.HasValue(), false); + } + + return true; +} + +CHIP_ERROR OTARequestor::BuildApplyUpdateRequest(ApplyUpdateRequest::Type & args) +{ + if (mUpdateToken.empty()) + { + // OTA Requestor shall use its node ID as the update token in case the original update + // token, received in QueryImageResponse, got lost. + VerifyOrReturnError(mServer != nullptr, CHIP_ERROR_INCORRECT_STATE); + + FabricInfo * fabricInfo = mServer->GetFabricTable().FindFabricWithIndex(mProviderFabricIndex); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); + + static_assert(sizeof(NodeId) == sizeof(uint64_t), "Unexpected NodeId size"); + Encoding::BigEndian::Put64(mUpdateTokenBuffer, fabricInfo->GetPeerId().GetNodeId()); + mUpdateToken = ByteSpan(mUpdateTokenBuffer, sizeof(NodeId)); + } + + args.updateToken = mUpdateToken; + args.newVersion = mUpdateVersion; + return CHIP_NO_ERROR; +} + } // namespace chip diff --git a/src/app/clusters/ota-requestor/OTARequestor.h b/src/app/clusters/ota-requestor/OTARequestor.h index 4425cfd0eb560c..2ffe6c1142a062 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.h +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -24,11 +24,11 @@ #include #include +#include +#include #include #include "BDXDownloader.h" -#include "OTARequestorDriver.h" -#include "OTARequestorInterface.h" namespace chip { @@ -41,6 +41,7 @@ class OTARequestor : public OTARequestorInterface { kQueryImage = 0, kStartBDX, + kApplyUpdate, }; OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {} @@ -58,6 +59,9 @@ class OTARequestor : public OTARequestorInterface // and download the new image if available OTATriggerResult TriggerImmediateQuery(); + // Send ApplyImage + void ApplyUpdate(); + // A setter for the delegate class pointer void SetOtaRequestorDriver(OTARequestorDriver * driver) { mOtaRequestorDriver = driver; } @@ -112,14 +116,19 @@ class OTARequestor : public OTARequestorInterface * Called to indicate test mode. This is when the Requestor is used as a test tool and the the provider parameters are supplied * explicitly. */ - void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex) + void TestModeSetProviderParameters(NodeId nodeId, FabricIndex fabIndex, EndpointId endpointId) { mProviderNodeId = nodeId; mProviderFabricIndex = fabIndex; + mProviderEndpointId = endpointId; } private: struct QueryImageRequest; + using QueryImageResponseDecodableType = app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType; + using ApplyUpdateResponseDecodableType = app::Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateResponse::DecodableType; + + static constexpr size_t kMaxUpdateTokenLen = 32; // TODO: the application should define this, along with initializing the BDXDownloader @@ -196,7 +205,17 @@ class OTARequestor : public OTARequestorInterface /** * Create a QueryImage request using values from the Basic cluster attributes */ - CHIP_ERROR BuildQueryImageRequest(QueryImageRequest & req); + CHIP_ERROR BuildQueryImageRequest(QueryImageRequest & request); + + /** + * Verify all required fields are present in the QueryImageResponse + */ + bool ValidateQueryImageResponse(const QueryImageResponseDecodableType & response) const; + + /** + * Create a ApplyUpdate request using values obtained from QueryImageResponse + */ + CHIP_ERROR BuildApplyUpdateRequest(app::Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateRequest::Type & args); /** * Session connection callbacks @@ -209,21 +228,29 @@ class OTARequestor : public OTARequestorInterface /** * QueryImage callbacks */ - static void - OnQueryImageResponse(void * context, - const app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType & response); + static void OnQueryImageResponse(void * context, const QueryImageResponseDecodableType & response); static void OnQueryImageFailure(void * context, EmberAfStatus status); + /** + * ApplyUpdate callbacks + */ + static void OnApplyUpdateResponse(void * context, const ApplyUpdateResponseDecodableType & response); + static void OnApplyUpdateFailure(void * context, EmberAfStatus); + OTARequestorDriver * mOtaRequestorDriver = nullptr; NodeId mProviderNodeId = kUndefinedNodeId; FabricIndex mProviderFabricIndex = kUndefinedFabricIndex; + EndpointId mProviderEndpointId = kRootEndpointId; uint32_t mOtaStartDelayMs = 0; CASESessionManager * mCASESessionManager = nullptr; OnConnectedAction mOnConnectedAction = kQueryImage; Messaging::ExchangeContext * mExchangeCtx = nullptr; BDXDownloader * mBdxDownloader = nullptr; // TODO: this should be OTADownloader BDXMessenger mBdxMessenger; // TODO: ideally this is held by the application - Server * mServer = nullptr; + uint8_t mUpdateTokenBuffer[kMaxUpdateTokenLen]; + ByteSpan mUpdateToken; + uint32_t mUpdateVersion = 0; + Server * mServer = nullptr; }; } // namespace chip diff --git a/src/app/clusters/ota-requestor/OTAImageProcessor.h b/src/include/platform/OTAImageProcessor.h similarity index 96% rename from src/app/clusters/ota-requestor/OTAImageProcessor.h rename to src/include/platform/OTAImageProcessor.h index 7afc845d56efdd..7aaa346a8a5196 100644 --- a/src/app/clusters/ota-requestor/OTAImageProcessor.h +++ b/src/include/platform/OTAImageProcessor.h @@ -58,6 +58,11 @@ class DLL_EXPORT OTAImageProcessorInterface */ virtual CHIP_ERROR Finalize() = 0; + /** + * Called when the OTA image should be applied. + */ + virtual CHIP_ERROR Apply() = 0; + /** * Called when the OTA image download process is incomplete or cannot continue. This may include but not limited to erasing * everything that has been written and releasing buffers. This must not be a blocking call. diff --git a/src/app/clusters/ota-requestor/OTARequestorDriver.h b/src/include/platform/OTARequestorDriver.h similarity index 100% rename from src/app/clusters/ota-requestor/OTARequestorDriver.h rename to src/include/platform/OTARequestorDriver.h diff --git a/src/app/clusters/ota-requestor/OTARequestorInterface.h b/src/include/platform/OTARequestorInterface.h similarity index 100% rename from src/app/clusters/ota-requestor/OTARequestorInterface.h rename to src/include/platform/OTARequestorInterface.h