Add ESP32 OTA firmware update with rollback confirmation.

Stream application binaries to the inactive OTA slot via POST /api/system/ota,
validate the esp_app_desc project name in core, and cancel rollback after a
healthy network boot through OtaService::confirmBoot().

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 09:41:29 +02:00
co-authored by Cursor
parent cc4a03168e
commit b0cff8369e
20 changed files with 862 additions and 13 deletions
@@ -0,0 +1,60 @@
/**
* @file OtaAppDescriptor.hpp
* @brief Host-testable validation of ESP-IDF app descriptors in OTA images.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-07
*/
#pragma once
#include "core/OtaImageError.hpp"
#include <cstddef>
#include <cstdint>
#include <expected>
#include <span>
namespace core {
/** Byte offset of esp_app_desc_t in a raw application .bin image. */
inline constexpr std::size_t kOtaAppDescriptorOffset = 0x20U;
/** Expected esp_app_desc_t::magic_word (ESP_APP_DESC_MAGIC_WORD). */
inline constexpr std::uint32_t kOtaAppDescriptorMagic = 0xABCD5432U;
/** Expected esp_app_desc_t::project_name for this tree (CMake project()). */
inline constexpr char kOtaProjectName[] = "digiradio";
/**
* @brief otaImageErrorToken — stable API/JSON error string.
*
* @dname otaImageErrorToken
* @param error Validation failure.
* @return Short token without secrets.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-07
*/
[[nodiscard]] const char* otaImageErrorToken(OtaImageError error) noexcept;
/**
* @brief validateOtaAppDescriptor — reject foreign or corrupt images.
*
* @dname validateOtaAppDescriptor
* @param imagePrefix First bytes of the incoming OTA stream (>= 0x20 + 72).
* @return Ok when magic and project_name match DigiRadio, else OtaImageError.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-07
*/
[[nodiscard]] std::expected<void, OtaImageError>
validateOtaAppDescriptor(std::span<const std::uint8_t> imagePrefix);
} // namespace core
@@ -0,0 +1,33 @@
/**
* @file OtaImageError.hpp
* @brief Failure causes for ESP32 firmware image header validation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-07
*/
#pragma once
namespace core {
/**
* @brief OtaImageError — firmware image descriptor validation failures.
*
* @dname OtaImageError
* @return n/a (type)
* @pubstate n/a
*
* @author Michele Bigi
* @date 2026-07-07
*/
enum class OtaImageError {
InsufficientHeader, ///< Fewer bytes than the app descriptor offset.
InvalidMagic, ///< magic_word != ESP_APP_DESC_MAGIC_WORD.
InvalidProject, ///< project_name does not match this firmware tree.
};
} // namespace core