ESP32_COMM 专家评审报告

评审日期 2026-10-19 · 代码基线 master af1f682(固件 v0.5.2,ESP-IDF v6.0,ESP32-C3FH4)

评审方式:多专家视角并行通读 + 主审逐条复核 · 本报告只新增 docs/,未改动任何源码

综合评分 5.1 / 10

1致命
12严重
17一般
7建议
23既有条目已修复
12既有条目仍存在
目录:1 项目概述与架构2 评审范围与方法3 问题总表4 问题详情5 既有评审跟踪6 修复路线图7 总结评分

1. 项目概述与架构

ESP32_COMM 是机器人 DCU 板上 ESP32-C3 的通信与 OTA 中枢固件:上位机经 WiFi HTTP / BLE GATT 把固件交给 ESP32;ESP32 自身走 app0/app1 AB 分区升级,GD32E508 固件经 SPI Link v2 六阶段握手 + AES-128 逐包加密中继,电机节点固件再经 GD32 CAN 二次中继;同时提供三路 CANFD 控制面与遥测回收。

[PC / 手机网页]──WiFi HTTP(22 路由) / BLE GATT(0xFFFF)──▶[ESP32-C3FH4]──SPI Link v2 13.33MHz 5线──▶[GD32E508]──3×CANFD──▶[电机节点]
                                                         │ gd32_ota 252KB 缓存            │ Bootloader / APP
                                                         │ app0/app1 AB 自升级            │ 电机 OTA CAN 中继
层目录 / 模块职责规模(行)
接入层server/ wifi_ota · http_handlers · ble_ota(_data/_host)STA/AP、HTTP 路由、NimBLE GATT、窗口 ACK / reorder≈ 6.6k(http_handlers 2832)
协调层ota/ OtaCoordinator · Esp32OtaCore9 态单例、唯一权威入口、150 s 忙态看门狗、epoch≈ 2.0k
执行层relay/ OtaRelay · storage/ Gd32FlashStore · monitor/ MotorOtaRelayGD32 中继状态机、gd32_ota 分区与 NVS meta、电机 OTA 喂数≈ 3.5k
传输层transport/ SpiMaster · protocol · canfd_bridge(+ 仓外 spi_link_v2)Link v2 主站(会话/序号/credit/CRC32/重试)、AES/nonce、CAN 0xC0~0xC8≈ 4.3k
横切monitor/ health · debug_log · can_feedback · cpu_stats · task_stack 等;core/健康门控、三路日志、遥测轮询、启动自检位图≈ 3.6k

任务模型:ota_relay(23) · wifi_ota/ble_ota(22) · health_mon(21) · ble_ota_worker(12) · spi_worker(8,唯一持 SPI) · ota_feed(7) · httpd(5) · can_fb(4) · cpu_stats/uart_rx(1)。

2. 评审范围与方法

3. 问题总表(37 条)

