Files
ESP32_COMM/main/storage/gd32_flash_store.cpp
T
小龙虾 ebb3dcdf38 feat(debug_log): 新增UART0 RX泵任务支持串口日志转发
新增UART0 RX泵任务功能,将GPIO20接收到的串口数据按切行、剥离后以tag=UART推入ring buffer,通过HTTP /logs接口显示在网页日志面板。
接线方式:任意串口源TX连接至H1/H2的ESP_RX引脚,波特率3M 8N1。

feat(motor_ota): GD32 OTA增加包级防呆机制防止错误固件刷入

中继触发前校验gd32_ota镜像向量表身份(Gd32FlashStore::validateStoredImageIdentity),
对Bootloader包或非APP包在WiFi /ota/relay与BLE 0xF7均拒绝中继,
避免GD32 APP区被错误包擦除。新增0x87完成码用于固件身份验证失败场景。

feat(motor_state): 扩展CAN状态数据结构支持CONFIG负载

扩展motor_slot_t结构体,新增config_rx_count、config_seq和config_data字段
用于存储CONFIG帧的相关信息。CAN状态数据包大小从44B扩展到76B,
页面总数从6调整为9,支持目标值回报的配置数据传输。

fix(motor_ota): 修复OTA取消流程中的信用度管理和任务清理问题

★ 先复位credit再发送CANCEL命令,防止传输卡住时背压导致CANCEL被拒绝。
延长取消等待时间至10s覆盖最坏情况,确保feedTask正常退出而非强制删除。
取消完成后复位credit防止锁存死锁,确保后续通信正常进行。

fix(http_server): 修复HTTP请求处理中的边界检查和安全验证

拒绝chunked请求(content_len<=0)防止无限循环,添加通道参数范围验证(ch 0~2),
在OTA中继触发前验证固件身份防止错误包刷入,使用http_json_append避免缓冲区越界。

refactor(gd32_flash): 实现GD32固件身份验证算法确保刷写安全

基于Cortex-M向量表内容实现包级身份校验,通过MSP和复位向量位置
区分APP包、Bootloader包和无效固件,为OTA安全提供基础保障。

BREAKING CHANGE: 电机状态API数据格式变更,CAN通道参数验证更严格
2026-09-01 02:49:34 +08:00

