diff --git a/CMakeLists.txt b/CMakeLists.txt index 99a4a2fa6..8341e3354 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ option(WARNING_AS_ERROR "Treat warnings as errors" ON) option(PEDANTIC_WARNINGS "Compile with pedantic warnings" OFF) option(BUILD_WITH_CODE_COVERAGE "Enable gcov code coverage" OFF) option(BUILD_OSTREE "Set to ON to compile with OSTree support" OFF) +option(BUILD_RAUC "Set to ON to compile with RAUC support" ON) option(BUILD_DEB "Set to ON to compile with debian packages support" OFF) option(BUILD_P11 "Support for key storage in a HSM via PKCS#11" OFF) option(BUILD_SOTA_TOOLS "Set to ON to build SOTA tools" OFF) @@ -109,6 +110,11 @@ else(BUILD_OSTREE) endif(NOT BUILD_SOTA_TOOLS) endif(BUILD_OSTREE) +if(BUILD_RAUC) + find_package(sdbus-c++ REQUIRED) + add_definitions(-DBUILD_RAUC) +endif(BUILD_RAUC) + if(BUILD_P11) find_package(LibP11 REQUIRED) add_definitions(-DBUILD_P11) @@ -319,6 +325,7 @@ include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third_party/googletest/googlete include_directories(SYSTEM ${JSONCPP_INCLUDE_DIRS}) include_directories(SYSTEM ${Boost_INCLUDE_DIR}) include_directories(SYSTEM ${LIBOSTREE_INCLUDE_DIRS}) +include_directories(SYSTEM ${LIBRAUC_INCLUDE_DIRS}) include_directories(SYSTEM ${SQLITE3_INCLUDE_DIRS}) include_directories(SYSTEM ${LIBP11_INCLUDE_DIR}) include_directories(SYSTEM ${sodium_INCLUDE_DIR}) @@ -365,6 +372,7 @@ set (AKTUALIZR_EXTERNAL_LIBS Threads::Threads ${sodium_LIBRARY_RELEASE} ${LIBOSTREE_LIBRARIES} + SDBusCpp::sdbus-c++ ${SQLITE3_LIBRARIES} ${LibArchive_LIBRARIES} ${LIBP11_LIBRARIES} diff --git a/include/libaktualizr/config.h b/include/libaktualizr/config.h index a3cbac199..c1a537ea8 100644 --- a/include/libaktualizr/config.h +++ b/include/libaktualizr/config.h @@ -80,9 +80,12 @@ struct UptaneConfig { // TODO: move these to their corresponding headers #define PACKAGE_MANAGER_NONE "none" #define PACKAGE_MANAGER_OSTREE "ostree" +#define PACKAGE_MANAGER_RAUC "rauc" #ifdef BUILD_OSTREE #define PACKAGE_MANAGER_DEFAULT PACKAGE_MANAGER_OSTREE +#elif BUILD_RAUC +#define PACKAGE_MANAGER_DEFAULT PACKAGE_MANAGER_RAUC #else #define PACKAGE_MANAGER_DEFAULT PACKAGE_MANAGER_NONE #endif diff --git a/include/libaktualizr/types.h b/include/libaktualizr/types.h index 5c9954cf5..87464ea93 100644 --- a/include/libaktualizr/types.h +++ b/include/libaktualizr/types.h @@ -435,6 +435,7 @@ class Target { * root commit object. */ bool IsOstree() const; + bool IsRauc() const; std::string type() const { return type_; } // Comparison is usually not meaningful. Use MatchTarget instead. diff --git a/src/libaktualizr/package_manager/CMakeLists.txt b/src/libaktualizr/package_manager/CMakeLists.txt index 8e716f47d..a01b0851c 100644 --- a/src/libaktualizr/package_manager/CMakeLists.txt +++ b/src/libaktualizr/package_manager/CMakeLists.txt @@ -25,6 +25,21 @@ if(BUILD_OSTREE) ARGS ${PROJECT_BINARY_DIR}/ostree_repo) endif(BUILD_OSTREE) +# RAUC backend +if(BUILD_RAUC) + target_sources(package_manager PRIVATE raucmanager.cc) + target_include_directories(package_manager PUBLIC ${LIBRAUC_INCLUDE_DIRS}) + set_target_properties(package_manager PROPERTIES + CXX_STANDARD 17 + CXX_EXTENSIONS off) + target_link_libraries(package_manager PUBLIC SDBusCpp::sdbus-c++) + + add_aktualizr_test(NAME raucmanager SOURCES raucmanager_test.cc PROJECT_WORKING_DIRECTORY) + set_target_properties(t_raucmanager PROPERTIES + CXX_STANDARD 17 + CXX_EXTENSIONS off) +endif(BUILD_RAUC) + add_aktualizr_test(NAME packagemanagerconfig SOURCES packagemanagerconfig_test.cc NO_VALGRIND) add_aktualizr_test(NAME packagemanager_factory SOURCES packagemanagerfactory_test.cc ARGS ${PROJECT_BINARY_DIR}/ostree_repo) @@ -38,4 +53,7 @@ aktualizr_source_file_checks(fetcher_death_test.cc packagemanagerfactory_test.cc ostreemanager_test.cc ostreemanager.cc - ostreemanager.h) + ostreemanager.h + raucmanager_test.cc + raucmanager.cc + raucmanager.h) diff --git a/src/libaktualizr/package_manager/raucmanager.cc b/src/libaktualizr/package_manager/raucmanager.cc new file mode 100644 index 000000000..d944bf712 --- /dev/null +++ b/src/libaktualizr/package_manager/raucmanager.cc @@ -0,0 +1,367 @@ +#include "raucmanager.h" +#include +#include +#include +#include "libaktualizr/packagemanagerfactory.h" +#include "logging/logging.h" + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +AUTO_REGISTER_PACKAGE_MANAGER(PACKAGE_MANAGER_RAUC, RaucManager); + +// Constructor: Initializes RAUC proxy and registers signal handlers +RaucManager::RaucManager(const PackageConfig& pconfig, const BootloaderConfig& bconfig, + const std::shared_ptr& storage, const std::shared_ptr& http, + Bootloader* bootloader) + : PackageManagerInterface(pconfig, BootloaderConfig(), storage, http), + bootloader_(bootloader == nullptr ? new Bootloader(bconfig, *storage) : bootloader) { + LOG_DEBUG << "Starting RAUC package manager ..."; + // initializing default variables + this->currentHash = ""; + this->currentHashCalculated.store(false); + this->installResultCode = data::ResultCode::Numeric::kUnknown; + this->installResultDescription = std::string(""); + + // Create a proxy to interact with RAUC over D-Bus + this->raucProxy_ = sdbus::createProxy(this->raucDestination, this->raucObjectPath); + + // Register signal handlers + this->raucProxy_->uponSignal(this->completedSignal) + .onInterface(this->installBundleInterface) + .call([this](const std::int32_t& status) { this->onCompleted(status); }); + + this->raucProxy_->uponSignal(this->propertiesChangedSignal) + .onInterface(this->propertiesChangedInterface) + .call([this](const std::string& interfaceName, const std::map& changedProperties, + std::vector invalidProperties + __attribute__((unused))) { this->onProgressChanged(interfaceName, changedProperties); }); + + this->raucProxy_->finishRegistration(); +} + +bool RaucManager::fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher, const KeyManager& keys, + const FetcherProgressCb& progress_cb, const api::FlowControlToken* token) { + if (!target.IsRauc()) { + // The case when the RAUC package manager is set as a package manager for aktualizr + // while the target is aimed for a Secondary ECU that is configured with another/non-RAUC package manager + return PackageManagerInterface::fetchTarget(target, fetcher, keys, progress_cb, token); + } + LOG_TRACE << "called RaucManager::fetchTarget()"; + installationComplete.store(false); + installationErrorLogged.store(false); + return true; +} + +data::InstallationResult RaucManager::install(const Uptane::Target& target) const { + LOG_TRACE << "called RaucManager::install()"; + // Extract bundle URI from the target object + std::string bundlePath = target.uri(); + LOG_INFO << "Target image URI: " << bundlePath; + // if(bundlePath == std::string::empty) { + // return data::InstallationResult(data::ResultCode::Numeric::kGeneralError, "Failed to get the bundle path from + // target"); + // } + + // Extract SHA256 hash from the target object + std::string sha256Hash = + target.custom_data()["rauc"]["rawHashes"]["sha256"].asString(); // Assuming Uptane::Target has this structure + LOG_INFO << "Target Image sha256 digest: " << sha256Hash; + // if(sha256Hash == std::string::empty) { + // return data::InstallationResult(data::ResultCode::Numeric::kGeneralError, "Failed to get the root sha256 hash + // from target"); + // } + + // Write the SHA256 hash to the expected file + try { + writeHashToFile(sha256Hash); + } catch (const std::exception& e) { + LOG_ERROR << "Error writing hash to file: " << e.what() << std::endl; + return data::InstallationResult(data::ResultCode::Numeric::kGeneralError, "Failed to write SHA256 hash to file"); + } + + // Send RAUC installation request + try { + sendRaucInstallRequest(bundlePath); + // std::cout << "Installation request sent for bundle: " << bundlePath << std::endl; + } catch (const std::runtime_error& e) { + LOG_ERROR << e.what() << std::endl; + return data::InstallationResult(data::ResultCode::Numeric::kGeneralError, + "Failed to send RAUC installation request"); + } + + // Wait for the 'Completed' signal + while (!this->installationComplete.load()) { + sleep(1); + } + + if (this->installResultCode == data::ResultCode::Numeric::kInstallFailed) { + return data::InstallationResult(this->installResultCode, this->installResultDescription); + } + + // set reboot flag to be notified later + if (bootloader_ != nullptr) { + bootloader_->rebootFlagSet(); + } + + sync(); + return data::InstallationResult( + this->installResultCode, this->installResultDescription); // The actual result will come from the signal handlers +} + +// Send a RAUC install request over DBus +void RaucManager::sendRaucInstallRequest(const std::string& bundlePath) const { + LOG_TRACE << "called RaucManager::sendRaucInstallRequest()"; + auto method = this->raucProxy_->createMethodCall(this->installBundleInterface, this->installBundleMethod); + std::map args; + method << bundlePath; + method << args; + + try { + this->raucProxy_->callMethod(method); + } catch (const sdbus::Error& e) { + throw std::runtime_error("Failed to send RAUC install request: " + e.getMessage()); + } +} + +// Signal handler for "Completed" event +void RaucManager::onCompleted(const std::int32_t& status) { + LOG_TRACE << "called RaucManager::getInstalledPackages() status: " << status; + if (status == 0) { + LOG_INFO << "Installation completed successfully with status code: " << status << std::endl; + this->installResultCode = data::ResultCode::Numeric::kNeedCompletion; + this->installResultDescription = "Installation Completed Successfully, restart required"; + } else { + LOG_INFO << "Installation did not complete successfully with status code: " << status << std::endl; + while (!this->installationErrorLogged) { + sleep(1); + } + this->installResultCode = data::ResultCode::Numeric::kInstallFailed; + this->installResultDescription = installResultError; + } + this->installationComplete.store(true); + return; +} + +// Signal handler for "PropertiesChanged" event (progress updates) +void RaucManager::onProgressChanged(const std::string& interfaceName, + const std::map& changedProperties) { + if (interfaceName == this->installBundleInterface) { + auto it = changedProperties.find(this->propertiesChangedProgress); + if (it != changedProperties.end()) { + auto progress = it->second.get>(); + auto percentage = progress.get<0>(); + auto message = progress.get<1>(); + auto depth = progress.get<2>(); + std::string level = "|"; + for (int i = 1; i < depth; i++) { + level.append(" |"); + } + LOG_INFO << level << "-\"" << message << "\" (" << percentage << "%)"; + } + + auto itError = changedProperties.find(this->propertiesChangedError); + if (itError != changedProperties.end()) { + std::string lastError = itError->second.get(); + LOG_ERROR << "Last Error: " << lastError << std::endl; + this->installResultError = lastError; + this->installationErrorLogged.store(true); + } + } +} + +Json::Value RaucManager::getInstalledPackages() const { + LOG_TRACE << "called RaucManager::getInstalledPackages()"; + std::string packages_str = Utils::readFile(config.packages_file); + std::vector package_lines; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + boost::split(package_lines, packages_str, boost::is_any_of("\n")); + Json::Value packages(Json::arrayValue); + for (auto it = package_lines.begin(); it != package_lines.end(); ++it) { + if (it->empty()) { + continue; + } + size_t pos = it->find(" "); + if (pos == std::string::npos) { + throw std::runtime_error("Wrong packages file format"); + } + Json::Value package; + package["name"] = it->substr(0, pos); + package["version"] = it->substr(pos + 1); + packages.append(package); + } + return packages; +} + +std::string RaucManager::getCurrentHash() const { + LOG_TRACE << "called RaucManager::getCurrentHash()"; + + // Check if the hash has already been calculated + if (this->currentHashCalculated) { + return currentHash; + } + + // Path to the script and the file that contains the hash + const std::string scriptPath = "/usr/bin/calc-root-hash.sh"; + const std::string hashFilePath = "/run/aktualizr/root-hash"; + + // Execute the script to calculate the hash + int ret = std::system(scriptPath.c_str()); + if (ret != 0) { + LOG_ERROR << "Failed to execute get current has script: " << scriptPath; + return ""; // Return empty string on failure + } + + // Read the hash from /run/aktualizr/root-hash + std::ifstream hashFile(hashFilePath); + if (!hashFile.is_open()) { + LOG_ERROR << "Failed to open hash file: " << hashFilePath; + return ""; // Return empty string on failure + } + + // Read the entire contents of the file into currentHash + std::stringstream buffer; + buffer << hashFile.rdbuf(); + currentHash = buffer.str(); + + // Close the file + hashFile.close(); + + // Remove any trailing newlines or spaces from the hash + currentHash.erase(currentHash.find_last_not_of(" \n\r\t") + 1); + + // Set the atomic flag to true + this->currentHashCalculated.store(true); + + LOG_INFO << "current hash: " << currentHash; + + return currentHash; +} + +Uptane::Target RaucManager::getCurrent() const { + LOG_TRACE << "called RaucManager::getCurrent()"; + const std::string current_hash = getCurrentHash(); + boost::optional current_version; + // This may appear Primary-specific, but since Secondaries only know about + // themselves, this actually works just fine for them, too. + storage_->loadPrimaryInstalledVersions(¤t_version, nullptr); + + if (!!current_version && current_version->sha256Hash() == current_hash) { + return *current_version; + } + + LOG_ERROR << "Current versions in storage and reported by RAUC do not match"; + + // Look into installation log to find a possible candidate. Again, despite the + // name, this will work for Secondaries as well. + std::vector installed_versions; + storage_->loadPrimaryInstallationLog(&installed_versions, false); + + // Version should be in installed versions. It's possible that multiple + // targets could have the same sha256Hash. In this case the safest assumption + // is that the most recent (the reverse of the vector) target is what we + // should return. + std::vector::reverse_iterator it; + for (it = installed_versions.rbegin(); it != installed_versions.rend(); it++) { + if (it->sha256Hash() == current_hash) { + return *it; + } + } + // We haven't found a matching target. This can occur when a device is + // freshly manufactured and the factory image is in a delegated target. + // Aktualizr will have had no reason to fetch the relevant delegation, and it + // doesn't know where in the delegation tree on the server it might be. + // See https://github.com/uptane/aktualizr/issues/1 for more details. In this + // case attempt to construct an approximate Uptane target. By getting the + // hash correct the server has a chance to figure out what is running on the + // device. + // Uptane::EcuMap ecus; + // std::vector hashes{Hash(Hash::Type::kSha256, current_hash)}; + return Uptane::Target::Unknown(); +} + +void RaucManager::completeInstall() const { + LOG_TRACE << "called RaucManager::completeInstall()"; + LOG_INFO << "About to reboot the system in order to apply pending updates..."; + bootloader_->reboot(); +} + +// Finalize the installation +data::InstallationResult RaucManager::finalizeInstall(const Uptane::Target& target) { + LOG_TRACE << "called RaucManager::finalizeInstall()"; + if (!bootloader_->rebootDetected()) { + return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion, + "Reboot is required for the pending update application"); + } + + LOG_INFO << "Checking installation of new RAUC image"; + const std::string current_hash = getCurrentHash(); + + data::InstallationResult install_result = + data::InstallationResult(data::ResultCode::Numeric::kOk, "Successfully booted on new version"); + + if (current_hash != target.sha256Hash()) { + LOG_ERROR << "Expected to boot " << target.sha256Hash() << " but found " << current_hash + << ". The system may have been rolled back."; + install_result = data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Wrong version booted"); + } + + bootloader_->rebootFlagClear(); + return install_result; +} + +// Function to create the /run/aktualizr directory if it does not exist +void RaucManager::createDirectoryIfNotExists(const std::string& directoryPath) const { + LOG_TRACE << "called RaucManager::createDirectoryIfNotExists()"; + struct stat st; + if (stat(directoryPath.c_str(), &st) != 0) { + if (mkdir(directoryPath.c_str(), 0755) != 0) { + throw std::runtime_error("Failed to create directory: " + directoryPath); + } + LOG_DEBUG << "Directory Created: " << directoryPath; + } else if (!S_ISDIR(st.st_mode)) { + throw std::runtime_error(directoryPath + " exists but is not a directory"); + } else { + LOG_DEBUG << "Directory Exists: " << directoryPath; + } +} + +// Function to write the SHA256 hash to /run/aktualizr/expected-digest +void RaucManager::writeHashToFile(const std::string& hash) const { + LOG_TRACE << "called RaucManager::writeHashToFile()"; + const std::string directoryPath = "/run/aktualizr"; + const std::string filePath = directoryPath + "/expected-digest"; + + // Create the directory if it doesn't exist + createDirectoryIfNotExists(directoryPath); + + std::ofstream file(filePath); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + filePath); + } + + file << hash; + + if (!file) { + throw std::runtime_error("Failed to write to file: " + filePath); + } + + file.close(); + + LOG_INFO << "SHA256 hash written and file closed: " << filePath << std::endl; + sync(); +} + +// Verify the target +TargetStatus RaucManager::verifyTarget(const Uptane::Target& target) const { + if (!target.IsRauc()) { + // The case when the OSTree package manager is set as a package manager for aktualizr + // while the target is aimed for a Secondary ECU that is configured with another/non-OSTree package manager + return PackageManagerInterface::verifyTarget(target); + } + LOG_TRACE << "called RaucManager::verifyTarget()"; + return TargetStatus::kGood; +} + +bool RaucManager::checkAvailableDiskSpace(uint64_t required_bytes) const { + LOG_TRACE << "called RaucManager::checkAvailableDiskSpace() required_bytes: " << required_bytes; + return true; +} \ No newline at end of file diff --git a/src/libaktualizr/package_manager/raucmanager.h b/src/libaktualizr/package_manager/raucmanager.h new file mode 100644 index 000000000..72476f2ad --- /dev/null +++ b/src/libaktualizr/package_manager/raucmanager.h @@ -0,0 +1,79 @@ +#ifndef RAUC_H_ +#define RAUC_H_ + +#include +#include +#include +#include +#include +#include +#include "bootloader/bootloader.h" +#include "json/json.h" +#include "libaktualizr/packagemanagerinterface.h" +#include "libaktualizr/types.h" +#include "storage/invstorage.h" +#include "utilities/utils.h" + +class RaucManager : public PackageManagerInterface { + public: + // Constructor, using PackageManagerInterface's constructor + RaucManager(const PackageConfig& pconfig, const BootloaderConfig& bconfig, const std::shared_ptr& storage, + const std::shared_ptr& http, Bootloader* bootloader = nullptr); + + // Destructor + ~RaucManager() override = default; + + RaucManager(const RaucManager&) = delete; + RaucManager(RaucManager&&) = delete; + RaucManager& operator=(const RaucManager&) = delete; + RaucManager& operator=(RaucManager&&) = delete; + + // Overriding necessary functions from PackageManagerInterface + std::string name() const override { return "rauc"; }; + Json::Value getInstalledPackages() const override; + virtual std::string getCurrentHash() const; + Uptane::Target getCurrent() const override; + data::InstallationResult install(const Uptane::Target& target) const override; + void completeInstall() const override; + data::InstallationResult finalizeInstall(const Uptane::Target& target) override; + bool fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher, const KeyManager& keys, + const FetcherProgressCb& progress_cb, const api::FlowControlToken* token) override; + TargetStatus verifyTarget(const Uptane::Target& target) const override; + bool checkAvailableDiskSpace(uint64_t required_bytes) const override; + + private: + // Signal handlers for installation progress and completion + void onCompleted(const std::int32_t& status); + void onProgressChanged(const std::string& interfaceName, const std::map& changedProps); + + // Method to send the installation request to RAUC via D-Bus + void sendRaucInstallRequest(const std::string& bundlePath) const; + + // Method to write the rootImage hash to desired file + void writeHashToFile(const std::string& hash) const; + void createDirectoryIfNotExists(const std::string& directoryPath) const; + // RAUC-related configurations and proxy object for DBus communication + data::ResultCode::Numeric installResultCode; + std::string installResultDescription; + std::string installResultError; + std::shared_ptr raucProxy_; + std::unique_ptr bootloader_; + // Atomic flag to indicate whether the installation is complete + std::atomic installationComplete; + std::atomic installationErrorLogged; + + mutable std::string currentHash; + mutable std::atomic currentHashCalculated; + + const std::string raucDestination = "de.pengutronix.rauc"; + const std::string raucObjectPath = "/"; + const std::string installBundleInterface = "de.pengutronix.rauc.Installer"; + const std::string installBundleMethod = "InstallBundle"; + const std::string completedSignal = "Completed"; + const std::string propertiesChangedProgress = "Progress"; + const std::string propertiesChangedError = "LastError"; + const std::string propertiesChangedSignal = "PropertiesChanged"; + const std::string propertiesChangedInterface = "org.freedesktop.DBus.Properties"; +}; + +#endif // RAUC_H_ diff --git a/src/libaktualizr/package_manager/raucmanager_test.cc b/src/libaktualizr/package_manager/raucmanager_test.cc new file mode 100644 index 000000000..03d165ea4 --- /dev/null +++ b/src/libaktualizr/package_manager/raucmanager_test.cc @@ -0,0 +1,9 @@ +#include + +#ifndef __NO_MAIN__ +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} +#endif \ No newline at end of file diff --git a/src/libaktualizr/primary/sotauptaneclient.cc b/src/libaktualizr/primary/sotauptaneclient.cc index 7cb0ddb52..2942c9cd2 100644 --- a/src/libaktualizr/primary/sotauptaneclient.cc +++ b/src/libaktualizr/primary/sotauptaneclient.cc @@ -937,6 +937,12 @@ result::UpdateCheck SotaUptaneClient::checkUpdates() { if (target.uri().empty() && !image_target->uri().empty()) { target.setUri(image_target->uri()); } + if (image_target->custom_data().isMember("rauc")) { + Json::Value custom_data_temp = target.custom_data(); + custom_data_temp["rauc"] = image_target->custom_data()["rauc"]; + custom_data_temp["targetFormat"] = image_target->custom_data()["targetFormat"]; + target.updateCustom(custom_data_temp); + } } } catch (const std::exception &e) { last_exception = std::current_exception(); @@ -1167,6 +1173,8 @@ bool SotaUptaneClient::isInstallCompletionRequired() { [&primary_ecu_serial](const std::pair &ecu) -> bool { return ecu.first == primary_ecu_serial; }) != pending_ecus.end(); + LOG_INFO << "pending_for_ecu: " << pending_for_ecu; + LOG_INFO << "config.uptane.force_install_completion: " << config.uptane.force_install_completion; return pending_for_ecu && config.uptane.force_install_completion; } diff --git a/src/libaktualizr/uptane/tuf.cc b/src/libaktualizr/uptane/tuf.cc index c4a3575e4..4f35a9cb9 100644 --- a/src/libaktualizr/uptane/tuf.cc +++ b/src/libaktualizr/uptane/tuf.cc @@ -112,6 +112,9 @@ void Target::updateCustom(const Json::Value &custom) { if (custom_.isMember("targetFormat")) { type_ = custom_["targetFormat"].asString(); + if (type_ == "BINARY" && custom_.isMember("rauc")) { + type_ = "RAUC"; + } } if (custom_.isMember("uri")) { @@ -188,6 +191,18 @@ bool Target::IsOstree() const { } } +bool Target::IsRauc() const { + // NOLINTNEXTLINE(bugprone-branch-clone) + if (type_ == "RAUC") { + // Modern servers explicitly specify the type of the target + return true; + } else { + // If type is explicitly not RAUC or the length is non-zero, then this + // is a firmware blob. + return false; + } +} + bool Target::MatchTarget(const Target &t2) const { // type_ (targetFormat) is only provided by the Image repo. // ecus_ is only provided by the Image repo.