编号严重度类别问题主要位置
F-01致命安全HTTP 全部端点零认证 + CORS * + /spi/send 原始透传main/server/http_handlers.cpp:77-83, 1685-1756
S-01严重构建干净克隆构建出的是另一份固件(关键配置文件未生效且自相矛盾)CMakeLists.txt
S-02严重OTA回滚保护取消过早:mark_app_valid 早于自检判定main/core/main.cpp:131-147
S-03严重并发看门狗/中止路径与中继任务并发:清会话密钥但中继任务不停main/ota/ota_coordinator.cpp:625-635
S-04严重OTA忙态看门狗 150 s 短于 GD32 中继合法最坏耗时main/include/app_config.h:126
S-05严重健壮性WiFi 运行期断线重试 5 次后永不重连,也不回退 APmain/server/wifi_ota.cpp:94-100, 180-192, 751-777
S-06严重安全GD32 握手 nonce 可预测且时间戳明文入日志main/relay/ota_relay.cpp:1027-1035
S-07严重健壮性GET /can/mode 查询失败时会把 CAN 接收模式写回 ALL_PASSmain/server/http_handlers.cpp:1020-1030
S-08严重安全凭据与主密钥明文入仓,AP 弱口令main/include/app_config.h:31-32, 39-40, 161-164
S-09严重安全BLE GATT 无配对/加密,范围内任意设备可 OTA 或重启main/server/ble_ota_host.cpp:366-376
S-10严重实时性RSSI 更新覆盖过温 SAFE_SHUTDOWN 健康状态main/monitor/health_monitor.cpp:578-600
S-11严重实时性栈溢出钩子中调用可阻塞的 uart_write_bytesmain/monitor/task_stack.cpp:27-40
S-12严重安全/spi/send 的 timeout_ms 无上限,可长期占死 SPI workermain/server/http_handlers.cpp:1715
G-01一般OTA完成回调不校验状态与 epoch,可能覆盖新会话main/ota/ota_coordinator.cpp:372-392, 568-576
G-02一般并发协调器锁超时 100 ms,lock 失败直接跳过状态切换main/include/app_config.h:124
G-03一般OTA确认 APP 存活前即清除 meta;失败路径连发 3 次 CANCELmain/relay/ota_relay.cpp:1554, 1573-1579, 1637-1643
G-04一般OTA校验缺口:先置 STORED 后算 SHA;ESP32 finalize 可不带 SHA、不比对实收字节main/storage/gd32_flash_store.cpp(finishGd32StorageAfterReceive 275-279 附近)
G-05一般内存OTA 对齐缓冲正常完成后不释放(常驻约 4 KB)main/ota/ota_coordinator.cpp:203, 625-635
G-06一般OTA电机 OTA 与 GD32 OTA 共用分区和 meta,互斥不完整main/server/http_handlers.cpp:2622-2642, 2697-2700, 2748-2776
G-07一般内存JSON 截断输出非法 JSON;motor_state 栈占用约 2.5 KBmain/server/http_handlers.cpp:2383, 2493
G-08一般健壮性输入校验不足与未定义行为main/server/http_handlers.cpp:323, 1056-1061, 1256, 1340-1341, 1521
G-09一般并发suspendStack 跨任务写非原子 BLE 连接状态main/server/ble_ota.cpp:276-279
G-10一般内存SPI 事务超时返回后 DMA 可能仍在使用栈上缓冲main/transport/spi_master.cpp:1001-1019
G-11一般健壮性tick 比较非回绕安全main/transport/spi_master.cpp:883-889, 1443-1445, 1560-1572, 2058-2061, 2156-2163, 2544-2556
G-12一般协议SPI 主站边界缺陷(resp_len 越读、out_init_status 未回填、HELLO 失败后会话不一致)main/transport/spi_master.cpp:175, 622-627, 1692-1697, 2005-2042
G-13一般构建CI 必然失败且不构建固件.github/workflows/
G-14一般可维护性任务数 > 20 时 CPU 统计冻结并每秒刷 WARNmain/monitor/cpu_stats.cpp:106-117
G-15一般安全UDP 日志不可关闭、目标硬编码,fd 关闭存在竞态main/monitor/debug_log.cpp:689-711
G-16一般可维护性日志环满时选择性覆盖导致 /logs 时间序错乱main/monitor/debug_log.cpp:341-352
G-17一般OTAOTA_MAX_FIRMWARE_SIZE 注释数值错误main/include/app_config.h:77
A-01建议构建仓库卫生:gitignore 行内注释失效、被忽略目录仍被跟踪、本机路径入库.gitignore:25,31,89-91
A-02建议文档版本信息与文档漂移main/include/version.h:18-21
A-03建议构建提交信息与内容不符,历史评审被静默删除git 历史 af1f682
A-04建议实时性CS 前 esp_rom_delay_us 忙等最长 1 msmain/transport/spi_master.cpp:946-952
A-05建议实时性间接优先级倒置main/core/main.cpp
A-06建议可维护性死代码与量程不符main/monitor/health_monitor.cpp:389-396, 365
A-07建议可维护性日志格式串与参数不匹配、注释过期main/transport/spi_master.cpp:924-925

4. 问题详情

致命(1)

致命 F-01 · HTTP 全部端点零认证 + CORS * + /spi/send 原始透传 [安全]

位置
main/server/http_handlers.cpp:77-83, 1685-1756
现象
22 条路由无任何鉴权;所有响应带 Access-Control-Allow-Origin: *;/spi/send 的 frame_type 直接取自请求(strtol),无白名单,经 SpiMaster::transact 原样下发 GD32,绕过 OtaCoordinator 互斥。
风险
同网段任意主机(或用户浏览的任意网页借 CORS 跨站)可上传/切换固件、重启、发 CAN 帧、向 GD32 注入任意 SPI 命令(含进 Boot、擦写类命令),机器人电机总线可被远程操控。
修改建议
最小改造:① 实施已设计的 Bearer token(NVS 存放,首次配网生成);② CORS 改为白名单来源或去掉;③ /spi/send 加 frame_type 白名单(仅只读诊断类),并在 OTA 忙时拒绝;④ 默认编译期关闭 /spi/send 与 /spi/speedtest。

严重(12)

严重 S-01 · 干净克隆构建出的是另一份固件(关键配置文件未生效且自相矛盾) [构建]

位置
CMakeLists.txt;sdkconfig.defaults;config/sdkconfig.defaults:7-13,23,34,89-90;.gitignore:33
现象
自定义分区表、4MB Flash、FREERTOS_HZ=1000、APP_ROLLBACK 只写在 config/sdkconfig.defaults,根 CMakeLists 未设置 SDKCONFIG_DEFAULTS,IDF 只读根目录 sdkconfig.defaults;sdkconfig 被 gitignore 未入库。config/ 版本还写着 FLASHMODE_QIO 与 BT_BLUEDROID_ENABLED,与根配置(DIO、NimBLE)冲突。
风险
新环境/CI/同事机器构建得到:单 factory 分区(无 gd32_ota、无 app0/app1,ESP32 自升级不可用)、HZ=100(pdMS_TO_TICKS(1/3/5)=0,SPI 等待退化为零超时/忙让)、无回滚。若有人改为引用 config/ 版本,则 QIO 导致 OTA 读回 SHA 必错。
修改建议
把分区表/Flash 大小/HZ/回滚并入根 sdkconfig.defaults,删除或标注废弃 config/sdkconfig.defaults;代码中加 static_assert(configTICK_RATE_HZ == 1000);CI 增加一次 idf.py build 并校验分区表。

严重 S-02 · 回滚保护取消过早:mark_app_valid 早于自检判定 [OTA]

