Files
小龙虾 5db3f66b8e feat(protocol): 更新SPI Link协议至v2版本并优化CAN通信
- 将SPI OTA帧格式升级为SPI Link v2,采用新的帧格式:
  | SOF0 0xA5 | SOF1 0x5A | VER 0x02 | FLAGS | SESSION 4B LE |
  TX_SEQ | ACK_SEQ | CREDIT | TYPE | LEN 2B LE | RSV 2B | PAYLOAD |
  CRC32 4B LE |
- 新增V2_OVERHEAD=20字节开销,APP最大payload为236字节,BOOT为108字节
- 修改帧类型定义:ENTER_BOOT从0xB5合并至0xB0,新增CAN业务类型0xC0~C3
- 更新CAN协议文档,支持SPI Link v2-only模式,调整最大业务payload计算
- 扩展0xC1帧功能,支持双向通信和1Hz主动推送ID快照
- 添加0xC3完整ID快照帧类型,包含seq/rx_count等完整字段
- 更新协议文档中的错误码定义和反馈payload格式

refactor(main): 重构主程序入口并添加CAN反馈任务

- 在main.cpp中添加CAN反馈任务,更新启动流程注释
- 创建createCriticalTask辅助函数,统一关键任务创建逻辑
- 调整SPI初始化顺序,确保CAN反馈任务依赖正确
- 优化错误处理机制,关键任务失败时立即重启
- 将GD32版本探测移至ota_relay任务处理
- 更新版本号至0.3.48,调整编译时间和Git哈希

config: 更新SPI和BLE配置参数

- 添加SPI主机相关配置参数,包括CS GPIO、READY GPIO等引脚定义
- 配置SPI缓冲区大小:APP为256字节,BOOT为128字节
- 更新BLE滑动窗口参数,ACK窗口设为32,数据队列深度为72
- 调整SPI工作队列大小至5120字节,增加任务堆栈监控说明
2026-07-16 23:00:15 +08:00

165 lines
4.8 KiB
C++
Raw Permalink 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.
/*
* protocol.cpp - OTA 握手辅助实现
*
* 当前保留的接口:
* - init :硬件 RNG 填充扰码常数
* - scrambleGenerate :server nonce 生成
* - sessionKeyInit :会话密钥写入
* - sessionKeyClear :会话密钥安全擦除
*
* 帧构建、AES-ECB/CBC 加解密、CRC16 等中间件代码已迁出(见 ota_relay / spi_link_v2)。
*/
#include <string.h>
#include "esp_random.h"
#include "app_config.h"
#include "protocol.h"
#include "monitor/debug_log.h"
// ============================================================
// Protocol 单例
// ============================================================
/**
* @brief 获取 Protocol 单例实例(Meyers 单例)。
* @return Protocol 单例引用。
*/
Protocol& Protocol::instance()
{
static Protocol inst;
return inst;
}
/**
* @brief 抗优化密钥/敏感缓冲擦除。
*
* 普通 memset 可能被编译器当作「无后续读取」而消除;
* 通过 volatile 逐字节写 0,保证运行时真正清零。
*
* @param p 起始地址;nullptr 忽略。
* @param n 字节数。
*/
void Protocol::secureWipe(void *p, size_t n)
{
if (p == nullptr || n == 0U) {
return;
}
volatile uint8_t *v = static_cast<volatile uint8_t *>(p);
while (n > 0U) {
*v++ = 0;
--n;
}
}
/**
* @brief 初始化协议层,打印初始化信息并设置初始化标志。
*
* 使用硬件 RNG 填充 m_scrambleConst。完整常数不再以 INFO 打 hex,
* 避免 UDP/串口日志侧信道;需要核对时开 DEBUG_DBG。
*/
void Protocol::init()
{
if (m_initialized) {
return;
}
DEBUG_INFO("PROT", "=== 初始化协议层(nonce 扰码 + 会话密钥管理)===");
esp_fill_random(m_scrambleConst, SCRAMBLE_CONST_LEN);
/* 不把完整扰码常数打到 INFO/UDP:减小日志侧信道;需要调试时开 DBG。 */
DEBUG_DBG("PROT", "SCRAMBLE_CONST ready (%u B from HW RNG)",
(unsigned)SCRAMBLE_CONST_LEN);
m_initialized = true;
}
/**
* @brief 根据时间戳、地址和随机种子生成 16 字节 nonce。
* @param timestamp 当前时间戳(tick)。
* @param mem_addr 内存地址(增加随机性)。
* @param rand_seed 随机种子。
* @param nonce_out 输出 16 字节 nonce 缓冲区;为 nullptr 时直接返回。
*
* 混合步骤概要:
* 1) state = timestamp|mem_addr|rand_seed|scrambleConst[0..3](小端)
* 2) 3 轮:逐字节 XOR 全表 scrambleConst + 循环左移 1
* 3) 2 轮:按 32-bit 字 XOR 完整 4B scrambleConst(memcpy 避免未对齐访问)
*
* @note 非标准 KDF;仅 OTA 握手用。未 init 时自动 init()。
*/
void Protocol::scrambleGenerate(uint32_t timestamp, uint32_t mem_addr,
uint32_t rand_seed, uint8_t *nonce_out)
{
if (nonce_out == nullptr) {
return;
}
if (!m_initialized) {
init();
}
uint8_t state[16];
/* 宿主小端布局;ESP32 / GD32 均为 LE,与对端一致。 */
memcpy(&state[0], &timestamp, 4);
memcpy(&state[4], &mem_addr, 4);
memcpy(&state[8], &rand_seed, 4);
memcpy(&state[12], m_scrambleConst, 4);
for (int round = 0; round < 3; round++) {
for (int i = 0; i < 16; i++) {
state[i] ^= m_scrambleConst[i];
state[i] = (uint8_t)((state[i] << 1) | (state[i] >> 7));
}
}
/* 按 32-bit 字再混一轮完整 4B 扰码(memcpy 避免未对齐 uint32_t*)。 */
for (int round = 0; round < 2; round++) {
for (int i = 0; i < 4; i++) {
uint32_t w = 0;
memcpy(&w, &state[i * 4], 4);
const uint32_t mix =
(uint32_t)m_scrambleConst[i * 4]
| ((uint32_t)m_scrambleConst[i * 4 + 1] << 8)
| ((uint32_t)m_scrambleConst[i * 4 + 2] << 16)
| ((uint32_t)m_scrambleConst[i * 4 + 3] << 24);
w ^= mix;
memcpy(&state[i * 4], &w, 4);
}
}
memcpy(nonce_out, state, 16);
}
/**
* @brief 会话密钥初始化。
* @param sess 会话上下文指针。
* @param k_session 16 字节会话密钥。
* @return 0 成功,-1 参数无效。
*
* 先 secureWipe 再拷贝,避免旧 key 残留。
*/
int Protocol::sessionKeyInit(session_ctx_t *sess, const uint8_t *k_session)
{
if (sess == nullptr || k_session == nullptr) {
return -1;
}
secureWipe(sess, sizeof(*sess));
memcpy(sess->key, k_session, SESSION_KEY_LEN);
return 0;
}
/**
* @brief 清除会话上下文中的密钥数据。
* @param sess 会话上下文指针;为 nullptr 时忽略。
*
* 使用 secureWipe,避免编译器优化掉「无读回的」密钥清零。
*/
void Protocol::sessionKeyClear(session_ctx_t *sess)
{
if (sess == nullptr) {
return;
}
secureWipe(sess->key, SESSION_KEY_LEN);
}