Skip to content

Commit

Permalink
[ota-requestor] Add ApplyUpdate command support to OTARequestor class (
Browse files Browse the repository at this point in the history
…#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.
  • Loading branch information
Damian-Nordic authored and pull[bot] committed Feb 14, 2024
1 parent 5036c3d commit 1399960
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 22 deletions.
5 changes: 5 additions & 0 deletions examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
3 changes: 2 additions & 1 deletion examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
#pragma once

#include <app/clusters/ota-requestor/OTADownloader.h>
#include <app/clusters/ota-requestor/OTAImageProcessor.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/OTAImageProcessor.h>

#include <fstream>

Expand All @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion examples/ota-requestor-app/linux/LinuxOTARequestorDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <platform/OTARequestorDriver.h>

namespace chip {

Expand Down
2 changes: 1 addition & 1 deletion examples/ota-requestor-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/app/clusters/ota-requestor/ClusterInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* to the OTA Requestor object that handles them
*/

#include "OTARequestorInterface.h"
#include <platform/OTARequestorInterface.h>

// OTA Software Update Requestor Cluster AnnounceOtaProvider Command callback
bool emberAfOtaSoftwareUpdateRequestorClusterAnnounceOtaProviderCallback(
Expand Down
4 changes: 2 additions & 2 deletions src/app/clusters/ota-requestor/OTADownloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@

#pragma once

#include "OTAImageProcessor.h"

#include <lib/core/CHIPError.h>
#include <platform/OTAImageProcessor.h>

namespace chip {

Expand Down Expand Up @@ -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; }

Expand Down
99 changes: 91 additions & 8 deletions src/app/clusters/ota-requestor/OTARequestor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <app-common/zap-generated/attributes/Accessors.h>
#include <lib/core/CHIPEncoding.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/OTAImageProcessor.h>
#include <protocols/bdx/BdxUri.h>
#include <zap-generated/CHIPClusters.h>

Expand Down Expand Up @@ -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<OTARequestor *>(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;
Expand All @@ -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);
Expand All @@ -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<OTARequestor *>(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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand All @@ -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 };
Expand Down Expand Up @@ -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
43 changes: 35 additions & 8 deletions src/app/clusters/ota-requestor/OTARequestor.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@

#include <app/CASESessionManager.h>
#include <app/server/Server.h>
#include <platform/OTARequestorDriver.h>
#include <platform/OTARequestorInterface.h>
#include <protocols/bdx/BdxMessages.h>

#include "BDXDownloader.h"
#include "OTARequestorDriver.h"
#include "OTARequestorInterface.h"

namespace chip {

Expand All @@ -41,6 +41,7 @@ class OTARequestor : public OTARequestorInterface
{
kQueryImage = 0,
kStartBDX,
kApplyUpdate,
};

OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {}
Expand All @@ -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; }

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
File renamed without changes.

0 comments on commit 1399960

Please sign in to comment.