位置
main/core/main.cpp:131-147
现象
esp_ota_mark_app_valid_cancel_rollback() 在 SystemBoot::report() 之前无条件调用;随后若 critical_ok 为 false 才 3 秒后 esp_restart()。
风险
新固件关键初始化失败(SPI/WiFi/HTTP 起不来)时已被标记有效,重启后仍运行坏固件,回滚机制形同虚设;在仅能网页升级的现场即“软变砖”。
修改建议
先 SystemBoot::report(),仅 critical_ok 才 mark valid;更稳妥是在 WiFi/HTTP 可达且 SPI L0 通过后(或前端确认后)再 mark,失败时 esp_ota_mark_app_invalid_rollback_and_reboot()。

严重 S-03 · 看门狗/中止路径与中继任务并发:清会话密钥但中继任务不停 [并发]

位置
main/ota/ota_coordinator.cpp:625-635;main/relay/ota_relay.cpp:539-554
现象
doAbortLocked 在 httpd 任务内、持协调器锁调用 OtaRelay::abortRemoteAndReset(true),其 reset() 清零 m_kSession / m_nonceServer、解除 ring 抑制;而 ota_relay 任务中的 forwardFirmware 没有任何取消标志,仍在用这些数据收发 SPI。OTA_RESULT_ERR_USER_ABORT 从未被触发。
风险
中继后续包以全零密钥加密发往 GD32、两个任务同时发 CANCEL/数据,GD32 半会话残留;与 S-04 叠加时在慢速但合法的中继中可复现。
修改建议
引入 std::atomic<bool> 取消标志 + 事件组:abort 只置标志并等待中继任务自行收尾(带超时),reset() 只能由中继任务自身调用;协调器不在持锁状态下做 1.5 s 级 SPI 操作。

严重 S-04 · 忙态看门狗 150 s 短于 GD32 中继合法最坏耗时 [OTA]

位置
main/include/app_config.h:126;main/relay/ota_relay.cpp:361-368
现象
OTA_COORD_BUSY_WATCHDOG_MS=150000;传输阶段自身预算上限 120 s,且只从 TRANSFERRING 开始计;进 Boot(2×12 s+HELLO)、握手 20×1.6 s、SIZE_NEG 最多 120×500 ms、FINISH 10 次+APP 存活轮询都在预算之外。
风险
接近满尺寸固件或总线繁忙时,本可成功的中继会被看门狗中止(并触发 S-03 的并发问题),表现为偶发“OTA 卡在 9x% 后失败”。
修改建议
看门狗改为按阶段续期(中继任务每阶段/每 N 包喂狗),或将阈值设为各阶段上限之和 + 裕量;统一由中继任务上报进度时间戳。

严重 S-05 · WiFi 运行期断线重试 5 次后永不重连,也不回退 AP [健壮性]

位置
main/server/wifi_ota.cpp:94-100, 180-192, 751-777
现象
重连任务达到 WIFI_STA_MAX_RETRY 后 setStaRetryExhausted(true);该标志只在 STA_START/CONNECTED/初始化时复位(156/168/291 行),断线事件在 exhausted 时不再唤醒重连;AP 回退只在开机流程中执行。
风险
路由器重启或短暂断电超过几次重试窗口后,设备永久失联,只能现场断电;HTTP/OTA/CAN 控制全部不可用。
修改建议
耗尽后改为长周期退避(如 30 s~5 min)持续重连,或运行期回退 AP+STA 并行;同时把耗尽状态纳入 /system/status 与健康监控。

严重 S-06 · GD32 握手 nonce 可预测且时间戳明文入日志 [安全]

位置
main/relay/ota_relay.cpp:1027-1035;main/transport/protocol.cpp:90-131
现象
nonce_server = scrambleGenerate(xTaskGetTickCount(), &m_nonceServer, 0x12345678);种子为常量、地址固定,按字节映射使同一次开机内 nonce 第 4~15 字节恒定,仅 4 字节 tick 变化;tick 以 DEBUG_INFO 明文打印(并经 UDP 广播到局域网)。
风险
会话密钥 K_session = AES(MASTER_KEY, nonce) 的新鲜性只剩 32 bit 且可从日志获知,配合硬编码 MASTER_KEY(S-08)可离线复现会话密钥、重放或伪造 OTA 数据。
修改建议
16 字节全部取自 esp_fill_random()(确保 RF 已开或 bootloader_random_enable);删除 timestamp 日志;scrambleGenerate 仅保留为兼容路径。

严重 S-07 · GET /can/mode 查询失败时会把 CAN 接收模式写回 ALL_PASS [健壮性]

位置
main/server/http_handlers.cpp:1020-1030
现象
0xC1 查询失败的回退路径调用 canFdSetRxMode(CAN_RX_MODE_ALL_PASS, &actual, 2000) 作为“查询”,这是一次写操作。
风险
前端轮询 GET 一次失败即静默把用户设定的 FILTER / CAN0→1/2 BRIDGE 模式改回全收,电机总线桥接中断,且无日志可见。GET 有副作用也违背 HTTP 语义。
修改建议
增加只读查询命令(或复用 0xC1 重试),失败时返回 {ok:false} 与缓存值,绝不写入。

