Files
ESP32_COMM/main/relay/ota_relay.cpp
T
小龙虾 e014ca7704 feat(ota): 实现GD32安全握手认证和OTA中继功能
- 更新固件版本从0.2.0到0.2.7
- 替换默认密钥为实际密钥值
- 添加GD32芯片UID运行时获取机制
- 实现AES-128加密验证challenge响应
- 集成NVS持久化存储OTA元数据
- 改进数据包传输可靠性,添加ACK确认和重试机制
- 新增结构化错误响应解析功能
- 实现GD32应用通知进入Bootloader流程
- 添加详细函数文档注释

refactor(ble): 优化BLE OTA服务端代码结构

- 为所有关键函数添加详细的Doxygen注释
- 改进固件大小验证逻辑,支持4字节对齐计算
- 优化数据块接收和处理流程
2026-05-26 02:44:57 +08:00

943 lines
32 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.
/*
* ota_relay.c - OTA 中继核心状态机实现
* T2.3, T2.4, T3.1 - T3.7
*/
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_err.h"
#include "esp_partition.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "aes/esp_aes.h"
#include "ota_relay.h"
#include "spi_master.h"
#include "protocol.h"
#include "health_monitor.h"
#include "monitor/debug_log.h"
/** @brief 当前 OTA 中继状态机状态。 */
relay_state_t g_relay_state = RELAY_IDLE;
static const esp_partition_t *g_gd32_partition = NULL;
/** @brief OTA 认证会话上下文,包含会话密钥和 IV。 */
static session_ctx_t g_session_ctx;
static uint8_t g_nonce_server[16] = {0};
static uint8_t g_k_session[16] = {0};
static uint8_t g_gd32_uid_runtime[12] = {0};
static constexpr size_t GD32_UID_LEN = 12;
static constexpr size_t GD32_CHALLENGE_CIPHER_LEN = 32;
static constexpr size_t GD32_CHALLENGE_PAYLOAD_WITH_SESSION = 36;
/** @brief OTA 命令分发队列,从 WiFi/BLE 任务接收指令。 */
QueueHandle_t g_ota_dispatch_queue = NULL;
/** @brief OTA 结果回传队列,将执行结果返回给调用方。 */
QueueHandle_t g_ota_result_queue = NULL;
/**
* @brief OTA 握手阶段:向 GD32 发送握手请求并等待进入认证流程。
* @return 操作结果码。
*/
static ota_result_t send_handshake_req(void);
/**
* @brief 认证阶段:生成随机数并完成与 GD32 的双向认证。
* @return 操作结果码。
*/
static ota_result_t send_nonce_and_authenticate(void);
/**
* @brief 向 GD32 发送待升级固件的总大小。
* @param size 固件总字节数。
* @return 操作结果码。
*/
static ota_result_t send_firmware_size(uint32_t size);
/**
* @brief 从 Flash 逐包读取固件并中继转发给 GD32。
* @return 操作结果码。
*/
static ota_result_t relay_packets_from_flash(void);
/**
* @brief OTA 收尾阶段:发送结束命令并等待 GD32 确认。
* @return 操作结果码。
*/
static ota_result_t finish_ota(void);
static int aes128_ecb_encrypt_block_local(const uint8_t *key, const uint8_t *in, uint8_t *out);
static int aes128_ecb_decrypt_block_local(const uint8_t *key, const uint8_t *in, uint8_t *out);
/**
* @brief 判断响应负载是否为带序号的结构化 ACK。
* @param payload 响应负载指针。
* @param len 负载长度。
* @param expected_seq 期望的序号。
* @return 匹配返回 true,否则返回 false。
*/
static bool is_structured_ack_with_seq(const uint8_t *payload, size_t len, uint32_t expected_seq)
{
if (!payload || len < 6) return false;
if (payload[0] != 0xBA || payload[1] != 0xC0) return false;
uint32_t seq = (uint32_t)payload[2] | ((uint32_t)payload[3] << 8) |
((uint32_t)payload[4] << 16) | ((uint32_t)payload[5] << 24);
return seq == expected_seq;
}
/**
* @brief 解析结构化错误响应中的序号和错误码。
* @param payload 响应负载指针。
* @param len 负载长度。
* @param seq_out 输出错误对应序号,可为 NULL。
* @param err_out 输出错误码,可为 NULL。
* @return 解析成功返回 true,否则返回 false。
*/
static bool parse_structured_error(const uint8_t *payload, size_t len, uint32_t *seq_out, uint8_t *err_out)
{
if (!payload || len < 7) return false;
if (payload[0] != 0xBA || payload[1] != 0xC0) return false;
if (seq_out) {
*seq_out = (uint32_t)payload[2] | ((uint32_t)payload[3] << 8) |
((uint32_t)payload[4] << 16) | ((uint32_t)payload[5] << 24);
}
if (err_out) *err_out = payload[6];
return true;
}
/**
* @brief 判断缓冲区是否全部由指定字节值组成。
* @param buf 输入缓冲区。
* @param len 缓冲区长度。
* @param value 目标字节值。
* @return 全部匹配返回 true,否则返回 false。
*/
static bool bytes_all_value(const uint8_t *buf, size_t len, uint8_t value)
{
if (!buf || len == 0) return false;
for (size_t i = 0; i < len; ++i) {
if (buf[i] != value) return false;
}
return true;
}
/**
* @brief 按小端序读取 4 字节无符号整数。
* @param p 输入字节指针(至少 4 字节)。
* @return 读取出的 uint32_t 数值。
*/
static uint32_t read_u32_le(const uint8_t *p)
{
return (uint32_t)p[0] |
((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
}
/**
* @brief 解析并校验 GD32 Challenge 响应,提取会话密钥和 UID。
* @param resp_payload Challenge 响应数据。
* @param resp_len 响应长度。
* @param out_k_session 输出会话密钥(16 字节)。
* @param out_uid 输出 UID(GD32_UID_LEN 字节)。
* @return 校验通过返回 true,否则返回 false。
*/
static bool parse_and_verify_challenge_payload(const uint8_t *resp_payload, size_t resp_len,
uint8_t *out_k_session, uint8_t *out_uid)
{
if (!resp_payload || !out_k_session || !out_uid) return false;
const uint8_t *cipher_blob = resp_payload;
size_t cipher_len = resp_len;
if (resp_len == GD32_CHALLENGE_PAYLOAD_WITH_SESSION) {
uint32_t session_num = read_u32_le(resp_payload);
DEBUG_INFO("OTA", "CHALLENGE session_num=0x%08" PRIX32, session_num);
cipher_blob = resp_payload + 4;
cipher_len = GD32_CHALLENGE_CIPHER_LEN;
}
if (cipher_len != GD32_CHALLENGE_CIPHER_LEN) {
DEBUG_ERR("OTA", "CHALLENGE validate failed: invalid payload length=%u", (unsigned)resp_len);
return false;
}
/*
if (cipher_len != GD32_CHALLENGE_CIPHER_LEN) {
DEBUG_ERR("OTA", "CHALLENGE payload长度异常: %u", (unsigned)resp_len);
return false;
}
*/
uint8_t expected_nonce_block[16] = {0};
if (aes128_ecb_encrypt_block_local(MASTER_KEY, g_nonce_server, expected_nonce_block) != 0) {
DEBUG_ERR("OTA", "CHALLENGE validate failed: nonce block encrypt failed");
return false;
}
/*
if (aes128_ecb_encrypt_block_local(MASTER_KEY, g_nonce_server, expected_nonce_block) != 0) {
DEBUG_ERR("OTA", "CHALLENGE校验失败: 计算nonce加密块失败");
return false;
}
*/
if (memcmp(cipher_blob, expected_nonce_block, sizeof(expected_nonce_block)) != 0) {
DEBUG_ERR("OTA", "CHALLENGE校验失败: nonce加密块不匹配");
return false;
}
memcpy(out_k_session, expected_nonce_block, sizeof(expected_nonce_block));
uint8_t device_info[16] = {0};
if (aes128_ecb_decrypt_block_local(out_k_session, &cipher_blob[16], device_info) != 0) {
DEBUG_ERR("OTA", "CHALLENGE校验失败: device_info解密失败");
return false;
}
// device_info[0..11] maps to contiguous GD32 UID bytes from
// DEVICE_SERIAL_ADDR_LSB (0x1FFFF7E8) to 0x1FFFF7F3.
memcpy(out_uid, device_info, GD32_UID_LEN);
if (bytes_all_value(out_uid, GD32_UID_LEN, 0x00U) ||
bytes_all_value(out_uid, GD32_UID_LEN, 0xFFU)) {
DEBUG_ERR("OTA", "CHALLENGE校验失败: UID无效(全00/全FF)");
return false;
}
char uid_hex[GD32_UID_LEN * 2 + 1] = {0};
for (size_t i = 0; i < GD32_UID_LEN; ++i) {
snprintf(&uid_hex[i * 2], sizeof(uid_hex) - i * 2, "%02X", out_uid[i]);
}
DEBUG_INFO("OTA",
"GD32 UID(%uB @0x%08" PRIX32 "/0x%08" PRIX32 "/0x%08" PRIX32 "): %s",
(unsigned)GD32_UID_LEN,
DEVICE_SERIAL_ADDR_LSB, DEVICE_SERIAL_ADDR_MID, DEVICE_SERIAL_ADDR_MSB,
uid_hex);
return true;
}
/**
* @brief 使用 AES-128 ECB 加密单个 16 字节分组。
* @param key 16 字节密钥。
* @param in 输入明文分组(16 字节)。
* @param out 输出密文分组(16 字节)。
* @return 0 表示成功,非 0 表示失败。
*/
static int aes128_ecb_encrypt_block_local(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
esp_aes_context aes;
esp_aes_init(&aes);
int ret = esp_aes_setkey(&aes, key, 128);
if (ret == 0) {
ret = esp_aes_crypt_ecb(&aes, ESP_AES_ENCRYPT, in, out);
}
esp_aes_free(&aes);
return ret;
}
/**
* @brief 使用 AES-128 ECB 解密单个 16 字节分组。
* @param key 16 字节密钥。
* @param in 输入密文分组(16 字节)。
* @param out 输出明文分组(16 字节)。
* @return 0 表示成功,非 0 表示失败。
*/
static int aes128_ecb_decrypt_block_local(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
esp_aes_context aes;
esp_aes_init(&aes);
int ret = esp_aes_setkey(&aes, key, 128);
if (ret == 0) {
ret = esp_aes_crypt_ecb(&aes, ESP_AES_DECRYPT, in, out);
}
esp_aes_free(&aes);
return ret;
}
/**
* @brief 按 3 个分组对 48 字节数据执行 AES-128 ECB 加密。
* @param key 16 字节密钥。
* @param in 输入明文(48 字节)。
* @param out 输出密文(48 字节)。
* @return 全部分组加密成功返回 true,否则返回 false。
*/
static bool aes128_ecb_encrypt_48bytes_local(const uint8_t *key, const uint8_t *in, uint8_t *out)
{
for (int i = 0; i < 3; ++i) {
if (aes128_ecb_encrypt_block_local(key, in + i * 16, out + i * 16) != 0) {
return false;
}
}
return true;
}
/**
* @brief 计算带 key 前缀的 CRC32(key + 48B 数据),输出小端 4 字节。
* @param key 16 字节会话密钥。
* @param data48 输入数据(48 字节)。
* @param output 输出 CRC32(4 字节)。
*/
static void calculate_crc32_with_key_local(const uint8_t *key, const uint8_t *data48, uint8_t *output)
{
uint32_t crc = 0xFFFFFFFFU;
for (int i = 0; i < 16; ++i) {
crc ^= key[i];
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U)));
}
}
for (size_t i = 0; i < GD32_OTA_PACKET_SIZE; ++i) {
crc ^= data48[i];
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U)));
}
}
crc ^= 0xFFFFFFFFU;
output[0] = (uint8_t)(crc & 0xFFU);
output[1] = (uint8_t)((crc >> 8) & 0xFFU);
output[2] = (uint8_t)((crc >> 16) & 0xFFU);
output[3] = (uint8_t)((crc >> 24) & 0xFFU);
}
/**
* @brief 记录响应类型和负载十六进制摘要,便于 OTA 阶段诊断。
* @param stage 当前阶段名称。
* @param resp_type 响应类型。
* @param payload 响应负载。
* @param len 响应负载长度。
*/
static void log_resp_payload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len)
{
char hex[193] = {0};
size_t n = (len > 32U) ? 32U : len;
for (size_t i = 0; i < n; ++i) {
snprintf(&hex[i * 2], sizeof(hex) - i * 2, "%02X", payload[i]);
}
DEBUG_INFO("OTA", "%s 响应: type=0x%02X len=%u payload[%u]=%s%s",
stage, resp_type, (unsigned)len, (unsigned)n,
n > 0 ? hex : "<empty>",
len > n ? "..." : "");
}
/**
* @brief 当收到 WAIT 响应时进行只读轮询,直到响应变为非 WAIT 或达到轮询上限。
* @param stage 当前阶段名称(用于日志)。
* @param resp_type 输入/输出响应类型。
* @param resp_payload 输出响应负载缓冲区。
* @param resp_max 响应缓冲区大小。
* @param resp_len 输出响应长度指针,可为 NULL。
* @param timeout_ms 每次轮询的超时毫秒。
* @param max_wait_rounds 最多轮询次数。
* @return ESP_OK 或底层 SPI 轮询错误码。
*/
static esp_err_t wait_until_not_wait(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)
{
if (!resp_type) return ESP_ERR_INVALID_ARG;
uint32_t round = 0;
while (*resp_type == SPI_OTA_RSP_WAIT && round < max_wait_rounds) {
++round;
DEBUG_WARN("OTA", "%s 收到 WAIT,开始第 %u 次只读轮询...", stage, round);
esp_err_t err = spi_ota_poll_response(resp_type, resp_payload, resp_max, resp_len, timeout_ms);
if (err != ESP_OK) {
return err;
}
log_resp_payload(stage, *resp_type, resp_payload, resp_len ? *resp_len : 0);
}
return ESP_OK;
}
/**
* @brief 从 Flash 加载元数据
* @param meta 输出参数,加载后的元数据结构体。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t meta_load(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 中没有元数据是正常状态(首次使用)
return ESP_OK;
}
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;
if (nvs_get_u32(handle, "chunk_count", &val) == ESP_OK) meta->chunk_count = val;
nvs_close(handle);
DEBUG_INFO("OTA", "元数据已加载: total=%u, stored=%u, seq=%u",
meta->total_size, meta->stored_size, meta->last_seq);
return ESP_OK;
}
/**
* @brief 保存元数据到 Flash
* @param meta 待保存的元数据结构体。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t meta_save(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_u32(handle, "chunk_count", meta->chunk_count);
err = nvs_commit(handle);
nvs_close(handle);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "NVS commit 失败: %s", esp_err_to_name(err));
} else {
DEBUG_INFO("OTA", "元数据已保存到 NVS");
}
return err;
}
/**
* @brief 传输过程中更新元数据
* @param stored_size 已存储的固件大小(字节)。
* @param last_seq 最后成功的分块序号。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq)
{
ota_meta_t meta;
esp_err_t err = meta_load(&meta);
if (err != ESP_OK) return err;
meta.stored_size = stored_size;
meta.last_seq = last_seq;
return meta_save(&meta);
}
/**
* @brief OTA 完成后或中止时清除元数据
* @return ESP_OK 成功,其他失败。
*/
esp_err_t meta_clear(void)
{
DEBUG_INFO("OTA", "清除 OTA 元数据");
ota_meta_t meta = {};
return meta_save(&meta);
}
/**
* @brief Flash 写入接口
* @param offset 分区内写入偏移。
* @param data 待写入的数据指针。
* @param len 数据长度(字节)。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len)
{
if (!g_gd32_partition) {
g_gd32_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
if (!g_gd32_partition) {
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
return ESP_ERR_NOT_FOUND;
}
}
return esp_partition_write(g_gd32_partition, offset, data, len);
}
/**
* @brief 从 OTA 缓存分区读取指定偏移和长度的数据块。
* @param offset 分区内读取偏移。
* @param data 读取缓冲区指针。
* @param len 读取长度(字节)。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len)
{
if (!g_gd32_partition) {
g_gd32_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
if (!g_gd32_partition) {
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
return ESP_ERR_NOT_FOUND;
}
}
return esp_partition_read(g_gd32_partition, offset, data, len);
}
/**
* @brief 阶段1 - 发起与 GD32 的握手
* @return 操作结果码。
*/
static void request_gd32_app_enter_bootloader(void)
{
DEBUG_INFO("OTA", "尝试通知 GD32 APP 进入 Bootloader OTA 窗口");
esp_err_t err = gd32_app_request_bootloader(500);
if (err == ESP_OK) {
DEBUG_INFO("OTA", "GD32 APP 已确认进入 Bootloader,等待复位完成");
vTaskDelay(pdMS_TO_TICKS(3500));
} else {
DEBUG_WARN("OTA", "GD32 APP 未确认进入 Bootloader,继续尝试直接握手: %s", esp_err_to_name(err));
}
}
static ota_result_t send_handshake_req(void)
{
DEBUG_INFO("OTA", "=== 阶段1: 握手开始 ===");
DEBUG_INFO("OTA", "发送 HANDSHAKE_REQ (0x00 0xAC)");
uint8_t payload[] = {0x00, 0xAC};
uint8_t resp_type = 0;
uint8_t resp_payload[64] = {0};
size_t resp_len = 0;
/** @brief 当前 OTA 中继状态机状态。 */
g_relay_state = RELAY_HANDSHAKE_SENT;
DEBUG_INFO("OTA", "状态: RELAY_HANDSHAKE_SENT");
esp_err_t err = spi_ota_transfer(SPI_OTA_CMD_REQ, payload, sizeof(payload),
&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000);
log_resp_payload("HANDSHAKE_REQ", resp_type, resp_payload, resp_len);
if (err == ESP_OK) {
err = wait_until_not_wait("HANDSHAKE_REQ", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 1000, 8);
}
if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK || resp_len < 2 ||
resp_payload[0] != 0x00 || resp_payload[1] != 0xAC) {
DEBUG_ERR("OTA", "HANDSHAKE_REQ 失败, err=%d, resp_type=0x%02X, resp_len=%zu", err, resp_type, resp_len);
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
DEBUG_INFO("OTA", "握手请求已确认");
return OTA_RESULT_OK;
}
/**
* @brief 发送随机数并与 GD32 相互认证
* @return 操作结果码。
*/
static ota_result_t send_nonce_and_authenticate(void)
{
uint8_t resp_type = 0;
uint8_t resp_payload[64] = {0};
size_t resp_len = 0;
uint32_t timestamp = xTaskGetTickCount();
uint32_t mem_addr = (uint32_t)(uintptr_t)g_nonce_server;
uint32_t rand_seed = 0x12345678;
scramble_generate(timestamp, mem_addr, rand_seed, g_nonce_server);
DEBUG_INFO("OTA", "--- 认证步骤 1: 发送 CHALLENGE ---");
DEBUG_INFO("OTA", "发送 nonce_server (16B), timestamp=0x%08X", timestamp);
g_relay_state = RELAY_CHALLENGE_SENT;
DEBUG_INFO("OTA", "状态: RELAY_CHALLENGE_SENT");
esp_err_t err = spi_ota_transfer(SPI_OTA_CMD_REQ, g_nonce_server, 16,
&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 5000);
log_resp_payload("CHALLENGE", resp_type, resp_payload, resp_len);
if (err == ESP_OK) {
err = wait_until_not_wait("CHALLENGE", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 1000, 12);
}
if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK) {
DEBUG_ERR("OTA", "CHALLENGE 发送失败, err=%d, resp_type=0x%02X, resp_len=%zu", err, resp_type, resp_len);
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
g_relay_state = RELAY_CHALLENGE_RESPOND;
DEBUG_INFO("OTA", "已收到 GD32 握手数据包");
DEBUG_INFO("OTA", "状态: RELAY_CHALLENGE_RESPOND");
if (!parse_and_verify_challenge_payload(resp_payload, resp_len, g_k_session, g_gd32_uid_runtime)) {
DEBUG_ERR("OTA", "CHALLENGE 解包/校验失败");
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_AUTH;
}
uint8_t plaintext[16];
for (int i = 0; i < 16; ++i) {
plaintext[i] = g_nonce_server[i] ^ 0x55U;
}
uint8_t encrypted_confirm[16] = {0};
if (aes128_ecb_encrypt_block_local(g_k_session, plaintext, encrypted_confirm) != 0) {
DEBUG_ERR("OTA", "加密确认块失败");
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_AUTH;
}
DEBUG_INFO("OTA", "--- 认证步骤 2: 发送 SESSION_KEY/CONFIRM ---");
g_relay_state = RELAY_SESSION_KEY_SENT;
DEBUG_INFO("OTA", "状态: RELAY_SESSION_KEY_SENT");
resp_len = 0;
err = spi_ota_transfer(SPI_OTA_CMD_REQ, encrypted_confirm, 16,
&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000);
log_resp_payload("CONFIRM", resp_type, resp_payload, resp_len);
if (err == ESP_OK) {
err = wait_until_not_wait("CONFIRM", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 1000, 8);
}
if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK || resp_len < 2 ||
resp_payload[0] != 0xBA || resp_payload[1] != 0xC0) {
DEBUG_ERR("OTA", "SESSION_KEY/CONFIRM 失败, err=%d, resp_type=0x%02X, resp_len=%zu", err, resp_type, resp_len);
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
g_relay_state = RELAY_AUTH_COMPLETE;
DEBUG_INFO("OTA", "状态: RELAY_AUTH_COMPLETE");
DEBUG_INFO("OTA", "=== 阶段1 认证完成: 安全通道已建立 ===");
session_key_init(&g_session_ctx, g_k_session);
return OTA_RESULT_OK;
}
/**
* @brief 向 GD32 发送固件总大小
* @param size 固件总字节数。
* @return 操作结果码。
*/
static ota_result_t send_firmware_size(uint32_t size)
{
uint8_t resp_type = 0;
uint8_t resp_payload[32] = {0};
size_t resp_len = 0;
DEBUG_INFO("OTA", "=== 阶段2: 发送固件大小 ===");
DEBUG_INFO("OTA", "固件大小: %u 字节", size);
uint8_t size_payload[4];
size_payload[0] = (uint8_t)(size & 0xFF);
size_payload[1] = (uint8_t)((size >> 8) & 0xFF);
size_payload[2] = (uint8_t)((size >> 16) & 0xFF);
size_payload[3] = (uint8_t)((size >> 24) & 0xFF);
g_relay_state = RELAY_SIZE_SENT;
DEBUG_INFO("OTA", "状态: RELAY_SIZE_SENT");
esp_err_t err = spi_ota_transfer(SPI_OTA_CMD_DATA, size_payload, sizeof(size_payload),
&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000);
log_resp_payload("SEND_SIZE", resp_type, resp_payload, resp_len);
if (err == ESP_OK) {
err = wait_until_not_wait("SEND_SIZE", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 1500, 12);
}
if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK || !is_structured_ack_with_seq(resp_payload, resp_len, 0U)) {
DEBUG_ERR("OTA", "SEND_SIZE 失败, err=%d, resp_type=0x%02X, resp_len=%zu", err, resp_type, resp_len);
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
DEBUG_INFO("OTA", "大小已发送: %u 字节,等待 GD32 确认...", size);
g_relay_state = RELAY_SIZE_ACK;
DEBUG_INFO("OTA", "状态: RELAY_SIZE_ACK");
return OTA_RESULT_OK;
}
/**
* @brief 从 Flash 向 GD32 转发存储的固件(48B/包)
* @return 操作结果码。
*/
static ota_result_t relay_packets_from_flash(void)
{
DEBUG_INFO("OTA", "=== 阶段3: 从 Flash 转发固件 ===");
ota_meta_t meta;
if (meta_load(&meta) != ESP_OK) {
DEBUG_ERR("OTA", "元数据加载失败");
return OTA_RESULT_ERR_STORAGE;
}
uint32_t total_packets = (meta.total_size + GD32_OTA_PACKET_SIZE - 1U) / GD32_OTA_PACKET_SIZE;
uint32_t flash_offset = 0;
uint8_t tx_buf[64] = {0};
uint8_t resp_type = 0;
uint8_t resp_payload[32] = {0};
size_t resp_len = 0;
ota_result_t result = OTA_RESULT_OK;
DEBUG_INFO("OTA", "总包数: %u, 每包 %u 字节", total_packets, (unsigned)GD32_OTA_PACKET_SIZE);
DEBUG_INFO("OTA", "状态: RELAY_RELAY_ACTIVE");
g_relay_state = RELAY_RELAY_ACTIVE;
for (uint32_t seq = 0; seq < total_packets; seq++) {
uint32_t retry_count = 0;
if ((seq % OTA_PACKET_HEALTH_CHECK_INTERVAL) == 0 || seq == 0 || seq == total_packets - 1) {
health_status_t health = health_check_before_ota();
if (health == HEALTH_MON_SHUTDOWN || health == HEALTH_MON_CRITICAL) {
DEBUG_ERR("OTA", "健康检查危险,中止 OTA");
return OTA_RESULT_ERR_HEALTH;
}
}
uint8_t plain48[GD32_OTA_PACKET_SIZE];
memset(plain48, 0xFF, sizeof(plain48));
size_t chunk_size = GD32_OTA_PACKET_SIZE;
if (seq == total_packets - 1) {
chunk_size = meta.total_size - (seq * GD32_OTA_PACKET_SIZE);
}
esp_err_t err = flash_read_chunk(flash_offset, plain48, chunk_size);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "Flash 读取失败, offset=%u, err=%d", flash_offset, err);
return OTA_RESULT_ERR_STORAGE;
}
tx_buf[0] = 0x00; tx_buf[1] = 0x00; tx_buf[2] = 0x00; tx_buf[3] = 0x00; /* session_num 预留 */
tx_buf[4] = (uint8_t)(seq & 0xFFU);
tx_buf[5] = (uint8_t)((seq >> 8) & 0xFFU);
tx_buf[6] = 0x00;
tx_buf[7] = 0x00;
if (!aes128_ecb_encrypt_48bytes_local(g_k_session, plain48, &tx_buf[8])) {
DEBUG_ERR("OTA", "包 %u 加密失败", seq);
return OTA_RESULT_ERR_AUTH;
}
uint32_t if_end = (seq == total_packets - 1) ? 0xFFFFFFFFU : 0x00000000U;
tx_buf[56] = (uint8_t)(if_end & 0xFFU);
tx_buf[57] = (uint8_t)((if_end >> 8) & 0xFFU);
tx_buf[58] = (uint8_t)((if_end >> 16) & 0xFFU);
tx_buf[59] = (uint8_t)((if_end >> 24) & 0xFFU);
calculate_crc32_with_key_local(g_k_session, plain48, &tx_buf[60]);
DEBUG_INFO("OTA", "包 %u: chunk=%u if_end=0x%08X crc32=%02X%02X%02X%02X",
seq, (unsigned)chunk_size, (unsigned)if_end,
tx_buf[60], tx_buf[61], tx_buf[62], tx_buf[63]);
g_relay_state = RELAY_PACKET_SENT;
while (1) {
resp_len = 0;
err = spi_ota_transfer(SPI_OTA_CMD_DATA, tx_buf, sizeof(tx_buf),
&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000);
log_resp_payload("DATA_PACKET", resp_type, resp_payload, resp_len);
if (err == ESP_OK) {
err = wait_until_not_wait("DATA_PACKET", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 1000, 10);
}
if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK) {
DEBUG_WARN("OTA", "数据包 %u 发送失败, err=%d, resp_type=0x%02X, resp_len=%zu", seq, err, resp_type, resp_len);
result = OTA_RESULT_ERR_TIMEOUT;
break;
}
uint32_t expected_ack = (seq == total_packets - 1) ? 0xFFFFFFFFU : (seq + 1U);
if (is_structured_ack_with_seq(resp_payload, resp_len, expected_ack)) {
flash_offset += (uint32_t)chunk_size;
g_relay_state = RELAY_PACKET_ACK_OK;
break;
}
uint32_t err_seq = 0;
uint8_t err_code = 0;
if (parse_structured_error(resp_payload, resp_len, &err_seq, &err_code) && err_seq == seq) {
DEBUG_WARN("OTA", "包 %u 收到结构化错误 err=0x%02X,准备重发", seq, err_code);
g_relay_state = RELAY_PACKET_ACK_RETRY;
if (++retry_count >= 3U) {
DEBUG_ERR("OTA", "包 %u 重发次数超限", seq);
result = (err_code == 0x04U) ? OTA_RESULT_ERR_CRC : OTA_RESULT_ERR_GD32;
break;
}
continue;
}
DEBUG_ERR("OTA", "包 %u ACK/ERROR 不匹配,len=%zu", seq, resp_len);
result = OTA_RESULT_ERR_GD32;
break;
}
if (result != OTA_RESULT_OK) {
break;
}
if (seq % (total_packets / 10 + 1) == 0) {
uint32_t progress = (seq * 100U) / total_packets;
DEBUG_INFO("OTA", "转发进度: %u/%u 包 (%u%%)", seq, total_packets, progress);
}
}
g_relay_state = RELAY_RECEIVE_DONE;
DEBUG_INFO("OTA", "状态: RELAY_RECEIVE_DONE");
return result;
}
/**
* @brief 结束 OTA - 发送 0x0A AC 并清理
* @return 操作结果码。
*/
static ota_result_t finish_ota(void)
{
DEBUG_INFO("OTA", "=== 阶段4: 结束 OTA ===");
DEBUG_INFO("OTA", "发送 FINISH_OTA (0x0A 0xAC),等待 GD32 确认即将跳转 APP");
uint8_t resp_type = 0;
uint8_t resp_payload[32] = {0};
size_t resp_len = 0;
uint8_t finish_payload[2] = {0x0A, 0xAC};
g_relay_state = RELAY_FINISH_SENT;
esp_err_t err = spi_ota_transfer(SPI_OTA_CMD_DATA, finish_payload, sizeof(finish_payload),
&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000);
log_resp_payload("FINISH_OTA", resp_type, resp_payload, resp_len);
if (err == ESP_OK) {
err = wait_until_not_wait("FINISH_OTA", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 1000, 10);
}
if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK || resp_len < 2 ||
resp_payload[0] != 0x0A || resp_payload[1] != 0xAC) {
DEBUG_ERR("OTA", "FINISH_OTA 失败, err=%d, resp_type=0x%02X, resp_len=%zu", err, resp_type, resp_len);
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_GD32;
}
g_relay_state = RELAY_DONE;
session_key_clear(&g_session_ctx);
memset(&g_session_ctx, 0, sizeof(session_ctx_t));
memset(g_k_session, 0, sizeof(g_k_session));
memset(g_nonce_server, 0, sizeof(g_nonce_server));
memset(g_gd32_uid_runtime, 0, sizeof(g_gd32_uid_runtime));
DEBUG_INFO("OTA", "状态: RELAY_DONE");
DEBUG_INFO("OTA", "=== OTA 完成 ===");
meta_clear();
return OTA_RESULT_OK;
}
/**
* @brief 启动完整 OTA 流程的主入口
* @return 操作结果码。
*/
ota_result_t ota_relay_start_handshake(void)
{
ota_result_t res;
request_gd32_app_enter_bootloader();
res = send_handshake_req();
if (res != OTA_RESULT_OK) return res;
res = send_nonce_and_authenticate();
if (res != OTA_RESULT_OK) return res;
ota_meta_t meta;
meta_load(&meta);
res = send_firmware_size(meta.total_size);
if (res != OTA_RESULT_OK) return res;
res = relay_packets_from_flash();
if (res != OTA_RESULT_OK) return res;
res = finish_ota();
return res;
}
/**
* @brief 从 Flash 向 GD32 转发固件
* @return 操作结果码。
*/
ota_result_t ota_relay_forward_firmware(void)
{
ota_meta_t meta;
if (meta_load(&meta) != ESP_OK) {
return OTA_RESULT_ERR_STORAGE;
}
DEBUG_INFO("OTA", "开始固件转发: %u 字节, %u 块",
meta.total_size, meta.chunk_count);
request_gd32_app_enter_bootloader();
ota_result_t res = send_handshake_req();
if (res != OTA_RESULT_OK) return res;
res = send_nonce_and_authenticate();
if (res != OTA_RESULT_OK) return res;
res = send_firmware_size(meta.total_size);
if (res != OTA_RESULT_OK) return res;
res = relay_packets_from_flash();
if (res != OTA_RESULT_OK) return res;
res = finish_ota();
return res;
}
/**
* @brief 获取当前状态
* @return 当前中继状态枚举值。
*/
relay_state_t ota_relay_get_state(void)
{
/** @brief 当前 OTA 中继状态机状态。 */
return g_relay_state;
}
/**
* @brief 重置状态机
*/
void ota_relay_reset(void)
{
/** @brief 当前 OTA 中继状态机状态。 */
g_relay_state = RELAY_IDLE;
session_key_clear(&g_session_ctx);
memset(g_gd32_uid_runtime, 0, sizeof(g_gd32_uid_runtime));
}
/**
* @brief OTA 中继 FreeRTOS 任务
* @param pvParameters FreeRTOS 任务参数,未使用。
*/
void ota_relay_task(void *pvParameters)
{
DEBUG_INFO("OTA", "OTA 中继任务启动");
/** @brief OTA 命令分发队列,从 WiFi/BLE 任务接收指令。 */
g_ota_dispatch_queue = xQueueCreate(4, sizeof(uint32_t));
/** @brief OTA 结果回传队列,将执行结果返回给调用方。 */
g_ota_result_queue = xQueueCreate(4, sizeof(uint32_t));
if (!g_ota_dispatch_queue || !g_ota_result_queue) {
DEBUG_ERR("OTA", "OTA 队列创建失败");
vTaskDelete(NULL);
return;
}
while (1) {
uint32_t msg = 0;
BaseType_t ret = xQueueReceive(g_ota_dispatch_queue, &msg, portMAX_DELAY);
if (ret != pdTRUE) {
continue;
}
DEBUG_INFO("OTA", "收到 OTA 调度信号: %lu,开始转发...", (unsigned long)msg);
ota_result_t result = ota_relay_forward_firmware();
uint32_t result_msg = (uint32_t)result;
xQueueSend(g_ota_result_queue, &result_msg, pdMS_TO_TICKS(100));
if (result == OTA_RESULT_OK) {
DEBUG_INFO("OTA", "OTA 转发成功完成");
} else {
DEBUG_ERR("OTA", "OTA 转发失败,错误码: %d", result);
ota_relay_reset();
}
}
}