From 7560508d1e0b27b362fac843da2f1c374ae87471 Mon Sep 17 00:00:00 2001 From: Carol Yang Date: Wed, 23 Mar 2022 09:38:26 -0700 Subject: [PATCH] [OTA] Add support for applying image for Linux/Darwin platform (#16482) - Make sure driver restores the core states if there is a problem detected at initialization --- config/standalone/CHIPProjectConfig.h | 8 ++ .../nxp/k32w/k32w0/main/AppTask.cpp | 2 +- .../ota-requestor-app/efr32/src/AppTask.cpp | 2 +- examples/ota-requestor-app/linux/main.cpp | 18 ++- examples/platform/efr32/OTAConfig.cpp | 2 +- .../DefaultOTARequestorStorage.cpp | 34 +++++ .../DefaultOTARequestorStorage.h | 8 ++ .../GenericOTARequestorDriver.cpp | 11 ++ .../clusters/ota-requestor/OTARequestor.cpp | 128 ++++++++++++++++-- src/app/clusters/ota-requestor/OTARequestor.h | 58 +++----- .../ota-requestor/OTARequestorInterface.h | 17 ++- .../ota-requestor/OTARequestorStorage.h | 9 ++ .../tests/TestDefaultOTARequestorStorage.cpp | 41 +++++- .../python/test/test_scripts/base.py | 4 +- src/lib/shell/commands/Ota.cpp | 4 +- src/lib/support/DefaultStorageKeyAllocator.h | 2 + src/platform/CYW30739/PlatformManagerImpl.cpp | 5 + src/platform/EFR32/OTAImageProcessorImpl.h | 4 +- src/platform/Linux/OTAImageProcessorImpl.cpp | 70 +++++++--- src/platform/Linux/OTAImageProcessorImpl.h | 12 +- .../nxp/k32w/k32w0/OTAImageProcessorImpl.cpp | 8 +- .../nxp/k32w/k32w0/OTAImageProcessorImpl.h | 4 +- 22 files changed, 351 insertions(+), 100 deletions(-) diff --git a/config/standalone/CHIPProjectConfig.h b/config/standalone/CHIPProjectConfig.h index 4b008de5f09d3b..919897fcf7ecae 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 "1.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/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp index 2d0fbf08c1e918..375cf6d8aa960d 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -127,7 +127,7 @@ CHIP_ERROR AppTask::Init() gRequestorCore.Init(Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader); gRequestorUser.Init(&gRequestorCore, &gImageProcessor); - gImageProcessor.SetOTAImageFile(CharSpan("test.txt")); + gImageProcessor.SetOTAImageFile("test.txt"); gImageProcessor.SetOTADownloader(&gDownloader); // Connect the gDownloader and Image Processor objects diff --git a/examples/ota-requestor-app/efr32/src/AppTask.cpp b/examples/ota-requestor-app/efr32/src/AppTask.cpp index 0659962b174a9d..95d2e9f36ca41c 100644 --- a/examples/ota-requestor-app/efr32/src/AppTask.cpp +++ b/examples/ota-requestor-app/efr32/src/AppTask.cpp @@ -542,7 +542,7 @@ void AppTask::InitOTARequestor() gRequestorUser.Init(&gRequestorCore, &gImageProcessor); - gImageProcessor.SetOTAImageFile(CharSpan("test.txt")); + gImageProcessor.SetOTAImageFile("test.txt"); gImageProcessor.SetOTADownloader(&gDownloader); // Connect the Downloader and Image Processor objects diff --git a/examples/ota-requestor-app/linux/main.cpp b/examples/ota-requestor-app/linux/main.cpp index add907124b41c5..df982509d4d8c8 100644 --- a/examples/ota-requestor-app/linux/main.cpp +++ b/examples/ota-requestor-app/linux/main.cpp @@ -139,7 +139,7 @@ static void InitOTARequestor(void) gRequestorCore.Init(chip::Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader); gRequestorUser.Init(&gRequestorCore, &gImageProcessor); - gImageProcessor.SetOTAImageFile(CharSpan::fromCharString(gOtaDownloadPath)); + gImageProcessor.SetOTAImageFile(gOtaDownloadPath); gImageProcessor.SetOTADownloader(&gDownloader); // Set the image processor instance used for handling image being downloaded @@ -213,5 +213,21 @@ 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; + } + + argv[0] = kImageExecPath; + 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/examples/platform/efr32/OTAConfig.cpp b/examples/platform/efr32/OTAConfig.cpp index c46fab91deb76b..b656fcfb3ff4b1 100644 --- a/examples/platform/efr32/OTAConfig.cpp +++ b/examples/platform/efr32/OTAConfig.cpp @@ -79,7 +79,7 @@ void OTAConfig::Init() gRequestorUser.SetPeriodicQueryTimeout(OTA_PERIODIC_TIMEOUT); gRequestorUser.Init(&gRequestorCore, &gImageProcessor); - gImageProcessor.SetOTAImageFile(chip::CharSpan("test.txt")); + gImageProcessor.SetOTAImageFile("test.txt"); gImageProcessor.SetOTADownloader(&gDownloader); // Connect the Downloader and Image Processor objects 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/GenericOTARequestorDriver.cpp b/src/app/clusters/ota-requestor/GenericOTARequestorDriver.cpp index 7f0cda72eeaa3f..cc2bdb109a82e3 100644 --- a/src/app/clusters/ota-requestor/GenericOTARequestorDriver.cpp +++ b/src/app/clusters/ota-requestor/GenericOTARequestorDriver.cpp @@ -70,12 +70,23 @@ void GenericOTARequestorDriver::Init(OTARequestorInterface * requestor, OTAImage if (error != CHIP_NO_ERROR) { ChipLogError(SoftwareUpdate, "Failed to confirm image: %" CHIP_ERROR_FORMAT, error.Format()); + mRequestor->Reset(); return; } mRequestor->NotifyUpdateApplied(); }); } + else if ((mRequestor->GetCurrentUpdateState() != OTAUpdateStateEnum::kIdle)) + { + // Not running a new image for the first time but also not in the idle state may indicate there is a problem + mRequestor->Reset(); + } + else + { + // Start the first periodic query timer + StartDefaultProviderTimer(); + } } bool GenericOTARequestorDriver::CanConsent() diff --git a/src/app/clusters/ota-requestor/OTARequestor.cpp b/src/app/clusters/ota-requestor/OTARequestor.cpp index 059c590f035fbd..f6b19f5981922c 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.cpp +++ b/src/app/clusters/ota-requestor/OTARequestor.cpp @@ -107,12 +107,35 @@ 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); - + // This initialization may occur due to the following: + // 1) Regular boot up - the states should already be correct + // 2) Reboot from applying an image - once the image has been confirmed, the provider will be notified of the new version and + // all relevant states will reset for a new OTA update. If the image cannot be confirmed, the driver will be responsible for + // resetting the states appropriately, including the current update state. + 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 +182,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 +275,22 @@ void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, CHIP_ERROR error requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, error); } +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(); +} + +void OTARequestor::Shutdown(void) +{ + mServer->DispatchShutDownAndStopEventLoop(); +} + EmberAfStatus OTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const AnnounceOtaProvider::DecodableType & commandData) @@ -365,14 +403,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 +542,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 +562,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 +827,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,19 +837,78 @@ 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 ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) + { + if (mUpdateToken.size() > 0) + { + error = mStorage->StoreUpdateToken(mUpdateToken); + } + else + { + error = mStorage->ClearUpdateToken(); + } + } + + if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) { - mStorage->StoreUpdateToken(mUpdateToken); + error = mStorage->StoreCurrentUpdateState(mCurrentUpdateState); } - if (error != CHIP_NO_ERROR) + if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) + { + if (mTargetVersion > 0) + { + error = mStorage->StoreTargetVersion(mTargetVersion); + } + else + { + error = mStorage->ClearTargetVersion(); + } + } + + if ((error != CHIP_NO_ERROR) && (error != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)) { ChipLogError(SoftwareUpdate, "Failed to store current update: %" CHIP_ERROR_FORMAT, error.Format()); } } +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::kIdle; + } + + if (mStorage->LoadTargetVersion(mTargetVersion) != CHIP_NO_ERROR) + { + mTargetVersion = 0; + } +} + // 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..664bf4c9b97947 100644 --- a/src/app/clusters/ota-requestor/OTARequestor.h +++ b/src/app/clusters/ota-requestor/OTARequestor.h @@ -40,6 +40,9 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe OTARequestor() : mOnConnectedCallback(OnConnected, this), mOnConnectionFailureCallback(OnConnectionFailure, this) {} //////////// OTARequestorInterface Implementation /////////////// + void Reset(void) override; + void Shutdown(void) override; + EmberAfStatus HandleAnnounceOTAProvider( app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOtaProvider::DecodableType & commandData) override; @@ -63,15 +66,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 +107,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 +260,11 @@ class OTARequestor : public OTARequestorInterface, public BDXDownloader::StateDe */ void StoreCurrentUpdateInfo(); + /** + * Load current update information to KVS + */ + void LoadCurrentUpdateInfo(); + /** * Session connection callbacks */ diff --git a/src/app/clusters/ota-requestor/OTARequestorInterface.h b/src/app/clusters/ota-requestor/OTARequestorInterface.h index f878fae67edfd1..855e08233b3f45 100644 --- a/src/app/clusters/ota-requestor/OTARequestorInterface.h +++ b/src/app/clusters/ota-requestor/OTARequestorInterface.h @@ -153,6 +153,12 @@ class OTARequestorInterface kWrongState = 2 }; + // Reset any relevant states + virtual void Reset(void) = 0; + + // 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 +186,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/app/tests/TestDefaultOTARequestorStorage.cpp b/src/app/tests/TestDefaultOTARequestorStorage.cpp index 168fc52c32c4d9..908d7cc7ea80d6 100644 --- a/src/app/tests/TestDefaultOTARequestorStorage.cpp +++ b/src/app/tests/TestDefaultOTARequestorStorage.cpp @@ -147,10 +147,49 @@ void TestUpdateToken(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR != otaStorage.LoadUpdateToken(readUpdateToken)); } +void TestCurrentUpdateState(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + DefaultOTARequestorStorage otaStorage; + otaStorage.Init(persistentStorage); + + OTARequestorStorage::OTAUpdateStateEnum updateState = OTARequestorStorage::OTAUpdateStateEnum::kApplying; + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == otaStorage.StoreCurrentUpdateState(updateState)); + + updateState = OTARequestorStorage::OTAUpdateStateEnum::kUnknown; + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == otaStorage.LoadCurrentUpdateState(updateState)); + NL_TEST_ASSERT(inSuite, updateState == OTARequestorStorage::OTAUpdateStateEnum::kApplying); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == otaStorage.ClearCurrentUpdateState()); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR != otaStorage.LoadCurrentUpdateState(updateState)); +} + +void TestTargetVersion(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate persistentStorage; + DefaultOTARequestorStorage otaStorage; + otaStorage.Init(persistentStorage); + + uint32_t targetVersion = 2; + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == otaStorage.StoreTargetVersion(targetVersion)); + + targetVersion = 0; + + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == otaStorage.LoadTargetVersion(targetVersion)); + NL_TEST_ASSERT(inSuite, targetVersion == 2); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == otaStorage.ClearTargetVersion()); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR != otaStorage.LoadTargetVersion(targetVersion)); +} + const nlTest sTests[] = { NL_TEST_DEF("Test default providers", TestDefaultProviders), NL_TEST_DEF("Test default providers (empty list)", TestDefaultProvidersEmpty), NL_TEST_DEF("Test current provider location", TestCurrentProviderLocation), - NL_TEST_DEF("Test update token", TestUpdateToken), NL_TEST_SENTINEL() }; + NL_TEST_DEF("Test update token", TestUpdateToken), + NL_TEST_DEF("Test current update state", TestCurrentUpdateState), + NL_TEST_DEF("Test target version", TestTargetVersion), + NL_TEST_SENTINEL() }; int TestSetup(void * inContext) { diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py index c06431411da516..d0f17af28e3d69 100644 --- a/src/controller/python/test/test_scripts/base.py +++ b/src/controller/python/test/test_scripts/base.py @@ -576,8 +576,8 @@ def TestReadBasicAttributes(self, nodeid: int, endpoint: int, group: int): "Location": "XX", "HardwareVersion": 0, "HardwareVersionString": "TEST_VERSION", - "SoftwareVersion": 0, - "SoftwareVersionString": "prerelease", + "SoftwareVersion": 1, + "SoftwareVersionString": "1.0", } failed_zcl = {} for basic_attr, expected_value in basic_cluster_attrs.items(): 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 bd46209f3c8f6c..971e2f301b5e3d 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"; } // [G]lobal [D]NS-related keys static const char * DNSExtendedDiscoveryTimeout() { return "g/d/edt"; } diff --git a/src/platform/CYW30739/PlatformManagerImpl.cpp b/src/platform/CYW30739/PlatformManagerImpl.cpp index d2db833c176b17..233e1b3fcc9f01 100644 --- a/src/platform/CYW30739/PlatformManagerImpl.cpp +++ b/src/platform/CYW30739/PlatformManagerImpl.cpp @@ -127,6 +127,11 @@ CHIP_ERROR PlatformManagerImpl::_StartEventLoopTask(void) return err; } +CHIP_ERROR PlatformManagerImpl::_StopEventLoopTask() +{ + return CHIP_NO_ERROR; +} + void PlatformManagerImpl::_LockChipStack(void) { const wiced_result_t result = wiced_rtos_lock_mutex(mMutex); diff --git a/src/platform/EFR32/OTAImageProcessorImpl.h b/src/platform/EFR32/OTAImageProcessorImpl.h index 7bb97dd2c96eae..b1198e71bb77ce 100644 --- a/src/platform/EFR32/OTAImageProcessorImpl.h +++ b/src/platform/EFR32/OTAImageProcessorImpl.h @@ -40,7 +40,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface CHIP_ERROR ConfirmCurrentImage() override { return CHIP_NO_ERROR; } void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } - void SetOTAImageFile(CharSpan name) { mImageFile = name; } + void SetOTAImageFile(const char * imageFile) { mImageFile = imageFile; } private: //////////// Actual handlers for the OTAImageProcessorInterface /////////////// @@ -66,7 +66,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface MutableByteSpan mBlock; OTADownloader * mDownloader; OTAImageHeaderParser mHeaderParser; - CharSpan mImageFile; + const char * mImageFile = nullptr; }; } // namespace chip diff --git a/src/platform/Linux/OTAImageProcessorImpl.cpp b/src/platform/Linux/OTAImageProcessorImpl.cpp index 048e397266c5a0..b9668ffc5e61c7 100644 --- a/src/platform/Linux/OTAImageProcessorImpl.cpp +++ b/src/platform/Linux/OTAImageProcessorImpl.cpp @@ -21,11 +21,13 @@ #include "OTAImageProcessorImpl.h" +#include + namespace chip { CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() { - if (mImageFile.empty()) + if (mImageFile == nullptr) { ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); return CHIP_ERROR_INTERNAL; @@ -49,7 +51,7 @@ CHIP_ERROR OTAImageProcessorImpl::Apply() CHIP_ERROR OTAImageProcessorImpl::Abort() { - if (mImageFile.empty()) + if (mImageFile == nullptr) { ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); return CHIP_ERROR_INTERNAL; @@ -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,16 +127,16 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) return; } + unlink(imageProcessor->mImageFile); + imageProcessor->mHeaderParser.Init(); - imageProcessor->mOfs.open(imageProcessor->mImageFile.data(), std::ofstream::out | std::ofstream::ate | std::ofstream::app); + imageProcessor->mOfs.open(imageProcessor->mImageFile, std::ofstream::out | std::ofstream::ate | std::ofstream::app); if (!imageProcessor->mOfs.good()) { imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_OPEN_FAILED); return; } - // TODO: if file already exists and is not empty, erase previous contents - imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); } @@ -120,24 +151,24 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context) imageProcessor->mOfs.close(); imageProcessor->ReleaseBlock(); - ChipLogProgress(SoftwareUpdate, "OTA image downloaded to %s", imageProcessor->mImageFile.data()); + ChipLogProgress(SoftwareUpdate, "OTA image downloaded to %s", imageProcessor->mImageFile); } 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 the location where the new image is to be executed from + unlink(kImageExecPath); + rename(imageProcessor->mImageFile, 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); 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..a8939512e113dd 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 char kImageExecPath[] = "/tmp/ota.update"; + class OTAImageProcessorImpl : public OTAImageProcessorInterface { public: @@ -36,11 +39,11 @@ 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; } + void SetOTAImageFile(const char * imageFile) { mImageFile = imageFile; } private: //////////// Actual handlers for the OTAImageProcessorInterface /////////////// @@ -66,8 +69,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface MutableByteSpan mBlock; OTADownloader * mDownloader; OTAImageHeaderParser mHeaderParser; - uint32_t mSoftwareVersion; - CharSpan mImageFile; + const char * mImageFile = nullptr; }; } // namespace chip diff --git a/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp index 67decb387c2639..895f116bc0d11c 100644 --- a/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp @@ -28,7 +28,7 @@ namespace chip { CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() { - if (mImageFile.empty()) + if (mImageFile == nullptr) { ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); return CHIP_ERROR_INTERNAL; @@ -51,7 +51,7 @@ CHIP_ERROR OTAImageProcessorImpl::Apply() CHIP_ERROR OTAImageProcessorImpl::Abort() { - if (mImageFile.empty()) + if (mImageFile == nullptr) { ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); return CHIP_ERROR_INTERNAL; @@ -95,7 +95,7 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) if (gOtaSuccess_c == OTA_ClientInit()) { - if (gOtaSuccess_c == OTA_StartImage(imageProcessor->mImageFile.size())) + if (gOtaSuccess_c == OTA_StartImage(strlen(imageProcessor->mImageFile))) { imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); } @@ -110,7 +110,7 @@ void OTAImageProcessorImpl::HandleAbort(intptr_t context) return; } - remove(imageProcessor->mImageFile.data()); + remove(imageProcessor->mImageFile); imageProcessor->ReleaseBlock(); } diff --git a/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h index 419ee4fb493266..845c43bfc85eea 100644 --- a/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h +++ b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h @@ -37,7 +37,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface CHIP_ERROR ConfirmCurrentImage() override { return CHIP_NO_ERROR; } void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } - void SetOTAImageFile(CharSpan name) { mImageFile = name; } + void SetOTAImageFile(const char * imageFile) { mImageFile = imageFile; } private: //////////// Actual handlers for the OTAImageProcessorInterface /////////////// @@ -58,7 +58,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface OTADownloader * mDownloader; MutableByteSpan mBlock; - CharSpan mImageFile; + const char * mImageFile = nullptr; }; } // namespace chip