严重 S-08 · 凭据与主密钥明文入仓,AP 弱口令 [安全]

位置
main/include/app_config.h:31-32, 39-40, 161-164;main/server/wifi_ota.cpp:264
现象
STA SSID/密码(真实口令)、AP 密码 12345678(WPA/WPA2 混合)、MASTER_KEY = 01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10(示例向量)均为源码常量,且已进入 git 历史。
风险
仓库任何读者可接入现场 WiFi 与设备 AP;MASTER_KEY 公开即 GD32 挑战-应答与逐包加密全部失效,可向 GD32 注入任意固件。
修改建议
迁移到 NVS(出厂配置写入)或 Kconfig + 不入库的 sdkconfig;轮换已泄露的 WiFi 口令;每设备派生密钥(MASTER_KEY ⊕ UID KDF);CI 加 gitleaks 扫描。

严重 S-09 · BLE GATT 无配对/加密,范围内任意设备可 OTA 或重启 [安全]

位置
main/server/ble_ota_host.cpp:366-376
现象
CTRL/DATA_IN 特征仅 BLE_GATT_CHR_F_WRITE / WRITE_NO_RSP,未要求 WRITE_ENC/AUTHEN,未配置 SM。
风险
10~30 m 内任意手机可发 0xFA 重启、0xFB 中止、上传固件触发 GD32 中继。
修改建议
启用 LE Secure Connections(Passkey 或 OOB),关键特征加 BLE_GATT_CHR_F_WRITE_ENC | WRITE_AUTHEN;或至少在 CTRL 上加应用层 token。

严重 S-10 · RSSI 更新覆盖过温 SAFE_SHUTDOWN 健康状态 [实时性]

位置
main/monitor/health_monitor.cpp:578-600;main/server/wifi_ota.cpp:853
现象
温度与 RSSI 共用 m_healthState;updateFromWifiRssi 只要 next != cur 就 setState(SYS_NORMAL/WARNING/CRITICAL),会把温度路径设置的 SYS_SAFE_SHUTDOWN 改写掉。
风险
过温门控被下一次 RSSI 采样清除,高温下仍允许启动 OTA(Flash 擦写 + 射频满负荷),存在硬件损伤与写坏风险。
修改建议
拆分温度/RSSI 子状态,整体状态取两者最严重值;SAFE_SHUTDOWN 只能由温度路径解除。

严重 S-11 · 栈溢出钩子中调用可阻塞的 uart_write_bytes [实时性]

位置
main/monitor/task_stack.cpp:27-40
现象
vApplicationStackOverflowHook 运行在 vTaskSwitchContext(调度器临界区)内,却调用带互斥锁、可能阻塞的 uart_write_bytes。
风险
溢出发生时可能在钩子内死锁/二次异常,丢失现场信息,甚至无法走到 esp_system_abort。
修改建议
改用 esp_rom_printf / ets_printf(ROM 直写),或只置标志后立即 abort,由 panic handler 输出任务名。

严重 S-12 · /spi/send 的 timeout_ms 无上限,可长期占死 SPI worker [安全]

位置
main/server/http_handlers.cpp:1715;main/transport/spi_master.cpp:646
现象
timeout_ms 由 double 直接强转 uint32;submitJob 内再 ×3+10 s,大数值会回绕或得到极长等待。
风险
一次请求即可让 httpd(单线程)与唯一的 spi_worker 长时间阻塞,CAN 反馈、心跳、OTA 全部停摆。
修改建议
限制 timeout_ms ∈ [10, 5000],超范围 400;submitJob 内做饱和运算。

一般(17)

一般 G-01 · 完成回调不校验状态与 epoch,可能覆盖新会话 [OTA]

位置
main/ota/ota_coordinator.cpp:372-392, 568-576
现象
notifyRelayComplete / notifyEsp32FinalizeComplete 直接设置 COMPLETE/ERROR,不检查当前状态和 epoch。
风险
看门狗中止后迟到的完成回调会把新开始的 GD32_STORING 会话改成 COMPLETE/ERROR。
修改建议
回调携带启动时的 epoch,不一致即丢弃并记日志。

一般 G-02 · 协调器锁超时 100 ms,lock 失败直接跳过状态切换 [并发]

位置
main/include/app_config.h:124;main/ota/ota_coordinator.cpp
现象
持锁期间存在远端 CANCEL(≤1.5 s)、NVS 擦写、esp_ota_begin 等长操作;其他调用方 100 ms 拿不到锁即放弃,关键通知(中继完成、恢复 CAN 反馈)被丢。
风险
状态卡住直至 150 s 看门狗中止,前端表现为“完成后长时间不恢复”。
修改建议
长操作移出临界区;关键通知改为投递队列由协调器任务串行处理。

一般 G-03 · 确认 APP 存活前即清除 meta;失败路径连发 3 次 CANCEL [OTA]

位置
main/relay/ota_relay.cpp:1554, 1573-1579, 1637-1643
现象
doFinishAndVerify 在 doWaitForAppAlive 成功前清除 gd32 meta;失败时 3 处各发一次 CANCEL(每次最长 1.5 s)。
风险
GD32 新 APP 起不来时无法用已缓存固件重试中继,必须重新上传;失败收尾最长多耗 4.5 s。
修改建议
APP 存活确认后再清 meta;CANCEL 收敛到单一出口。

