From df0455ada3306e05e5b883ccfe2bfb2c13f3ca33 Mon Sep 17 00:00:00 2001 From: Carol Yang Date: Thu, 2 Dec 2021 15:34:15 -0800 Subject: [PATCH] [OTA] Add Linux implementation of OTAImageProcessorInterface (#12493) --- examples/ota-requestor-app/linux/BUILD.gn | 1 + .../linux/LinuxOTAImageProcessor.cpp | 204 ++++++++++++++++++ .../linux/LinuxOTAImageProcessor.h | 57 +++-- examples/ota-requestor-app/linux/main.cpp | 2 + .../clusters/ota-requestor/OTADownloader.h | 12 +- .../ota-requestor/OTAImageProcessor.h | 88 ++++++-- 6 files changed, 317 insertions(+), 47 deletions(-) create mode 100644 examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp diff --git a/examples/ota-requestor-app/linux/BUILD.gn b/examples/ota-requestor-app/linux/BUILD.gn index aa54192e4333d7..2fa7cad7391c49 100644 --- a/examples/ota-requestor-app/linux/BUILD.gn +++ b/examples/ota-requestor-app/linux/BUILD.gn @@ -17,6 +17,7 @@ import("//build_overrides/chip.gni") executable("chip-ota-requestor-app") { sources = [ + "LinuxOTAImageProcessor.cpp", "LinuxOTARequestorDriver.cpp", "main.cpp", ] diff --git a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp new file mode 100644 index 00000000000000..44afe5605a43e1 --- /dev/null +++ b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.cpp @@ -0,0 +1,204 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "LinuxOTAImageProcessor.h" + +namespace chip { + +// TODO: Dummy function to be removed once BDX downloader is implemented and can return a real instance +OTADownloader * GetDownloaderInstance() +{ + return nullptr; +} + +CHIP_ERROR LinuxOTAImageProcessor::PrepareDownload() +{ + if (mParams.imageFile.empty()) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxOTAImageProcessor::Finalize() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxOTAImageProcessor::Abort() +{ + if (mParams.imageFile.empty()) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxOTAImageProcessor::ProcessBlock(ByteSpan & block) +{ + if (!mOfs.is_open() || !mOfs.good()) + { + return CHIP_ERROR_INTERNAL; + } + + if ((block.data() == nullptr) || block.empty()) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // Store block data for HandleProcessBlock to access + CHIP_ERROR err = SetBlock(block); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot set block data: %" CHIP_ERROR_FORMAT, err.Format()); + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +void LinuxOTAImageProcessor::HandlePrepareDownload(intptr_t context) +{ + OTADownloader * downloader = GetDownloaderInstance(); + if (downloader == nullptr) + { + ChipLogError(SoftwareUpdate, "No known OTA downloader"); + return; + } + + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + downloader->OnPreparedForDownload(CHIP_ERROR_INVALID_ARGUMENT); + return; + } + + imageProcessor->mOfs.open(imageProcessor->mParams.imageFile.data(), + std::ofstream::out | std::ofstream::ate | std::ofstream::app); + if (!imageProcessor->mOfs.good()) + { + downloader->OnPreparedForDownload(CHIP_ERROR_OPEN_FAILED); + return; + } + + downloader->OnPreparedForDownload(CHIP_NO_ERROR); +} + +void LinuxOTAImageProcessor::HandleFinalize(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->mOfs.close(); + imageProcessor->ReleaseBlock(); +} + +void LinuxOTAImageProcessor::HandleAbort(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + imageProcessor->mOfs.close(); + remove(imageProcessor->mParams.imageFile.data()); + imageProcessor->ReleaseBlock(); +} + +void LinuxOTAImageProcessor::HandleProcessBlock(intptr_t context) +{ + OTADownloader * downloader = GetDownloaderInstance(); + if (downloader == nullptr) + { + ChipLogError(SoftwareUpdate, "No known OTA downloader"); + return; + } + + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + downloader->OnBlockProcessed(CHIP_ERROR_INVALID_ARGUMENT, OTADownloader::kEnd); + return; + } + + // TODO: Process block header if any + + if (!imageProcessor->mOfs.write(reinterpret_cast(imageProcessor->mBlock.data()), + static_cast(imageProcessor->mBlock.size()))) + { + downloader->OnBlockProcessed(CHIP_ERROR_WRITE_FAILED, OTADownloader::kEnd); + return; + } + + imageProcessor->mParams.downloadedBytes += imageProcessor->mBlock.size(); + downloader->OnBlockProcessed(CHIP_NO_ERROR, OTADownloader::kGetNext); +} + +CHIP_ERROR LinuxOTAImageProcessor::SetBlock(ByteSpan & block) +{ + if ((block.data() == nullptr) || block.empty()) + { + return CHIP_NO_ERROR; + } + + // Allocate memory for block data if it has not been done yet + if (mBlock.empty()) + { + mBlock = MutableByteSpan(static_cast(chip::Platform::MemoryAlloc(block.size())), block.size()); + if (mBlock.data() == nullptr) + { + return CHIP_ERROR_NO_MEMORY; + } + } + + // Store the actual block data + CHIP_ERROR err = CopySpanToMutableSpan(block, mBlock); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot copy block data: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxOTAImageProcessor::ReleaseBlock() +{ + if (mBlock.data() != nullptr) + { + chip::Platform::MemoryFree(mBlock.data()); + } + + mBlock = MutableByteSpan(); + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h index 2a5a41ad2de52f..72e528749a6138 100644 --- a/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h +++ b/examples/ota-requestor-app/linux/LinuxOTAImageProcessor.h @@ -16,30 +16,43 @@ * limitations under the License. */ -/* This file contains the decalarions for the Linux implementation of the - * the OTAImageProcessorDriver interface class - */ - -#include "app/clusters/ota-requestor/OTAImageProcessor.h" - -class LinuxOTAImageProcessor : public OTAImageProcessorDriver -{ +#pragma once - // Virtuial functions from OTAImageProcessorDriver -- start - // Open file, find block of space in persistent memory, or allocate a buffer, etc. - CHIP_ERROR PrepareDownload() { return CHIP_NO_ERROR; } +#include +#include - // Must not be a blocking call to support cases that require IO to elements such as // external peripherals/radios - CHIP_ERROR ProcessBlock(chip::ByteSpan & data) { return CHIP_NO_ERROR; } +#include - // Close file, close persistent storage, etc - CHIP_ERROR Finalize() { return CHIP_NO_ERROR; } +namespace chip { - chip::Optional PercentComplete() { return chip::Optional(0); } - - // Clean up the download which could mean erasing everything that was written, - // releasing buffers, etc. - CHIP_ERROR Abort() { return CHIP_NO_ERROR; } - - // Virtuial functions from OTAImageProcessorDriver -- end +class LinuxOTAImageProcessor : public OTAImageProcessorInterface +{ +public: + //////////// OTAImageProcessorInterface Implementation /////////////// + CHIP_ERROR PrepareDownload() override; + CHIP_ERROR Finalize() override; + CHIP_ERROR Abort() override; + CHIP_ERROR ProcessBlock(ByteSpan & block) override; + +private: + //////////// Actual handlers for the OTAImageProcessorInterface /////////////// + static void HandlePrepareDownload(intptr_t context); + static void HandleFinalize(intptr_t context); + static void HandleAbort(intptr_t context); + static void HandleProcessBlock(intptr_t context); + + /** + * Called to allocate memory for mBlock if necessary and set it to block + */ + CHIP_ERROR SetBlock(ByteSpan & block); + + /** + * Called to release allocated memory for mBlock + */ + CHIP_ERROR ReleaseBlock(); + + std::ofstream mOfs; + MutableByteSpan mBlock; }; + +} // namespace chip diff --git a/examples/ota-requestor-app/linux/main.cpp b/examples/ota-requestor-app/linux/main.cpp index e4aa27ee85ddf4..f966b95b18c30a 100644 --- a/examples/ota-requestor-app/linux/main.cpp +++ b/examples/ota-requestor-app/linux/main.cpp @@ -33,9 +33,11 @@ using chip::CharSpan; using chip::DeviceProxy; using chip::EndpointId; using chip::FabricIndex; +using chip::LinuxOTAImageProcessor; using chip::NodeId; using chip::OnDeviceConnected; using chip::OnDeviceConnectionFailure; +using chip::OTADownloader; using chip::PeerId; using chip::Server; using chip::VendorId; diff --git a/src/app/clusters/ota-requestor/OTADownloader.h b/src/app/clusters/ota-requestor/OTADownloader.h index 3f7147f79aace8..af355d54b472ea 100644 --- a/src/app/clusters/ota-requestor/OTADownloader.h +++ b/src/app/clusters/ota-requestor/OTADownloader.h @@ -27,6 +27,8 @@ #pragma once +namespace chip { + // A class that abstracts the image download functionality from the particular // protocol used for that (BDX or possibly HTTPS) class OTADownloader @@ -38,7 +40,7 @@ class OTADownloader void virtual BeginDownload(){}; // Platform calls this method upon the completion of PrepareDownload() processing - void virtual OnPreparedForDownload(){}; + void virtual OnPreparedForDownload(CHIP_ERROR status){}; // Action parameter type for the OnBlockProcessed() enum BlockActionType @@ -48,10 +50,10 @@ class OTADownloader }; // Platform calls this method upon the completion of ProcessBlock() processing - void virtual OnBlockProcessed(BlockActionType action){}; + void virtual OnBlockProcessed(CHIP_ERROR status, BlockActionType action){}; // A setter for the delegate class pointer - void SetImageProcessorDelegate(OTAImageProcessorDriver * delegate) { mImageProcessorDelegate = delegate; } + void SetImageProcessorDelegate(OTAImageProcessorInterface * delegate) { mImageProcessorDelegate = delegate; } // API declarations end @@ -59,7 +61,7 @@ class OTADownloader virtual ~OTADownloader() = default; private: - OTAImageProcessorDriver * mImageProcessorDelegate; + OTAImageProcessorInterface * mImageProcessorDelegate; }; // Set the object implementing OTADownloader @@ -67,3 +69,5 @@ void SetDownloaderInstance(OTADownloader * instance); // Get the object implementing OTADownloaderInterface OTADownloader * GetDownloaderInstance(); + +} // namespace chip diff --git a/src/app/clusters/ota-requestor/OTAImageProcessor.h b/src/app/clusters/ota-requestor/OTAImageProcessor.h index 3bd9a65b7bd90c..7afc845d56efdd 100644 --- a/src/app/clusters/ota-requestor/OTAImageProcessor.h +++ b/src/app/clusters/ota-requestor/OTAImageProcessor.h @@ -16,36 +16,82 @@ * limitations under the License. */ -/* This file contains the declarations for OTAImageProcessorDriver, a platform-agnostic - * interface for processing downloaded chunks of OTA image data. - * Each platform should provide an implementation of this interface. - */ - #pragma once -// This is a platform-agnostic interface for processing downloaded -// chunks of OTA image data (data could be raw image data meant for flash or -// metadata). Each platform should provide an implementation of this -// interface. +#include +#include +#include +#include + +namespace chip { + +struct OTAImageProcessorParams +{ + CharSpan imageFile; + uint64_t downloadedBytes; + uint64_t totalFileBytes; +}; -class OTAImageProcessorDriver +/** + * @class OTAImageProcessorInterface + * + * @brief + * This is a platform-agnostic interface for processing downloaded + * chunks of OTA image data. The data could be raw image data meant for flash or + * metadata. Each platform should provide an implementation of this + * interface. + */ +class DLL_EXPORT OTAImageProcessorInterface { public: - // Open file, find block of space in persistent memory, or allocate a buffer, etc. - virtual CHIP_ERROR PrepareDownload() = 0; + virtual ~OTAImageProcessorInterface() {} - // Must not be a blocking call to support cases that require IO to elements such as // external peripherals/radios - virtual CHIP_ERROR ProcessBlock(chip::ByteSpan & data) = 0; + /** + * Called to prepare for an OTA image download. This may include but not limited to opening the file, finding a block of space + * in persistent memory, and allocating a buffer. This must not be a blocking call. + */ + virtual CHIP_ERROR PrepareDownload() = 0; - // Close file, close persistent storage, etc + /** + * Called when the OTA image download process has completed. This may include but not limited to closing the file and persistent + * storage. This must not be a blocking call. + */ virtual CHIP_ERROR Finalize() = 0; - virtual chip::Optional PercentComplete() = 0; - - // Clean up the download which could mean erasing everything that was written, - // releasing buffers, etc. + /** + * 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. + */ virtual CHIP_ERROR Abort() = 0; - // Destructor - virtual ~OTAImageProcessorDriver() = default; + /** + * Called to process a downloaded block of data. This must not be a blocking call to support cases that require IO to elements + * such as external peripherals/radios. This must not be a blocking call. + */ + virtual CHIP_ERROR ProcessBlock(ByteSpan & block) = 0; + + /** + * Called to setup params for the OTA image download + */ + virtual void SetOTAImageProcessorParams(OTAImageProcessorParams & params) { mParams = params; }; + + /** + * Called to check the current download status of the OTA image download. + */ + virtual uint8_t GetPercentComplete() + { + if (mParams.totalFileBytes == 0) + { + return 0; + } + else + { + return static_cast((mParams.downloadedBytes * 100) / mParams.totalFileBytes); + } + } + +protected: + OTAImageProcessorParams mParams; }; + +} // namespace chip