diff --git a/config/standalone/CHIPProjectConfig.h b/config/standalone/CHIPProjectConfig.h index 4b008de5f09d3b..dec16a633ac165 100644 --- a/config/standalone/CHIPProjectConfig.h +++ b/config/standalone/CHIPProjectConfig.h @@ -69,6 +69,14 @@ #define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 4 #endif +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 1 +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "v1.0" +#endif + // // Default of 8 ECs is not sufficient for some of the unit tests // that try to validate multiple simultaneous interactions. diff --git a/examples/ota-requestor-app/linux/main.cpp b/examples/ota-requestor-app/linux/main.cpp index b02cae2c49ac3f..b86082c6d6b9ff 100644 --- a/examples/ota-requestor-app/linux/main.cpp +++ b/examples/ota-requestor-app/linux/main.cpp @@ -189,5 +189,23 @@ int main(int argc, char * argv[]) { VerifyOrDie(ChipLinuxAppInit(argc, argv, &cmdLineOptions) == 0); ChipLinuxAppMainLoop(); + + // If the event loop had been stopped due to an update being applied, boot into the new image + if (gRequestorCore.GetCurrentUpdateState() == OTARequestor::OTAUpdateStateEnum::kApplying) + { + if (kMaxFilePathSize <= strlen(kImageExecPath)) + { + ChipLogError(SoftwareUpdate, "Buffer too small for the new image file path: %s", kImageExecPath); + return -1; + } + + char execFilePathBuf[kMaxFilePathSize]; + strncpy(execFilePathBuf, kImageExecPath, strlen(kImageExecPath)); + argv[0] = execFilePathBuf; + execv(argv[0], argv); + + // If successfully executing the new iamge, execv should not return + ChipLogError(SoftwareUpdate, "The OTA image is invalid"); + } return 0; } diff --git a/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.cpp b/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.cpp index e16a10f0300350..17374b43e7dc99 100644 --- a/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.cpp +++ b/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.cpp @@ -134,6 +134,40 @@ CHIP_ERROR DefaultOTARequestorStorage::LoadUpdateToken(MutableByteSpan & updateT return Load(DefaultStorageKeyAllocator::OTAUpdateToken(), updateToken); } +CHIP_ERROR DefaultOTARequestorStorage::StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState) +{ + return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState(), ¤tUpdateState, + sizeof(currentUpdateState)); +} + +CHIP_ERROR DefaultOTARequestorStorage::LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState) +{ + uint16_t size = static_cast(sizeof(currentUpdateState)); + return mPersistentStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState(), ¤tUpdateState, size); +} + +CHIP_ERROR DefaultOTARequestorStorage::ClearCurrentUpdateState() +{ + return mPersistentStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState()); +} + +CHIP_ERROR DefaultOTARequestorStorage::StoreTargetVersion(uint32_t targetVersion) +{ + return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::OTATargetVersion(), &targetVersion, + sizeof(targetVersion)); +} + +CHIP_ERROR DefaultOTARequestorStorage::LoadTargetVersion(uint32_t & targetVersion) +{ + uint16_t size = static_cast(sizeof(targetVersion)); + return mPersistentStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::OTATargetVersion(), &targetVersion, size); +} + +CHIP_ERROR DefaultOTARequestorStorage::ClearTargetVersion() +{ + return mPersistentStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::OTATargetVersion()); +} + CHIP_ERROR DefaultOTARequestorStorage::Load(const char * key, MutableByteSpan & buffer) { uint16_t size = static_cast(buffer.size()); diff --git a/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.h b/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.h index a9f973de43c726..048e8d4d09d973 100644 --- a/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.h +++ b/src/app/clusters/ota-requestor/DefaultOTARequestorStorage.h @@ -40,6 +40,14 @@ class DefaultOTARequestorStorage : public OTARequestorStorage CHIP_ERROR ClearUpdateToken() override; CHIP_ERROR LoadUpdateToken(MutableByteSpan & updateToken) override; + CHIP_ERROR StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState) override; + CHIP_ERROR LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState) override; + CHIP_ERROR ClearCurrentUpdateState() override; + + CHIP_ERROR StoreTargetVersion(uint32_t targetVersion) override; + CHIP_ERROR LoadTargetVersion(uint32_t & targetVersion) override; + CHIP_ERROR ClearTargetVersion() override; + private: CHIP_ERROR Load(const char * key, MutableByteSpan & buffer); PersistentStorageDelegate * mPersistentStorage = nullptr; diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp index 059c590f035fbd..a383c9910b8ec3 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.cpp +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -107,12 +107,40 @@ void OTARequestor::InitState(intptr_t context) OTARequestor * requestorCore = reinterpret_cast(context); VerifyOrDie(requestorCore != nullptr); - // This results in the initial periodic timer kicking off - requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); + if (requestorCore->mCurrentUpdateState != OTAUpdateStateEnum::kApplying) + { + // This results in the initial periodic timer kicking off + requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); + } + else + { + // This may have been a reboot from applying an image + OtaRequestorServerSetUpdateState(requestorCore->mCurrentUpdateState); + } OtaRequestorServerSetUpdateStateProgress(app::DataModel::NullNullable); } +CHIP_ERROR OTARequestor::Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver, + BDXDownloader & downloader) +{ + mServer = &server; + mCASESessionManager = server.GetCASESessionManager(); + mStorage = &storage; + mOtaRequestorDriver = &driver; + mBdxDownloader = &downloader; + + ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(mCurrentVersion)); + + // Load data from KVS + LoadCurrentUpdateInfo(); + + // Schedule the initializations that needs to be performed in the CHIP context + DeviceLayer::PlatformMgr().ScheduleWork(InitState, reinterpret_cast(this)); + + return chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(OnCommissioningCompleteRequestor, reinterpret_cast(this)); +} + void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response) { LogQueryImageResponse(response); @@ -159,7 +187,6 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse memcpy(fileDesignator.data(), update.fileDesignator.data(), update.fileDesignator.size()); fileDesignator.reduce_size(update.fileDesignator.size()); requestorCore->mFileDesignator = fileDesignator; - requestorCore->StoreCurrentUpdateInfo(); requestorCore->mOtaRequestorDriver->UpdateAvailable(update, System::Clock::Seconds32(response.delayedActionTime.ValueOr(0))); @@ -253,6 +280,11 @@ void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, CHIP_ERROR error requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, error); } +void OTARequestor::Shutdown(void) +{ + mServer->DispatchShutDownAndStopEventLoop(); +} + EmberAfStatus OTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const AnnounceOtaProvider::DecodableType & commandData) @@ -365,14 +397,14 @@ void OTARequestor::CancelImageUpdate() RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kUnknown); } -CHIP_ERROR OTARequestor::GetUpdateProgress(EndpointId endpointId, app::DataModel::Nullable & progress) +CHIP_ERROR OTARequestor::GetUpdateStateProgressAttribute(EndpointId endpointId, app::DataModel::Nullable & progress) { VerifyOrReturnError(OtaRequestorServerGetUpdateStateProgress(endpointId, progress) == EMBER_ZCL_STATUS_SUCCESS, CHIP_ERROR_BAD_REQUEST); return CHIP_NO_ERROR; } -CHIP_ERROR OTARequestor::GetState(EndpointId endpointId, OTAUpdateStateEnum & state) +CHIP_ERROR OTARequestor::GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state) { VerifyOrReturnError(OtaRequestorServerGetUpdateState(endpointId, state) == EMBER_ZCL_STATUS_SUCCESS, CHIP_ERROR_BAD_REQUEST); return CHIP_NO_ERROR; @@ -504,6 +536,10 @@ void OTARequestor::DownloadUpdateDelayedOnUserConsent() void OTARequestor::ApplyUpdate() { RecordNewUpdateState(OTAUpdateStateEnum::kApplying, OTAChangeReasonEnum::kSuccess); + + // If image is successfully applied, the device will reboot so persist all relevant data + StoreCurrentUpdateInfo(); + ConnectToProvider(kApplyUpdate); } @@ -520,9 +556,6 @@ void OTARequestor::NotifyUpdateApplied() OtaRequestorServerOnVersionApplied(mCurrentVersion, productId); - // There is no response for a notify so consider this OTA complete - RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); - ConnectToProvider(kNotifyUpdateApplied); } @@ -788,7 +821,9 @@ CHIP_ERROR OTARequestor::SendNotifyUpdateAppliedRequest(OperationalDeviceProxy & Controller::OtaSoftwareUpdateProviderCluster cluster; cluster.Associate(&deviceProxy, mProviderLocation.Value().endpoint); - mProviderLocation.ClearValue(); // Clearing the last used provider location to start afresh on reboot + // There is no response for a notify so consider this OTA complete. Clear the provider location and reset any states to indicate + // so. + Reset(); return cluster.InvokeCommand(args, this, OnNotifyUpdateAppliedResponse, OnNotifyUpdateAppliedFailure); } @@ -796,11 +831,43 @@ CHIP_ERROR OTARequestor::SendNotifyUpdateAppliedRequest(OperationalDeviceProxy & void OTARequestor::StoreCurrentUpdateInfo() { // TODO: change OTA requestor storage interface to store both values at once - CHIP_ERROR error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value()); + CHIP_ERROR error = CHIP_NO_ERROR; + if (mProviderLocation.HasValue()) + { + error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value()); + } + else + { + error = mStorage->ClearCurrentProviderLocation(); + } + + if (error == CHIP_NO_ERROR) + { + if (mUpdateToken.size() > 0) + { + error = mStorage->StoreUpdateToken(mUpdateToken); + } + else + { + error = mStorage->ClearUpdateToken(); + } + } + + if (error == CHIP_NO_ERROR) + { + error = mStorage->StoreCurrentUpdateState(mCurrentUpdateState); + } if (error == CHIP_NO_ERROR) { - mStorage->StoreUpdateToken(mUpdateToken); + if (mTargetVersion > 0) + { + error = mStorage->StoreTargetVersion(mTargetVersion); + } + else + { + error = mStorage->ClearTargetVersion(); + } } if (error != CHIP_NO_ERROR) @@ -809,6 +876,44 @@ void OTARequestor::StoreCurrentUpdateInfo() } } +void OTARequestor::LoadCurrentUpdateInfo() +{ + mStorage->LoadDefaultProviders(mDefaultOtaProviderList); + + ProviderLocationType providerLocation; + if (mStorage->LoadCurrentProviderLocation(providerLocation) == CHIP_NO_ERROR) + { + mProviderLocation.SetValue(providerLocation); + } + + MutableByteSpan updateToken(mUpdateTokenBuffer); + if (mStorage->LoadUpdateToken(updateToken) == CHIP_NO_ERROR) + { + mUpdateToken = updateToken; + } + + if (mStorage->LoadCurrentUpdateState(mCurrentUpdateState) != CHIP_NO_ERROR) + { + mCurrentUpdateState = OTAUpdateStateEnum::kUnknown; + } + + if (mStorage->LoadTargetVersion(mTargetVersion) != CHIP_NO_ERROR) + { + mTargetVersion = 0; + } +} + +void OTARequestor::Reset() +{ + mProviderLocation.ClearValue(); + mUpdateToken.reduce_size(0); + RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess); + mTargetVersion = 0; + + // Persist in case of a reboot or crash + StoreCurrentUpdateInfo(); +} + // Invoked when the device becomes commissioned void OTARequestor::OnCommissioningCompleteRequestor(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) { diff --git a/src/app/clusters/ota-requestor/OTARequestor.h b/src/app/clusters/ota-requestor/OTARequestor.h index 7ac6d08a0bae8b..51f6c4f4ec48e3 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.h +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -40,6 +40,8 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {} //////////// OTARequestorInterface Implementation /////////////// + void Shutdown(void) override; + EmberAfStatus HandleAnnounceOTAProvider( app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) override; @@ -63,15 +65,19 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe // Initiate the session to send NotifyUpdateApplied command void NotifyUpdateApplied() override; - // Get image update progress in percents unit - CHIP_ERROR GetUpdateProgress(EndpointId endpointId, app::DataModel::Nullable & progress) override; + // Get the value of the UpdateStateProgress attribute (in percentage) of the OTA Software Update Requestor Cluster on the given + // endpoint + CHIP_ERROR GetUpdateStateProgressAttribute(EndpointId endpointId, app::DataModel::Nullable & progress) override; - // Get requestor state - CHIP_ERROR GetState(EndpointId endpointId, OTAUpdateStateEnum & state) override; + // Get the value of the UpdateState attribute of the OTA Software Update Requestor Cluster on the given endpoint + CHIP_ERROR GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state) override; // Get the current state of the OTA update OTAUpdateStateEnum GetCurrentUpdateState() override { return mCurrentUpdateState; } + // Get the target version of the OTA update + uint32_t GetTargetVersion() override { return mTargetVersion; } + // Application directs the Requestor to cancel image update in progress. All the Requestor state is // cleared, UpdateState is reset to Idle void CancelImageUpdate() override; @@ -100,42 +106,10 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe //////////// OTARequestor public APIs /////////////// /** - * Called to perform some initialization including: - * - Set server instance used to get access to the system resources necessary to open CASE sessions and drive - * BDX transfers - * - Set the OTA requestor driver instance used to communicate download progress and errors - * - Set the BDX downloader instance used for initiating BDX downloads + * Called to perform some initialization. Note that some states that must be initalized in the CHIP context will be deferred to + * InitState. */ - CHIP_ERROR Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver, BDXDownloader & downloader) - { - mServer = &server; - mCASESessionManager = server.GetCASESessionManager(); - mStorage = &storage; - mOtaRequestorDriver = &driver; - mBdxDownloader = &downloader; - - ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(mCurrentVersion)); - - storage.LoadDefaultProviders(mDefaultOtaProviderList); - - ProviderLocationType providerLocation; - if (storage.LoadCurrentProviderLocation(providerLocation) == CHIP_NO_ERROR) - { - mProviderLocation.SetValue(providerLocation); - } - - MutableByteSpan updateToken(mUpdateTokenBuffer); - if (storage.LoadUpdateToken(updateToken) == CHIP_NO_ERROR) - { - mUpdateToken = updateToken; - } - - // Schedule the initializations that needs to be performed in the CHIP context - DeviceLayer::PlatformMgr().ScheduleWork(InitState, reinterpret_cast(this)); - - return chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(OnCommissioningCompleteRequestor, - reinterpret_cast(this)); - } + CHIP_ERROR Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver, BDXDownloader & downloader); private: using QueryImageResponseDecodableType = app::Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::DecodableType; @@ -285,6 +259,16 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe */ void StoreCurrentUpdateInfo(); + /** + * Load current update information to KVS + */ + void LoadCurrentUpdateInfo(); + + /** + * Reset the states for the next OTA update + */ + void Reset(); + /** * Session connection callbacks */ diff --git a/src/app/clusters/ota-requestor/OTARequestorInterface.h b/src/app/clusters/ota-requestor/OTARequestorInterface.h index f878fae67edfd1..6c6f5216ebf8f1 100644 --- a/src/app/clusters/ota-requestor/OTARequestorInterface.h +++ b/src/app/clusters/ota-requestor/OTARequestorInterface.h @@ -153,6 +153,9 @@ class OTARequestorInterface kWrongState = 2 }; + // Perform any clean up necessary + virtual void Shutdown(void) = 0; + // Handler for the AnnounceOTAProvider command virtual EmberAfStatus HandleAnnounceOTAProvider( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, @@ -180,15 +183,20 @@ class OTARequestorInterface // Initiate the session to send NotifyUpdateApplied command virtual void NotifyUpdateApplied() = 0; - // Get image update progress in percents unit - virtual CHIP_ERROR GetUpdateProgress(EndpointId endpointId, chip::app::DataModel::Nullable & progress) = 0; + // Get the value of the UpdateStateProgress attribute (in percentage) of the OTA Software Update Requestor Cluster on the given + // endpoint + virtual CHIP_ERROR GetUpdateStateProgressAttribute(EndpointId endpointId, + chip::app::DataModel::Nullable & progress) = 0; // Get the value of the UpdateState attribute of the OTA Software Update Requestor Cluster on the given endpoint - virtual CHIP_ERROR GetState(EndpointId endpointId, OTAUpdateStateEnum & state) = 0; + virtual CHIP_ERROR GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state) = 0; // Get the current state of the OTA update virtual OTAUpdateStateEnum GetCurrentUpdateState() = 0; + // Get the target version of the OTA update + virtual uint32_t GetTargetVersion() = 0; + // Application directs the Requestor to cancel image update in progress. All the Requestor state is // cleared, UpdateState is reset to Idle virtual void CancelImageUpdate() = 0; diff --git a/src/app/clusters/ota-requestor/OTARequestorStorage.h b/src/app/clusters/ota-requestor/OTARequestorStorage.h index 42a918be21f795..5275146e1fc8ac 100644 --- a/src/app/clusters/ota-requestor/OTARequestorStorage.h +++ b/src/app/clusters/ota-requestor/OTARequestorStorage.h @@ -29,6 +29,7 @@ class OTARequestorStorage { public: using ProviderLocationType = app::Clusters::OtaSoftwareUpdateRequestor::Structs::ProviderLocation::Type; + using OTAUpdateStateEnum = app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum; virtual ~OTARequestorStorage() {} @@ -42,6 +43,14 @@ class OTARequestorStorage virtual CHIP_ERROR StoreUpdateToken(ByteSpan updateToken) = 0; virtual CHIP_ERROR LoadUpdateToken(MutableByteSpan & updateToken) = 0; virtual CHIP_ERROR ClearUpdateToken() = 0; + + virtual CHIP_ERROR StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState) = 0; + virtual CHIP_ERROR LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState) = 0; + virtual CHIP_ERROR ClearCurrentUpdateState() = 0; + + virtual CHIP_ERROR StoreTargetVersion(uint32_t targetVersion) = 0; + virtual CHIP_ERROR LoadTargetVersion(uint32_t & targetVersion) = 0; + virtual CHIP_ERROR ClearTargetVersion() = 0; }; } // namespace chip diff --git a/src/lib/shell/commands/Ota.cpp b/src/lib/shell/commands/Ota.cpp index 1b144dce750ee0..4cc005c5d423bf 100644 --- a/src/lib/shell/commands/Ota.cpp +++ b/src/lib/shell/commands/Ota.cpp @@ -59,7 +59,7 @@ CHIP_ERROR NotifyImageHandler(int argc, char ** argv) static void HandleState(intptr_t context) { app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum state; - CHIP_ERROR err = GetRequestorInstance()->GetState(0, state); + CHIP_ERROR err = GetRequestorInstance()->GetUpdateStateAttribute(0, state); if (err == CHIP_NO_ERROR) { @@ -108,7 +108,7 @@ static void HandleState(intptr_t context) static void HandleProgress(intptr_t context) { chip::app::DataModel::Nullable progress; - CHIP_ERROR err = GetRequestorInstance()->GetUpdateProgress(0, progress); + CHIP_ERROR err = GetRequestorInstance()->GetUpdateStateProgressAttribute(0, progress); if (err == CHIP_NO_ERROR) { diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index d9df01301ef9b2..6e9c0f8c114869 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -79,6 +79,8 @@ class DefaultStorageKeyAllocator static const char * OTADefaultProviders() { return "o/dp"; } static const char * OTACurrentProvider() { return "o/cp"; } static const char * OTAUpdateToken() { return "o/ut"; } + static const char * OTACurrentUpdateState() { return "o/us"; } + static const char * OTATargetVersion() { return "o/tv"; } private: // The ENFORCE_FORMAT args are "off by one" because this is a class method, diff --git a/src/platform/Linux/OTAImageProcessorImpl.cpp b/src/platform/Linux/OTAImageProcessorImpl.cpp index 048e397266c5a0..844482431fa4a3 100644 --- a/src/platform/Linux/OTAImageProcessorImpl.cpp +++ b/src/platform/Linux/OTAImageProcessorImpl.cpp @@ -21,6 +21,8 @@ #include "OTAImageProcessorImpl.h" +#include + namespace chip { CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() @@ -82,6 +84,35 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) return CHIP_NO_ERROR; } +bool OTAImageProcessorImpl::IsFirstImageRun() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return false; + } + + return requestor->GetCurrentUpdateState() == OTARequestorInterface::OTAUpdateStateEnum::kApplying; +} + +CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage() +{ + OTARequestorInterface * requestor = chip::GetRequestorInstance(); + if (requestor == nullptr) + { + return CHIP_ERROR_INTERNAL; + } + + uint32_t currentVersion; + ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion)); + if (currentVersion != requestor->GetTargetVersion()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_NO_ERROR; +} + void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) { auto * imageProcessor = reinterpret_cast(context); @@ -96,6 +127,8 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) return; } + unlink(imageProcessor->mImageFile.data()); + imageProcessor->mHeaderParser.Init(); imageProcessor->mOfs.open(imageProcessor->mImageFile.data(), std::ofstream::out | std::ofstream::ate | std::ofstream::app); if (!imageProcessor->mOfs.good()) @@ -104,8 +137,6 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) return; } - // TODO: if file already exists and is not empty, erase previous contents - imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); } @@ -126,18 +157,18 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context) void OTAImageProcessorImpl::HandleApply(intptr_t context) { auto * imageProcessor = reinterpret_cast(context); - if (imageProcessor == nullptr) - { - return; - } + VerifyOrReturn(imageProcessor != nullptr); OTARequestorInterface * requestor = chip::GetRequestorInstance(); - if (requestor != nullptr) - { - // TODO: Implement restarting into new image instead of changing the version - DeviceLayer::ConfigurationMgr().StoreSoftwareVersion(imageProcessor->mSoftwareVersion); - requestor->NotifyUpdateApplied(); - } + VerifyOrReturn(requestor != nullptr); + + // Move the downloaded image to th location where the new image is to be executed from + unlink(kImageExecPath); + rename(imageProcessor->mImageFile.data(), kImageExecPath); + chmod(kImageExecPath, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + + // Shutdown the stack and expect to boot into the new image once shutdown is complete + requestor->Shutdown(); } void OTAImageProcessorImpl::HandleAbort(intptr_t context) @@ -149,7 +180,7 @@ void OTAImageProcessorImpl::HandleAbort(intptr_t context) } imageProcessor->mOfs.close(); - remove(imageProcessor->mImageFile.data()); + unlink(imageProcessor->mImageFile.data()); imageProcessor->ReleaseBlock(); } @@ -197,11 +228,6 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessHeader(ByteSpan & block) ReturnErrorCodeIf(error == CHIP_ERROR_BUFFER_TOO_SMALL, CHIP_NO_ERROR); ReturnErrorOnFailure(error); - // We save the software version to be used in the next NotifyUpdateApplied, but it's a non-standard - // behavior of the Linux implementation and the pattern should not be blindly followed by real-life - // products. In general, it's up to the implementation to decide which header fields will be - // validated or presented to the user. - mSoftwareVersion = header.mSoftwareVersion; mParams.totalFileBytes = header.mPayloadSize; mHeaderParser.Clear(); } diff --git a/src/platform/Linux/OTAImageProcessorImpl.h b/src/platform/Linux/OTAImageProcessorImpl.h index 79638334c447c5..a0d4298b4f1dd3 100644 --- a/src/platform/Linux/OTAImageProcessorImpl.h +++ b/src/platform/Linux/OTAImageProcessorImpl.h @@ -27,6 +27,9 @@ namespace chip { +// Full file path to where the new image will be executed from post-download +static constexpr char kImageExecPath[] = "/tmp/ota.update"; + class OTAImageProcessorImpl : public OTAImageProcessorInterface { public: @@ -36,8 +39,8 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface CHIP_ERROR Apply() override; CHIP_ERROR Abort() override; CHIP_ERROR ProcessBlock(ByteSpan & block) override; - bool IsFirstImageRun() override { return false; } - CHIP_ERROR ConfirmCurrentImage() override { return CHIP_NO_ERROR; } + bool IsFirstImageRun() override; + CHIP_ERROR ConfirmCurrentImage() override; void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } void SetOTAImageFile(CharSpan name) { mImageFile = name; } @@ -66,7 +69,6 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface MutableByteSpan mBlock; OTADownloader * mDownloader; OTAImageHeaderParser mHeaderParser; - uint32_t mSoftwareVersion; CharSpan mImageFile; };