一般 G-04 · 校验缺口:先置 STORED 后算 SHA;ESP32 finalize 可不带 SHA、不比对实收字节 [OTA]

位置
main/storage/gd32_flash_store.cpp(finishGd32StorageAfterReceive 275-279 附近);main/ota/esp32_ota_core.cpp finalize
现象
SHA 计算失败时状态仍为 STORED、checksum 全零(中继时会再算一次兜底);/ota/update 请求体缺少 sha256 或 ≥256B 时以 nullptr 跳过比对,且不检查 m_received == 声明大小。
风险
截断上传可能被当作完整固件写入启动分区(依赖 esp_ota_end 的镜像校验兜底)。
修改建议
SHA 成功后再置 STORED;finalize 强制要求 SHA 并校验字节数;请求体超长时排空并 400。

一般 G-05 · OTA 对齐缓冲正常完成后不释放(常驻约 4 KB) [内存]

位置
main/ota/ota_coordinator.cpp:203, 625-635;main/storage/gd32_flash_store.cpp:162-179
现象
acquireAlignBufForOta() 在 beginGd32Storage 分配;releaseAlignBufForOta() 只在中止路径调用。
风险
成功完成一次 GD32 上传后约 4 KB 内部 DRAM 常驻,与 0.5.0 “上传缓冲 RAII 省 4 KB” 的目标相抵。
修改建议
在 completeGd32Storage / 中继完成路径同样释放,或用 RAII 绑定会话生命周期。

一般 G-06 · 电机 OTA 与 GD32 OTA 共用分区和 meta,互斥不完整 [OTA]

位置
main/server/http_handlers.cpp:2622-2642, 2697-2700, 2748-2776
现象
/motor/ota 只检查“当前空闲”,不占用协调器;/motor/ota/relay 不区分 meta 来源也不检查协调器状态;注释“relay 复核 SHA”与“relay 不做 SHA 校验”自相矛盾。
风险
与 BLE 的 GD32 存储并发写同一分区;可能把 GD32 镜像中继给电机节点。
修改建议
电机 OTA 纳入 OtaCoordinator 状态机(新增 MOTOR_STORING/RELAYING),meta 增加镜像类型字段并在中继前校验。

一般 G-07 · JSON 截断输出非法 JSON;motor_state 栈占用约 2.5 KB [内存]

位置
main/server/http_handlers.cpp:2383, 2493;main/server/http_util.h
现象
http_json_append 截断时钳制在 cap-1,调用方 len>0 判断失效;/motor/state JSON 每条约 290 B,循环仅留 128 B 余量,超过约 10 个电机必然截断;栈上 motor_state_table_t 约 2.5 KB(httpd 栈 7 KB)。
风险
前端解析失败;httpd 栈余量被压缩。
修改建议
改 chunked 逐条输出;大结构改静态或堆分配。

一般 G-08 · 输入校验不足与未定义行为 [健壮性]

位置
main/server/http_handlers.cpp:323, 1056-1061, 1256, 1340-1341, 1521
现象
double→uint32/uint8 强转无范围检查;readInt 有符号溢出;http_hex_to_bytes 不校验非 hex 字符;POST /can/mode 非法 mode 静默当 ALL_PASS;send_batch 最多 128 帧×80 ms 阻塞 httpd 约 10 s。
风险
异常输入导致错误下发或 httpd 长时间无响应。
修改建议
统一参数解析 helper(范围、类型、hex 校验),非法即 400;批量发送移到后台任务。

一般 G-09 · suspendStack 跨任务写非原子 BLE 连接状态 [并发]

位置
main/server/ble_ota.cpp:276-279
现象
在 httpd 任务里直接写 m_bleConnected / m_connHandle,与 NimBLE host 任务竞争。
风险
挂起/恢复期间状态错乱,可能在挂起后被断连事件重新开启广播(待实机确认)。
修改建议
通过 ble_npl 事件投递到 host 任务内修改,或改 atomic。

一般 G-10 · SPI 事务超时返回后 DMA 可能仍在使用栈上缓冲 [内存]

位置
main/transport/spi_master.cpp:1001-1019
现象
spi_device_get_trans_result 重试 1 s 仍超时即返回,栈上的 transaction 与 staging 缓冲可能仍被 DMA 访问。
风险
极端情况下栈数据被改写(use-after-return),表现为随机崩溃。
修改建议
超时后复位 SPI 总线/设备再返回,或改为静态/成员缓冲。

一般 G-11 · tick 比较非回绕安全 [健壮性]

位置
main/transport/spi_master.cpp:883-889, 1443-1445, 1560-1572, 2058-2061, 2156-2163, 2544-2556
现象
多处 now >= deadline / tick <= deadline 直接比较。
风险
运行约 49.7 天(1 kHz)回绕时,个别等待可能变成极长阻塞。
修改建议
统一改用 xTaskCheckForTimeOut 或 (int32_t)(now - deadline) >= 0。

一般 G-12 · SPI 主站边界缺陷(resp_len 越读、out_init_status 未回填、HELLO 失败后会话不一致) [协议]

