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