- 添加 beginEsp32OtaWithRetry 统一重试逻辑,防止锁残留导致的并发问题 - 实现 finalizeForReboot 和 restartEsp32 统一封装收尾重启流程 - 修复BLE/WiFi双通道并发OTA的安全隐患,增强系统状态检查 refactor(ota): 优化SHA计算内存管理 - 使用静态缓冲区替代动态分配,减少内存碎片 - 重构OTA协调器状态机,增强错误处理机制 perf(monitor): 增强堆内存监控功能 - 添加最大空闲块大小监控,提供更详细的内存状态信息 - 更新健康监测算法,提高内存异常检测准确性
325 lines
12 KiB
C++
325 lines
12 KiB
C++
/*
|
||
* ota_relay.h - OTA 中继事件驱动状态机
|
||
*
|
||
* OtaRelay 将 GD32 SPI OTA 协议封装为事件驱动的状态机:
|
||
* 1. 每个协议阶段对应一个状态(共 6 个阶段状态 + ERROR)
|
||
* 2. 状态通过 processEvent() 驱动转换,而非顺序赋值
|
||
* 3. 外部(OtaCoordinator / transport layer)通过 dispatchQueue 触发中继
|
||
* 4. forwardFirmware() 保留为便捷入口,内部驱动状态机循环
|
||
*
|
||
* 状态转换:
|
||
* IDLE → HANDSHAKING → SIZE_NEG → TRANSFERRING → FINISHING → DONE
|
||
* 任意状态 ──(error)──→ ERROR
|
||
* ERROR/DONE ──(reset)──→ IDLE
|
||
*/
|
||
#ifndef OTA_RELAY_H
|
||
#define OTA_RELAY_H
|
||
|
||
#include <stdint.h>
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/queue.h"
|
||
#include "freertos/semphr.h"
|
||
#include "transport/protocol.h"
|
||
#include "transport/spi_master.h"
|
||
#include "esp_partition.h"
|
||
|
||
#define OTA_META_NVS_NAMESPACE "ota_meta"
|
||
|
||
typedef struct {
|
||
uint32_t total_size;
|
||
uint32_t stored_size;
|
||
uint32_t last_seq;
|
||
uint8_t checksum[32];
|
||
} ota_meta_t;
|
||
|
||
// ============================================================
|
||
// 阶段状态枚举(精简为 7 个状态,原 17 个"微状态"合并为阶段)
|
||
// ============================================================
|
||
typedef enum {
|
||
RELAY_IDLE = 0, // 空闲
|
||
RELAY_HANDSHAKING, // 阶段1: 握手 + 挑战认证
|
||
RELAY_SIZE_NEG, // 阶段2: 固件大小协商
|
||
RELAY_TRANSFERRING, // 阶段3: 数据包传输
|
||
RELAY_FINISHING, // 阶段4-5: 结束OTA + APP存活验证
|
||
RELAY_DONE, // 完成
|
||
RELAY_ERROR = 0xFF // 错误
|
||
} relay_state_t;
|
||
|
||
// ============================================================
|
||
// 事件类型(驱动状态转换)
|
||
// ============================================================
|
||
typedef enum {
|
||
RELAY_EVT_START = 0, // 启动中继流程
|
||
RELAY_EVT_RESP_OK, // 收到 GD32 有效响应
|
||
RELAY_EVT_RESP_ERROR, // 收到 GD32 错误响应
|
||
RELAY_EVT_TIMEOUT, // 超时
|
||
RELAY_EVT_HEALTH_CRIT, // 健康检查危险
|
||
RELAY_EVT_ABORT // 外部中止
|
||
} relay_event_t;
|
||
|
||
typedef enum {
|
||
OTA_RESULT_OK = 0,
|
||
OTA_RESULT_ERR_TIMEOUT = -1,
|
||
OTA_RESULT_ERR_CRC = -2,
|
||
OTA_RESULT_ERR_AUTH = -3,
|
||
OTA_RESULT_ERR_STORAGE = -4,
|
||
OTA_RESULT_ERR_GD32 = -5,
|
||
OTA_RESULT_ERR_HEALTH = -6,
|
||
OTA_RESULT_ERR_USER_ABORT = -7
|
||
} ota_result_t;
|
||
|
||
class OtaRelay {
|
||
public:
|
||
/**
|
||
* @brief 获取OtaRelay单例引用.
|
||
*/
|
||
static OtaRelay& instance();
|
||
|
||
/**
|
||
* @brief OTA中继FreeRTOS任务入口.
|
||
*/
|
||
static void taskEntry(void *pvParameters);
|
||
|
||
/**
|
||
* @brief 从Flash向GD32转发固件(便捷入口,内部驱动状态机循环).
|
||
* @return 操作结果码.
|
||
*/
|
||
ota_result_t forwardFirmware();
|
||
|
||
// ── 事件驱动接口 ──
|
||
/**
|
||
* @brief 向状态机投递事件,驱动状态转换.
|
||
* @param event 事件类型.
|
||
* @param payload 关联数据(响应载荷等),可为nullptr.
|
||
* @param len 数据长度.
|
||
* @return 当前操作结果码(OTA_RESULT_OK 表示事件已处理且可继续).
|
||
*/
|
||
ota_result_t processEvent(relay_event_t event, const uint8_t *payload, size_t len);
|
||
|
||
// ── 状态查询(线程安全)──
|
||
/**
|
||
* @brief 获取当前中继状态.
|
||
*/
|
||
static relay_state_t getState();
|
||
/**
|
||
* @brief 获取中继当前序号.
|
||
*/
|
||
static uint32_t getRelaySeq();
|
||
/**
|
||
* @brief 获取中继总包数.
|
||
*/
|
||
static uint32_t getRelayTotalPackets();
|
||
/**
|
||
* @brief 获取GD32 APP存活标志位.
|
||
*/
|
||
static bool getGd32AppAlive();
|
||
/**
|
||
* @brief 获取GD32固件版本.
|
||
*/
|
||
static void getGd32Version(gd32_version_t *out);
|
||
/**
|
||
* @brief 设置中继状态.
|
||
*/
|
||
static void setState(relay_state_t s);
|
||
/**
|
||
* @brief 设置中继当前序号.
|
||
*/
|
||
static void setRelaySeq(uint32_t seq);
|
||
/**
|
||
* @brief 设置中继总包数.
|
||
*/
|
||
static void setRelayTotalPackets(uint32_t total);
|
||
/**
|
||
* @brief 设置GD32 APP存活标志位.
|
||
*/
|
||
static void setGd32AppAlive(bool alive);
|
||
/**
|
||
* @brief 设置GD32固件版本.
|
||
*/
|
||
static void setGd32Version(const gd32_version_t &ver);
|
||
|
||
/**
|
||
* @brief 重置状态机到 IDLE.
|
||
*/
|
||
void reset();
|
||
|
||
// ── Flash 操作 ──
|
||
/**
|
||
* @brief Flash写入接口(支持非4字节对齐写入).
|
||
*/
|
||
static esp_err_t flashWriteChunk(uint32_t offset, const uint8_t *data, size_t len);
|
||
/**
|
||
* @brief 从OTA缓存分区读取数据块.
|
||
*/
|
||
static esp_err_t flashReadChunk(uint32_t offset, uint8_t *data, size_t len);
|
||
/**
|
||
* @brief 从Flash加载元数据.
|
||
*/
|
||
static esp_err_t metaLoad(ota_meta_t *meta);
|
||
/**
|
||
* @brief 保存元数据到Flash.
|
||
*/
|
||
static esp_err_t metaSave(const ota_meta_t *meta);
|
||
/**
|
||
* @brief 传输过程中更新元数据.
|
||
*/
|
||
static esp_err_t metaUpdate(uint32_t stored_size, uint32_t last_seq);
|
||
/**
|
||
* @brief OTA完成或中止时清除元数据.
|
||
*/
|
||
static esp_err_t metaClear();
|
||
/**
|
||
* @brief 计算gd32_ota分区中已存储固件的SHA-256.
|
||
*/
|
||
static esp_err_t computeFlashSha256(uint8_t out_sha256[32]);
|
||
/**
|
||
* @brief 固件接收完毕后计算SHA-256并写入元数据.
|
||
*/
|
||
static esp_err_t metaFinalizeChecksum();
|
||
/**
|
||
* @brief 校验固件完整性(SHA-256).
|
||
*/
|
||
static esp_err_t metaVerifyChecksum();
|
||
/**
|
||
* @brief 校验前端提供的 SHA-256 是否与设备端计算的一致。
|
||
* BLE/WiFi 传输层共用:内部完成 metaFinalizeChecksum → metaLoad → memcmp。
|
||
* 无论是否匹配,都会通过 out_device_sha 回传设备端 SHA(便于前端核对)。
|
||
* @param frontend_sha32 前端 32 字节原始 SHA(非 hex)。
|
||
* @param out_device_sha 可为 nullptr;非空时输出设备端 32 字节 SHA。
|
||
* @return ESP_OK 匹配;ESP_ERR_INVALID_CRC 不匹配;其他 = 元数据/计算失败。
|
||
*/
|
||
static esp_err_t verifyFrontendSha(const uint8_t frontend_sha32[32],
|
||
uint8_t out_device_sha[32]);
|
||
|
||
// ── 公开状态成员 ──
|
||
static relay_state_t relayState;
|
||
static uint32_t relaySeq;
|
||
static uint32_t relayTotalPackets;
|
||
static volatile bool gd32AppAlive;
|
||
static gd32_version_t gd32Version;
|
||
static QueueHandle_t dispatchQueue;
|
||
static QueueHandle_t resultQueue;
|
||
|
||
// ── 线程安全锁 ──
|
||
static SemaphoreHandle_t s_stateMutex;
|
||
static void initMutex();
|
||
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(50);
|
||
|
||
class StateLockGuard {
|
||
public:
|
||
explicit StateLockGuard(TickType_t timeout = kLockTimeout) {
|
||
m_locked = (s_stateMutex && xSemaphoreTake(s_stateMutex, timeout) == pdTRUE);
|
||
}
|
||
~StateLockGuard() {
|
||
if (m_locked && s_stateMutex) { xSemaphoreGive(s_stateMutex); }
|
||
}
|
||
bool isLocked() const { return m_locked; }
|
||
StateLockGuard(const StateLockGuard&) = delete;
|
||
StateLockGuard& operator=(const StateLockGuard&) = delete;
|
||
private:
|
||
bool m_locked = false;
|
||
};
|
||
|
||
// ── 实例状态 ──
|
||
const esp_partition_t *m_gd32Partition = NULL;
|
||
session_ctx_t m_sessionCtx;
|
||
uint8_t m_nonceServer[16] = {0};
|
||
uint8_t m_kSession[16] = {0};
|
||
uint8_t m_gd32UidRuntime[12] = {0};
|
||
|
||
static constexpr size_t GD32_UID_LEN = 12;
|
||
static constexpr size_t GD32_CHALLENGE_RESPONSE_LEN = 36;
|
||
|
||
private:
|
||
OtaRelay() = default;
|
||
~OtaRelay() = default;
|
||
OtaRelay(const OtaRelay&) = delete;
|
||
OtaRelay& operator=(const OtaRelay&) = delete;
|
||
|
||
// ── 状态机内部类型 ──
|
||
/**
|
||
* @brief 状态处理函数类型:接收事件 + 载荷,返回结果码.
|
||
* 返回 OTA_RESULT_OK 表示事件已成功处理且状态机可继续;
|
||
* 返回负值表示错误,状态机转入 RELAY_ERROR.
|
||
*/
|
||
typedef ota_result_t (OtaRelay::*StateHandler)(relay_event_t event,
|
||
const uint8_t *payload, size_t len);
|
||
|
||
struct RelayStateDef {
|
||
const char *name; // 状态名称(日志用)
|
||
StateHandler handler; // 事件处理器
|
||
};
|
||
|
||
// ── 状态表(const,编译期确定)──
|
||
static const RelayStateDef s_stateTable[];
|
||
|
||
// ── 各状态的事件处理器 ──
|
||
ota_result_t handleIdle(relay_event_t event, const uint8_t *payload, size_t len);
|
||
ota_result_t handleHandshaking(relay_event_t event, const uint8_t *payload, size_t len);
|
||
ota_result_t handleSizeNeg(relay_event_t event, const uint8_t *payload, size_t len);
|
||
ota_result_t handleTransferring(relay_event_t event, const uint8_t *payload, size_t len);
|
||
ota_result_t handleFinishing(relay_event_t event, const uint8_t *payload, size_t len);
|
||
ota_result_t handleDone(relay_event_t event, const uint8_t *payload, size_t len);
|
||
ota_result_t handleError(relay_event_t event, const uint8_t *payload, size_t len);
|
||
|
||
// ── 协议子步骤(由状态 handler 内部调用)──
|
||
/**
|
||
* @brief 请求GD32 APP进入Bootloader.
|
||
*/
|
||
esp_err_t requestGd32AppEnterBootloader();
|
||
/**
|
||
* @brief 执行握手请求(发送 HANDSHAKE_REQ).
|
||
*/
|
||
ota_result_t doHandshakeReq();
|
||
/**
|
||
* @brief 执行挑战认证.
|
||
*/
|
||
ota_result_t doChallengeAndAuth();
|
||
/**
|
||
* @brief 发送固件总大小.
|
||
*/
|
||
ota_result_t doSendFirmwareSize(uint32_t size);
|
||
/**
|
||
* @brief 转发下一批数据包(由 TRANSFERRING 状态循环调用).
|
||
*/
|
||
ota_result_t doRelayPackets(TickType_t deadline_tick);
|
||
/**
|
||
* @brief 发送结束指令并验证 APP 存活.
|
||
*/
|
||
ota_result_t doFinishAndVerify();
|
||
|
||
// ── 传输进度跟踪(状态机内部)──
|
||
uint32_t m_transferTotalPackets = 0;
|
||
uint32_t m_transferCurrentSeq = 0;
|
||
uint32_t m_transferFlashOffset = 0;
|
||
uint32_t m_transferRetryCount = 0;
|
||
TickType_t m_transferDeadline = 0;
|
||
|
||
// ── 加密/工具方法(不变)──
|
||
static int aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in, uint8_t *out);
|
||
static void logRespPayload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len);
|
||
esp_err_t waitUntilNotWait(const char *stage, uint8_t *resp_type,
|
||
uint8_t *resp_payload, size_t resp_max,
|
||
size_t *resp_len, uint32_t timeout_ms,
|
||
uint32_t max_wait_rounds);
|
||
static bool isStructuredAckWithSeq(const uint8_t *payload, size_t len, uint32_t expected_seq);
|
||
static bool parseStructuredError(const uint8_t *payload, size_t len, uint32_t *seq_out, uint8_t *err_out);
|
||
static bool isHandshakeEchoAck(const uint8_t *payload, size_t len);
|
||
static bool isChallengePayloadAck(const uint8_t *payload, size_t len);
|
||
static bool isConfirmPlainAck(const uint8_t *payload, size_t len);
|
||
static bool isSizeSeq0Ack(const uint8_t *payload, size_t len);
|
||
static bool isFinishPlainAck(const uint8_t *payload, size_t len);
|
||
static esp_err_t pollUntilExpectedAckShape(const char *stage,
|
||
uint8_t *resp_type, uint8_t *resp_payload,
|
||
size_t resp_max, size_t *resp_len,
|
||
uint32_t poll_timeout_ms, uint32_t wait_rounds,
|
||
uint32_t maxExtraPolls,
|
||
bool (*matcher)(const uint8_t *, size_t));
|
||
static bool parseAndVerifyChallengePayload(const uint8_t *resp_payload, size_t resp_len,
|
||
uint8_t *out_k_session, uint8_t *out_uid);
|
||
static bool aes128EcbEncrypt48Bytes(const uint8_t *key, const uint8_t *in, uint8_t *out);
|
||
static void calculateCrc32WithKey(const uint8_t *key, const uint8_t *data48, uint8_t *output);
|
||
};
|
||
|
||
#endif // OTA_RELAY_H
|