位置
main/transport/spi_master.cpp:175, 622-627, 1692-1697, 2005-2042
现象
copyFoundFrame 写入未截断长度,0x82 统计按其读取 resp_payload[0..5];submitJob 不回填 out_init_status(当前调用方传 nullptr,潜在);ENTER_BOOT 内 HELLO 重同步失败后 m_v2Active=false 且持有 GD32 未知的新 session。
风险
小缓冲调用方越界读;跨任务查询拿不到结果;失败后一段时间内会话校验失效。
修改建议
长度统一截断到 resp_max;补齐输出字段回填;HELLO 失败恢复旧 session 或强制下一帧 sess0。

一般 G-13 · CI 必然失败且不构建固件 [构建]

位置
.github/workflows/spi-link-selftest.yml
现象
.github/workflows/spi-link-selftest.yml 检查 ../spi_link 不存在即 exit 1,而没有任何步骤提供该目录;workflow 也不执行 idf.py build。另外 Gitea 上是否启用 Actions 未知。
风险
门禁形同虚设,S-01 一类构建问题无法被发现。
修改建议
把 spi_link 独立成仓并以 submodule 引入(同时解决 components/transport 的 ../../../spi_link 仓外依赖),CI 增加 espressif/idf:v6.0 容器构建。

一般 G-14 · 任务数 > 20 时 CPU 统计冻结并每秒刷 WARN [可维护性]

位置
main/monitor/cpu_stats.cpp:106-117
现象
uxTaskGetSystemState 在数组不足时返回 0 而非截断,快照冻结;常驻约 19 个任务,加按需任务即超限。
风险
/system/status 的 CPU 数据失真,日志被刷屏。
修改建议
数组按 uxTaskGetNumberOfTasks()+裕量动态分配,WARN 节流。

一般 G-15 · UDP 日志不可关闭、目标硬编码,fd 关闭存在竞态 [安全]

位置
main/monitor/debug_log.cpp:689-711;main/include/app_config.h:96
现象
有 IP 即向 192.168.31.3:6666 发送全部日志(含 S-06 的 nonce 时间戳);一个任务读取 socket fd 后另一任务可能将其关闭,fd 被 httpd 复用时日志会写进 HTTP 连接。
风险
信息泄露;极端情况下污染 HTTP 响应流。
修改建议
Kconfig 开关默认关闭;fd 访问加锁或改单写者任务。

一般 G-16 · 日志环满时选择性覆盖导致 /logs 时间序错乱 [可维护性]

位置
main/monitor/debug_log.cpp:341-352
现象
满环时跳过 ERR/WARN、覆盖 INFO/DBG 槽位。
风险
排查事故时日志顺序失真。
修改建议
分级双环,或保留序号字段由前端排序。

一般 G-17 · OTA_MAX_FIRMWARE_SIZE 注释数值错误 [OTA]

位置
main/include/app_config.h:77
现象
216*1024 = 221184 = 0x36000,与 GD32 APP_MAX_SIZE 实际对齐;注释却写 “0x35F90”(=221072)。0627 P0-3 对 0x36000 的换算(221296)同样有误。
风险
维护者据注释误判上限,引发两侧不一致。
修改建议
改为 0x36000 字面量 + static_assert,注释删除错误换算。

建议(7)

建议 A-01 · 仓库卫生:gitignore 行内注释失效、被忽略目录仍被跟踪、本机路径入库 [构建]

位置
.gitignore:25,31,89-91;.trae/;.zcode/;.vscode/settings.json
现象
行内 # 注释使 *.exe、flasher_args.json、*.workspace、*.suo 规则失效;.trae/(5)、.zcode/(11) 过程文件已在 gitignore 中却仍被跟踪;.vscode 含本机 Windows 路径与 COM15(且写成 Robot_v2\DFU)。
风险
噪声与泄露本机信息。
修改建议
注释另起一行;git rm --cached 过程目录;.vscode 改模板。

建议 A-02 · 版本信息与文档漂移 [文档]

位置
main/include/version.h:18-21;README.md
现象
GIT_HASH="770ae14" 而 HEAD 为 af1f682(落后 12 个提交);FW_VERSION_STRING 后挂超长变更注释;README/白皮书顶部写“git 770ae14”;README 任务表名称与代码不符(ble_data_worker→ble_ota_worker、motor_feed→ota_feed),缺 httpd/ota_cancel 等任务;引用的 .cache/ 前端快照不在仓库。
风险
现场版本追溯困难。
修改建议
构建时用 git describe 生成 GIT_HASH;变更记录移到 CHANGELOG.md。

建议 A-03 · 提交信息与内容不符,历史评审被静默删除 [构建]

位置
git 历史 af1f682
现象
af1f682 标为 “docs(readme): 更新文档说明”,实际删除 docs/esp32_architecture_review_2026-06-27.md 等 4 份文档;更早 5b2c5f8 删除了 2026-06-13 评审与 2026-05-05 通信梳理。
风险
评审结论无从追溯,问题被重复发现。
修改建议
评审记录统一归档到 docs/review/(本次已恢复存档),删除须独立提交并说明。

建议 A-04 · CS 前 esp_rom_delay_us 忙等最长 1 ms [实时性]

位置
main/transport/spi_master.cpp:946-952
现象
单核芯片上 prio 8 的 spi_worker 每帧前忙等,期间低优先级任务饥饿。
风险
CPU 利用率与 BLE/WiFi 吞吐受影响。
修改建议
改为基于 esp_timer 的剩余时间判断,< 100 µs 才忙等。

