From 93585f94ecc1d33b0526ec23b49fbbf44e616152 Mon Sep 17 00:00:00 2001 From: hhvrc Date: Thu, 10 Oct 2024 16:08:36 +0200 Subject: [PATCH 1/6] Refactor OTA Update Manager --- include/Common.h | 10 + include/config/OtaUpdateConfig.h | 4 +- include/http/FirmwareCDN.h | 28 + include/ota/FirmwareBinaryHash.h | 13 + include/ota/FirmwareReleaseInfo.h | 14 + include/{ => ota}/OtaUpdateChannel.h | 0 include/ota/OtaUpdateClient.h | 20 + include/{ => ota}/OtaUpdateManager.h | 12 +- include/{ => ota}/OtaUpdateStep.h | 0 src/GatewayClient.cpp | 2 +- src/OtaUpdateManager.cpp | 688 ------------------ .../websocket/gateway/OtaInstall.cpp | 2 +- src/http/FirmwareCDN.cpp | 157 ++++ src/main.cpp | 2 +- src/ota/OtaUpdateClient.cpp | 275 +++++++ src/ota/OtaUpdateManager.cpp | 326 +++++++++ 16 files changed, 850 insertions(+), 703 deletions(-) create mode 100644 include/http/FirmwareCDN.h create mode 100644 include/ota/FirmwareBinaryHash.h create mode 100644 include/ota/FirmwareReleaseInfo.h rename include/{ => ota}/OtaUpdateChannel.h (100%) create mode 100644 include/ota/OtaUpdateClient.h rename include/{ => ota}/OtaUpdateManager.h (59%) rename include/{ => ota}/OtaUpdateStep.h (100%) delete mode 100644 src/OtaUpdateManager.cpp create mode 100644 src/http/FirmwareCDN.cpp create mode 100644 src/ota/OtaUpdateClient.cpp create mode 100644 src/ota/OtaUpdateManager.cpp diff --git a/include/Common.h b/include/Common.h index 23c4d0e8..ca754ebb 100644 --- a/include/Common.h +++ b/include/Common.h @@ -21,6 +21,16 @@ #endif #define OPENSHOCK_FW_CDN_URL(path) "https://" OPENSHOCK_FW_CDN_DOMAIN path +#define OPENSHOCK_FW_CDN_CHANNEL_URL(ch) OPENSHOCK_FW_CDN_URL("/version-" ch ".txt") +#define OPENSHOCK_FW_CDN_STABLE_URL OPENSHOCK_FW_CDN_CHANNEL_URL("stable") +#define OPENSHOCK_FW_CDN_BETA_URL OPENSHOCK_FW_CDN_CHANNEL_URL("beta") +#define OPENSHOCK_FW_CDN_DEVELOP_URL OPENSHOCK_FW_CDN_CHANNEL_URL("develop") +#define OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT OPENSHOCK_FW_CDN_URL("/%s") +#define OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/boards.txt" +#define OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/" OPENSHOCK_FW_BOARD +#define OPENSHOCK_FW_CDN_APP_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/app.bin" +#define OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/staticfs.bin" +#define OPENSHOCK_FW_CDN_SHA256_HASHES_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/hashes.sha256.txt" #define OPENSHOCK_GPIO_INVALID -1 diff --git a/include/config/OtaUpdateConfig.h b/include/config/OtaUpdateConfig.h index 3bcec5ef..6323586c 100644 --- a/include/config/OtaUpdateConfig.h +++ b/include/config/OtaUpdateConfig.h @@ -2,8 +2,8 @@ #include "config/ConfigBase.h" #include "FirmwareBootType.h" -#include "OtaUpdateChannel.h" -#include "OtaUpdateStep.h" +#include "ota/OtaUpdateChannel.h" +#include "ota/OtaUpdateStep.h" #include diff --git a/include/http/FirmwareCDN.h b/include/http/FirmwareCDN.h new file mode 100644 index 00000000..7134a9d3 --- /dev/null +++ b/include/http/FirmwareCDN.h @@ -0,0 +1,28 @@ +#pragma once + +#include "http/HTTPRequestManager.h" +#include "ota/FirmwareBinaryHash.h" +#include "ota/OtaUpdateChannel.h" +#include "SemVer.h" + +#include + +namespace OpenShock::HTTP::FirmwareCDN { + /// @brief Fetches the firmware version for the given channel from the firmware CDN. + /// Valid response codes: 200, 304 + /// @param channel The channel to fetch the firmware version for. + /// @return The firmware version or an error response. + HTTP::Response GetFirmwareVersion(OtaUpdateChannel channel); + + /// @brief Fetches the list of available boards for the given firmware version from the firmware CDN. + /// Valid response codes: 200, 304 + /// @param version The firmware version to fetch the boards for. + /// @return The list of available boards or an error response. + HTTP::Response> GetFirmwareBoards(const OpenShock::SemVer& version); + + /// @brief Fetches the binary hashes for the given firmware version from the firmware CDN. + /// Valid response codes: 200, 304 + /// @param version The firmware version to fetch the binary hashes for. + /// @return The binary hashes or an error response. + HTTP::Response> GetFirmwareBinaryHashes(const OpenShock::SemVer& version); +} // namespace OpenShock::HTTP::FirmwareCDN diff --git a/include/ota/FirmwareBinaryHash.h b/include/ota/FirmwareBinaryHash.h new file mode 100644 index 00000000..72442c52 --- /dev/null +++ b/include/ota/FirmwareBinaryHash.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace OpenShock +{ + struct FirmwareBinaryHash + { + std::string name; + uint8_t hash[32]; + }; +} // namespace OpenShock diff --git a/include/ota/FirmwareReleaseInfo.h b/include/ota/FirmwareReleaseInfo.h new file mode 100644 index 00000000..63a59e7e --- /dev/null +++ b/include/ota/FirmwareReleaseInfo.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace OpenShock +{ + struct FirmwareReleaseInfo { + std::string appBinaryUrl; + uint8_t appBinaryHash[32]; + std::string filesystemBinaryUrl; + uint8_t filesystemBinaryHash[32]; + }; +} // namespace OpenShock diff --git a/include/OtaUpdateChannel.h b/include/ota/OtaUpdateChannel.h similarity index 100% rename from include/OtaUpdateChannel.h rename to include/ota/OtaUpdateChannel.h diff --git a/include/ota/OtaUpdateClient.h b/include/ota/OtaUpdateClient.h new file mode 100644 index 00000000..37505ebb --- /dev/null +++ b/include/ota/OtaUpdateClient.h @@ -0,0 +1,20 @@ +#pragma once + +#include "SemVer.h" + +#include + +namespace OpenShock { + class OtaUpdateClient { + public: + OtaUpdateClient(const OpenShock::SemVer& version); + ~OtaUpdateClient(); + + bool Start(); + private: + void _task(); + + OpenShock::SemVer m_version; + TaskHandle_t m_taskHandle; + }; +} diff --git a/include/OtaUpdateManager.h b/include/ota/OtaUpdateManager.h similarity index 59% rename from include/OtaUpdateManager.h rename to include/ota/OtaUpdateManager.h index fc2b6b6a..2816817a 100644 --- a/include/OtaUpdateManager.h +++ b/include/ota/OtaUpdateManager.h @@ -1,6 +1,7 @@ #pragma once #include "FirmwareBootType.h" +#include "FirmwareReleaseInfo.h" #include "OtaUpdateChannel.h" #include "SemVer.h" @@ -12,16 +13,7 @@ namespace OpenShock::OtaUpdateManager { [[nodiscard]] bool Init(); - struct FirmwareRelease { - std::string appBinaryUrl; - uint8_t appBinaryHash[32]; - std::string filesystemBinaryUrl; - uint8_t filesystemBinaryHash[32]; - }; - - bool TryGetFirmwareVersion(OtaUpdateChannel channel, OpenShock::SemVer& version); - bool TryGetFirmwareBoards(const OpenShock::SemVer& version, std::vector& boards); - bool TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareRelease& release); + bool TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareReleaseInfo& release); bool TryStartFirmwareInstallation(const OpenShock::SemVer& version); diff --git a/include/OtaUpdateStep.h b/include/ota/OtaUpdateStep.h similarity index 100% rename from include/OtaUpdateStep.h rename to include/ota/OtaUpdateStep.h diff --git a/src/GatewayClient.cpp b/src/GatewayClient.cpp index 3bd2d0b0..d55708c8 100644 --- a/src/GatewayClient.cpp +++ b/src/GatewayClient.cpp @@ -6,7 +6,7 @@ const char* const TAG = "GatewayClient"; #include "config/Config.h" #include "event_handlers/WebSocket.h" #include "Logging.h" -#include "OtaUpdateManager.h" +#include "ota/OtaUpdateManager.h" #include "serialization/WSGateway.h" #include "Time.h" #include "util/CertificateUtils.h" diff --git a/src/OtaUpdateManager.cpp b/src/OtaUpdateManager.cpp deleted file mode 100644 index 907bda9a..00000000 --- a/src/OtaUpdateManager.cpp +++ /dev/null @@ -1,688 +0,0 @@ -#include "OtaUpdateManager.h" - -const char* const TAG = "OtaUpdateManager"; - -#include "CaptivePortal.h" -#include "Common.h" -#include "config/Config.h" -#include "GatewayConnectionManager.h" -#include "Hashing.h" -#include "http/HTTPRequestManager.h" -#include "Logging.h" -#include "SemVer.h" -#include "serialization/WSGateway.h" -#include "Time.h" -#include "util/HexUtils.h" -#include "util/PartitionUtils.h" -#include "util/StringUtils.h" -#include "util/TaskUtils.h" -#include "wifi/WiFiManager.h" - -#include -#include - -#include -#include - -#include -#include - -using namespace std::string_view_literals; - -#define OPENSHOCK_FW_CDN_CHANNEL_URL(ch) OPENSHOCK_FW_CDN_URL("/version-" ch ".txt") - -#define OPENSHOCK_FW_CDN_STABLE_URL OPENSHOCK_FW_CDN_CHANNEL_URL("stable") -#define OPENSHOCK_FW_CDN_BETA_URL OPENSHOCK_FW_CDN_CHANNEL_URL("beta") -#define OPENSHOCK_FW_CDN_DEVELOP_URL OPENSHOCK_FW_CDN_CHANNEL_URL("develop") - -#define OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT OPENSHOCK_FW_CDN_URL("/%s") -#define OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/boards.txt" - -#define OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/" OPENSHOCK_FW_BOARD - -#define OPENSHOCK_FW_CDN_APP_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/app.bin" -#define OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/staticfs.bin" -#define OPENSHOCK_FW_CDN_SHA256_HASHES_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/hashes.sha256.txt" - -/// @brief Stops initArduino() from handling OTA rollbacks -/// @todo Get rid of Arduino entirely. >:( -/// -/// @see .platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-misc.c -/// @return true -bool verifyRollbackLater() { - return true; -} - -using namespace OpenShock; - -enum OtaTaskEventFlag : uint32_t { - OTA_TASK_EVENT_UPDATE_REQUESTED = 1 << 0, - OTA_TASK_EVENT_WIFI_DISCONNECTED = 1 << 1, // If both connected and disconnected are set, disconnected takes priority. - OTA_TASK_EVENT_WIFI_CONNECTED = 1 << 2, -}; - -static esp_ota_img_states_t _otaImageState; -static OpenShock::FirmwareBootType _bootType; -static TaskHandle_t _taskHandle; -static OpenShock::SemVer _requestedVersion; -static SemaphoreHandle_t _requestedVersionMutex = xSemaphoreCreateMutex(); - -bool _tryQueueUpdateRequest(const OpenShock::SemVer& version) { - if (xSemaphoreTake(_requestedVersionMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { - OS_LOGE(TAG, "Failed to take requested version mutex"); - return false; - } - - _requestedVersion = version; - - xSemaphoreGive(_requestedVersionMutex); - - xTaskNotify(_taskHandle, OTA_TASK_EVENT_UPDATE_REQUESTED, eSetBits); - - return true; -} - -bool _tryGetRequestedVersion(OpenShock::SemVer& version) { - if (xSemaphoreTake(_requestedVersionMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { - OS_LOGE(TAG, "Failed to take requested version mutex"); - return false; - } - - version = _requestedVersion; - - xSemaphoreGive(_requestedVersionMutex); - - return true; -} - -void _otaEvGotIPHandler(arduino_event_t* event) { - (void)event; - xTaskNotify(_taskHandle, OTA_TASK_EVENT_WIFI_CONNECTED, eSetBits); -} -void _otaEvWiFiDisconnectedHandler(arduino_event_t* event) { - (void)event; - xTaskNotify(_taskHandle, OTA_TASK_EVENT_WIFI_DISCONNECTED, eSetBits); -} - -bool _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask task, float progress) { - int32_t updateId; - if (!Config::GetOtaUpdateId(updateId)) { - OS_LOGE(TAG, "Failed to get OTA update ID"); - return false; - } - - if (!Serialization::Gateway::SerializeOtaInstallProgressMessage(updateId, task, progress, GatewayConnectionManager::SendMessageBIN)) { - OS_LOGE(TAG, "Failed to send OTA install progress message"); - return false; - } - - return true; -} -bool _sendFailureMessage(std::string_view message, bool fatal = false) { - int32_t updateId; - if (!Config::GetOtaUpdateId(updateId)) { - OS_LOGE(TAG, "Failed to get OTA update ID"); - return false; - } - - if (!Serialization::Gateway::SerializeOtaInstallFailedMessage(updateId, message, fatal, GatewayConnectionManager::SendMessageBIN)) { - OS_LOGE(TAG, "Failed to send OTA install failed message"); - return false; - } - - return true; -} - -bool _flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) { - OS_LOGD(TAG, "Flashing app partition"); - - if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingApplication, 0.0f)) { - return false; - } - - auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool { - OS_LOGD(TAG, "Flashing app partition: %u / %u (%.2f%%)", current, total, progress * 100.0f); - - _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingApplication, progress); - - return true; - }; - - if (!OpenShock::FlashPartitionFromUrl(partition, remoteUrl, remoteHash, onProgress)) { - OS_LOGE(TAG, "Failed to flash app partition"); - _sendFailureMessage("Failed to flash app partition"sv); - return false; - } - - if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::MarkingApplicationBootable, 0.0f)) { - return false; - } - - // Set app partition bootable. - if (esp_ota_set_boot_partition(partition) != ESP_OK) { - OS_LOGE(TAG, "Failed to set app partition bootable"); - _sendFailureMessage("Failed to set app partition bootable"sv); - return false; - } - - return true; -} - -bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) { - if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::PreparingForInstall, 0.0f)) { - return false; - } - - // Make sure captive portal is stopped, timeout after 5 seconds. - if (!CaptivePortal::ForceClose(5000U)) { - OS_LOGE(TAG, "Failed to force close captive portal (timed out)"); - _sendFailureMessage("Failed to force close captive portal (timed out)"sv); - return false; - } - - OS_LOGD(TAG, "Flashing filesystem partition"); - - if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingFilesystem, 0.0f)) { - return false; - } - - auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool { - OS_LOGD(TAG, "Flashing filesystem partition: %u / %u (%.2f%%)", current, total, progress * 100.0f); - - _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingFilesystem, progress); - - return true; - }; - - if (!OpenShock::FlashPartitionFromUrl(parition, remoteUrl, remoteHash, onProgress)) { - OS_LOGE(TAG, "Failed to flash filesystem partition"); - _sendFailureMessage("Failed to flash filesystem partition"sv); - return false; - } - - if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::VerifyingFilesystem, 0.0f)) { - return false; - } - - // Attempt to mount filesystem. - fs::LittleFSFS test; - if (!test.begin(false, "/static", 10, "static0")) { - OS_LOGE(TAG, "Failed to mount filesystem"); - _sendFailureMessage("Failed to mount filesystem"sv); - return false; - } - test.end(); - - OpenShock::CaptivePortal::ForceClose(false); - - return true; -} - -void _otaUpdateTask(void* arg) { - (void)arg; - - OS_LOGD(TAG, "OTA update task started"); - - bool connected = false; - bool updateRequested = false; - int64_t lastUpdateCheck = 0; - - // Update task loop. - while (true) { - // Wait for event. - uint32_t eventBits = 0; - xTaskNotifyWait(0, UINT32_MAX, &eventBits, pdMS_TO_TICKS(5000)); // TODO: wait for rest time - - updateRequested |= (eventBits & OTA_TASK_EVENT_UPDATE_REQUESTED) != 0; - - if ((eventBits & OTA_TASK_EVENT_WIFI_DISCONNECTED) != 0) { - OS_LOGD(TAG, "WiFi disconnected"); - connected = false; - continue; // No further processing needed. - } - - if ((eventBits & OTA_TASK_EVENT_WIFI_CONNECTED) != 0 && !connected) { - OS_LOGD(TAG, "WiFi connected"); - connected = true; - } - - // If we're not connected, continue. - if (!connected) { - continue; - } - - int64_t now = OpenShock::millis(); - - Config::OtaUpdateConfig config; - if (!Config::GetOtaUpdateConfig(config)) { - OS_LOGE(TAG, "Failed to get OTA update config"); - continue; - } - - if (!config.isEnabled) { - OS_LOGD(TAG, "OTA updates are disabled, skipping update check"); - continue; - } - - bool firstCheck = lastUpdateCheck == 0; - int64_t diff = now - lastUpdateCheck; - int64_t diffMins = diff / 60'000LL; - - bool check = false; - check |= config.checkOnStartup && firstCheck; // On startup - check |= config.checkPeriodically && diffMins >= config.checkInterval; // Periodically - check |= updateRequested && (firstCheck || diffMins >= 1); // Update requested - - if (!check) { - continue; - } - - lastUpdateCheck = now; - - if (config.requireManualApproval) { - OS_LOGD(TAG, "Manual approval required, skipping update check"); - // TODO: IMPLEMENT - continue; - } - - OpenShock::SemVer version; - if (updateRequested) { - updateRequested = false; - - if (!_tryGetRequestedVersion(version)) { - OS_LOGE(TAG, "Failed to get requested version"); - continue; - } - - OS_LOGD(TAG, "Update requested for version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - } else { - OS_LOGD(TAG, "Checking for updates"); - - // Fetch current version. - if (!OtaUpdateManager::TryGetFirmwareVersion(config.updateChannel, version)) { - OS_LOGE(TAG, "Failed to fetch firmware version"); - continue; - } - - OS_LOGD(TAG, "Remote version: %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - } - - if (version.toString() == OPENSHOCK_FW_VERSION) { // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - OS_LOGI(TAG, "Requested version is already installed"); - continue; - } - - // Generate random int32_t for this update. - int32_t updateId = static_cast(esp_random()); - if (!Config::SetOtaUpdateId(updateId)) { - OS_LOGE(TAG, "Failed to set OTA update ID"); - continue; - } - if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Updating)) { - OS_LOGE(TAG, "Failed to set OTA update step"); - continue; - } - - if (!Serialization::Gateway::SerializeOtaInstallStartedMessage(updateId, version, GatewayConnectionManager::SendMessageBIN)) { - OS_LOGE(TAG, "Failed to serialize OTA install started message"); - continue; - } - - if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FetchingMetadata, 0.0f)) { - continue; - } - - // Fetch current release. - OtaUpdateManager::FirmwareRelease release; - if (!OtaUpdateManager::TryGetFirmwareRelease(version, release)) { - OS_LOGE(TAG, "Failed to fetch firmware release"); // TODO: Send error message to server - _sendFailureMessage("Failed to fetch firmware release"sv); - continue; - } - - // Print release. - OS_LOGD(TAG, "Firmware release:"); - OS_LOGD(TAG, " Version: %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - OS_LOGD(TAG, " App binary URL: %s", release.appBinaryUrl.c_str()); - OS_LOGD(TAG, " App binary hash: %s", HexUtils::ToHex<32>(release.appBinaryHash).data()); - OS_LOGD(TAG, " Filesystem binary URL: %s", release.filesystemBinaryUrl.c_str()); - OS_LOGD(TAG, " Filesystem binary hash: %s", HexUtils::ToHex<32>(release.filesystemBinaryHash).data()); - - // Get available app update partition. - const esp_partition_t* appPartition = esp_ota_get_next_update_partition(nullptr); - if (appPartition == nullptr) { - OS_LOGE(TAG, "Failed to get app update partition"); // TODO: Send error message to server - _sendFailureMessage("Failed to get app update partition"sv); - continue; - } - - // Get filesystem partition. - const esp_partition_t* filesystemPartition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "static0"); - if (filesystemPartition == nullptr) { - OS_LOGE(TAG, "Failed to find filesystem partition"); // TODO: Send error message to server - _sendFailureMessage("Failed to find filesystem partition"sv); - continue; - } - - // Increase task watchdog timeout. - // Prevents panics on some ESP32s when clearing large partitions. - esp_task_wdt_init(15, true); - - // Flash app and filesystem partitions. - if (!_flashFilesystemPartition(filesystemPartition, release.filesystemBinaryUrl, release.filesystemBinaryHash)) continue; - if (!_flashAppPartition(appPartition, release.appBinaryUrl, release.appBinaryHash)) continue; - - // Set OTA boot type in config. - if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Updated)) { - OS_LOGE(TAG, "Failed to set OTA update step"); - _sendFailureMessage("Failed to set OTA update step"sv); - continue; - } - - // Set task watchdog timeout back to default. - esp_task_wdt_init(5, true); - - // Send reboot message. - _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::Rebooting, 0.0f); - - // Reboot into new firmware. - OS_LOGI(TAG, "Restarting into new firmware..."); - vTaskDelay(pdMS_TO_TICKS(200)); - break; - } - - // Restart. - esp_restart(); -} - -bool _tryGetStringList(std::string_view url, std::vector& list) { - auto response = OpenShock::HTTP::GetString( - url, - { - {"Accept", "text/plain"} - }, - {200, 304} - ); - if (response.result != OpenShock::HTTP::RequestResult::Success) { - OS_LOGE(TAG, "Failed to fetch list: [%u] %s", response.code, response.data.c_str()); - return false; - } - - list.clear(); - - std::string_view data = response.data; - - auto lines = OpenShock::StringSplitNewLines(data); - list.reserve(lines.size()); - - for (auto line : lines) { - line = OpenShock::StringTrim(line); - - if (line.empty()) { - continue; - } - - list.push_back(std::string(line)); - } - - return true; -} - -bool OtaUpdateManager::Init() { - OS_LOGN(TAG, "Fetching current partition"); - - // Fetch current partition info. - const esp_partition_t* partition = esp_ota_get_running_partition(); - if (partition == nullptr) { - OS_PANIC(TAG, "Failed to get currently running partition"); - return false; // This will never be reached, but the compiler doesn't know that. - } - - OS_LOGD(TAG, "Fetching partition state"); - - // Get OTA state for said partition. - esp_err_t err = esp_ota_get_state_partition(partition, &_otaImageState); - if (err != ESP_OK) { - OS_PANIC(TAG, "Failed to get partition state: %s", esp_err_to_name(err)); - return false; // This will never be reached, but the compiler doesn't know that. - } - - OS_LOGD(TAG, "Fetching previous update step"); - OtaUpdateStep updateStep; - if (!Config::GetOtaUpdateStep(updateStep)) { - OS_LOGE(TAG, "Failed to get OTA update step"); - return false; - } - - // Infer boot type from update step. - switch (updateStep) { - case OtaUpdateStep::Updated: - _bootType = FirmwareBootType::NewFirmware; - break; - case OtaUpdateStep::Validating: // If the update step is validating, we have failed in the middle of validating the new firmware, meaning this is a rollback. - case OtaUpdateStep::RollingBack: - _bootType = FirmwareBootType::Rollback; - break; - default: - _bootType = FirmwareBootType::Normal; - break; - } - - if (updateStep == OtaUpdateStep::Updated) { - if (!Config::SetOtaUpdateStep(OtaUpdateStep::Validating)) { - OS_PANIC(TAG, "Failed to set OTA update step in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? - } - } - - WiFi.onEvent(_otaEvGotIPHandler, ARDUINO_EVENT_WIFI_STA_GOT_IP); - WiFi.onEvent(_otaEvGotIPHandler, ARDUINO_EVENT_WIFI_STA_GOT_IP6); - WiFi.onEvent(_otaEvWiFiDisconnectedHandler, ARDUINO_EVENT_WIFI_STA_DISCONNECTED); - - // Start OTA update task. - TaskUtils::TaskCreateExpensive(_otaUpdateTask, "OTA Update", 8192, nullptr, 1, &_taskHandle); // PROFILED: 6.2KB stack usage - - return true; -} - -bool OtaUpdateManager::TryGetFirmwareVersion(OtaUpdateChannel channel, OpenShock::SemVer& version) { - std::string_view channelIndexUrl; - switch (channel) { - case OtaUpdateChannel::Stable: - channelIndexUrl = std::string_view(OPENSHOCK_FW_CDN_STABLE_URL); - break; - case OtaUpdateChannel::Beta: - channelIndexUrl = std::string_view(OPENSHOCK_FW_CDN_BETA_URL); - break; - case OtaUpdateChannel::Develop: - channelIndexUrl = std::string_view(OPENSHOCK_FW_CDN_DEVELOP_URL); - break; - default: - OS_LOGE(TAG, "Unknown channel: %u", channel); - return false; - } - - OS_LOGD(TAG, "Fetching firmware version from %s", channelIndexUrl); - - auto response = OpenShock::HTTP::GetString( - channelIndexUrl, - { - {"Accept", "text/plain"} - }, - {200, 304} - ); - if (response.result != OpenShock::HTTP::RequestResult::Success) { - OS_LOGE(TAG, "Failed to fetch firmware version: [%u] %s", response.code, response.data.c_str()); - return false; - } - - if (!OpenShock::TryParseSemVer(response.data, version)) { - OS_LOGE(TAG, "Failed to parse firmware version: %.*s", response.data.size(), response.data.data()); - return false; - } - - return true; -} - -bool OtaUpdateManager::TryGetFirmwareBoards(const OpenShock::SemVer& version, std::vector& boards) { - std::string channelIndexUrl; - if (!FormatToString(channelIndexUrl, OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT, version.toString().c_str())) { // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - OS_LOGE(TAG, "Failed to format URL"); - return false; - } - - OS_LOGD(TAG, "Fetching firmware boards from %s", channelIndexUrl.c_str()); - - if (!_tryGetStringList(channelIndexUrl, boards)) { - OS_LOGE(TAG, "Failed to fetch firmware boards"); - return false; - } - - return true; -} - -bool _tryParseIntoHash(std::string_view hash, uint8_t (&hashBytes)[32]) { - if (!HexUtils::TryParseHex(hash.data(), hash.size(), hashBytes, 32)) { - OS_LOGE(TAG, "Failed to parse hash: %.*s", hash.size(), hash.data()); - return false; - } - - return true; -} - -bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareRelease& release) { - auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - - if (!FormatToString(release.appBinaryUrl, OPENSHOCK_FW_CDN_APP_URL_FORMAT, versionStr.c_str())) { - OS_LOGE(TAG, "Failed to format URL"); - return false; - } - - if (!FormatToString(release.filesystemBinaryUrl, OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT, versionStr.c_str())) { - OS_LOGE(TAG, "Failed to format URL"); - return false; - } - - // Construct hash URLs. - std::string sha256HashesUrl; - if (!FormatToString(sha256HashesUrl, OPENSHOCK_FW_CDN_SHA256_HASHES_URL_FORMAT, versionStr.c_str())) { - OS_LOGE(TAG, "Failed to format URL"); - return false; - } - - // Fetch hashes. - auto sha256HashesResponse = OpenShock::HTTP::GetString( - sha256HashesUrl, - { - {"Accept", "text/plain"} - }, - {200, 304} - ); - if (sha256HashesResponse.result != OpenShock::HTTP::RequestResult::Success) { - OS_LOGE(TAG, "Failed to fetch hashes: [%u] %s", sha256HashesResponse.code, sha256HashesResponse.data.c_str()); - return false; - } - - auto hashesLines = OpenShock::StringSplitNewLines(sha256HashesResponse.data); - - // Parse hashes. - bool foundAppHash = false, foundFilesystemHash = false; - for (std::string_view line : hashesLines) { - auto parts = OpenShock::StringSplitWhiteSpace(line); - if (parts.size() != 2) { - OS_LOGE(TAG, "Invalid hashes entry: %.*s", line.size(), line.data()); - return false; - } - - auto hash = OpenShock::StringTrim(parts[0]); - auto file = OpenShock::StringTrim(parts[1]); - - if (OpenShock::StringStartsWith(file, "./"sv)) { - file = file.substr(2); - } - - if (hash.size() != 64) { - OS_LOGE(TAG, "Invalid hash: %.*s", hash.size(), hash.data()); - return false; - } - - if (file == "app.bin") { - if (foundAppHash) { - OS_LOGE(TAG, "Duplicate hash for app.bin"); - return false; - } - - if (!_tryParseIntoHash(hash, release.appBinaryHash)) { - return false; - } - - foundAppHash = true; - } else if (file == "staticfs.bin") { - if (foundFilesystemHash) { - OS_LOGE(TAG, "Duplicate hash for staticfs.bin"); - return false; - } - - if (!_tryParseIntoHash(hash, release.filesystemBinaryHash)) { - return false; - } - - foundFilesystemHash = true; - } - } - - return true; -} - -bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) { - OS_LOGD(TAG, "Requesting firmware version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - - return _tryQueueUpdateRequest(version); -} - -FirmwareBootType OtaUpdateManager::GetFirmwareBootType() { - return _bootType; -} - -bool OtaUpdateManager::IsValidatingApp() { - return _otaImageState == ESP_OTA_IMG_PENDING_VERIFY; -} - -void OtaUpdateManager::InvalidateAndRollback() { - // Set OTA boot type in config. - if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::RollingBack)) { - OS_PANIC(TAG, "Failed to set OTA firmware boot type in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? - return; - } - - switch (esp_ota_mark_app_invalid_rollback_and_reboot()) { - case ESP_FAIL: - OS_LOGE(TAG, "Rollback failed (ESP_FAIL)"); - break; - case ESP_ERR_OTA_ROLLBACK_FAILED: - OS_LOGE(TAG, "Rollback failed (ESP_ERR_OTA_ROLLBACK_FAILED)"); - break; - default: - OS_LOGE(TAG, "Rollback failed (Unknown)"); - break; - } - - // Set OTA boot type in config. - if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::None)) { - OS_LOGE(TAG, "Failed to set OTA firmware boot type"); - } - - esp_restart(); -} - -void OtaUpdateManager::ValidateApp() { - if (esp_ota_mark_app_valid_cancel_rollback() != ESP_OK) { - OS_PANIC(TAG, "Unable to mark app as valid, WTF?"); // TODO: Wtf do we do here? - } - - // Set OTA boot type in config. - if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Validated)) { - OS_PANIC(TAG, "Failed to set OTA firmware boot type in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? - } - - _otaImageState = ESP_OTA_IMG_VALID; -} diff --git a/src/event_handlers/websocket/gateway/OtaInstall.cpp b/src/event_handlers/websocket/gateway/OtaInstall.cpp index 55812ce1..f79849df 100644 --- a/src/event_handlers/websocket/gateway/OtaInstall.cpp +++ b/src/event_handlers/websocket/gateway/OtaInstall.cpp @@ -4,7 +4,7 @@ const char* const TAG = "ServerMessageHandlers"; #include "CaptivePortal.h" #include "Logging.h" -#include "OtaUpdateManager.h" +#include "ota/OtaUpdateManager.h" #include diff --git a/src/http/FirmwareCDN.cpp b/src/http/FirmwareCDN.cpp new file mode 100644 index 00000000..35ed5670 --- /dev/null +++ b/src/http/FirmwareCDN.cpp @@ -0,0 +1,157 @@ +#include + +#include "http/FirmwareCDN.h" + +#include "Common.h" +#include "Logging.h" +#include "util/StringUtils.h" +#include "util/HexUtils.h" + +const char* const TAG = "FirmwareCDN"; + +using namespace std::string_view_literals; + + +using namespace OpenShock; + +HTTP::Response HTTP::FirmwareCDN::GetFirmwareVersion(OtaUpdateChannel channel) { + std::string_view channelIndexUrl; + switch (channel) { + case OtaUpdateChannel::Stable: + channelIndexUrl = OPENSHOCK_FW_CDN_STABLE_URL""sv; + break; + case OtaUpdateChannel::Beta: + channelIndexUrl = OPENSHOCK_FW_CDN_BETA_URL""sv; + break; + case OtaUpdateChannel::Develop: + channelIndexUrl = OPENSHOCK_FW_CDN_DEVELOP_URL""sv; + break; + default: + OS_LOGE(TAG, "Unknown channel: %u", channel); + return {RequestResult::InternalError, 0, {}}; + } + + OS_LOGD(TAG, "Fetching firmware version from %.*s", channelIndexUrl.size(), channelIndexUrl.data()); + + auto response = OpenShock::HTTP::GetString( + channelIndexUrl, + { + {"Accept", "text/plain"} + }, + {200, 304} + ); + + if (response.result != OpenShock::HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch firmware version: [%u] %s", response.code, response.data.c_str()); + return {RequestResult::InternalError, 0, {}}; + } + + OpenShock::SemVer version; + if (!OpenShock::TryParseSemVer(response.data, version)) { + OS_LOGE(TAG, "Failed to parse firmware version: %.*s", response.data.size(), response.data.data()); + return {RequestResult::ParseFailed, response.code, {}}; + } + + return {response.result, response.code, version}; +} + +HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBoards(const OpenShock::SemVer& version) { + std::string channelIndexUrl; + if (!FormatToString(channelIndexUrl, OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT, version.toString().c_str())) { // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + OS_LOGE(TAG, "Failed to format URL"); + return {RequestResult::InternalError, 0, {}}; + } + + OS_LOGD(TAG, "Fetching firmware boards from %s", channelIndexUrl.c_str()); + + auto response = OpenShock::HTTP::GetString( + channelIndexUrl, + { + {"Accept", "text/plain"} + }, + {200, 304} + ); + + if (response.result != OpenShock::HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch firmware boards: [%u] %s", response.code, response.data.c_str()); + return {RequestResult::InternalError, 0, {}}; + } + + auto lines = OpenShock::StringSplitNewLines(response.data); + + std::vector boards; + boards.reserve(lines.size()); + + for (auto line : lines) { + line = OpenShock::StringTrim(line); + + if (line.empty()) { + continue; + } + + boards.push_back(std::string(line)); + } + + return {response.result, response.code, std::move(boards)}; +} + +HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBinaryHashes(const OpenShock::SemVer& version) { + auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + + // Construct hash URLs. + std::string sha256HashesUrl; + if (!FormatToString(sha256HashesUrl, OPENSHOCK_FW_CDN_SHA256_HASHES_URL_FORMAT, versionStr.c_str())) { + OS_LOGE(TAG, "Failed to format URL"); + return {RequestResult::InternalError, 0, {}}; + } + + // Fetch hashes. + auto sha256HashesResponse = OpenShock::HTTP::GetString( + sha256HashesUrl, + { + {"Accept", "text/plain"} + }, + {200, 304} + ); + if (sha256HashesResponse.result != OpenShock::HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch hashes: [%u] %s", sha256HashesResponse.code, sha256HashesResponse.data.c_str()); + return {RequestResult::InternalError, 0, {}}; + } + + auto hashesLines = OpenShock::StringSplitNewLines(sha256HashesResponse.data); + + // Parse hashes. + std::vector hashes; + for (std::string_view line : hashesLines) { + auto parts = OpenShock::StringSplitWhiteSpace(line); + if (parts.size() != 2) { + OS_LOGE(TAG, "Invalid hashes entry: %.*s", line.size(), line.data()); + return {RequestResult::InternalError, 0, {}}; + } + + auto hash = OpenShock::StringTrim(parts[0]); + auto file = OpenShock::StringTrim(parts[1]); + + if (OpenShock::StringStartsWith(file, "./"sv)) { + file = file.substr(2); + } + + if (hash.size() != 64) { + OS_LOGE(TAG, "Invalid hash: %.*s", hash.size(), hash.data()); + return {RequestResult::InternalError, 0, {}}; + } + + FirmwareBinaryHash binaryHash; + + if (!HexUtils::TryParseHex(hash.data(), hash.size(), binaryHash.hash, sizeof(binaryHash.hash))) { + OS_LOGE(TAG, "Failed to parse hash: %.*s", hash.size(), hash.data()); + return {RequestResult::InternalError, 0, {}}; + } + + binaryHash.name = std::string(file); + + hashes.push_back(std::move(binaryHash)); + } + + return {RequestResult::Success, 200, std::move(hashes)}; +} diff --git a/src/main.cpp b/src/main.cpp index 42fa8cb7..6028d91f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,7 @@ const char* const TAG = "main"; #include "EStopManager.h" #include "GatewayConnectionManager.h" #include "Logging.h" -#include "OtaUpdateManager.h" +#include "ota/OtaUpdateManager.h" #include "serial/SerialInputHandler.h" #include "util/TaskUtils.h" #include "VisualStateManager.h" diff --git a/src/ota/OtaUpdateClient.cpp b/src/ota/OtaUpdateClient.cpp new file mode 100644 index 00000000..b84db1d2 --- /dev/null +++ b/src/ota/OtaUpdateClient.cpp @@ -0,0 +1,275 @@ +#include + +#include "Logging.h" +#include "ota/OtaUpdateClient.h" +#include "ota/OtaUpdateStep.h" +#include "serialization/WSGateway.h" + +#include "util/FnProxy.h" +#include "util/HextUtils.h" +#include "util/PartitionUtils.h" +#include "util/TaskUtils.h" + +#include +#include +#include + +#include + +const char* const TAG = "OtaUpdateClient"; + +using namespace OpenShock; + +bool _tryStartUpdate(const OpenShock::SemVer& version) { + if (xSemaphoreTake(_requestedVersionMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { + OS_LOGE(TAG, "Failed to take requested version mutex"); + return false; + } + + _requestedVersion = version; + + xSemaphoreGive(_requestedVersionMutex); + + xTaskNotify(_taskHandle, OTA_TASK_EVENT_UPDATE_REQUESTED, eSetBits); + + return true; +} + +bool _tryGetRequestedVersion(OpenShock::SemVer& version) { + if (xSemaphoreTake(_requestedVersionMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { + OS_LOGE(TAG, "Failed to take requested version mutex"); + return false; + } + + version = _requestedVersion; + + xSemaphoreGive(_requestedVersionMutex); + + return true; +} + +bool _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask task, float progress) { + int32_t updateId; + if (!Config::GetOtaUpdateId(updateId)) { + OS_LOGE(TAG, "Failed to get OTA update ID"); + return false; + } + + if (!Serialization::Gateway::SerializeOtaInstallProgressMessage(updateId, task, progress, GatewayConnectionManager::SendMessageBIN)) { + OS_LOGE(TAG, "Failed to send OTA install progress message"); + return false; + } + + return true; +} +bool _sendFailureMessage(std::string_view message, bool fatal = false) { + int32_t updateId; + if (!Config::GetOtaUpdateId(updateId)) { + OS_LOGE(TAG, "Failed to get OTA update ID"); + return false; + } + + if (!Serialization::Gateway::SerializeOtaInstallFailedMessage(updateId, message, fatal, GatewayConnectionManager::SendMessageBIN)) { + OS_LOGE(TAG, "Failed to send OTA install failed message"); + return false; + } + + return true; +} + +bool _flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) { + OS_LOGD(TAG, "Flashing app partition"); + + if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingApplication, 0.0f)) { + return false; + } + + auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool { + OS_LOGD(TAG, "Flashing app partition: %u / %u (%.2f%%)", current, total, progress * 100.0f); + + _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingApplication, progress); + + return true; + }; + + if (!OpenShock::FlashPartitionFromUrl(partition, remoteUrl, remoteHash, onProgress)) { + OS_LOGE(TAG, "Failed to flash app partition"); + _sendFailureMessage("Failed to flash app partition"sv); + return false; + } + + if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::MarkingApplicationBootable, 0.0f)) { + return false; + } + + // Set app partition bootable. + if (esp_ota_set_boot_partition(partition) != ESP_OK) { + OS_LOGE(TAG, "Failed to set app partition bootable"); + _sendFailureMessage("Failed to set app partition bootable"sv); + return false; + } + + return true; +} + +bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) { + if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::PreparingForInstall, 0.0f)) { + return false; + } + + // Make sure captive portal is stopped, timeout after 5 seconds. + if (!CaptivePortal::ForceClose(5000U)) { + OS_LOGE(TAG, "Failed to force close captive portal (timed out)"); + _sendFailureMessage("Failed to force close captive portal (timed out)"sv); + return false; + } + + OS_LOGD(TAG, "Flashing filesystem partition"); + + if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingFilesystem, 0.0f)) { + return false; + } + + auto onProgress = [](std::size_t current, std::size_t total, float progress) -> bool { + OS_LOGD(TAG, "Flashing filesystem partition: %u / %u (%.2f%%)", current, total, progress * 100.0f); + + _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingFilesystem, progress); + + return true; + }; + + if (!OpenShock::FlashPartitionFromUrl(parition, remoteUrl, remoteHash, onProgress)) { + OS_LOGE(TAG, "Failed to flash filesystem partition"); + _sendFailureMessage("Failed to flash filesystem partition"sv); + return false; + } + + if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::VerifyingFilesystem, 0.0f)) { + return false; + } + + // Attempt to mount filesystem. + fs::LittleFSFS test; + if (!test.begin(false, "/static", 10, "static0")) { + OS_LOGE(TAG, "Failed to mount filesystem"); + _sendFailureMessage("Failed to mount filesystem"sv); + return false; + } + test.end(); + + OpenShock::CaptivePortal::ForceClose(false); + + return true; +} + +OtaUpdateClient::OtaUpdateClient(const OpenShock::SemVer& version) + : m_version(version) + , m_taskHandle(nullptr) +{ +} + +OtaUpdateClient::~OtaUpdateClient() +{ + if (m_taskHandle != nullptr) { + vTaskDelete(m_taskHandle); + } +} + +bool OtaUpdateClient::Start() +{ + if (m_taskHandle != nullptr) { + OS_LOGE(TAG, "Task already started"); + return false; + } + + if (TaskUtils::TaskCreateExpensive(&Util::FnProxy<&OtaUpdateClient::_task>, TAG, 8192, this, 1, &m_taskHandle) != pdPASS) { + OS_LOGE(TAG, "Failed to create OTA update task"); + return false; + } + + return true; +} + +void OtaUpdateClient::_task() +{ + // Generate random int32_t for this update. + int32_t updateId = static_cast(esp_random()); + if (!Config::SetOtaUpdateId(updateId)) { + OS_LOGE(TAG, "Failed to set OTA update ID"); + continue; + } + if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Updating)) { + OS_LOGE(TAG, "Failed to set OTA update step"); + continue; + } + + if (!Serialization::Gateway::SerializeOtaInstallStartedMessage(updateId, version, GatewayConnectionManager::SendMessageBIN)) { + OS_LOGE(TAG, "Failed to serialize OTA install started message"); + continue; + } + + if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FetchingMetadata, 0.0f)) { + continue; + } + + // Fetch current release. + OtaUpdateManager::FirmwareRelease release; + if (!OtaUpdateManager::TryGetFirmwareRelease(version, release)) { + OS_LOGE(TAG, "Failed to fetch firmware release"); // TODO: Send error message to server + _sendFailureMessage("Failed to fetch firmware release"sv); + continue; + } + + // Print release. + OS_LOGD(TAG, "Firmware release:"); + OS_LOGD(TAG, " Version: %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + OS_LOGD(TAG, " App binary URL: %s", release.appBinaryUrl.c_str()); + OS_LOGD(TAG, " App binary hash: %s", HexUtils::ToHex<32>(release.appBinaryHash).data()); + OS_LOGD(TAG, " Filesystem binary URL: %s", release.filesystemBinaryUrl.c_str()); + OS_LOGD(TAG, " Filesystem binary hash: %s", HexUtils::ToHex<32>(release.filesystemBinaryHash).data()); + + // Get available app update partition. + const esp_partition_t* appPartition = esp_ota_get_next_update_partition(nullptr); + if (appPartition == nullptr) { + OS_LOGE(TAG, "Failed to get app update partition"); // TODO: Send error message to server + _sendFailureMessage("Failed to get app update partition"sv); + continue; + } + + // Get filesystem partition. + const esp_partition_t* filesystemPartition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "static0"); + if (filesystemPartition == nullptr) { + OS_LOGE(TAG, "Failed to find filesystem partition"); // TODO: Send error message to server + _sendFailureMessage("Failed to find filesystem partition"sv); + continue; + } + + // Increase task watchdog timeout. + // Prevents panics on some ESP32s when clearing large partitions. + esp_task_wdt_init(15, true); + + // Flash app and filesystem partitions. + if (!_flashFilesystemPartition(filesystemPartition, release.filesystemBinaryUrl, release.filesystemBinaryHash)) continue; + if (!_flashAppPartition(appPartition, release.appBinaryUrl, release.appBinaryHash)) continue; + + // Set OTA boot type in config. + if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Updated)) { + OS_LOGE(TAG, "Failed to set OTA update step"); + _sendFailureMessage("Failed to set OTA update step"sv); + continue; + } + + // Set task watchdog timeout back to default. + esp_task_wdt_init(5, true); + + // Send reboot message. + _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::Rebooting, 0.0f); + + // Reboot into new firmware. + OS_LOGI(TAG, "Restarting into new firmware..."); + vTaskDelay(pdMS_TO_TICKS(200)); + break; + + // Restart. + esp_restart(); +} diff --git a/src/ota/OtaUpdateManager.cpp b/src/ota/OtaUpdateManager.cpp new file mode 100644 index 00000000..e8158d09 --- /dev/null +++ b/src/ota/OtaUpdateManager.cpp @@ -0,0 +1,326 @@ +#include + +#include "ota/OtaUpdateManager.h" + +const char* const TAG = "OtaUpdateManager"; + +#include "Common.h" +#include "config/Config.h" +#include "http/FirmwareCDN.h" +#include "ota/OtaUpdateClient.h" +#include "ota/OtaUpdateStep.h" +#include "util/StringUtils.h" +#include "util/TaskUtils.h" +#include "Logging.h" +#include "SemVer.h" + +#include // TODO: Get rid of Arduino entirely. >:( + +#include +#include + +#include +#include +#include + +using namespace std::string_view_literals; + +/// @brief Stops initArduino() from handling OTA rollbacks +/// @todo Get rid of Arduino entirely. >:( +/// +/// @see .platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-misc.c +/// @return true +bool verifyRollbackLater() { + return true; +} + +using namespace OpenShock; + +enum OtaTaskEventFlag : uint32_t { + OTA_TASK_EVENT_UPDATE_REQUESTED = 1 << 0, + OTA_TASK_EVENT_WIFI_DISCONNECTED = 1 << 1, // If both connected and disconnected are set, disconnected takes priority. + OTA_TASK_EVENT_WIFI_CONNECTED = 1 << 2, +}; + +static esp_ota_img_states_t _otaImageState; +static OpenShock::FirmwareBootType _bootType; +static TaskHandle_t _watcherTaskHandle = nullptr; +static SemaphoreHandle_t _updateClientMutex = xSemaphoreCreateMutex(); +static std::unique_ptr _updateClient = nullptr; + +bool _tryStartUpdate(const OpenShock::SemVer& version) { + if (xSemaphoreTake(_updateClientMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { + OS_LOGE(TAG, "Failed to take requested version mutex"); + return false; + } + + if (_updateClient != nullptr) { + xSemaphoreGive(_updateClientMutex); + OS_LOGE(TAG, "Update client already started"); + return false; + } + + _updateClient = std::make_unique(version); + + if (!_updateClient->Start()) { + _updateClient.reset(); + xSemaphoreGive(_updateClientMutex); + OS_LOGE(TAG, "Failed to start update client"); + return false; + } + + xSemaphoreGive(_updateClientMutex); + + OS_LOGD(TAG, "Update client started"); + + return true; +} + +void _otaEvGotIPHandler(arduino_event_t*) { + xTaskNotify(_watcherTaskHandle, OTA_TASK_EVENT_WIFI_CONNECTED, eSetBits); +} +void _otaEvWiFiDisconnectedHandler(arduino_event_t*) { + xTaskNotify(_watcherTaskHandle, OTA_TASK_EVENT_WIFI_DISCONNECTED, eSetBits); +} + +void _otaWatcherTask(void*) { + + OS_LOGD(TAG, "OTA update task started"); + + bool connected = false; + bool updateRequested = false; + int64_t lastUpdateCheck = 0; + + // Update task loop. + while (true) { + // Wait for event. + uint32_t eventBits = 0; + xTaskNotifyWait(0, UINT32_MAX, &eventBits, pdMS_TO_TICKS(5000)); // TODO: wait for rest time + + updateRequested |= (eventBits & OTA_TASK_EVENT_UPDATE_REQUESTED) != 0; + + if ((eventBits & OTA_TASK_EVENT_WIFI_DISCONNECTED) != 0) { + OS_LOGD(TAG, "WiFi disconnected"); + connected = false; + continue; // No further processing needed. + } + + if ((eventBits & OTA_TASK_EVENT_WIFI_CONNECTED) != 0 && !connected) { + OS_LOGD(TAG, "WiFi connected"); + connected = true; + } + + // If we're not connected, continue. + if (!connected) { + continue; + } + + int64_t now = OpenShock::millis(); + + Config::OtaUpdateConfig config; + if (!Config::GetOtaUpdateConfig(config)) { + OS_LOGE(TAG, "Failed to get OTA update config"); + continue; + } + + if (!config.isEnabled) { + OS_LOGD(TAG, "OTA updates are disabled, skipping update check"); + continue; + } + + bool firstCheck = lastUpdateCheck == 0; + int64_t diff = now - lastUpdateCheck; + int64_t diffMins = diff / 60'000LL; + + bool check = false; + check |= config.checkOnStartup && firstCheck; // On startup + check |= config.checkPeriodically && diffMins >= config.checkInterval; // Periodically + check |= updateRequested && (firstCheck || diffMins >= 1); // Update requested + + if (!check) { + continue; + } + + lastUpdateCheck = now; + + if (config.requireManualApproval) { + OS_LOGD(TAG, "Manual approval required, skipping update check"); + // TODO: IMPLEMENT + continue; + } + + OpenShock::SemVer version; + if (updateRequested) { + updateRequested = false; + + if (!_tryGetRequestedVersion(version)) { + OS_LOGE(TAG, "Failed to get requested version"); + continue; + } + + OS_LOGD(TAG, "Update requested for version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + } else { + OS_LOGD(TAG, "Checking for updates"); + + // Fetch current version. + if (!OtaUpdateManager::TryGetFirmwareVersion(config.updateChannel, version)) { + OS_LOGE(TAG, "Failed to fetch firmware version"); + continue; + } + + OS_LOGD(TAG, "Remote version: %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + } + + if (version.toString() == OPENSHOCK_FW_VERSION) { // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + OS_LOGI(TAG, "Requested version is already installed"); + continue; + } + + + } +} + +bool OtaUpdateManager::Init() { + OS_LOGN(TAG, "Fetching current partition"); + + // Fetch current partition info. + const esp_partition_t* partition = esp_ota_get_running_partition(); + if (partition == nullptr) { + OS_PANIC(TAG, "Failed to get currently running partition"); + return false; // This will never be reached, but the compiler doesn't know that. + } + + OS_LOGD(TAG, "Fetching partition state"); + + // Get OTA state for said partition. + esp_err_t err = esp_ota_get_state_partition(partition, &_otaImageState); + if (err != ESP_OK) { + OS_PANIC(TAG, "Failed to get partition state: %s", esp_err_to_name(err)); + return false; // This will never be reached, but the compiler doesn't know that. + } + + OS_LOGD(TAG, "Fetching previous update step"); + OtaUpdateStep updateStep; + if (!Config::GetOtaUpdateStep(updateStep)) { + OS_LOGE(TAG, "Failed to get OTA update step"); + return false; + } + + // Infer boot type from update step. + switch (updateStep) { + case OtaUpdateStep::Updated: + _bootType = FirmwareBootType::NewFirmware; + break; + case OtaUpdateStep::Validating: // If the update step is validating, we have failed in the middle of validating the new firmware, meaning this is a rollback. + case OtaUpdateStep::RollingBack: + _bootType = FirmwareBootType::Rollback; + break; + default: + _bootType = FirmwareBootType::Normal; + break; + } + + if (updateStep == OtaUpdateStep::Updated) { + if (!Config::SetOtaUpdateStep(OtaUpdateStep::Validating)) { + OS_PANIC(TAG, "Failed to set OTA update step in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? + } + } + + WiFi.onEvent(_otaEvGotIPHandler, ARDUINO_EVENT_WIFI_STA_GOT_IP); + WiFi.onEvent(_otaEvGotIPHandler, ARDUINO_EVENT_WIFI_STA_GOT_IP6); + WiFi.onEvent(_otaEvWiFiDisconnectedHandler, ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + + if (TaskUtils::TaskCreateExpensive(_otaWatcherTask, "OtaWatcherTask", 8192, nullptr, 1, &_watcherTaskHandle) != pdPASS) { + OS_LOGE(TAG, "Failed to create OTA watcher task"); + return false; + } + + return true; +} + +bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareReleaseInfo& release) { + auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + + if (!FormatToString(release.appBinaryUrl, OPENSHOCK_FW_CDN_APP_URL_FORMAT, versionStr.c_str())) { + OS_LOGE(TAG, "Failed to format URL"); + return false; + } + + if (!FormatToString(release.filesystemBinaryUrl, OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT, versionStr.c_str())) { + OS_LOGE(TAG, "Failed to format URL"); + return false; + } + + // Fetch hashes. + auto response = HTTP::FirmwareCDN::GetFirmwareBinaryHashes(version); + if (response.result != HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch hashes: [%u]", response.code); + return false; + } + + for (auto binaryHash : response.data) { + if (binaryHash.name == "app.bin") { + static_assert(sizeof(release.appBinaryHash) == sizeof(binaryHash.hash), "Hash size mismatch"); + memcpy(release.appBinaryHash, binaryHash.hash, sizeof(release.appBinaryHash)); + } else if (binaryHash.name == "staticfs.bin") { + static_assert(sizeof(release.filesystemBinaryHash) == sizeof(binaryHash.hash), "Hash size mismatch"); + memcpy(release.filesystemBinaryHash, binaryHash.hash, sizeof(release.filesystemBinaryHash)); + } + } + + return true; +} + +bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) { + OS_LOGD(TAG, "Requesting firmware version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + + return _tryStartUpdate(version); +} + +FirmwareBootType OtaUpdateManager::GetFirmwareBootType() { + return _bootType; +} + +bool OtaUpdateManager::IsValidatingApp() { + return _otaImageState == ESP_OTA_IMG_PENDING_VERIFY; +} + +void OtaUpdateManager::InvalidateAndRollback() { + // Set OTA boot type in config. + if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::RollingBack)) { + OS_PANIC(TAG, "Failed to set OTA firmware boot type in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? + return; + } + + switch (esp_ota_mark_app_invalid_rollback_and_reboot()) { + case ESP_FAIL: + OS_LOGE(TAG, "Rollback failed (ESP_FAIL)"); + break; + case ESP_ERR_OTA_ROLLBACK_FAILED: + OS_LOGE(TAG, "Rollback failed (ESP_ERR_OTA_ROLLBACK_FAILED)"); + break; + default: + OS_LOGE(TAG, "Rollback failed (Unknown)"); + break; + } + + // Set OTA boot type in config. + if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::None)) { + OS_LOGE(TAG, "Failed to set OTA firmware boot type"); + } + + esp_restart(); +} + +void OtaUpdateManager::ValidateApp() { + if (esp_ota_mark_app_valid_cancel_rollback() != ESP_OK) { + OS_PANIC(TAG, "Unable to mark app as valid, WTF?"); // TODO: Wtf do we do here? + } + + // Set OTA boot type in config. + if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::Validated)) { + OS_PANIC(TAG, "Failed to set OTA firmware boot type in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? + } + + _otaImageState = ESP_OTA_IMG_VALID; +} From f6e2c2cfb4cc7252a6f386294ca28c1e7bda088a Mon Sep 17 00:00:00 2001 From: hhvrc Date: Fri, 11 Oct 2024 00:34:06 +0200 Subject: [PATCH 2/6] Formatting --- include/Common.h | 27 +++++++-------- include/config/OtaUpdateConfig.h | 11 +------ include/ota/FirmwareBinaryHash.h | 8 ++--- include/ota/FirmwareReleaseInfo.h | 5 ++- include/ota/OtaUpdateChannel.h | 5 +-- include/ota/OtaUpdateClient.h | 3 +- include/ota/OtaUpdateStep.h | 3 +- src/ota/OtaUpdateClient.cpp | 18 ++++++---- src/ota/OtaUpdateManager.cpp | 55 ++++++++++++++++++------------- 9 files changed, 71 insertions(+), 64 deletions(-) diff --git a/include/Common.h b/include/Common.h index ca754ebb..35423fa1 100644 --- a/include/Common.h +++ b/include/Common.h @@ -3,11 +3,11 @@ #include #include -#define DISABLE_COPY(TypeName) \ - TypeName(const TypeName&) = delete; \ +#define DISABLE_COPY(TypeName) \ + TypeName(const TypeName&) = delete; \ void operator=(const TypeName&) = delete -#define DISABLE_MOVE(TypeName) \ - TypeName(TypeName&&) = delete; \ +#define DISABLE_MOVE(TypeName) \ + TypeName(TypeName&&) = delete; \ void operator=(TypeName&&) = delete #ifndef OPENSHOCK_API_DOMAIN @@ -20,14 +20,14 @@ #error "OPENSHOCK_FW_VERSION must be defined" #endif -#define OPENSHOCK_FW_CDN_URL(path) "https://" OPENSHOCK_FW_CDN_DOMAIN path -#define OPENSHOCK_FW_CDN_CHANNEL_URL(ch) OPENSHOCK_FW_CDN_URL("/version-" ch ".txt") -#define OPENSHOCK_FW_CDN_STABLE_URL OPENSHOCK_FW_CDN_CHANNEL_URL("stable") -#define OPENSHOCK_FW_CDN_BETA_URL OPENSHOCK_FW_CDN_CHANNEL_URL("beta") -#define OPENSHOCK_FW_CDN_DEVELOP_URL OPENSHOCK_FW_CDN_CHANNEL_URL("develop") -#define OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT OPENSHOCK_FW_CDN_URL("/%s") -#define OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/boards.txt" -#define OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/" OPENSHOCK_FW_BOARD +#define OPENSHOCK_FW_CDN_URL(path) "https://" OPENSHOCK_FW_CDN_DOMAIN path +#define OPENSHOCK_FW_CDN_CHANNEL_URL(ch) OPENSHOCK_FW_CDN_URL("/version-" ch ".txt") +#define OPENSHOCK_FW_CDN_STABLE_URL OPENSHOCK_FW_CDN_CHANNEL_URL("stable") +#define OPENSHOCK_FW_CDN_BETA_URL OPENSHOCK_FW_CDN_CHANNEL_URL("beta") +#define OPENSHOCK_FW_CDN_DEVELOP_URL OPENSHOCK_FW_CDN_CHANNEL_URL("develop") +#define OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT OPENSHOCK_FW_CDN_URL("/%s") +#define OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/boards.txt" +#define OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT OPENSHOCK_FW_CDN_BOARDS_BASE_URL_FORMAT "/" OPENSHOCK_FW_BOARD #define OPENSHOCK_FW_CDN_APP_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/app.bin" #define OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/staticfs.bin" #define OPENSHOCK_FW_CDN_SHA256_HASHES_URL_FORMAT OPENSHOCK_FW_CDN_VERSION_BASE_URL_FORMAT "/hashes.sha256.txt" @@ -50,7 +50,8 @@ // Check if Arduino.h exists, if not instruct the developer to remove "arduino-esp32" from the useragent and replace it with "ESP-IDF", after which the developer may remove this warning. #if defined(__has_include) && !__has_include("Arduino.h") -#warning "Let it be known that Arduino hath finally been cast aside in favor of the noble ESP-IDF! I beseech thee, kind sir or madam, wouldst thou kindly partake in the honors of expunging 'arduino-esp32' from yonder useragent aloft, and in its stead, bestow the illustrious 'ESP-IDF'?" +#warning \ + "Let it be known that Arduino hath finally been cast aside in favor of the noble ESP-IDF! I beseech thee, kind sir or madam, wouldst thou kindly partake in the honors of expunging 'arduino-esp32' from yonder useragent aloft, and in its stead, bestow the illustrious 'ESP-IDF'?" #endif #if __cplusplus >= 202'302L diff --git a/include/config/OtaUpdateConfig.h b/include/config/OtaUpdateConfig.h index 6323586c..1f67bcb1 100644 --- a/include/config/OtaUpdateConfig.h +++ b/include/config/OtaUpdateConfig.h @@ -11,16 +11,7 @@ namespace OpenShock::Config { struct OtaUpdateConfig : public ConfigBase { OtaUpdateConfig(); OtaUpdateConfig( - bool isEnabled, - std::string cdnDomain, - OtaUpdateChannel updateChannel, - bool checkOnStartup, - bool checkPeriodically, - uint16_t checkInterval, - bool allowBackendManagement, - bool requireManualApproval, - int32_t updateId, - OtaUpdateStep updateStep + bool isEnabled, std::string cdnDomain, OtaUpdateChannel updateChannel, bool checkOnStartup, bool checkPeriodically, uint16_t checkInterval, bool allowBackendManagement, bool requireManualApproval, int32_t updateId, OtaUpdateStep updateStep ); bool isEnabled; diff --git a/include/ota/FirmwareBinaryHash.h b/include/ota/FirmwareBinaryHash.h index 72442c52..1b4b642f 100644 --- a/include/ota/FirmwareBinaryHash.h +++ b/include/ota/FirmwareBinaryHash.h @@ -3,11 +3,9 @@ #include #include -namespace OpenShock -{ - struct FirmwareBinaryHash - { +namespace OpenShock { + struct FirmwareBinaryHash { std::string name; uint8_t hash[32]; }; -} // namespace OpenShock +} // namespace OpenShock diff --git a/include/ota/FirmwareReleaseInfo.h b/include/ota/FirmwareReleaseInfo.h index 63a59e7e..febd8039 100644 --- a/include/ota/FirmwareReleaseInfo.h +++ b/include/ota/FirmwareReleaseInfo.h @@ -3,12 +3,11 @@ #include #include -namespace OpenShock -{ +namespace OpenShock { struct FirmwareReleaseInfo { std::string appBinaryUrl; uint8_t appBinaryHash[32]; std::string filesystemBinaryUrl; uint8_t filesystemBinaryHash[32]; }; -} // namespace OpenShock +} // namespace OpenShock diff --git a/include/ota/OtaUpdateChannel.h b/include/ota/OtaUpdateChannel.h index e0bbf2fa..3ab942b3 100644 --- a/include/ota/OtaUpdateChannel.h +++ b/include/ota/OtaUpdateChannel.h @@ -2,13 +2,14 @@ #include "serialization/_fbs/HubConfig_generated.h" -#include #include +#include namespace OpenShock { typedef OpenShock::Serialization::Configuration::OtaUpdateChannel OtaUpdateChannel; - inline bool TryParseOtaUpdateChannel(OtaUpdateChannel& channel, const char* str) { + inline bool TryParseOtaUpdateChannel(OtaUpdateChannel& channel, const char* str) + { if (strcasecmp(str, "stable") == 0) { channel = OtaUpdateChannel::Stable; return true; diff --git a/include/ota/OtaUpdateClient.h b/include/ota/OtaUpdateClient.h index 37505ebb..ed985b40 100644 --- a/include/ota/OtaUpdateClient.h +++ b/include/ota/OtaUpdateClient.h @@ -11,10 +11,11 @@ namespace OpenShock { ~OtaUpdateClient(); bool Start(); + private: void _task(); OpenShock::SemVer m_version; TaskHandle_t m_taskHandle; }; -} +} // namespace OpenShock diff --git a/include/ota/OtaUpdateStep.h b/include/ota/OtaUpdateStep.h index 28a9dae7..8fae996f 100644 --- a/include/ota/OtaUpdateStep.h +++ b/include/ota/OtaUpdateStep.h @@ -8,7 +8,8 @@ namespace OpenShock { typedef OpenShock::Serialization::Configuration::OtaUpdateStep OtaUpdateStep; - inline bool TryParseOtaUpdateStep(OtaUpdateStep& channel, const char* str) { + inline bool TryParseOtaUpdateStep(OtaUpdateStep& channel, const char* str) + { if (strcasecmp(str, "none") == 0) { channel = OtaUpdateStep::None; return true; diff --git a/src/ota/OtaUpdateClient.cpp b/src/ota/OtaUpdateClient.cpp index b84db1d2..391ef36d 100644 --- a/src/ota/OtaUpdateClient.cpp +++ b/src/ota/OtaUpdateClient.cpp @@ -20,7 +20,8 @@ const char* const TAG = "OtaUpdateClient"; using namespace OpenShock; -bool _tryStartUpdate(const OpenShock::SemVer& version) { +bool _tryStartUpdate(const OpenShock::SemVer& version) +{ if (xSemaphoreTake(_requestedVersionMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { OS_LOGE(TAG, "Failed to take requested version mutex"); return false; @@ -35,7 +36,8 @@ bool _tryStartUpdate(const OpenShock::SemVer& version) { return true; } -bool _tryGetRequestedVersion(OpenShock::SemVer& version) { +bool _tryGetRequestedVersion(OpenShock::SemVer& version) +{ if (xSemaphoreTake(_requestedVersionMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { OS_LOGE(TAG, "Failed to take requested version mutex"); return false; @@ -48,7 +50,8 @@ bool _tryGetRequestedVersion(OpenShock::SemVer& version) { return true; } -bool _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask task, float progress) { +bool _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask task, float progress) +{ int32_t updateId; if (!Config::GetOtaUpdateId(updateId)) { OS_LOGE(TAG, "Failed to get OTA update ID"); @@ -62,7 +65,8 @@ bool _sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask task, f return true; } -bool _sendFailureMessage(std::string_view message, bool fatal = false) { +bool _sendFailureMessage(std::string_view message, bool fatal = false) +{ int32_t updateId; if (!Config::GetOtaUpdateId(updateId)) { OS_LOGE(TAG, "Failed to get OTA update ID"); @@ -77,7 +81,8 @@ bool _sendFailureMessage(std::string_view message, bool fatal = false) { return true; } -bool _flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) { +bool _flashAppPartition(const esp_partition_t* partition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) +{ OS_LOGD(TAG, "Flashing app partition"); if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::FlashingApplication, 0.0f)) { @@ -112,7 +117,8 @@ bool _flashAppPartition(const esp_partition_t* partition, std::string_view remot return true; } -bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) { +bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view remoteUrl, const uint8_t (&remoteHash)[32]) +{ if (!_sendProgressMessage(Serialization::Gateway::OtaInstallProgressTask::PreparingForInstall, 0.0f)) { return false; } diff --git a/src/ota/OtaUpdateManager.cpp b/src/ota/OtaUpdateManager.cpp index e8158d09..f2463a8d 100644 --- a/src/ota/OtaUpdateManager.cpp +++ b/src/ota/OtaUpdateManager.cpp @@ -7,14 +7,14 @@ const char* const TAG = "OtaUpdateManager"; #include "Common.h" #include "config/Config.h" #include "http/FirmwareCDN.h" +#include "Logging.h" #include "ota/OtaUpdateClient.h" #include "ota/OtaUpdateStep.h" +#include "SemVer.h" #include "util/StringUtils.h" #include "util/TaskUtils.h" -#include "Logging.h" -#include "SemVer.h" -#include // TODO: Get rid of Arduino entirely. >:( +#include // TODO: Get rid of Arduino entirely. >:( #include #include @@ -30,7 +30,8 @@ using namespace std::string_view_literals; /// /// @see .platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-misc.c /// @return true -bool verifyRollbackLater() { +bool verifyRollbackLater() +{ return true; } @@ -44,11 +45,12 @@ enum OtaTaskEventFlag : uint32_t { static esp_ota_img_states_t _otaImageState; static OpenShock::FirmwareBootType _bootType; -static TaskHandle_t _watcherTaskHandle = nullptr; -static SemaphoreHandle_t _updateClientMutex = xSemaphoreCreateMutex(); +static TaskHandle_t _watcherTaskHandle = nullptr; +static SemaphoreHandle_t _updateClientMutex = xSemaphoreCreateMutex(); static std::unique_ptr _updateClient = nullptr; -bool _tryStartUpdate(const OpenShock::SemVer& version) { +bool _tryStartUpdate(const OpenShock::SemVer& version) +{ if (xSemaphoreTake(_updateClientMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { OS_LOGE(TAG, "Failed to take requested version mutex"); return false; @@ -76,19 +78,21 @@ bool _tryStartUpdate(const OpenShock::SemVer& version) { return true; } -void _otaEvGotIPHandler(arduino_event_t*) { +void _otaEvGotIPHandler(arduino_event_t*) +{ xTaskNotify(_watcherTaskHandle, OTA_TASK_EVENT_WIFI_CONNECTED, eSetBits); } -void _otaEvWiFiDisconnectedHandler(arduino_event_t*) { +void _otaEvWiFiDisconnectedHandler(arduino_event_t*) +{ xTaskNotify(_watcherTaskHandle, OTA_TASK_EVENT_WIFI_DISCONNECTED, eSetBits); } -void _otaWatcherTask(void*) { - +void _otaWatcherTask(void*) +{ OS_LOGD(TAG, "OTA update task started"); - bool connected = false; - bool updateRequested = false; + bool connected = false; + bool updateRequested = false; int64_t lastUpdateCheck = 0; // Update task loop. @@ -128,7 +132,7 @@ void _otaWatcherTask(void*) { continue; } - bool firstCheck = lastUpdateCheck == 0; + bool firstCheck = lastUpdateCheck == 0; int64_t diff = now - lastUpdateCheck; int64_t diffMins = diff / 60'000LL; @@ -175,12 +179,11 @@ void _otaWatcherTask(void*) { OS_LOGI(TAG, "Requested version is already installed"); continue; } - - } } -bool OtaUpdateManager::Init() { +bool OtaUpdateManager::Init() +{ OS_LOGN(TAG, "Fetching current partition"); // Fetch current partition info. @@ -238,7 +241,8 @@ bool OtaUpdateManager::Init() { return true; } -bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareReleaseInfo& release) { +bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareReleaseInfo& release) +{ auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this if (!FormatToString(release.appBinaryUrl, OPENSHOCK_FW_CDN_APP_URL_FORMAT, versionStr.c_str())) { @@ -271,21 +275,25 @@ bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, F return true; } -bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) { +bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) +{ OS_LOGD(TAG, "Requesting firmware version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this return _tryStartUpdate(version); } -FirmwareBootType OtaUpdateManager::GetFirmwareBootType() { +FirmwareBootType OtaUpdateManager::GetFirmwareBootType() +{ return _bootType; } -bool OtaUpdateManager::IsValidatingApp() { +bool OtaUpdateManager::IsValidatingApp() +{ return _otaImageState == ESP_OTA_IMG_PENDING_VERIFY; } -void OtaUpdateManager::InvalidateAndRollback() { +void OtaUpdateManager::InvalidateAndRollback() +{ // Set OTA boot type in config. if (!Config::SetOtaUpdateStep(OpenShock::OtaUpdateStep::RollingBack)) { OS_PANIC(TAG, "Failed to set OTA firmware boot type in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? @@ -312,7 +320,8 @@ void OtaUpdateManager::InvalidateAndRollback() { esp_restart(); } -void OtaUpdateManager::ValidateApp() { +void OtaUpdateManager::ValidateApp() +{ if (esp_ota_mark_app_valid_cancel_rollback() != ESP_OK) { OS_PANIC(TAG, "Unable to mark app as valid, WTF?"); // TODO: Wtf do we do here? } From 2d73dfda443b7f52da17b69e872efda4c8341e1d Mon Sep 17 00:00:00 2001 From: hhvrc Date: Fri, 11 Oct 2024 00:34:20 +0200 Subject: [PATCH 3/6] Oops --- src/ota/OtaUpdateClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ota/OtaUpdateClient.cpp b/src/ota/OtaUpdateClient.cpp index 391ef36d..e202bb8f 100644 --- a/src/ota/OtaUpdateClient.cpp +++ b/src/ota/OtaUpdateClient.cpp @@ -6,7 +6,7 @@ #include "serialization/WSGateway.h" #include "util/FnProxy.h" -#include "util/HextUtils.h" +#include "util/HexUtils.h" #include "util/PartitionUtils.h" #include "util/TaskUtils.h" From 0311393e3db08ab16e59b296d0833838981d3c7a Mon Sep 17 00:00:00 2001 From: hhvrc Date: Fri, 11 Oct 2024 14:12:19 +0200 Subject: [PATCH 4/6] Fix http call --- src/ota/OtaUpdateManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ota/OtaUpdateManager.cpp b/src/ota/OtaUpdateManager.cpp index f2463a8d..6d72b210 100644 --- a/src/ota/OtaUpdateManager.cpp +++ b/src/ota/OtaUpdateManager.cpp @@ -167,7 +167,8 @@ void _otaWatcherTask(void*) OS_LOGD(TAG, "Checking for updates"); // Fetch current version. - if (!OtaUpdateManager::TryGetFirmwareVersion(config.updateChannel, version)) { + auto result = HTTP::FirmwareCDN::GetFirmwareVersion(config.updateChannel); + if (result.result != HTTP::RequestResult::Success) { OS_LOGE(TAG, "Failed to fetch firmware version"); continue; } From ea0f0c6dd5b84694f2c5e96fecb613d83d5bbb2a Mon Sep 17 00:00:00 2001 From: hhvrc Date: Fri, 11 Oct 2024 15:41:04 +0200 Subject: [PATCH 5/6] More refactoring fixup --- include/http/FirmwareCDN.h | 7 ++++ include/ota/OtaUpdateManager.h | 2 - src/http/FirmwareCDN.cpp | 53 +++++++++++++++++++---- src/ota/OtaUpdateClient.cpp | 23 +++++++--- src/ota/OtaUpdateManager.cpp | 77 ++++++---------------------------- 5 files changed, 82 insertions(+), 80 deletions(-) diff --git a/include/http/FirmwareCDN.h b/include/http/FirmwareCDN.h index 7134a9d3..2634bbe2 100644 --- a/include/http/FirmwareCDN.h +++ b/include/http/FirmwareCDN.h @@ -2,6 +2,7 @@ #include "http/HTTPRequestManager.h" #include "ota/FirmwareBinaryHash.h" +#include "ota/FirmwareReleaseInfo.h" #include "ota/OtaUpdateChannel.h" #include "SemVer.h" @@ -25,4 +26,10 @@ namespace OpenShock::HTTP::FirmwareCDN { /// @param version The firmware version to fetch the binary hashes for. /// @return The binary hashes or an error response. HTTP::Response> GetFirmwareBinaryHashes(const OpenShock::SemVer& version); + + /// @brief Fetches the firmware release information for the given firmware version from the firmware CDN. + /// Valid response codes: 200, 304 + /// @param version The firmware version to fetch the release information for. + /// @return The firmware release information or an error response. + HTTP::Response GetFirmwareReleaseInfo(const OpenShock::SemVer& version); } // namespace OpenShock::HTTP::FirmwareCDN diff --git a/include/ota/OtaUpdateManager.h b/include/ota/OtaUpdateManager.h index 2816817a..f8cda171 100644 --- a/include/ota/OtaUpdateManager.h +++ b/include/ota/OtaUpdateManager.h @@ -13,8 +13,6 @@ namespace OpenShock::OtaUpdateManager { [[nodiscard]] bool Init(); - bool TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareReleaseInfo& release); - bool TryStartFirmwareInstallation(const OpenShock::SemVer& version); FirmwareBootType GetFirmwareBootType(); diff --git a/src/http/FirmwareCDN.cpp b/src/http/FirmwareCDN.cpp index 35ed5670..04c0f4bd 100644 --- a/src/http/FirmwareCDN.cpp +++ b/src/http/FirmwareCDN.cpp @@ -4,27 +4,27 @@ #include "Common.h" #include "Logging.h" -#include "util/StringUtils.h" #include "util/HexUtils.h" +#include "util/StringUtils.h" const char* const TAG = "FirmwareCDN"; using namespace std::string_view_literals; - using namespace OpenShock; -HTTP::Response HTTP::FirmwareCDN::GetFirmwareVersion(OtaUpdateChannel channel) { +HTTP::Response HTTP::FirmwareCDN::GetFirmwareVersion(OtaUpdateChannel channel) +{ std::string_view channelIndexUrl; switch (channel) { case OtaUpdateChannel::Stable: - channelIndexUrl = OPENSHOCK_FW_CDN_STABLE_URL""sv; + channelIndexUrl = OPENSHOCK_FW_CDN_STABLE_URL ""sv; break; case OtaUpdateChannel::Beta: - channelIndexUrl = OPENSHOCK_FW_CDN_BETA_URL""sv; + channelIndexUrl = OPENSHOCK_FW_CDN_BETA_URL ""sv; break; case OtaUpdateChannel::Develop: - channelIndexUrl = OPENSHOCK_FW_CDN_DEVELOP_URL""sv; + channelIndexUrl = OPENSHOCK_FW_CDN_DEVELOP_URL ""sv; break; default: OS_LOGE(TAG, "Unknown channel: %u", channel); @@ -55,7 +55,8 @@ HTTP::Response HTTP::FirmwareCDN::GetFirmwareVersion(OtaUpdat return {response.result, response.code, version}; } -HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBoards(const OpenShock::SemVer& version) { +HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBoards(const OpenShock::SemVer& version) +{ std::string channelIndexUrl; if (!FormatToString(channelIndexUrl, OPENSHOCK_FW_CDN_BOARDS_INDEX_URL_FORMAT, version.toString().c_str())) { // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this OS_LOGE(TAG, "Failed to format URL"); @@ -95,7 +96,8 @@ HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBoards(co return {response.result, response.code, std::move(boards)}; } -HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBinaryHashes(const OpenShock::SemVer& version) { +HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBinaryHashes(const OpenShock::SemVer& version) +{ auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this // Construct hash URLs. @@ -155,3 +157,38 @@ HTTP::Response> HTTP::FirmwareCDN::GetFirmwareBi return {RequestResult::Success, 200, std::move(hashes)}; } + +HTTP::Response HTTP::FirmwareCDN::GetFirmwareReleaseInfo(const OpenShock::SemVer& version) +{ + auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + + FirmwareReleaseInfo release; + if (!FormatToString(release.appBinaryUrl, OPENSHOCK_FW_CDN_APP_URL_FORMAT, versionStr.c_str())) { + OS_LOGE(TAG, "Failed to format URL"); + return {RequestResult::InternalError, 0, {}}; + } + + if (!FormatToString(release.filesystemBinaryUrl, OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT, versionStr.c_str())) { + OS_LOGE(TAG, "Failed to format URL"); + return {RequestResult::InternalError, 0, {}}; + } + + // Fetch hashes. + auto response = GetFirmwareBinaryHashes(version); + if (response.result != HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch hashes: [%u]", response.code); + return {response.result, response.code, {}}; + } + + for (auto binaryHash : response.data) { + if (binaryHash.name == "app.bin") { + static_assert(sizeof(release.appBinaryHash) == sizeof(binaryHash.hash), "Hash size mismatch"); + memcpy(release.appBinaryHash, binaryHash.hash, sizeof(release.appBinaryHash)); + } else if (binaryHash.name == "staticfs.bin") { + static_assert(sizeof(release.filesystemBinaryHash) == sizeof(binaryHash.hash), "Hash size mismatch"); + memcpy(release.filesystemBinaryHash, binaryHash.hash, sizeof(release.filesystemBinaryHash)); + } + } + + return {response.result, response.code, release}; +} diff --git a/src/ota/OtaUpdateClient.cpp b/src/ota/OtaUpdateClient.cpp index e202bb8f..fbb35f37 100644 --- a/src/ota/OtaUpdateClient.cpp +++ b/src/ota/OtaUpdateClient.cpp @@ -1,24 +1,33 @@ #include +#include "CaptivePortal.h" +#include "config/Config.h" +#include "GatewayConnectionManager.h" +#include "http/FirmwareCDN.h" #include "Logging.h" +#include "ota/FirmwareReleaseInfo.h" #include "ota/OtaUpdateClient.h" +#include "ota/OtaUpdateManager.h" #include "ota/OtaUpdateStep.h" #include "serialization/WSGateway.h" - #include "util/FnProxy.h" #include "util/HexUtils.h" #include "util/PartitionUtils.h" #include "util/TaskUtils.h" +#include + #include #include #include +#include #include const char* const TAG = "OtaUpdateClient"; using namespace OpenShock; +using namespace std::string_view_literals; bool _tryStartUpdate(const OpenShock::SemVer& version) { @@ -209,7 +218,7 @@ void OtaUpdateClient::_task() continue; } - if (!Serialization::Gateway::SerializeOtaInstallStartedMessage(updateId, version, GatewayConnectionManager::SendMessageBIN)) { + if (!Serialization::Gateway::SerializeOtaInstallStartedMessage(updateId, m_version, GatewayConnectionManager::SendMessageBIN)) { OS_LOGE(TAG, "Failed to serialize OTA install started message"); continue; } @@ -219,16 +228,18 @@ void OtaUpdateClient::_task() } // Fetch current release. - OtaUpdateManager::FirmwareRelease release; - if (!OtaUpdateManager::TryGetFirmwareRelease(version, release)) { - OS_LOGE(TAG, "Failed to fetch firmware release"); // TODO: Send error message to server + auto response = HTTP::FirmwareCDN::GetFirmwareReleaseInfo(m_version); + if (response.result != HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch firmware release: [%u]", response.code); _sendFailureMessage("Failed to fetch firmware release"sv); continue; } + auto& release = response.data; + // Print release. OS_LOGD(TAG, "Firmware release:"); - OS_LOGD(TAG, " Version: %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this + OS_LOGD(TAG, " Version: %s", m_version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this OS_LOGD(TAG, " App binary URL: %s", release.appBinaryUrl.c_str()); OS_LOGD(TAG, " App binary hash: %s", HexUtils::ToHex<32>(release.appBinaryHash).data()); OS_LOGD(TAG, " Filesystem binary URL: %s", release.filesystemBinaryUrl.c_str()); diff --git a/src/ota/OtaUpdateManager.cpp b/src/ota/OtaUpdateManager.cpp index 6d72b210..ba215930 100644 --- a/src/ota/OtaUpdateManager.cpp +++ b/src/ota/OtaUpdateManager.cpp @@ -38,9 +38,8 @@ bool verifyRollbackLater() using namespace OpenShock; enum OtaTaskEventFlag : uint32_t { - OTA_TASK_EVENT_UPDATE_REQUESTED = 1 << 0, - OTA_TASK_EVENT_WIFI_DISCONNECTED = 1 << 1, // If both connected and disconnected are set, disconnected takes priority. - OTA_TASK_EVENT_WIFI_CONNECTED = 1 << 2, + OTA_TASK_EVENT_WIFI_DISCONNECTED = 1 << 0, // If both connected and disconnected are set, disconnected takes priority. + OTA_TASK_EVENT_WIFI_CONNECTED = 1 << 1, }; static esp_ota_img_states_t _otaImageState; @@ -92,7 +91,6 @@ void _otaWatcherTask(void*) OS_LOGD(TAG, "OTA update task started"); bool connected = false; - bool updateRequested = false; int64_t lastUpdateCheck = 0; // Update task loop. @@ -101,8 +99,6 @@ void _otaWatcherTask(void*) uint32_t eventBits = 0; xTaskNotifyWait(0, UINT32_MAX, &eventBits, pdMS_TO_TICKS(5000)); // TODO: wait for rest time - updateRequested |= (eventBits & OTA_TASK_EVENT_UPDATE_REQUESTED) != 0; - if ((eventBits & OTA_TASK_EVENT_WIFI_DISCONNECTED) != 0) { OS_LOGD(TAG, "WiFi disconnected"); connected = false; @@ -139,7 +135,6 @@ void _otaWatcherTask(void*) bool check = false; check |= config.checkOnStartup && firstCheck; // On startup check |= config.checkPeriodically && diffMins >= config.checkInterval; // Periodically - check |= updateRequested && (firstCheck || diffMins >= 1); // Update requested if (!check) { continue; @@ -153,33 +148,16 @@ void _otaWatcherTask(void*) continue; } - OpenShock::SemVer version; - if (updateRequested) { - updateRequested = false; - - if (!_tryGetRequestedVersion(version)) { - OS_LOGE(TAG, "Failed to get requested version"); - continue; - } - - OS_LOGD(TAG, "Update requested for version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - } else { - OS_LOGD(TAG, "Checking for updates"); - - // Fetch current version. - auto result = HTTP::FirmwareCDN::GetFirmwareVersion(config.updateChannel); - if (result.result != HTTP::RequestResult::Success) { - OS_LOGE(TAG, "Failed to fetch firmware version"); - continue; - } - - OS_LOGD(TAG, "Remote version: %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - } + OS_LOGD(TAG, "Checking for updates"); - if (version.toString() == OPENSHOCK_FW_VERSION) { // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - OS_LOGI(TAG, "Requested version is already installed"); + // Fetch current version. + auto result = HTTP::FirmwareCDN::GetFirmwareVersion(config.updateChannel); + if (result.result != HTTP::RequestResult::Success) { + OS_LOGE(TAG, "Failed to fetch firmware version"); continue; } + + OS_LOGD(TAG, "Remote version: %s", result.data.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this } } @@ -242,42 +220,13 @@ bool OtaUpdateManager::Init() return true; } -bool OtaUpdateManager::TryGetFirmwareRelease(const OpenShock::SemVer& version, FirmwareReleaseInfo& release) +bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) { - auto versionStr = version.toString(); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - - if (!FormatToString(release.appBinaryUrl, OPENSHOCK_FW_CDN_APP_URL_FORMAT, versionStr.c_str())) { - OS_LOGE(TAG, "Failed to format URL"); - return false; - } - - if (!FormatToString(release.filesystemBinaryUrl, OPENSHOCK_FW_CDN_FILESYSTEM_URL_FORMAT, versionStr.c_str())) { - OS_LOGE(TAG, "Failed to format URL"); - return false; - } - - // Fetch hashes. - auto response = HTTP::FirmwareCDN::GetFirmwareBinaryHashes(version); - if (response.result != HTTP::RequestResult::Success) { - OS_LOGE(TAG, "Failed to fetch hashes: [%u]", response.code); - return false; - } - - for (auto binaryHash : response.data) { - if (binaryHash.name == "app.bin") { - static_assert(sizeof(release.appBinaryHash) == sizeof(binaryHash.hash), "Hash size mismatch"); - memcpy(release.appBinaryHash, binaryHash.hash, sizeof(release.appBinaryHash)); - } else if (binaryHash.name == "staticfs.bin") { - static_assert(sizeof(release.filesystemBinaryHash) == sizeof(binaryHash.hash), "Hash size mismatch"); - memcpy(release.filesystemBinaryHash, binaryHash.hash, sizeof(release.filesystemBinaryHash)); - } + if (version == OPENSHOCK_FW_VERSION ""sv) { + OS_LOGI(TAG, "Requested version is already installed"); + return true; } - return true; -} - -bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) -{ OS_LOGD(TAG, "Requesting firmware version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this return _tryStartUpdate(version); From e4077493185447e7b44a8378fcd9f9c9815fdf9a Mon Sep 17 00:00:00 2001 From: hhvrc Date: Thu, 14 Nov 2024 14:31:48 +0100 Subject: [PATCH 6/6] Apply other lost changes --- src/ota/OtaUpdateClient.cpp | 2 - src/ota/OtaUpdateManager.cpp | 99 +++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/ota/OtaUpdateClient.cpp b/src/ota/OtaUpdateClient.cpp index fbb35f37..d2fe361c 100644 --- a/src/ota/OtaUpdateClient.cpp +++ b/src/ota/OtaUpdateClient.cpp @@ -172,8 +172,6 @@ bool _flashFilesystemPartition(const esp_partition_t* parition, std::string_view } test.end(); - OpenShock::CaptivePortal::ForceClose(false); - return true; } diff --git a/src/ota/OtaUpdateManager.cpp b/src/ota/OtaUpdateManager.cpp index ba215930..d39c5cb2 100644 --- a/src/ota/OtaUpdateManager.cpp +++ b/src/ota/OtaUpdateManager.cpp @@ -11,6 +11,7 @@ const char* const TAG = "OtaUpdateManager"; #include "ota/OtaUpdateClient.h" #include "ota/OtaUpdateStep.h" #include "SemVer.h" +#include "SimpleMutex.h" #include "util/StringUtils.h" #include "util/TaskUtils.h" @@ -35,58 +36,78 @@ bool verifyRollbackLater() return true; } -using namespace OpenShock; - enum OtaTaskEventFlag : uint32_t { OTA_TASK_EVENT_WIFI_DISCONNECTED = 1 << 0, // If both connected and disconnected are set, disconnected takes priority. OTA_TASK_EVENT_WIFI_CONNECTED = 1 << 1, }; -static esp_ota_img_states_t _otaImageState; -static OpenShock::FirmwareBootType _bootType; -static TaskHandle_t _watcherTaskHandle = nullptr; -static SemaphoreHandle_t _updateClientMutex = xSemaphoreCreateMutex(); -static std::unique_ptr _updateClient = nullptr; +static esp_ota_img_states_t s_otaImageState; +static OpenShock::FirmwareBootType s_bootType; +static TaskHandle_t s_taskHandle = nullptr; +static OpenShock::SimpleMutex s_clientMtx = {}; +static std::unique_ptr s_client = nullptr; -bool _tryStartUpdate(const OpenShock::SemVer& version) +using namespace OpenShock; + +static bool tryStartUpdate(const OpenShock::SemVer& version) { - if (xSemaphoreTake(_updateClientMutex, pdMS_TO_TICKS(1000)) != pdTRUE) { + if (!s_clientMtx.lock(pdMS_TO_TICKS(1000))) { OS_LOGE(TAG, "Failed to take requested version mutex"); return false; } - if (_updateClient != nullptr) { - xSemaphoreGive(_updateClientMutex); + if (s_client != nullptr) { + s_clientMtx.unlock(); OS_LOGE(TAG, "Update client already started"); return false; } - _updateClient = std::make_unique(version); + s_client = std::make_unique(version); - if (!_updateClient->Start()) { - _updateClient.reset(); - xSemaphoreGive(_updateClientMutex); + if (!s_client->Start()) { + s_client.reset(); + s_clientMtx.unlock(); OS_LOGE(TAG, "Failed to start update client"); return false; } - xSemaphoreGive(_updateClientMutex); + s_clientMtx.unlock(); OS_LOGD(TAG, "Update client started"); return true; } -void _otaEvGotIPHandler(arduino_event_t*) +static void wifiDisconnectedEventHandler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { - xTaskNotify(_watcherTaskHandle, OTA_TASK_EVENT_WIFI_CONNECTED, eSetBits); + (void)event_handler_arg; + (void)event_base; + (void)event_id; + (void)event_data; + + xTaskNotify(s_taskHandle, OTA_TASK_EVENT_WIFI_DISCONNECTED, eSetBits); } -void _otaEvWiFiDisconnectedHandler(arduino_event_t*) + +static void ipEventHandler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { - xTaskNotify(_watcherTaskHandle, OTA_TASK_EVENT_WIFI_DISCONNECTED, eSetBits); + (void)event_handler_arg; + (void)event_base; + (void)event_data; + + switch (event_id) { + case IP_EVENT_GOT_IP6: + case IP_EVENT_STA_GOT_IP: + xTaskNotify(s_taskHandle, OTA_TASK_EVENT_WIFI_CONNECTED, eSetBits); + break; + case IP_EVENT_STA_LOST_IP: + xTaskNotify(s_taskHandle, OTA_TASK_EVENT_WIFI_DISCONNECTED, eSetBits); + break; + default: + return; + } } -void _otaWatcherTask(void*) +static void watcherTask(void*) { OS_LOGD(TAG, "OTA update task started"); @@ -163,6 +184,8 @@ void _otaWatcherTask(void*) bool OtaUpdateManager::Init() { + esp_err_t err; + OS_LOGN(TAG, "Fetching current partition"); // Fetch current partition info. @@ -175,7 +198,7 @@ bool OtaUpdateManager::Init() OS_LOGD(TAG, "Fetching partition state"); // Get OTA state for said partition. - esp_err_t err = esp_ota_get_state_partition(partition, &_otaImageState); + err = esp_ota_get_state_partition(partition, &s_otaImageState); if (err != ESP_OK) { OS_PANIC(TAG, "Failed to get partition state: %s", esp_err_to_name(err)); return false; // This will never be reached, but the compiler doesn't know that. @@ -191,14 +214,14 @@ bool OtaUpdateManager::Init() // Infer boot type from update step. switch (updateStep) { case OtaUpdateStep::Updated: - _bootType = FirmwareBootType::NewFirmware; + s_bootType = FirmwareBootType::NewFirmware; break; case OtaUpdateStep::Validating: // If the update step is validating, we have failed in the middle of validating the new firmware, meaning this is a rollback. case OtaUpdateStep::RollingBack: - _bootType = FirmwareBootType::Rollback; + s_bootType = FirmwareBootType::Rollback; break; default: - _bootType = FirmwareBootType::Normal; + s_bootType = FirmwareBootType::Normal; break; } @@ -208,11 +231,19 @@ bool OtaUpdateManager::Init() } } - WiFi.onEvent(_otaEvGotIPHandler, ARDUINO_EVENT_WIFI_STA_GOT_IP); - WiFi.onEvent(_otaEvGotIPHandler, ARDUINO_EVENT_WIFI_STA_GOT_IP6); - WiFi.onEvent(_otaEvWiFiDisconnectedHandler, ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + err = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, ipEventHandler, nullptr); + if (err != ESP_OK) { + OS_LOGE(TAG, "Failed to register event handler for IP_EVENT: %s", esp_err_to_name(err)); + return false; + } + + err = esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, wifiDisconnectedEventHandler, nullptr); + if (err != ESP_OK) { + OS_LOGE(TAG, "Failed to register event handler for WIFI_EVENT: %s", esp_err_to_name(err)); + return false; + } - if (TaskUtils::TaskCreateExpensive(_otaWatcherTask, "OtaWatcherTask", 8192, nullptr, 1, &_watcherTaskHandle) != pdPASS) { + if (TaskUtils::TaskCreateExpensive(watcherTask, "OtaWatcherTask", 8192, nullptr, 1, &s_taskHandle) != pdPASS) { OS_LOGE(TAG, "Failed to create OTA watcher task"); return false; } @@ -220,7 +251,7 @@ bool OtaUpdateManager::Init() return true; } -bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& version) +bool OtaUpdateManager::TryStartFirmwareUpdate(const OpenShock::SemVer& version) { if (version == OPENSHOCK_FW_VERSION ""sv) { OS_LOGI(TAG, "Requested version is already installed"); @@ -229,17 +260,17 @@ bool OtaUpdateManager::TryStartFirmwareInstallation(const OpenShock::SemVer& ver OS_LOGD(TAG, "Requesting firmware version %s", version.toString().c_str()); // TODO: This is abusing the SemVer::toString() method causing alot of string copies, fix this - return _tryStartUpdate(version); + return tryStartUpdate(version); } FirmwareBootType OtaUpdateManager::GetFirmwareBootType() { - return _bootType; + return s_bootType; } bool OtaUpdateManager::IsValidatingApp() { - return _otaImageState == ESP_OTA_IMG_PENDING_VERIFY; + return s_otaImageState == ESP_OTA_IMG_PENDING_VERIFY; } void OtaUpdateManager::InvalidateAndRollback() @@ -281,5 +312,5 @@ void OtaUpdateManager::ValidateApp() OS_PANIC(TAG, "Failed to set OTA firmware boot type in critical section"); // TODO: THIS IS A CRITICAL SECTION, WHAT DO WE DO? } - _otaImageState = ESP_OTA_IMG_VALID; + s_otaImageState = ESP_OTA_IMG_VALID; }