675 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* gd32_flash_store.cpp - GD32 OTA 固件 Flash 存储抽象层实现
*
* 从 OtaRelay 剥离的 Flash 分区读写、NVS 元数据持久化和 SHA-256 校验。
*/
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "esp_partition.h"
#include "esp_task_wdt.h"
#include "nvs_flash.h"
#include "nvs.h"
#define MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS
#include "mbedtls/private/sha256.h"
#include "gd32_flash_store.h"
#include "app_config.h" // OTA_BLOCK_SIZE(flashWriteChunk 静态对齐缓冲容量)
#include "monitor/debug_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// ============================================================
// 单例
// ============================================================
Gd32FlashStore& Gd32FlashStore::instance()
{
static Gd32FlashStore inst;
return inst;
}
// 构造时创建 NVS 互斥锁。
// 单例静态局部变量在首次 instance() 调用时构造,此时 FreeRTOS 已就绪。
Gd32FlashStore::Gd32FlashStore()
{
m_nvsMutex = xSemaphoreCreateMutex();
if (!m_nvsMutex) {
DEBUG_ERR("OTA", "Gd32FlashStore NVS mutex 创建失败");
}
}
// ============================================================
// NVS 互斥锁辅助
// ============================================================
bool Gd32FlashStore::lockNvs(uint32_t timeout_ms)
{
// 参考 OtaCoordinator::lock:返回 take 结果,由调用方传回 unlockNvs 决定是否 give。
if (!m_nvsMutex) return false;
return xSemaphoreTake(m_nvsMutex, pdMS_TO_TICKS(timeout_ms)) == pdTRUE;
}
void Gd32FlashStore::unlockNvs(bool handle)
{
// 仅在对应 lockNvs 成功 take 时才 give,避免误释放。
if (handle && m_nvsMutex) {
xSemaphoreGive(m_nvsMutex);
}
}
// ============================================================
// 分区懒初始化
// ============================================================
const esp_partition_t* Gd32FlashStore::getPartition()
{
if (!m_partition) {
m_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
if (!m_partition) {
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
}
}
return m_partition;
}
// ============================================================
// Flash 分区读写
// ============================================================
void Gd32FlashStore::beginWriteSession(bool full_erase)
{
m_erasedUpTo = 0;
if (!full_erase) {
return; // GD32 OTA:保持原优化,由 flashWriteChunk 按需擦除与收流交织
}
/* ★ motor OTA:强制擦除整个 gd32_ota 分区,而不是只重置水位。
* 之前只重置 m_erasedUpTo=0,依赖 flashWriteChunk 按需擦除覆盖到的扇区,
* 导致两类隐患:
* 1. 上次刷的更大固件在高位扇区残留,本次若擦除位错误会与残留 AND 产生位翻转
* (正是 0xAC20 处 0x19→0x18 单 bit 翻转的根因)。
* 2. ensureErasedForWrite 按水位推进,若某次擦除返回 OK 但物理失败,
* 旧数据残留 + AND 语义写入 = 静默位错误。
* 强制全擦(252KB ~200ms)确保每次上传从全 0xFF 干净状态开始,
* 即使新固件只覆盖前 44KB,剩余高位扇区也回到擦除态,杜绝残留干扰。
* 擦除失败立刻报错,不让坏状态继续。 */
const esp_partition_t *part = getPartition();
if (!part) {
DEBUG_ERR("OTA", "beginWriteSession: 分区未找到,无法预擦除");
return;
}
esp_err_t err = erasePartition(part->size);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "beginWriteSession: 预擦除整个分区失败: %s", esp_err_to_name(err));
/* 不 return(void 函数);后续 flashWriteChunk 的 ensureErasedForWrite
* 仍会尝试擦除目标扇区,给数据写入多一次机会。 */
} else {
m_erasedUpTo = part->size; /* 全擦完成,后续 ensureErasedForWrite 全部跳过 */
DEBUG_INFO("OTA", "beginWriteSession: 已预擦除整个 gd32_ota 分区 (%lu B)",
(unsigned long)part->size);
}
}
/** @brief 确保 [offset, offset+len) 覆盖的扇区已擦除(从 m_erasedUpTo 顺序推进)。 */
esp_err_t Gd32FlashStore::ensureErasedForWrite(uint32_t offset, size_t len)
{
if (len == 0) return ESP_OK;
const esp_partition_t *part = getPartition();
if (!part) return ESP_ERR_NOT_FOUND;
const uint32_t end = offset + (uint32_t)len;
// 目标区间已在水位内:无需再擦(顺序 OTA 不回写旧扇区)
if (end <= m_erasedUpTo) return ESP_OK;
// 水位始终扇区对齐;只从 m_erasedUpTo 向高地址推进,禁止重擦已写扇区。
// 若写入跳空,一并擦掉空隙,保持 [0, m_erasedUpTo) 连续已擦。
uint32_t erase_to = (end + (kFlashSectorSize - 1U)) & ~(kFlashSectorSize - 1U);
if (erase_to > part->size) {
erase_to = part->size;
}
uint32_t erase_from = m_erasedUpTo;
if (erase_from >= erase_to) return ESP_OK;
for (uint32_t addr = erase_from; addr < erase_to; ) {
uint32_t chunk = kFlashSectorSize;
if (addr + chunk > erase_to) {
chunk = erase_to - addr;
}
esp_err_t err = esp_partition_erase_range(part, addr, chunk);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "ensureErased: erase @%" PRIu32 " len=%" PRIu32 " failed: %s",
addr, chunk, esp_err_to_name(err));
return err;
}
addr += chunk;
m_erasedUpTo = addr;
(void)esp_task_wdt_reset();
taskYIELD(); /* 长时间兜底擦时让 WiFi/LWIP 有机会跑 */
}
return ESP_OK;
}
// ============================================================
// OTA 期对齐缓冲池(2026-08 内存优化)
//
// flashWriteChunk 在非 4 字节对齐写入时需要临时缓冲读回 padding。
// 原实现每次 malloc/free,OTA 收流时与非对齐块叠加产生堆抖动。
// 现改为 OTA begin 时一次性 calloc,end/abort 时 free,消除抖动
// 又不增加常驻 BSS(非 OTA 期零占用)。
// ============================================================
static uint8_t *s_align_buf = nullptr;
static constexpr size_t kAlignBufCap = OTA_BLOCK_SIZE + 8;
void Gd32FlashStore::acquireAlignBufForOta()
{
if (!s_align_buf) {
s_align_buf = (uint8_t *)calloc(1, kAlignBufCap);
if (!s_align_buf) {
DEBUG_WARN("OTA", "对齐缓冲分配失败,回退 malloc free=%u",
(unsigned)esp_get_free_heap_size());
}
}
}
void Gd32FlashStore::releaseAlignBufForOta()
{
if (s_align_buf) {
free(s_align_buf);
s_align_buf = nullptr;
}
}
/** @brief 取对齐缓冲。优先用 OTA 期预分配;未分配或太小则临时 malloc。 */
static uint8_t *acquireAlignBuf(size_t needed)
{
if (s_align_buf && needed <= kAlignBufCap) {
return s_align_buf;
}
// OTA 未预分配(begin 失败)或包过大:回退临时 malloc
return (uint8_t *)malloc(needed);
}
/** @brief 归还对齐缓冲。OTA 池的不释放;临时 malloc 的 free。 */
static void releaseAlignBuf(uint8_t *buf)
{
if (buf && buf != s_align_buf) {
free(buf);
}
}
/** @brief 向 gd32_ota 分区写入数据块,自动处理非 4 字节对齐的读写。 */
esp_err_t Gd32FlashStore::flashWriteChunk(uint32_t offset, const uint8_t *data, size_t len)
{
if (len == 0U) {
return ESP_OK;
}
if (data == nullptr) {
return ESP_ERR_INVALID_ARG;
}
const esp_partition_t *part = getPartition();
if (!part) return ESP_ERR_NOT_FOUND;
// 计算 head/tail padding,使写入地址和长度都对齐到 4 字节
size_t head_pad = offset % 4;
uint32_t aligned_offset = offset - head_pad;
size_t total = head_pad + len;
size_t aligned_len = (total + 3) & ~(size_t)3;
if (aligned_offset + aligned_len > part->size) {
DEBUG_ERR("OTA", "flashWrite越界 off=%" PRIu32 " n=%zu part=%" PRIu32,
offset, len, part->size);
return ESP_ERR_INVALID_SIZE;
}
// 按需擦除目标扇区(与收流交织;prepare 不再整区预擦)
esp_err_t erase_err = ensureErasedForWrite(aligned_offset, aligned_len);
if (erase_err != ESP_OK) {
return erase_err;
}
// 快速路径:offset 和 len 都对齐
if (head_pad == 0 && len % 4 == 0) {
return esp_partition_write(part, offset, data, len);
}
if (head_pad > 0 || len % 4 != 0) {
DEBUG_WARN("OTA", "flashWrite非对齐 off=%" PRIu32 " n=%zu head=%zu",
offset, len, head_pad);
}
/* OTA 期按需对齐缓冲(OTA begin 时 calloc,end/abort 时 free):
* 消除每非对齐块的 malloc/free 抖动,又不增加常驻 BSS。
* 容量 = OTA_BLOCK_SIZE(4KB) + 最多 3 字节 tail pad。 */
uint8_t *aligned_buf = acquireAlignBuf(aligned_len);
if (!aligned_buf) {
DEBUG_ERR("OTA", "flashWriteChunk 对齐缓冲不足/未分配: %zu", aligned_len);
return ESP_ERR_NO_MEM;
}
// 读回 head padding
if (head_pad > 0) {
esp_err_t err = esp_partition_read(part, aligned_offset, aligned_buf, head_pad);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "flashWriteChunk读回head失败: offset=%" PRIu32 " head=%zu err=%s",
aligned_offset, head_pad, esp_err_to_name(err));
releaseAlignBuf(aligned_buf);
return err;
}
}
// 拷贝实际数据
memcpy(aligned_buf + head_pad, data, len);
// 读回 tail padding
size_t tail_pad = aligned_len - total;
if (tail_pad > 0) {
esp_err_t err = esp_partition_read(part, aligned_offset + total, aligned_buf + total, tail_pad);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "flashWriteChunk读回tail失败: offset=%" PRIu32 " tail=%zu err=%s",
aligned_offset + total, tail_pad, esp_err_to_name(err));
releaseAlignBuf(aligned_buf);
return err;
}
}
esp_err_t err = esp_partition_write(part, aligned_offset, aligned_buf, aligned_len);
releaseAlignBuf(aligned_buf);
return err;
}
/** @brief 从 gd32_ota 分区读取数据块。 */
esp_err_t Gd32FlashStore::flashReadChunk(uint32_t offset, uint8_t *data, size_t len)
{
if (len == 0U) {
return ESP_OK;
}
if (data == nullptr) {
return ESP_ERR_INVALID_ARG;
}
const esp_partition_t *part = getPartition();
if (!part) return ESP_ERR_NOT_FOUND;
/* 防坏 meta / 错误 offset 读越界 */
if (offset >= part->size || len > (size_t)(part->size - offset)) {
DEBUG_ERR("OTA", "flashReadChunk越界: offset=%" PRIu32 " len=%zu part=%" PRIu32,
offset, len, part->size);
return ESP_ERR_INVALID_SIZE;
}
return esp_partition_read(part, offset, data, len);
}
/** @brief 擦除 gd32_ota 分区指定范围。 */
esp_err_t Gd32FlashStore::erasePartition(uint32_t size)
{
const esp_partition_t *part = getPartition();
if (!part) return ESP_ERR_NOT_FOUND;
if (size > part->size) {
DEBUG_ERR("OTA", "erasePartition: size=%" PRIu32 " > partition=%" PRIu32, size, part->size);
return ESP_ERR_INVALID_SIZE;
}
// 按扇区擦除并喂狗/yield,避免大块 erase 长时间占住调用任务饿死 WiFi
uint32_t done = 0;
while (done < size) {
uint32_t chunk = kFlashSectorSize;
if (done + chunk > size) {
chunk = size - done;
}
esp_err_t err = esp_partition_erase_range(part, done, chunk);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "erasePartition: erase @%" PRIu32 " len=%" PRIu32 " failed: %s",
done, chunk, esp_err_to_name(err));
return err;
}
done += chunk;
if (done > m_erasedUpTo) {
m_erasedUpTo = done;
}
(void)esp_task_wdt_reset();
taskYIELD();
}
return ESP_OK;
}
// ============================================================
// NVS 元数据持久化
// ============================================================
/** @brief 已持锁版本:从 NVS 加载 OTA 元数据,命名空间不存在时返回全零结构。 */
esp_err_t Gd32FlashStore::metaLoadLocked(ota_meta_t *meta)
{
if (!meta) return ESP_ERR_INVALID_ARG;
memset(meta, 0, sizeof(ota_meta_t));
nvs_handle_t handle;
esp_err_t err = nvs_open(OTA_META_NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
// 区分 nvs_open 失败原因,不再一律当 OK。
// ESP_ERR_NVS_NOT_FOUND = 命名空间不存在(首次使用/被擦除),视为空元数据,保持兼容。
// 其他错误(NVS 未初始化/分区丢失/损坏等)返回真实错误码,
// 避免掩盖底层故障导致调用方误判为"空元数据"继续执行。
if (err == ESP_ERR_NVS_NOT_FOUND) {
return ESP_OK;
}
DEBUG_WARN("OTA", "metaLoad nvs_open 失败(非 NOT_FOUND): %s", esp_err_to_name(err));
return err;
}
uint32_t val = 0;
if (nvs_get_u32(handle, "total_size", &val) == ESP_OK) meta->total_size = val;
if (nvs_get_u32(handle, "stored_size", &val) == ESP_OK) meta->stored_size = val;
if (nvs_get_u32(handle, "last_seq", &val) == ESP_OK) meta->last_seq = val;
size_t sha_len = sizeof(meta->checksum);
if (nvs_get_blob(handle, "checksum", meta->checksum, &sha_len) == ESP_OK && sha_len == sizeof(meta->checksum)) {
} else {
memset(meta->checksum, 0, sizeof(meta->checksum));
}
nvs_close(handle);
return ESP_OK;
}
/** @brief 从 NVS 加载 OTA 元数据(加锁包装)。 */
esp_err_t Gd32FlashStore::metaLoad(ota_meta_t *meta)
{
if (!meta) return ESP_ERR_INVALID_ARG;
bool locked = lockNvs(kNvsLockTimeoutMs);
if (!locked) {
// 加锁失败不能跳过锁直接访问 NVS,清零输出并返回超时
memset(meta, 0, sizeof(ota_meta_t));
DEBUG_WARN("OTA", "metaLoad 加锁超时");
return ESP_ERR_TIMEOUT;
}
esp_err_t err = metaLoadLocked(meta);
unlockNvs(true);
return err;
}
/** @brief 已持锁版本:将 OTA 元数据写入 NVS。 */
esp_err_t Gd32FlashStore::metaSaveLocked(const ota_meta_t *meta)
{
if (!meta) return ESP_ERR_INVALID_ARG;
nvs_handle_t handle;
esp_err_t err = nvs_open(OTA_META_NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "无法打开 NVS 写入: %s", esp_err_to_name(err));
return err;
}
nvs_set_u32(handle, "total_size", meta->total_size);
nvs_set_u32(handle, "stored_size", meta->stored_size);
nvs_set_u32(handle, "last_seq", meta->last_seq);
nvs_set_blob(handle, "checksum", meta->checksum, sizeof(meta->checksum));
err = nvs_commit(handle);
nvs_close(handle);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "NVS commit 失败: %s", esp_err_to_name(err));
}
return err;
}
/** @brief 将 OTA 元数据写入 NVS(加锁包装)。 */
esp_err_t Gd32FlashStore::metaSave(const ota_meta_t *meta)
{
if (!meta) return ESP_ERR_INVALID_ARG;
bool locked = lockNvs(kNvsLockTimeoutMs);
if (!locked) {
DEBUG_WARN("OTA", "metaSave 加锁超时");
return ESP_ERR_TIMEOUT;
}
esp_err_t err = metaSaveLocked(meta);
unlockNvs(true);
return err;
}
/**
* @brief 传输过程中更新元数据的 stored_size 和 last_seq 字段。
* 整个读-改-写过程持锁,避免并发 metaUpdate 丢失更新。
* 内部调用已持锁的 metaLoadLocked/metaSaveLocked,不再经公开方法重复加锁。
*/
esp_err_t Gd32FlashStore::metaUpdate(uint32_t stored_size, uint32_t last_seq)
{
bool locked = lockNvs(kNvsLockTimeoutMs);
if (!locked) {
DEBUG_WARN("OTA", "metaUpdate 加锁超时");
return ESP_ERR_TIMEOUT;
}
ota_meta_t meta;
esp_err_t err = metaLoadLocked(&meta);
if (err == ESP_OK) {
meta.stored_size = stored_size;
meta.last_seq = last_seq;
err = metaSaveLocked(&meta);
}
unlockNvs(true);
return err;
}
/** @brief OTA 完成或中止时清除元数据(全零写入)。 */
esp_err_t Gd32FlashStore::metaClear()
{
DEBUG_DBG("OTA", "清除 OTA 元数据");
bool locked = lockNvs(kNvsLockTimeoutMs);
if (!locked) {
DEBUG_WARN("OTA", "metaClear 加锁超时");
return ESP_ERR_TIMEOUT;
}
ota_meta_t meta = {};
esp_err_t err = metaSaveLocked(&meta);
unlockNvs(true);
return err;
}
// ============================================================
// SHA-256 校验
// ============================================================
/** @brief 从 gd32_ota 分区读取固件并计算 SHA-256,输出 32 字节哈希。 */
esp_err_t Gd32FlashStore::computeFlashSha256(uint8_t out_sha256[32])
{
if (!out_sha256) return ESP_ERR_INVALID_ARG;
ota_meta_t meta;
esp_err_t err = metaLoad(&meta);
if (err != ESP_OK || meta.total_size == 0) {
DEBUG_ERR("OTA", "computeSha256: 元数据无效");
return ESP_ERR_INVALID_STATE;
}
/* static context:OTA 串行;勿放 httpd 栈(上传 100% 后 SHA 易栈溢出) */
static mbedtls_sha256_context s_sha_ctx;
mbedtls_sha256_init(&s_sha_ctx);
if (mbedtls_sha256_starts(&s_sha_ctx, 0) != 0) {
mbedtls_sha256_free(&s_sha_ctx);
return ESP_FAIL;
}
static constexpr size_t kChunkSize = 1024;
uint8_t *buf = (uint8_t *)malloc(kChunkSize);
if (!buf) {
mbedtls_sha256_free(&s_sha_ctx);
return ESP_ERR_NO_MEM;
}
uint32_t remaining = meta.total_size;
uint32_t offset = 0;
uint32_t wdt_chunk = 0;
while (remaining > 0) {
size_t to_read = (remaining > kChunkSize) ? kChunkSize : remaining;
err = flashReadChunk(offset, buf, to_read);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "computeSha256: Flash 读取失败 offset=%" PRIu32, offset);
free(buf);
mbedtls_sha256_free(&s_sha_ctx);
return err;
}
if (mbedtls_sha256_update(&s_sha_ctx, buf, to_read) != 0) {
free(buf);
mbedtls_sha256_free(&s_sha_ctx);
return ESP_FAIL;
}
offset += to_read;
remaining -= to_read;
if ((++wdt_chunk & 0x0FU) == 0U) {
(void)esp_task_wdt_reset();
taskYIELD();
}
}
free(buf);
int ret = mbedtls_sha256_finish(&s_sha_ctx, out_sha256);
mbedtls_sha256_free(&s_sha_ctx);
return (ret == 0) ? ESP_OK : ESP_FAIL;
}
/** @brief 固件接收完毕后计算 SHA-256 并写入 meta.checksum。 */
esp_err_t Gd32FlashStore::metaFinalizeChecksum()
{
ota_meta_t meta;
esp_err_t err = metaLoad(&meta);
if (err != ESP_OK) return err;
if (meta.total_size == 0 || meta.stored_size < meta.total_size) {
DEBUG_ERR("OTA", "metaFinalize: 固件不完整 stored=%" PRIu32 " total=%" PRIu32,
meta.stored_size, meta.total_size);
return ESP_ERR_INVALID_STATE;
}
err = computeFlashSha256(meta.checksum);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "metaFinalize: SHA-256 计算失败");
return err;
}
char hex[65] = {0};
for (int i = 0; i < 32; ++i)
snprintf(&hex[i * 2], sizeof(hex) - i * 2, "%02X", meta.checksum[i]);
DEBUG_INFO("OTA", "固件 SHA-256: %s", hex);
return metaSave(&meta);
}
/** @brief 校验固件完整性:重新计算 Flash SHA-256 并与 meta.checksum 比对。 */
esp_err_t Gd32FlashStore::metaVerifyChecksum()
{
ota_meta_t meta;
esp_err_t err = metaLoad(&meta);
if (err != ESP_OK) return err;
// 检查 checksum 是否全零(未计算过)
// 注意:返回 INVALID_STATE 而非 ESP_OK,由调用方决定是否容忍。
// 若直接返回 OK 跳过校验,meta 被意外清零时会掩盖固件损坏。
static const uint8_t kZeroSha[32] = {0};
if (memcmp(meta.checksum, kZeroSha, sizeof(meta.checksum)) == 0) {
DEBUG_WARN("OTA", "metaVerify: checksum未算→INVALID_STATE");
return ESP_ERR_INVALID_STATE;
}
uint8_t computed[32] = {0};
err = computeFlashSha256(computed);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "metaVerify: SHA-256 计算失败");
return err;
}
if (memcmp(computed, meta.checksum, 32) != 0) {
char hex_stored[65] = {0}, hex_computed[65] = {0};
for (int i = 0; i < 32; ++i) {
snprintf(&hex_stored[i * 2], sizeof(hex_stored) - i * 2, "%02X", meta.checksum[i]);
snprintf(&hex_computed[i * 2], sizeof(hex_computed) - i * 2, "%02X", computed[i]);
}
DEBUG_ERR("OTA", "固件完整性校验失败!");
DEBUG_ERR("OTA", " 存储 SHA-256: %s", hex_stored);
DEBUG_ERR("OTA", " 计算 SHA-256: %s", hex_computed);
return ESP_ERR_INVALID_CRC;
}
DEBUG_INFO("OTA", "固件完整性校验通过 (SHA-256 匹配)");
return ESP_OK;
}
esp_err_t Gd32FlashStore::verifyFrontendSha(const uint8_t frontend_sha32[32],
uint8_t out_device_sha[32])
{
if (!frontend_sha32) return ESP_ERR_INVALID_ARG;
/* 上传收尾 finishGd32StorageAfterReceive 通常已 metaFinalizeChecksum(并打过 SHA 日志)。
* relay 触发时若 checksum 已有,直接比对,避免整盘重算 + 再打一遍「固件 SHA-256」。 */
ota_meta_t meta;
esp_err_t err = metaLoad(&meta);
if (err != ESP_OK) return err;
static const uint8_t kZeroSha[32] = {0};
if (memcmp(meta.checksum, kZeroSha, sizeof(meta.checksum)) == 0) {
err = metaFinalizeChecksum();
if (err != ESP_OK) return err;
err = metaLoad(&meta);
if (err != ESP_OK) return err;
}
if (out_device_sha) memcpy(out_device_sha, meta.checksum, 32);
// 3) 比对前端 SHA 与设备端 SHA
if (memcmp(meta.checksum, frontend_sha32, 32) != 0) {
return ESP_ERR_INVALID_CRC;
}
return ESP_OK;
}
// ============================================================
// 包级身份校验(向量表内容判定)
// ============================================================
/* GD32E508 Flash 布局常量,与 GD32E50X_BOOT sys/config.h 对齐:
* BOOT 0x08000000 ~ 0x08011FFF(72KB)
* APP 0x08048000 ~ 0x0807DFFB(APPLATEST_START + APP_MAX_SIZE,216KB-4)
* SRAM 0x20000000 ~ 0x2001FFFF(128KB)
* APP 固件链接基址 0x08048000、BOOT 链接基址 0x08000000,
* 两者向量表第二字(Reset_Handler)必然落在各自区域内,据此区分包类型。
* 注意:BOOT 侧 validateApplication() 使用同一判据,修改需两边同步。 */
static constexpr uint32_t kGd32BootRegionStart = 0x08000000U;
static constexpr uint32_t kGd32BootRegionEnd = 0x08012000U;
static constexpr uint32_t kGd32AppRegionStart = 0x08048000U;
static constexpr uint32_t kGd32AppRegionEnd = 0x08048000U + 0x00035FFCU;
static constexpr uint32_t kGd32SramStart = 0x20000000U;
static constexpr uint32_t kGd32SramEnd = 0x20020000U;
static inline uint32_t rd_le32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
Gd32ImageIdentity Gd32FlashStore::validateStoredImageIdentity()
{
uint8_t vec[8] = {0};
if (flashReadChunk(0, vec, sizeof(vec)) != ESP_OK) {
DEBUG_ERR("OTA", "身份校验:读取向量表失败");
return Gd32ImageIdentity::kReadFailed;
}
const uint32_t msp = rd_le32(&vec[0]);
const uint32_t reset = rd_le32(&vec[4]) & ~1U; // 清除 Thumb 位
const bool msp_valid = (msp >= kGd32SramStart && msp < kGd32SramEnd);
const bool reset_is_app = (reset >= kGd32AppRegionStart && reset < kGd32AppRegionEnd);
const bool reset_is_boot = (reset >= kGd32BootRegionStart && reset < kGd32BootRegionEnd);
if (msp_valid && reset_is_app) {
return Gd32ImageIdentity::kApp;
}
if (reset_is_boot) {
DEBUG_ERR("OTA", "身份校验:检测到 Bootloader 包 (MSP=0x%08lX RH=0x%08lX),禁止刷入 APP 区",
(unsigned long)msp, (unsigned long)reset);
return Gd32ImageIdentity::kBootloader;
}
DEBUG_ERR("OTA", "身份校验:向量表非法 (MSP=0x%08lX RH=0x%08lX),非 GD32 APP 固件",
(unsigned long)msp, (unsigned long)reset);
return Gd32ImageIdentity::kInvalid;
}