建议 A-05 · 间接优先级倒置 [实时性]

位置
main/core/main.cpp;README 任务表
现象
ota_relay(23)、health_mon(21) 通过 spi_worker(8) 完成 SPI 事务,等待期间被 9~20 优先级任务(ble_ota_worker 12、lwIP 18 等)抢占。
风险
高优先级任务的时延不可预期。
修改建议
Job 提交时临时提升 worker 优先级(优先级继承)或让 worker 位于调用方之上。

建议 A-06 · 死代码与量程不符 [可维护性]

位置
main/monitor/health_monitor.cpp:389-396, 365;main/monitor/task_stack.cpp:30-39
现象
g_stackOverflowDetected 置位后钩子立即 abort,health_mon 永远看不到;温度传感器量程配置为 20~80 °C,而 TEMP_CRITICAL=100 °C 超出量程。
风险
告警逻辑失效。
修改建议
删除死分支或改为 RTC_NOINIT 变量跨重启上报;量程改 -10~110。

建议 A-07 · 日志格式串与参数不匹配、注释过期 [可维护性]

位置
main/transport/spi_master.cpp:924-925;main/server/wifi_ota.cpp:403
现象
格式串 3 个说明符却传 5 个参数;路由注册注释写 35 次,实际 22 路由+21 OPTIONS=43 次。
风险
日志误导。
修改建议
开启 -Wformat 并修正注释。

5. 既有评审结论跟踪

来源:0816=project_introduction.html §19 安全审计(2026-08-16);0613=CODE_REVIEW_REPORT_2026-06-13.html(v0.3.12);0627=esp32_architecture_review_2026-06-27.md;0505=COMMUNICATION_AUDIT_2026-05-05.md。

统计:已修复 23 · 部分修复 5 · 仍存在/结论不成立 12 · 未逐条核对 2。总体看,历史上的并发、函数拆分、资源清理类问题大多已整改;安全类结论(认证、凭据、签名、ECB、nonce)一条都没有关闭。

来源编号原结论当前状态当前代码证据
0816①HTTP 22 端点零认证仍存在http_handlers.cpp:77-83 CORS *,无鉴权;见 F-01
0816②WiFi 凭据与 MASTER_KEY 明文入仓仍存在app_config.h:31-40,161-164;见 S-08
0816③无固件签名仍存在仅 SHA-256 完整性;未启用 Secure Boot / 签名校验
0816④AES-ECB 确定性模式仍存在ota_relay.cpp 609-620, 836-855 esp_aes_crypt_ecb
0816缓解nonce 每会话随机绑定防重放结论不成立nonce 仅 tick 4 字节变化且明文入日志;见 S-06
0613#1SPI 总线无并发保护已修复spi_master.cpp:588-821 单 worker + Job 池 + m_mutex
0613#2硬编码占位密钥仍存在同 0816 ②
0613#3Flash 写入无对齐检查已修复gd32_flash_store.cpp:199-276 flashWriteChunk 头尾 pad read-modify-write
0613#4BLE/WiFi 双通道 ESP32 OTA 冲突已修复Esp32OtaCore s_occupied + OtaCoordinator 单例互斥
0613#5SPI 帧 XOR 伪校验已修复(迁移)otaCrc8 已移除,Link v2 用 CRC32-MPEG2(实现在仓外 spi_link,未能直接核对)
0613#60xA5 常量碰撞已修复spi_master.h:33 SPI_APP_ENTER_BOOT_ACK_OK=0xB5
0613#7OTA 会话无全局超时部分修复传输阶段 120 s 预算 + 150 s 看门狗,但看门狗短于最坏耗时且无法停止中继任务;见 S-03/S-04
0613#8CORS 允许所有来源仍存在http_handlers.cpp:79
0613#11OtaRelay 静态状态无锁部分修复成员已 private + StateLockGuard,reset() 仍可被外部任务调用;见 S-03
0613#16nonce 仅用 esp_random 一次/可预测仍存在(形式变化)现为 tick+固定地址+常量种子;见 S-06
0613#17ota_meta checksum 从未填充已修复metaFinalizeChecksum + metaVerifyChecksum(gd32_flash_store.cpp:555-620)
0613#18ESP32 OTA 无回滚验证部分修复代码调用 mark_app_valid,但调用过早且回滚开关所在配置未生效;见 S-01/S-02
0613#19SPI 忽略 timeout已修复spi_master.cpp:981-1019 有界等待(残留 G-10)
0613#20esp_ota_write 失败未 abort已修复esp32_ota_core finalizeCleanupAndReturn/beginCleanupAndReturn
0613#22SPI 无就绪信号已修复DATA_READY GPIO10 ISR + credit 流控
0627P0-1metaVerifyChecksum 全零跳过校验已修复gd32_flash_store.cpp:563-569 返回 ESP_ERR_INVALID_STATE
0627P0-2RSSI CRITICAL→NORMAL 无法降级已修复(引入新问题)health_monitor.cpp:596-598 可直接恢复 NORMAL,但会覆盖过温状态;见 S-10
0627P0-3OTA_MAX_FIRMWARE_SIZE 注释不一致仍存在(原结论换算亦有误)app_config.h:77;216 KiB 恰为 0x36000,注释 0x35F90 错误;见 G-17
0627P0-4凭证硬编码入仓库仍存在同 0816 ②
0627P0-5BleEsp32FinishCtx 三处 free 易漏已修复ble_ota_host.cpp:183-193 由任务负责 free,失败路径统一释放
0627P0-7doRelayPackets 180 行 + goto已修复拆出 sendRelayPacketWithRetry,阈值命名常量
0627P0-8ble_ota_process_data_batch 245 行已修复已拆为 process_esp32_packet / process_gd32_packet(ble_ota_data.cpp:878, 1001, 1167)
0627P1-AOtaRelay 状态成员封装破坏部分修复已 private;锁内外混合访问仍在
0627P1-9/P1-10getSnapshot 双重加锁 / 多次取状态已修复stateToString + isIdleState,快照持锁一次取值
0627P1-11/P1-12esp32_ota_core 清理序列重复已修复finalizeCleanupAndReturn / beginCleanupAndReturn
0627P1-13/P1-14doFinishAndVerify 双职责 / APP 探测重复已修复doSendFinishOta + doWaitForAppAlive;probeAndCacheAppAlive
0627P1-17metaUpdate 读改写非原子已修复gd32_flash_store.cpp:432-448 全程持 m_nvsMutex
0627P1-18metaLoad nvs_open 失败当 OK已修复gd32_flash_store.cpp:345-355 区分 NOT_FOUND
0627P1-22s_ota_recv_buf 全局共享已修复0.5.0 改为 OTA 会话内 RAII malloc
0627P1-23一次性任务创建失败上报不一致已修复ble_ota_host.cpp:130-135, 189-194, 212-215 均 sendFinishEvent
0627P1-24reorder slot malloc 失败静默丢弃已修复ble_ota_data.cpp:466-474 发窗口 ACK 并返回 ESP_ERR_NO_MEM
0627P1-25protocol.h 死代码未逐条核对—
0627P2-88notifyRelayStarted/Complete 纯转发仍存在ota_coordinator.cpp
0627P2-89两个通知函数几乎相同部分修复已共用 helper
0627P2-920xF0F0F0F0 魔法数字已修复kLastPacketAckSeq
0627其余 P2样板抽取/配置化/风格类约 50 项未逐条核对低风险风格项,本次未逐条复核,建议下轮评审处理
0505UDPUDP 日志可选、建议可关闭仍存在无开关,默认开启并硬编码目标;见 G-15

6. 优先级修复路线图

阶段目标条目预估
P0 · 本周堵住远程控制面,恢复可复现构建F-01(token + CORS 白名单 + /spi/send 白名单/默认关闭)、S-12、S-01(合并 sdkconfig.defaults + HZ static_assert)、S-08(轮换 WiFi 口令,凭据迁 NVS)2~3 人日
P1 · 2 周内消除变砖与失联路径S-02(mark_app_valid 后移)、S-03 + S-04(中继取消标志与分阶段看门狗)、S-05(断线长周期重连)、S-07、S-10、S-11、G-01、G-024~6 人日,需 GD32 全链路 OTA 回归
P2 · 1 个月加固密码学与无线面S-06(esp_fill_random nonce)、S-09(BLE LESC + 加密特征)、0816 ③④(签名 / AES-CTR 或 GCM,需三工程同步)、G-06、G-155~8 人日(含 GD32 侧)
P3 · 持续工程卫生G-13(spi_link submodule + 固件构建 CI)、G-03~G-05、G-07~G-12、G-14、G-16、G-17、A-01~A-07随迭代

7. 总结评分

维度评分理由
固件架构与分层7 / 10接入/协调/执行/传输分层清晰,协调器单例 + 单 SPI worker 设计合理
SPI 协议与传输6 / 10会话/序号/credit/CRC32 完备,诊断充分;边界与回绕细节待补
OTA 可靠性5 / 10互斥与 epoch 进步明显;中止协作、看门狗预算、回滚时机存在变砖级缺口
FreeRTOS 与实时性6 / 10任务优先级经事故调优;钩子与健康状态逻辑有真实缺陷
网络与无线安全2 / 10零认证 + CORS * + 原始 SPI 透传 + 硬编码密钥 + BLE 无加密
代码质量与可维护性6 / 10历史评审的大函数/重复样板多数已整改;超长文件(http_handlers 2.8k 行)仍需拆分
构建与仓库卫生3 / 10干净克隆不可复现、CI 必败、仓外依赖、过程文件入库
文档6 / 10白皮书详尽,但版本/任务表漂移,历史评审被静默删除
综合5.1 / 10链路与 OTA 工程扎实、诊断能力强,历史评审的结构性问题大多已整改;但“可信局域网”前提下的零认证控制面、不可复现的构建配置、回滚时机与中止协作缺陷,使其距离量产/现场部署仍有明显距离。
结论:建议在下一个版本(0.5.3)前完成 P0 全部条目,并把本报告纳入 docs/project_introduction.html §19 的跟踪;后续评审请统一归档到 docs/review/,不要随文档整理删除。

本报告由 Hark 专家评审生成 · 2026-10-19 · 基线 af1f682。行号以该提交为准,后续提交可能漂移。