Files
ESP32_COMM/docs/review/ESP32_COMM_专家评审_2026-10-19.html
T

160 lines
56 KiB
HTML
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.
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32_COMM 专家评审 2026-10-19</title><style>
:root{--ink:#1e293b;--muted:#64748b;--line:#e2e8f0;--bg:#f6f8fb;--card:#fff;--esp:#2563eb}
*{box-sizing:border-box}body{font-family:"Microsoft YaHei","PingFang SC","Segoe UI",Arial,sans-serif;color:var(--ink);background:var(--bg);margin:0;line-height:1.7;font-size:14.5px}
.wrap{max-width:1180px;margin:0 auto;padding:26px 20px 80px}
.hero{background:linear-gradient(135deg,#3b0a0a 0%,#7f1d1d 55%,#b91c1c 100%);color:#fff;border-radius:14px;padding:28px 32px;margin-bottom:22px}
.hero h1{margin:0 0 6px;font-size:25px}.hero p{margin:3px 0;color:#fee2e2;font-size:13.5px}
h2{font-size:20px;margin:40px 0 12px;padding:6px 0 6px 14px;border-left:5px solid #b91c1c;background:linear-gradient(90deg,#fef2f2,transparent);border-radius:0 8px 8px 0}
h3{font-size:16px;margin:22px 0 8px;color:#3b0a0a}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px 20px;margin:12px 0}
table{border-collapse:collapse;width:100%;margin:10px 0;font-size:13px;background:#fff}
th,td{border:1px solid var(--line);padding:6px 9px;text-align:left;vertical-align:top}
th{background:#f1f5f9;font-weight:600;white-space:nowrap}tr:nth-child(even) td{background:#fafcff}
code{background:#eef2f7;border:1px solid #e2e8f0;border-radius:4px;padding:0 4px;font-size:12.5px;font-family:Consolas,monospace;word-break:break-all}
pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:14px 16px;overflow-x:auto;font-size:12.5px;line-height:1.5}
.tag{display:inline-block;padding:1px 8px;border-radius:10px;font-size:12px;font-weight:600;white-space:nowrap}
.sev-f{background:#7f1d1d;color:#fff}.sev-s{background:#dc2626;color:#fff}.sev-g{background:#f59e0b;color:#fff}.sev-a{background:#64748b;color:#fff}
.st-ok{background:#dcfce7;color:#166534}.st-part{background:#fef9c3;color:#854d0e}.st-bad{background:#fee2e2;color:#991b1b}.st-na{background:#e2e8f0;color:#334155}
.kpis{display:flex;gap:12px;flex-wrap:wrap;margin:14px 0}.kpi{flex:1;min-width:120px;background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px 14px;text-align:center}
.kpi b{display:block;font-size:26px}.kpi span{color:var(--muted);font-size:12.5px}
.finding{background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px 16px;margin:10px 0}
.finding h4{margin:0 0 6px;font-size:15px}.finding dl{display:grid;grid-template-columns:72px 1fr;gap:4px 10px;margin:0}
.finding dt{color:var(--muted);font-weight:600}.finding dd{margin:0}
.toc a{color:var(--esp);text-decoration:none;margin-right:14px;white-space:nowrap}
.note{color:var(--muted);font-size:13px}
</style></head><body><div class="wrap">
<div class="hero"><h1>ESP32_COMM 专家评审报告</h1>
<p>评审日期 2026-10-19 · 代码基线 master <code style="background:#fff2;color:#fff;border:0">af1f682</code>(固件 v0.5.2,ESP-IDF v6.0,ESP32-C3FH4)</p>
<p>评审方式:多专家视角并行通读 + 主审逐条复核 · 本报告只新增 docs/,未改动任何源码</p>
<p>综合评分 <b style="font-size:20px">5.1 / 10</b></p></div>
<div class="kpis">
<div class="kpi"><b style="color:#7f1d1d">1</b><span>致命</span></div>
<div class="kpi"><b style="color:#dc2626">12</b><span>严重</span></div>
<div class="kpi"><b style="color:#d97706">17</b><span>一般</span></div>
<div class="kpi"><b style="color:#64748b">7</b><span>建议</span></div>
<div class="kpi"><b style="color:#166534">23</b><span>既有条目已修复</span></div>
<div class="kpi"><b style="color:#991b1b">12</b><span>既有条目仍存在</span></div>
</div>
<div class="card toc"><b>目录:</b><a href="#s1">1 项目概述与架构</a><a href="#s2">2 评审范围与方法</a><a href="#s3">3 问题总表</a><a href="#s4">4 问题详情</a><a href="#s5">5 既有评审跟踪</a><a href="#s6">6 修复路线图</a><a href="#s7">7 总结评分</a></div>
<h2 id="s1">1. 项目概述与架构</h2>
<div class="card"><p>ESP32_COMM 是机器人 DCU 板上 ESP32-C3 的通信与 OTA 中枢固件:上位机经 WiFi HTTP / BLE GATT 把固件交给 ESP32;ESP32 自身走 app0/app1 AB 分区升级,GD32E508 固件经 <b>SPI Link v2</b> 六阶段握手 + AES-128 逐包加密中继,电机节点固件再经 GD32 CAN 二次中继;同时提供三路 CANFD 控制面与遥测回收。</p>
<pre>[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 中继</pre>
<table><tr><th>层</th><th>目录 / 模块</th><th>职责</th><th>规模(行)</th></tr>
<tr><td>接入层</td><td><code>server/</code> wifi_ota · http_handlers · ble_ota(_data/_host)</td><td>STA/AP、HTTP 路由、NimBLE GATT、窗口 ACK / reorder</td><td>≈ 6.6k(http_handlers 2832)</td></tr>
<tr><td>协调层</td><td><code>ota/</code> OtaCoordinator · Esp32OtaCore</td><td>9 态单例、唯一权威入口、150 s 忙态看门狗、epoch</td><td>≈ 2.0k</td></tr>
<tr><td>执行层</td><td><code>relay/</code> OtaRelay · <code>storage/</code> Gd32FlashStore · <code>monitor/</code> MotorOtaRelay</td><td>GD32 中继状态机、gd32_ota 分区与 NVS meta、电机 OTA 喂数</td><td>≈ 3.5k</td></tr>
<tr><td>传输层</td><td><code>transport/</code> SpiMaster · protocol · canfd_bridge(+ 仓外 spi_link_v2)</td><td>Link v2 主站(会话/序号/credit/CRC32/重试)、AES/nonce、CAN 0xC0~0xC8</td><td>≈ 4.3k</td></tr>
<tr><td>横切</td><td><code>monitor/</code> health · debug_log · can_feedback · cpu_stats · task_stack 等;<code>core/</code></td><td>健康门控、三路日志、遥测轮询、启动自检位图</td><td>≈ 3.6k</td></tr></table>
<p class="note">任务模型: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)。</p></div>
<h2 id="s2">2. 评审范围与方法</h2>
<div class="card"><ul>
<li><b>范围</b>:<code>main/</code> 全部 60 个 C/C++ 源文件(≈ 2.0 万行)、<code>components/transport</code>、<code>config/</code>、两份 <code>sdkconfig.defaults</code>、<code>sdkconfig.ci</code>、<code>dependencies.lock</code>、CMake 与 Kconfig、CI workflow、<code>.devcontainer</code>/<code>.vscode</code>/<code>.gitignore</code>、Python 工具脚本、README 与白皮书、git 历史。</li>
<li><b>专家组</b>:① SPI 协议与传输;② OTA 可靠性(协调器 / 本机 OTA / GD32 中继 / 存储 / 电机 OTA);③ 网络与无线安全(HTTP / WiFi / BLE);④ FreeRTOS 实时性 + 构建与仓库卫生 + 代码质量。各组独立通读,主审对每条结论回到当前代码二次核对行号(基线 af1f682)。</li>
<li><b>既有评审</b>:当前树仅有 <code>docs/project_introduction.html</code> §18/§19(含 2026-08-16 安全审计)与 README。git 历史中另有三份已被删除的评审:2026-06-27 架构扫描(af1f682 删除)、2026-06-13 高风险评审与 2026-05-05 通信梳理(5b2c5f8 删除)。本次已原样恢复至 <a href="archive/">docs/review/archive/</a>,并在第 5 节逐条跟踪。</li>
<li><b>严重度</b>:致命=可远程任意控制/变砖/必现崩溃;严重=特定条件下数据损坏、失联、OTA 失败或明显安全缺陷;一般=健壮性/一致性缺陷;建议=可维护性/风格。</li>
<li><b>局限</b>:未上板实测;仓外依赖 <code>spi_link/spi_link_v2.*</code> 不在本仓库,CRC/帧格式部分只能按调用侧判断;0627 评审中约 50 条低风险风格类 P2 条目未逐条复核;网页前端(tools/)已移出仓库,不在范围内。</li></ul></div>
<h2 id="s3">3. 问题总表(37 条)</h2>
<table><tr><th>编号</th><th>严重度</th><th>类别</th><th>问题</th><th>主要位置</th></tr><tr><td><a href='#F-01'>F-01</a></td><td><span class='tag sev-f'>致命</span></td><td>安全</td><td>HTTP 全部端点零认证 + CORS * + /spi/send 原始透传</td><td><code>main/server/http_handlers.cpp:77-83, 1685-1756</code></td></tr><tr><td><a href='#S-01'>S-01</a></td><td><span class='tag sev-s'>严重</span></td><td>构建</td><td>干净克隆构建出的是另一份固件(关键配置文件未生效且自相矛盾)</td><td><code>CMakeLists.txt</code></td></tr><tr><td><a href='#S-02'>S-02</a></td><td><span class='tag sev-s'>严重</span></td><td>OTA</td><td>回滚保护取消过早:mark_app_valid 早于自检判定</td><td><code>main/core/main.cpp:131-147</code></td></tr><tr><td><a href='#S-03'>S-03</a></td><td><span class='tag sev-s'>严重</span></td><td>并发</td><td>看门狗/中止路径与中继任务并发:清会话密钥但中继任务不停</td><td><code>main/ota/ota_coordinator.cpp:625-635</code></td></tr><tr><td><a href='#S-04'>S-04</a></td><td><span class='tag sev-s'>严重</span></td><td>OTA</td><td>忙态看门狗 150 s 短于 GD32 中继合法最坏耗时</td><td><code>main/include/app_config.h:126</code></td></tr><tr><td><a href='#S-05'>S-05</a></td><td><span class='tag sev-s'>严重</span></td><td>健壮性</td><td>WiFi 运行期断线重试 5 次后永不重连,也不回退 AP</td><td><code>main/server/wifi_ota.cpp:94-100, 180-192, 751-777</code></td></tr><tr><td><a href='#S-06'>S-06</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>GD32 握手 nonce 可预测且时间戳明文入日志</td><td><code>main/relay/ota_relay.cpp:1027-1035</code></td></tr><tr><td><a href='#S-07'>S-07</a></td><td><span class='tag sev-s'>严重</span></td><td>健壮性</td><td>GET /can/mode 查询失败时会把 CAN 接收模式写回 ALL_PASS</td><td><code>main/server/http_handlers.cpp:1020-1030</code></td></tr><tr><td><a href='#S-08'>S-08</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>凭据与主密钥明文入仓,AP 弱口令</td><td><code>main/include/app_config.h:31-32, 39-40, 161-164</code></td></tr><tr><td><a href='#S-09'>S-09</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>BLE GATT 无配对/加密,范围内任意设备可 OTA 或重启</td><td><code>main/server/ble_ota_host.cpp:366-376</code></td></tr><tr><td><a href='#S-10'>S-10</a></td><td><span class='tag sev-s'>严重</span></td><td>实时性</td><td>RSSI 更新覆盖过温 SAFE_SHUTDOWN 健康状态</td><td><code>main/monitor/health_monitor.cpp:578-600</code></td></tr><tr><td><a href='#S-11'>S-11</a></td><td><span class='tag sev-s'>严重</span></td><td>实时性</td><td>栈溢出钩子中调用可阻塞的 uart_write_bytes</td><td><code>main/monitor/task_stack.cpp:27-40</code></td></tr><tr><td><a href='#S-12'>S-12</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>/spi/send 的 timeout_ms 无上限,可长期占死 SPI worker</td><td><code>main/server/http_handlers.cpp:1715</code></td></tr><tr><td><a href='#G-01'>G-01</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>完成回调不校验状态与 epoch,可能覆盖新会话</td><td><code>main/ota/ota_coordinator.cpp:372-392, 568-576</code></td></tr><tr><td><a href='#G-02'>G-02</a></td><td><span class='tag sev-g'>一般</span></td><td>并发</td><td>协调器锁超时 100 ms,lock 失败直接跳过状态切换</td><td><code>main/include/app_config.h:124</code></td></tr><tr><td><a href='#G-03'>G-03</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>确认 APP 存活前即清除 meta;失败路径连发 3 次 CANCEL</td><td><code>main/relay/ota_relay.cpp:1554, 1573-1579, 1637-1643</code></td></tr><tr><td><a href='#G-04'>G-04</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>校验缺口:先置 STORED 后算 SHA;ESP32 finalize 可不带 SHA、不比对实收字节</td><td><code>main/storage/gd32_flash_store.cpp(finishGd32StorageAfterReceive 275-279 附近)</code></td></tr><tr><td><a href='#G-05'>G-05</a></td><td><span class='tag sev-g'>一般</span></td><td>内存</td><td>OTA 对齐缓冲正常完成后不释放(常驻约 4 KB)</td><td><code>main/ota/ota_coordinator.cpp:203, 625-635</code></td></tr><tr><td><a href='#G-06'>G-06</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>电机 OTA 与 GD32 OTA 共用分区和 meta,互斥不完整</td><td><code>main/server/http_handlers.cpp:2622-2642, 2697-2700, 2748-2776</code></td></tr><tr><td><a href='#G-07'>G-07</a></td><td><span class='tag sev-g'>一般</span></td><td>内存</td><td>JSON 截断输出非法 JSON;motor_state 栈占用约 2.5 KB</td><td><code>main/server/http_handlers.cpp:2383, 2493</code></td></tr><tr><td><a href='#G-08'>G-08</a></td><td><span class='tag sev-g'>一般</span></td><td>健壮性</td><td>输入校验不足与未定义行为</td><td><code>main/server/http_handlers.cpp:323, 1056-1061, 1256, 1340-1341, 1521</code></td></tr><tr><td><a href='#G-09'>G-09</a></td><td><span class='tag sev-g'>一般</span></td><td>并发</td><td>suspendStack 跨任务写非原子 BLE 连接状态</td><td><code>main/server/ble_ota.cpp:276-279</code></td></tr><tr><td><a href='#G-10'>G-10</a></td><td><span class='tag sev-g'>一般</span></td><td>内存</td><td>SPI 事务超时返回后 DMA 可能仍在使用栈上缓冲</td><td><code>main/transport/spi_master.cpp:1001-1019</code></td></tr><tr><td><a href='#G-11'>G-11</a></td><td><span class='tag sev-g'>一般</span></td><td>健壮性</td><td>tick 比较非回绕安全</td><td><code>main/transport/spi_master.cpp:883-889, 1443-1445, 1560-1572, 2058-2061, 2156-2163, 2544-2556</code></td></tr><tr><td><a href='#G-12'>G-12</a></td><td><span class='tag sev-g'>一般</span></td><td>协议</td><td>SPI 主站边界缺陷(resp_len 越读、out_init_status 未回填、HELLO 失败后会话不一致)</td><td><code>main/transport/spi_master.cpp:175, 622-627, 1692-1697, 2005-2042</code></td></tr><tr><td><a href='#G-13'>G-13</a></td><td><span class='tag sev-g'>一般</span></td><td>构建</td><td>CI 必然失败且不构建固件</td><td><code>.github/workflows/</code></td></tr><tr><td><a href='#G-14'>G-14</a></td><td><span class='tag sev-g'>一般</span></td><td>可维护性</td><td>任务数 &gt; 20 时 CPU 统计冻结并每秒刷 WARN</td><td><code>main/monitor/cpu_stats.cpp:106-117</code></td></tr><tr><td><a href='#G-15'>G-15</a></td><td><span class='tag sev-g'>一般</span></td><td>安全</td><td>UDP 日志不可关闭、目标硬编码,fd 关闭存在竞态</td><td><code>main/monitor/debug_log.cpp:689-711</code></td></tr><tr><td><a href='#G-16'>G-16</a></td><td><span class='tag sev-g'>一般</span></td><td>可维护性</td><td>日志环满时选择性覆盖导致 /logs 时间序错乱</td><td><code>main/monitor/debug_log.cpp:341-352</code></td></tr><tr><td><a href='#G-17'>G-17</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>OTA_MAX_FIRMWARE_SIZE 注释数值错误</td><td><code>main/include/app_config.h:77</code></td></tr><tr><td><a href='#A-01'>A-01</a></td><td><span class='tag sev-a'>建议</span></td><td>构建</td><td>仓库卫生:gitignore 行内注释失效、被忽略目录仍被跟踪、本机路径入库</td><td><code>.gitignore:25,31,89-91</code></td></tr><tr><td><a href='#A-02'>A-02</a></td><td><span class='tag sev-a'>建议</span></td><td>文档</td><td>版本信息与文档漂移</td><td><code>main/include/version.h:18-21</code></td></tr><tr><td><a href='#A-03'>A-03</a></td><td><span class='tag sev-a'>建议</span></td><td>构建</td><td>提交信息与内容不符,历史评审被静默删除</td><td><code>git 历史 af1f682</code></td></tr><tr><td><a href='#A-04'>A-04</a></td><td><span class='tag sev-a'>建议</span></td><td>实时性</td><td>CS 前 esp_rom_delay_us 忙等最长 1 ms</td><td><code>main/transport/spi_master.cpp:946-952</code></td></tr><tr><td><a href='#A-05'>A-05</a></td><td><span class='tag sev-a'>建议</span></td><td>实时性</td><td>间接优先级倒置</td><td><code>main/core/main.cpp</code></td></tr><tr><td><a href='#A-06'>A-06</a></td><td><span class='tag sev-a'>建议</span></td><td>可维护性</td><td>死代码与量程不符</td><td><code>main/monitor/health_monitor.cpp:389-396, 365</code></td></tr><tr><td><a href='#A-07'>A-07</a></td><td><span class='tag sev-a'>建议</span></td><td>可维护性</td><td>日志格式串与参数不匹配、注释过期</td><td><code>main/transport/spi_master.cpp:924-925</code></td></tr></table>
<h2 id="s4">4. 问题详情</h2><h3>致命(1)</h3><div class="finding" id="F-01"><h4><span class="tag sev-f">致命</span> F-01 · HTTP 全部端点零认证 + CORS * + /spi/send 原始透传 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:77-83, 1685-1756</code></dd>
<dt>现象</dt><dd>22 条路由无任何鉴权;所有响应带 Access-Control-Allow-Origin: *;/spi/send 的 frame_type 直接取自请求(strtol),无白名单,经 SpiMaster::transact 原样下发 GD32,绕过 OtaCoordinator 互斥。</dd><dt>风险</dt><dd>同网段任意主机(或用户浏览的任意网页借 CORS 跨站)可上传/切换固件、重启、发 CAN 帧、向 GD32 注入任意 SPI 命令(含进 Boot、擦写类命令),机器人电机总线可被远程操控。</dd><dt>修改建议</dt><dd>最小改造:① 实施已设计的 Bearer token(NVS 存放,首次配网生成);② CORS 改为白名单来源或去掉;③ /spi/send 加 frame_type 白名单(仅只读诊断类),并在 OTA 忙时拒绝;④ 默认编译期关闭 /spi/send 与 /spi/speedtest。</dd></dl></div><h3>严重(12)</h3><div class="finding" id="S-01"><h4><span class="tag sev-s">严重</span> S-01 · 干净克隆构建出的是另一份固件(关键配置文件未生效且自相矛盾) <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>CMakeLists.txt;sdkconfig.defaults;config/sdkconfig.defaults:7-13,23,34,89-90;.gitignore:33</code></dd>
<dt>现象</dt><dd>自定义分区表、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)冲突。</dd><dt>风险</dt><dd>新环境/CI/同事机器构建得到:单 factory 分区(无 gd32_ota、无 app0/app1,ESP32 自升级不可用)、HZ=100(pdMS_TO_TICKS(1/3/5)=0,SPI 等待退化为零超时/忙让)、无回滚。若有人改为引用 config/ 版本,则 QIO 导致 OTA 读回 SHA 必错。</dd><dt>修改建议</dt><dd>把分区表/Flash 大小/HZ/回滚并入根 sdkconfig.defaults,删除或标注废弃 config/sdkconfig.defaults;代码中加 static_assert(configTICK_RATE_HZ == 1000);CI 增加一次 idf.py build 并校验分区表。</dd></dl></div><div class="finding" id="S-02"><h4><span class="tag sev-s">严重</span> S-02 · 回滚保护取消过早:mark_app_valid 早于自检判定 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/core/main.cpp:131-147</code></dd>
<dt>现象</dt><dd>esp_ota_mark_app_valid_cancel_rollback() 在 SystemBoot::report() 之前无条件调用;随后若 critical_ok 为 false 才 3 秒后 esp_restart()。</dd><dt>风险</dt><dd>新固件关键初始化失败(SPI/WiFi/HTTP 起不来)时已被标记有效,重启后仍运行坏固件,回滚机制形同虚设;在仅能网页升级的现场即“软变砖”。</dd><dt>修改建议</dt><dd>先 SystemBoot::report(),仅 critical_ok 才 mark valid;更稳妥是在 WiFi/HTTP 可达且 SPI L0 通过后(或前端确认后)再 mark,失败时 esp_ota_mark_app_invalid_rollback_and_reboot()。</dd></dl></div><div class="finding" id="S-03"><h4><span class="tag sev-s">严重</span> S-03 · 看门狗/中止路径与中继任务并发:清会话密钥但中继任务不停 <span class="note">[并发]</span></h4>
<dl><dt>位置</dt><dd><code>main/ota/ota_coordinator.cpp:625-635;main/relay/ota_relay.cpp:539-554</code></dd>
<dt>现象</dt><dd>doAbortLocked 在 httpd 任务内、持协调器锁调用 OtaRelay::abortRemoteAndReset(true),其 reset() 清零 m_kSession / m_nonceServer、解除 ring 抑制;而 ota_relay 任务中的 forwardFirmware 没有任何取消标志,仍在用这些数据收发 SPI。OTA_RESULT_ERR_USER_ABORT 从未被触发。</dd><dt>风险</dt><dd>中继后续包以全零密钥加密发往 GD32、两个任务同时发 CANCEL/数据,GD32 半会话残留;与 S-04 叠加时在慢速但合法的中继中可复现。</dd><dt>修改建议</dt><dd>引入 std::atomic&lt;bool&gt; 取消标志 + 事件组:abort 只置标志并等待中继任务自行收尾(带超时),reset() 只能由中继任务自身调用;协调器不在持锁状态下做 1.5 s 级 SPI 操作。</dd></dl></div><div class="finding" id="S-04"><h4><span class="tag sev-s">严重</span> S-04 · 忙态看门狗 150 s 短于 GD32 中继合法最坏耗时 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:126;main/relay/ota_relay.cpp:361-368</code></dd>
<dt>现象</dt><dd>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 存活轮询都在预算之外。</dd><dt>风险</dt><dd>接近满尺寸固件或总线繁忙时,本可成功的中继会被看门狗中止(并触发 S-03 的并发问题),表现为偶发“OTA 卡在 9x% 后失败”。</dd><dt>修改建议</dt><dd>看门狗改为按阶段续期(中继任务每阶段/每 N 包喂狗),或将阈值设为各阶段上限之和 + 裕量;统一由中继任务上报进度时间戳。</dd></dl></div><div class="finding" id="S-05"><h4><span class="tag sev-s">严重</span> S-05 · WiFi 运行期断线重试 5 次后永不重连,也不回退 AP <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/wifi_ota.cpp:94-100, 180-192, 751-777</code></dd>
<dt>现象</dt><dd>重连任务达到 WIFI_STA_MAX_RETRY 后 setStaRetryExhausted(true);该标志只在 STA_START/CONNECTED/初始化时复位(156/168/291 行),断线事件在 exhausted 时不再唤醒重连;AP 回退只在开机流程中执行。</dd><dt>风险</dt><dd>路由器重启或短暂断电超过几次重试窗口后,设备永久失联,只能现场断电;HTTP/OTA/CAN 控制全部不可用。</dd><dt>修改建议</dt><dd>耗尽后改为长周期退避(如 30 s~5 min)持续重连,或运行期回退 AP+STA 并行;同时把耗尽状态纳入 /system/status 与健康监控。</dd></dl></div><div class="finding" id="S-06"><h4><span class="tag sev-s">严重</span> S-06 · GD32 握手 nonce 可预测且时间戳明文入日志 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/relay/ota_relay.cpp:1027-1035;main/transport/protocol.cpp:90-131</code></dd>
<dt>现象</dt><dd>nonce_server = scrambleGenerate(xTaskGetTickCount(), &amp;m_nonceServer, 0x12345678);种子为常量、地址固定,按字节映射使同一次开机内 nonce 第 4~15 字节恒定,仅 4 字节 tick 变化;tick 以 DEBUG_INFO 明文打印(并经 UDP 广播到局域网)。</dd><dt>风险</dt><dd>会话密钥 K_session = AES(MASTER_KEY, nonce) 的新鲜性只剩 32 bit 且可从日志获知,配合硬编码 MASTER_KEY(S-08)可离线复现会话密钥、重放或伪造 OTA 数据。</dd><dt>修改建议</dt><dd>16 字节全部取自 esp_fill_random()(确保 RF 已开或 bootloader_random_enable);删除 timestamp 日志;scrambleGenerate 仅保留为兼容路径。</dd></dl></div><div class="finding" id="S-07"><h4><span class="tag sev-s">严重</span> S-07 · GET /can/mode 查询失败时会把 CAN 接收模式写回 ALL_PASS <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:1020-1030</code></dd>
<dt>现象</dt><dd>0xC1 查询失败的回退路径调用 canFdSetRxMode(CAN_RX_MODE_ALL_PASS, &amp;actual, 2000) 作为“查询”,这是一次写操作。</dd><dt>风险</dt><dd>前端轮询 GET 一次失败即静默把用户设定的 FILTER / CAN0→1/2 BRIDGE 模式改回全收,电机总线桥接中断,且无日志可见。GET 有副作用也违背 HTTP 语义。</dd><dt>修改建议</dt><dd>增加只读查询命令(或复用 0xC1 重试),失败时返回 {ok:false} 与缓存值,绝不写入。</dd></dl></div><div class="finding" id="S-08"><h4><span class="tag sev-s">严重</span> S-08 · 凭据与主密钥明文入仓,AP 弱口令 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:31-32, 39-40, 161-164;main/server/wifi_ota.cpp:264</code></dd>
<dt>现象</dt><dd>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 历史。</dd><dt>风险</dt><dd>仓库任何读者可接入现场 WiFi 与设备 AP;MASTER_KEY 公开即 GD32 挑战-应答与逐包加密全部失效,可向 GD32 注入任意固件。</dd><dt>修改建议</dt><dd>迁移到 NVS(出厂配置写入)或 Kconfig + 不入库的 sdkconfig;轮换已泄露的 WiFi 口令;每设备派生密钥(MASTER_KEY ⊕ UID KDF);CI 加 gitleaks 扫描。</dd></dl></div><div class="finding" id="S-09"><h4><span class="tag sev-s">严重</span> S-09 · BLE GATT 无配对/加密,范围内任意设备可 OTA 或重启 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/ble_ota_host.cpp:366-376</code></dd>
<dt>现象</dt><dd>CTRL/DATA_IN 特征仅 BLE_GATT_CHR_F_WRITE / WRITE_NO_RSP,未要求 WRITE_ENC/AUTHEN,未配置 SM。</dd><dt>风险</dt><dd>10~30 m 内任意手机可发 0xFA 重启、0xFB 中止、上传固件触发 GD32 中继。</dd><dt>修改建议</dt><dd>启用 LE Secure Connections(Passkey 或 OOB),关键特征加 BLE_GATT_CHR_F_WRITE_ENC | WRITE_AUTHEN;或至少在 CTRL 上加应用层 token。</dd></dl></div><div class="finding" id="S-10"><h4><span class="tag sev-s">严重</span> S-10 · RSSI 更新覆盖过温 SAFE_SHUTDOWN 健康状态 <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/health_monitor.cpp:578-600;main/server/wifi_ota.cpp:853</code></dd>
<dt>现象</dt><dd>温度与 RSSI 共用 m_healthState;updateFromWifiRssi 只要 next != cur 就 setState(SYS_NORMAL/WARNING/CRITICAL),会把温度路径设置的 SYS_SAFE_SHUTDOWN 改写掉。</dd><dt>风险</dt><dd>过温门控被下一次 RSSI 采样清除,高温下仍允许启动 OTA(Flash 擦写 + 射频满负荷),存在硬件损伤与写坏风险。</dd><dt>修改建议</dt><dd>拆分温度/RSSI 子状态,整体状态取两者最严重值;SAFE_SHUTDOWN 只能由温度路径解除。</dd></dl></div><div class="finding" id="S-11"><h4><span class="tag sev-s">严重</span> S-11 · 栈溢出钩子中调用可阻塞的 uart_write_bytes <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/task_stack.cpp:27-40</code></dd>
<dt>现象</dt><dd>vApplicationStackOverflowHook 运行在 vTaskSwitchContext(调度器临界区)内,却调用带互斥锁、可能阻塞的 uart_write_bytes。</dd><dt>风险</dt><dd>溢出发生时可能在钩子内死锁/二次异常,丢失现场信息,甚至无法走到 esp_system_abort。</dd><dt>修改建议</dt><dd>改用 esp_rom_printf / ets_printf(ROM 直写),或只置标志后立即 abort,由 panic handler 输出任务名。</dd></dl></div><div class="finding" id="S-12"><h4><span class="tag sev-s">严重</span> S-12 · /spi/send 的 timeout_ms 无上限,可长期占死 SPI worker <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:1715;main/transport/spi_master.cpp:646</code></dd>
<dt>现象</dt><dd>timeout_ms 由 double 直接强转 uint32;submitJob 内再 ×3+10 s,大数值会回绕或得到极长等待。</dd><dt>风险</dt><dd>一次请求即可让 httpd(单线程)与唯一的 spi_worker 长时间阻塞,CAN 反馈、心跳、OTA 全部停摆。</dd><dt>修改建议</dt><dd>限制 timeout_ms ∈ [10, 5000],超范围 400;submitJob 内做饱和运算。</dd></dl></div><h3>一般(17)</h3><div class="finding" id="G-01"><h4><span class="tag sev-g">一般</span> G-01 · 完成回调不校验状态与 epoch,可能覆盖新会话 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/ota/ota_coordinator.cpp:372-392, 568-576</code></dd>
<dt>现象</dt><dd>notifyRelayComplete / notifyEsp32FinalizeComplete 直接设置 COMPLETE/ERROR,不检查当前状态和 epoch。</dd><dt>风险</dt><dd>看门狗中止后迟到的完成回调会把新开始的 GD32_STORING 会话改成 COMPLETE/ERROR。</dd><dt>修改建议</dt><dd>回调携带启动时的 epoch,不一致即丢弃并记日志。</dd></dl></div><div class="finding" id="G-02"><h4><span class="tag sev-g">一般</span> G-02 · 协调器锁超时 100 ms,lock 失败直接跳过状态切换 <span class="note">[并发]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:124;main/ota/ota_coordinator.cpp</code></dd>
<dt>现象</dt><dd>持锁期间存在远端 CANCEL(≤1.5 s)、NVS 擦写、esp_ota_begin 等长操作;其他调用方 100 ms 拿不到锁即放弃,关键通知(中继完成、恢复 CAN 反馈)被丢。</dd><dt>风险</dt><dd>状态卡住直至 150 s 看门狗中止,前端表现为“完成后长时间不恢复”。</dd><dt>修改建议</dt><dd>长操作移出临界区;关键通知改为投递队列由协调器任务串行处理。</dd></dl></div><div class="finding" id="G-03"><h4><span class="tag sev-g">一般</span> G-03 · 确认 APP 存活前即清除 meta;失败路径连发 3 次 CANCEL <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/relay/ota_relay.cpp:1554, 1573-1579, 1637-1643</code></dd>
<dt>现象</dt><dd>doFinishAndVerify 在 doWaitForAppAlive 成功前清除 gd32 meta;失败时 3 处各发一次 CANCEL(每次最长 1.5 s)。</dd><dt>风险</dt><dd>GD32 新 APP 起不来时无法用已缓存固件重试中继,必须重新上传;失败收尾最长多耗 4.5 s。</dd><dt>修改建议</dt><dd>APP 存活确认后再清 meta;CANCEL 收敛到单一出口。</dd></dl></div><div class="finding" id="G-04"><h4><span class="tag sev-g">一般</span> G-04 · 校验缺口:先置 STORED 后算 SHA;ESP32 finalize 可不带 SHA、不比对实收字节 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/storage/gd32_flash_store.cpp(finishGd32StorageAfterReceive 275-279 附近);main/ota/esp32_ota_core.cpp finalize</code></dd>
<dt>现象</dt><dd>SHA 计算失败时状态仍为 STORED、checksum 全零(中继时会再算一次兜底);/ota/update 请求体缺少 sha256 或 ≥256B 时以 nullptr 跳过比对,且不检查 m_received == 声明大小。</dd><dt>风险</dt><dd>截断上传可能被当作完整固件写入启动分区(依赖 esp_ota_end 的镜像校验兜底)。</dd><dt>修改建议</dt><dd>SHA 成功后再置 STORED;finalize 强制要求 SHA 并校验字节数;请求体超长时排空并 400。</dd></dl></div><div class="finding" id="G-05"><h4><span class="tag sev-g">一般</span> G-05 · OTA 对齐缓冲正常完成后不释放(常驻约 4 KB) <span class="note">[内存]</span></h4>
<dl><dt>位置</dt><dd><code>main/ota/ota_coordinator.cpp:203, 625-635;main/storage/gd32_flash_store.cpp:162-179</code></dd>
<dt>现象</dt><dd>acquireAlignBufForOta() 在 beginGd32Storage 分配;releaseAlignBufForOta() 只在中止路径调用。</dd><dt>风险</dt><dd>成功完成一次 GD32 上传后约 4 KB 内部 DRAM 常驻,与 0.5.0 “上传缓冲 RAII 省 4 KB” 的目标相抵。</dd><dt>修改建议</dt><dd>在 completeGd32Storage / 中继完成路径同样释放,或用 RAII 绑定会话生命周期。</dd></dl></div><div class="finding" id="G-06"><h4><span class="tag sev-g">一般</span> G-06 · 电机 OTA 与 GD32 OTA 共用分区和 meta,互斥不完整 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:2622-2642, 2697-2700, 2748-2776</code></dd>
<dt>现象</dt><dd>/motor/ota 只检查“当前空闲”,不占用协调器;/motor/ota/relay 不区分 meta 来源也不检查协调器状态;注释“relay 复核 SHA”与“relay 不做 SHA 校验”自相矛盾。</dd><dt>风险</dt><dd>与 BLE 的 GD32 存储并发写同一分区;可能把 GD32 镜像中继给电机节点。</dd><dt>修改建议</dt><dd>电机 OTA 纳入 OtaCoordinator 状态机(新增 MOTOR_STORING/RELAYING),meta 增加镜像类型字段并在中继前校验。</dd></dl></div><div class="finding" id="G-07"><h4><span class="tag sev-g">一般</span> G-07 · JSON 截断输出非法 JSON;motor_state 栈占用约 2.5 KB <span class="note">[内存]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:2383, 2493;main/server/http_util.h</code></dd>
<dt>现象</dt><dd>http_json_append 截断时钳制在 cap-1,调用方 len&gt;0 判断失效;/motor/state JSON 每条约 290 B,循环仅留 128 B 余量,超过约 10 个电机必然截断;栈上 motor_state_table_t 约 2.5 KB(httpd 栈 7 KB)。</dd><dt>风险</dt><dd>前端解析失败;httpd 栈余量被压缩。</dd><dt>修改建议</dt><dd>改 chunked 逐条输出;大结构改静态或堆分配。</dd></dl></div><div class="finding" id="G-08"><h4><span class="tag sev-g">一般</span> G-08 · 输入校验不足与未定义行为 <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:323, 1056-1061, 1256, 1340-1341, 1521</code></dd>
<dt>现象</dt><dd>double→uint32/uint8 强转无范围检查;readInt 有符号溢出;http_hex_to_bytes 不校验非 hex 字符;POST /can/mode 非法 mode 静默当 ALL_PASS;send_batch 最多 128 帧×80 ms 阻塞 httpd 约 10 s。</dd><dt>风险</dt><dd>异常输入导致错误下发或 httpd 长时间无响应。</dd><dt>修改建议</dt><dd>统一参数解析 helper(范围、类型、hex 校验),非法即 400;批量发送移到后台任务。</dd></dl></div><div class="finding" id="G-09"><h4><span class="tag sev-g">一般</span> G-09 · suspendStack 跨任务写非原子 BLE 连接状态 <span class="note">[并发]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/ble_ota.cpp:276-279</code></dd>
<dt>现象</dt><dd>在 httpd 任务里直接写 m_bleConnected / m_connHandle,与 NimBLE host 任务竞争。</dd><dt>风险</dt><dd>挂起/恢复期间状态错乱,可能在挂起后被断连事件重新开启广播(待实机确认)。</dd><dt>修改建议</dt><dd>通过 ble_npl 事件投递到 host 任务内修改,或改 atomic。</dd></dl></div><div class="finding" id="G-10"><h4><span class="tag sev-g">一般</span> G-10 · SPI 事务超时返回后 DMA 可能仍在使用栈上缓冲 <span class="note">[内存]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:1001-1019</code></dd>
<dt>现象</dt><dd>spi_device_get_trans_result 重试 1 s 仍超时即返回,栈上的 transaction 与 staging 缓冲可能仍被 DMA 访问。</dd><dt>风险</dt><dd>极端情况下栈数据被改写(use-after-return),表现为随机崩溃。</dd><dt>修改建议</dt><dd>超时后复位 SPI 总线/设备再返回,或改为静态/成员缓冲。</dd></dl></div><div class="finding" id="G-11"><h4><span class="tag sev-g">一般</span> G-11 · tick 比较非回绕安全 <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:883-889, 1443-1445, 1560-1572, 2058-2061, 2156-2163, 2544-2556</code></dd>
<dt>现象</dt><dd>多处 now &gt;= deadline / tick &lt;= deadline 直接比较。</dd><dt>风险</dt><dd>运行约 49.7 天(1 kHz)回绕时,个别等待可能变成极长阻塞。</dd><dt>修改建议</dt><dd>统一改用 xTaskCheckForTimeOut 或 (int32_t)(now - deadline) &gt;= 0。</dd></dl></div><div class="finding" id="G-12"><h4><span class="tag sev-g">一般</span> G-12 · SPI 主站边界缺陷(resp_len 越读、out_init_status 未回填、HELLO 失败后会话不一致) <span class="note">[协议]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:175, 622-627, 1692-1697, 2005-2042</code></dd>
<dt>现象</dt><dd>copyFoundFrame 写入未截断长度,0x82 统计按其读取 resp_payload[0..5];submitJob 不回填 out_init_status(当前调用方传 nullptr,潜在);ENTER_BOOT 内 HELLO 重同步失败后 m_v2Active=false 且持有 GD32 未知的新 session。</dd><dt>风险</dt><dd>小缓冲调用方越界读;跨任务查询拿不到结果;失败后一段时间内会话校验失效。</dd><dt>修改建议</dt><dd>长度统一截断到 resp_max;补齐输出字段回填;HELLO 失败恢复旧 session 或强制下一帧 sess0。</dd></dl></div><div class="finding" id="G-13"><h4><span class="tag sev-g">一般</span> G-13 · CI 必然失败且不构建固件 <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>.github/workflows/spi-link-selftest.yml</code></dd>
<dt>现象</dt><dd>.github/workflows/spi-link-selftest.yml 检查 ../spi_link 不存在即 exit 1,而没有任何步骤提供该目录;workflow 也不执行 idf.py build。另外 Gitea 上是否启用 Actions 未知。</dd><dt>风险</dt><dd>门禁形同虚设,S-01 一类构建问题无法被发现。</dd><dt>修改建议</dt><dd>把 spi_link 独立成仓并以 submodule 引入(同时解决 components/transport 的 ../../../spi_link 仓外依赖),CI 增加 espressif/idf:v6.0 容器构建。</dd></dl></div><div class="finding" id="G-14"><h4><span class="tag sev-g">一般</span> G-14 · 任务数 &gt; 20 时 CPU 统计冻结并每秒刷 WARN <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/cpu_stats.cpp:106-117</code></dd>
<dt>现象</dt><dd>uxTaskGetSystemState 在数组不足时返回 0 而非截断,快照冻结;常驻约 19 个任务,加按需任务即超限。</dd><dt>风险</dt><dd>/system/status 的 CPU 数据失真,日志被刷屏。</dd><dt>修改建议</dt><dd>数组按 uxTaskGetNumberOfTasks()+裕量动态分配,WARN 节流。</dd></dl></div><div class="finding" id="G-15"><h4><span class="tag sev-g">一般</span> G-15 · UDP 日志不可关闭、目标硬编码,fd 关闭存在竞态 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/debug_log.cpp:689-711;main/include/app_config.h:96</code></dd>
<dt>现象</dt><dd>有 IP 即向 192.168.31.3:6666 发送全部日志(含 S-06 的 nonce 时间戳);一个任务读取 socket fd 后另一任务可能将其关闭,fd 被 httpd 复用时日志会写进 HTTP 连接。</dd><dt>风险</dt><dd>信息泄露;极端情况下污染 HTTP 响应流。</dd><dt>修改建议</dt><dd>Kconfig 开关默认关闭;fd 访问加锁或改单写者任务。</dd></dl></div><div class="finding" id="G-16"><h4><span class="tag sev-g">一般</span> G-16 · 日志环满时选择性覆盖导致 /logs 时间序错乱 <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/debug_log.cpp:341-352</code></dd>
<dt>现象</dt><dd>满环时跳过 ERR/WARN、覆盖 INFO/DBG 槽位。</dd><dt>风险</dt><dd>排查事故时日志顺序失真。</dd><dt>修改建议</dt><dd>分级双环,或保留序号字段由前端排序。</dd></dl></div><div class="finding" id="G-17"><h4><span class="tag sev-g">一般</span> G-17 · OTA_MAX_FIRMWARE_SIZE 注释数值错误 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:77</code></dd>
<dt>现象</dt><dd>216*1024 = 221184 = 0x36000,与 GD32 APP_MAX_SIZE 实际对齐;注释却写 “0x35F90”(=221072)。0627 P0-3 对 0x36000 的换算(221296)同样有误。</dd><dt>风险</dt><dd>维护者据注释误判上限,引发两侧不一致。</dd><dt>修改建议</dt><dd>改为 0x36000 字面量 + static_assert,注释删除错误换算。</dd></dl></div><h3>建议(7)</h3><div class="finding" id="A-01"><h4><span class="tag sev-a">建议</span> A-01 · 仓库卫生:gitignore 行内注释失效、被忽略目录仍被跟踪、本机路径入库 <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>.gitignore:25,31,89-91;.trae/;.zcode/;.vscode/settings.json</code></dd>
<dt>现象</dt><dd>行内 # 注释使 *.exe、flasher_args.json、*.workspace、*.suo 规则失效;.trae/(5)、.zcode/(11) 过程文件已在 gitignore 中却仍被跟踪;.vscode 含本机 Windows 路径与 COM15(且写成 Robot_v2\DFU)。</dd><dt>风险</dt><dd>噪声与泄露本机信息。</dd><dt>修改建议</dt><dd>注释另起一行;git rm --cached 过程目录;.vscode 改模板。</dd></dl></div><div class="finding" id="A-02"><h4><span class="tag sev-a">建议</span> A-02 · 版本信息与文档漂移 <span class="note">[文档]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/version.h:18-21;README.md</code></dd>
<dt>现象</dt><dd>GIT_HASH=&quot;770ae14&quot; 而 HEAD 为 af1f682(落后 12 个提交);FW_VERSION_STRING 后挂超长变更注释;README/白皮书顶部写“git 770ae14”;README 任务表名称与代码不符(ble_data_worker→ble_ota_worker、motor_feed→ota_feed),缺 httpd/ota_cancel 等任务;引用的 .cache/ 前端快照不在仓库。</dd><dt>风险</dt><dd>现场版本追溯困难。</dd><dt>修改建议</dt><dd>构建时用 git describe 生成 GIT_HASH;变更记录移到 CHANGELOG.md。</dd></dl></div><div class="finding" id="A-03"><h4><span class="tag sev-a">建议</span> A-03 · 提交信息与内容不符,历史评审被静默删除 <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>git 历史 af1f682</code></dd>
<dt>现象</dt><dd>af1f682 标为 “docs(readme): 更新文档说明”,实际删除 docs/esp32_architecture_review_2026-06-27.md 等 4 份文档;更早 5b2c5f8 删除了 2026-06-13 评审与 2026-05-05 通信梳理。</dd><dt>风险</dt><dd>评审结论无从追溯,问题被重复发现。</dd><dt>修改建议</dt><dd>评审记录统一归档到 docs/review/(本次已恢复存档),删除须独立提交并说明。</dd></dl></div><div class="finding" id="A-04"><h4><span class="tag sev-a">建议</span> A-04 · CS 前 esp_rom_delay_us 忙等最长 1 ms <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:946-952</code></dd>
<dt>现象</dt><dd>单核芯片上 prio 8 的 spi_worker 每帧前忙等,期间低优先级任务饥饿。</dd><dt>风险</dt><dd>CPU 利用率与 BLE/WiFi 吞吐受影响。</dd><dt>修改建议</dt><dd>改为基于 esp_timer 的剩余时间判断,&lt; 100 µs 才忙等。</dd></dl></div><div class="finding" id="A-05"><h4><span class="tag sev-a">建议</span> A-05 · 间接优先级倒置 <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/core/main.cpp;README 任务表</code></dd>
<dt>现象</dt><dd>ota_relay(23)、health_mon(21) 通过 spi_worker(8) 完成 SPI 事务,等待期间被 9~20 优先级任务(ble_ota_worker 12、lwIP 18 等)抢占。</dd><dt>风险</dt><dd>高优先级任务的时延不可预期。</dd><dt>修改建议</dt><dd>Job 提交时临时提升 worker 优先级(优先级继承)或让 worker 位于调用方之上。</dd></dl></div><div class="finding" id="A-06"><h4><span class="tag sev-a">建议</span> A-06 · 死代码与量程不符 <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/health_monitor.cpp:389-396, 365;main/monitor/task_stack.cpp:30-39</code></dd>
<dt>现象</dt><dd>g_stackOverflowDetected 置位后钩子立即 abort,health_mon 永远看不到;温度传感器量程配置为 20~80 °C,而 TEMP_CRITICAL=100 °C 超出量程。</dd><dt>风险</dt><dd>告警逻辑失效。</dd><dt>修改建议</dt><dd>删除死分支或改为 RTC_NOINIT 变量跨重启上报;量程改 -10~110。</dd></dl></div><div class="finding" id="A-07"><h4><span class="tag sev-a">建议</span> A-07 · 日志格式串与参数不匹配、注释过期 <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:924-925;main/server/wifi_ota.cpp:403</code></dd>
<dt>现象</dt><dd>格式串 3 个说明符却传 5 个参数;路由注册注释写 35 次,实际 22 路由+21 OPTIONS=43 次。</dd><dt>风险</dt><dd>日志误导。</dd><dt>修改建议</dt><dd>开启 -Wformat 并修正注释。</dd></dl></div>
<h2 id="s5">5. 既有评审结论跟踪</h2>
<div class="card"><p>来源:<b>0816</b>=project_introduction.html §19 安全审计(2026-08-16);<b>0613</b>=<a href="archive/CODE_REVIEW_REPORT_2026-06-13.html">CODE_REVIEW_REPORT_2026-06-13.html</a>(v0.3.12);<b>0627</b>=<a href="archive/esp32_architecture_review_2026-06-27.md">esp32_architecture_review_2026-06-27.md</a>;<b>0505</b>=<a href="archive/COMMUNICATION_AUDIT_2026-05-05.md">COMMUNICATION_AUDIT_2026-05-05.md</a>。</p>
<p>统计:已修复 23 · 部分修复 5 · 仍存在/结论不成立 12 · 未逐条核对 2。总体看,历史上的并发、函数拆分、资源清理类问题大多已整改;<b>安全类结论(认证、凭据、签名、ECB、nonce)一条都没有关闭</b>。</p></div>
<table><tr><th>来源</th><th>编号</th><th>原结论</th><th>当前状态</th><th>当前代码证据</th></tr><tr><td>0816</td><td>①</td><td>HTTP 22 端点零认证</td><td><span class='tag st-bad'>仍存在</span></td><td>http_handlers.cpp:77-83 CORS *,无鉴权;见 F-01</td></tr><tr><td>0816</td><td>②</td><td>WiFi 凭据与 MASTER_KEY 明文入仓</td><td><span class='tag st-bad'>仍存在</span></td><td>app_config.h:31-40,161-164;见 S-08</td></tr><tr><td>0816</td><td>③</td><td>无固件签名</td><td><span class='tag st-bad'>仍存在</span></td><td>仅 SHA-256 完整性;未启用 Secure Boot / 签名校验</td></tr><tr><td>0816</td><td>④</td><td>AES-ECB 确定性模式</td><td><span class='tag st-bad'>仍存在</span></td><td>ota_relay.cpp 609-620, 836-855 esp_aes_crypt_ecb</td></tr><tr><td>0816</td><td>缓解</td><td>nonce 每会话随机绑定防重放</td><td><span class='tag st-bad'>结论不成立</span></td><td>nonce 仅 tick 4 字节变化且明文入日志;见 S-06</td></tr><tr><td>0613</td><td>#1</td><td>SPI 总线无并发保护</td><td><span class='tag st-ok'>已修复</span></td><td>spi_master.cpp:588-821 单 worker + Job 池 + m_mutex</td></tr><tr><td>0613</td><td>#2</td><td>硬编码占位密钥</td><td><span class='tag st-bad'>仍存在</span></td><td>同 0816 ②</td></tr><tr><td>0613</td><td>#3</td><td>Flash 写入无对齐检查</td><td><span class='tag st-ok'>已修复</span></td><td>gd32_flash_store.cpp:199-276 flashWriteChunk 头尾 pad read-modify-write</td></tr><tr><td>0613</td><td>#4</td><td>BLE/WiFi 双通道 ESP32 OTA 冲突</td><td><span class='tag st-ok'>已修复</span></td><td>Esp32OtaCore s_occupied + OtaCoordinator 单例互斥</td></tr><tr><td>0613</td><td>#5</td><td>SPI 帧 XOR 伪校验</td><td><span class='tag st-ok'>已修复(迁移)</span></td><td>otaCrc8 已移除,Link v2 用 CRC32-MPEG2(实现在仓外 spi_link,未能直接核对)</td></tr><tr><td>0613</td><td>#6</td><td>0xA5 常量碰撞</td><td><span class='tag st-ok'>已修复</span></td><td>spi_master.h:33 SPI_APP_ENTER_BOOT_ACK_OK=0xB5</td></tr><tr><td>0613</td><td>#7</td><td>OTA 会话无全局超时</td><td><span class='tag st-part'>部分修复</span></td><td>传输阶段 120 s 预算 + 150 s 看门狗,但看门狗短于最坏耗时且无法停止中继任务;见 S-03/S-04</td></tr><tr><td>0613</td><td>#8</td><td>CORS 允许所有来源</td><td><span class='tag st-bad'>仍存在</span></td><td>http_handlers.cpp:79</td></tr><tr><td>0613</td><td>#11</td><td>OtaRelay 静态状态无锁</td><td><span class='tag st-part'>部分修复</span></td><td>成员已 private + StateLockGuard,reset() 仍可被外部任务调用;见 S-03</td></tr><tr><td>0613</td><td>#16</td><td>nonce 仅用 esp_random 一次/可预测</td><td><span class='tag st-bad'>仍存在(形式变化)</span></td><td>现为 tick+固定地址+常量种子;见 S-06</td></tr><tr><td>0613</td><td>#17</td><td>ota_meta checksum 从未填充</td><td><span class='tag st-ok'>已修复</span></td><td>metaFinalizeChecksum + metaVerifyChecksum(gd32_flash_store.cpp:555-620)</td></tr><tr><td>0613</td><td>#18</td><td>ESP32 OTA 无回滚验证</td><td><span class='tag st-part'>部分修复</span></td><td>代码调用 mark_app_valid,但调用过早且回滚开关所在配置未生效;见 S-01/S-02</td></tr><tr><td>0613</td><td>#19</td><td>SPI 忽略 timeout</td><td><span class='tag st-ok'>已修复</span></td><td>spi_master.cpp:981-1019 有界等待(残留 G-10)</td></tr><tr><td>0613</td><td>#20</td><td>esp_ota_write 失败未 abort</td><td><span class='tag st-ok'>已修复</span></td><td>esp32_ota_core finalizeCleanupAndReturn/beginCleanupAndReturn</td></tr><tr><td>0613</td><td>#22</td><td>SPI 无就绪信号</td><td><span class='tag st-ok'>已修复</span></td><td>DATA_READY GPIO10 ISR + credit 流控</td></tr><tr><td>0627</td><td>P0-1</td><td>metaVerifyChecksum 全零跳过校验</td><td><span class='tag st-ok'>已修复</span></td><td>gd32_flash_store.cpp:563-569 返回 ESP_ERR_INVALID_STATE</td></tr><tr><td>0627</td><td>P0-2</td><td>RSSI CRITICAL→NORMAL 无法降级</td><td><span class='tag st-ok'>已修复(引入新问题)</span></td><td>health_monitor.cpp:596-598 可直接恢复 NORMAL,但会覆盖过温状态;见 S-10</td></tr><tr><td>0627</td><td>P0-3</td><td>OTA_MAX_FIRMWARE_SIZE 注释不一致</td><td><span class='tag st-bad'>仍存在(原结论换算亦有误)</span></td><td>app_config.h:77;216 KiB 恰为 0x36000,注释 0x35F90 错误;见 G-17</td></tr><tr><td>0627</td><td>P0-4</td><td>凭证硬编码入仓库</td><td><span class='tag st-bad'>仍存在</span></td><td>同 0816 ②</td></tr><tr><td>0627</td><td>P0-5</td><td>BleEsp32FinishCtx 三处 free 易漏</td><td><span class='tag st-ok'>已修复</span></td><td>ble_ota_host.cpp:183-193 由任务负责 free,失败路径统一释放</td></tr><tr><td>0627</td><td>P0-7</td><td>doRelayPackets 180 行 + goto</td><td><span class='tag st-ok'>已修复</span></td><td>拆出 sendRelayPacketWithRetry,阈值命名常量</td></tr><tr><td>0627</td><td>P0-8</td><td>ble_ota_process_data_batch 245 行</td><td><span class='tag st-ok'>已修复</span></td><td>已拆为 process_esp32_packet / process_gd32_packet(ble_ota_data.cpp:878, 1001, 1167)</td></tr><tr><td>0627</td><td>P1-A</td><td>OtaRelay 状态成员封装破坏</td><td><span class='tag st-part'>部分修复</span></td><td>已 private;锁内外混合访问仍在</td></tr><tr><td>0627</td><td>P1-9/P1-10</td><td>getSnapshot 双重加锁 / 多次取状态</td><td><span class='tag st-ok'>已修复</span></td><td>stateToString + isIdleState,快照持锁一次取值</td></tr><tr><td>0627</td><td>P1-11/P1-12</td><td>esp32_ota_core 清理序列重复</td><td><span class='tag st-ok'>已修复</span></td><td>finalizeCleanupAndReturn / beginCleanupAndReturn</td></tr><tr><td>0627</td><td>P1-13/P1-14</td><td>doFinishAndVerify 双职责 / APP 探测重复</td><td><span class='tag st-ok'>已修复</span></td><td>doSendFinishOta + doWaitForAppAlive;probeAndCacheAppAlive</td></tr><tr><td>0627</td><td>P1-17</td><td>metaUpdate 读改写非原子</td><td><span class='tag st-ok'>已修复</span></td><td>gd32_flash_store.cpp:432-448 全程持 m_nvsMutex</td></tr><tr><td>0627</td><td>P1-18</td><td>metaLoad nvs_open 失败当 OK</td><td><span class='tag st-ok'>已修复</span></td><td>gd32_flash_store.cpp:345-355 区分 NOT_FOUND</td></tr><tr><td>0627</td><td>P1-22</td><td>s_ota_recv_buf 全局共享</td><td><span class='tag st-ok'>已修复</span></td><td>0.5.0 改为 OTA 会话内 RAII malloc</td></tr><tr><td>0627</td><td>P1-23</td><td>一次性任务创建失败上报不一致</td><td><span class='tag st-ok'>已修复</span></td><td>ble_ota_host.cpp:130-135, 189-194, 212-215 均 sendFinishEvent</td></tr><tr><td>0627</td><td>P1-24</td><td>reorder slot malloc 失败静默丢弃</td><td><span class='tag st-ok'>已修复</span></td><td>ble_ota_data.cpp:466-474 发窗口 ACK 并返回 ESP_ERR_NO_MEM</td></tr><tr><td>0627</td><td>P1-25</td><td>protocol.h 死代码</td><td><span class='tag st-na'>未逐条核对</span></td><td>—</td></tr><tr><td>0627</td><td>P2-88</td><td>notifyRelayStarted/Complete 纯转发</td><td><span class='tag st-bad'>仍存在</span></td><td>ota_coordinator.cpp</td></tr><tr><td>0627</td><td>P2-89</td><td>两个通知函数几乎相同</td><td><span class='tag st-part'>部分修复</span></td><td>已共用 helper</td></tr><tr><td>0627</td><td>P2-92</td><td>0xF0F0F0F0 魔法数字</td><td><span class='tag st-ok'>已修复</span></td><td>kLastPacketAckSeq</td></tr><tr><td>0627</td><td>其余 P2</td><td>样板抽取/配置化/风格类约 50 项</td><td><span class='tag st-na'>未逐条核对</span></td><td>低风险风格项,本次未逐条复核,建议下轮评审处理</td></tr><tr><td>0505</td><td>UDP</td><td>UDP 日志可选、建议可关闭</td><td><span class='tag st-bad'>仍存在</span></td><td>无开关,默认开启并硬编码目标;见 G-15</td></tr></table>
<h2 id="s6">6. 优先级修复路线图</h2>
<table><tr><th>阶段</th><th>目标</th><th>条目</th><th>预估</th></tr>
<tr><td><span class="tag sev-f">P0 · 本周</span></td><td>堵住远程控制面,恢复可复现构建</td><td>F-01(token + CORS 白名单 + /spi/send 白名单/默认关闭)、S-12、S-01(合并 sdkconfig.defaults + HZ static_assert)、S-08(轮换 WiFi 口令,凭据迁 NVS)</td><td>2~3 人日</td></tr>
<tr><td><span class="tag sev-s">P1 · 2 周内</span></td><td>消除变砖与失联路径</td><td>S-02(mark_app_valid 后移)、S-03 + S-04(中继取消标志与分阶段看门狗)、S-05(断线长周期重连)、S-07、S-10、S-11、G-01、G-02</td><td>4~6 人日,需 GD32 全链路 OTA 回归</td></tr>
<tr><td><span class="tag sev-g">P2 · 1 个月</span></td><td>加固密码学与无线面</td><td>S-06(esp_fill_random nonce)、S-09(BLE LESC + 加密特征)、0816 ③④(签名 / AES-CTR 或 GCM,需三工程同步)、G-06、G-15</td><td>5~8 人日(含 GD32 侧)</td></tr>
<tr><td><span class="tag sev-a">P3 · 持续</span></td><td>工程卫生</td><td>G-13(spi_link submodule + 固件构建 CI)、G-03~G-05、G-07~G-12、G-14、G-16、G-17、A-01~A-07</td><td>随迭代</td></tr></table>
<h2 id="s7">7. 总结评分</h2>
<table><tr><th>维度</th><th>评分</th><th>理由</th></tr><tr><td>固件架构与分层</td><td><b>7</b> / 10</td><td>接入/协调/执行/传输分层清晰,协调器单例 + 单 SPI worker 设计合理</td></tr><tr><td>SPI 协议与传输</td><td><b>6</b> / 10</td><td>会话/序号/credit/CRC32 完备,诊断充分;边界与回绕细节待补</td></tr><tr><td>OTA 可靠性</td><td><b>5</b> / 10</td><td>互斥与 epoch 进步明显;中止协作、看门狗预算、回滚时机存在变砖级缺口</td></tr><tr><td>FreeRTOS 与实时性</td><td><b>6</b> / 10</td><td>任务优先级经事故调优;钩子与健康状态逻辑有真实缺陷</td></tr><tr><td>网络与无线安全</td><td><b>2</b> / 10</td><td>零认证 + CORS * + 原始 SPI 透传 + 硬编码密钥 + BLE 无加密</td></tr><tr><td>代码质量与可维护性</td><td><b>6</b> / 10</td><td>历史评审的大函数/重复样板多数已整改;超长文件(http_handlers 2.8k 行)仍需拆分</td></tr><tr><td>构建与仓库卫生</td><td><b>3</b> / 10</td><td>干净克隆不可复现、CI 必败、仓外依赖、过程文件入库</td></tr><tr><td>文档</td><td><b>6</b> / 10</td><td>白皮书详尽,但版本/任务表漂移,历史评审被静默删除</td></tr>
<tr><td><b>综合</b></td><td><b>5.1 / 10</b></td><td>链路与 OTA 工程扎实、诊断能力强,历史评审的结构性问题大多已整改;但“可信局域网”前提下的零认证控制面、不可复现的构建配置、回滚时机与中止协作缺陷,使其距离量产/现场部署仍有明显距离。</td></tr></table>
<div class="card"><b>结论</b>:建议在下一个版本(0.5.3)前完成 P0 全部条目,并把本报告纳入 <code>docs/project_introduction.html</code> §19 的跟踪;后续评审请统一归档到 <code>docs/review/</code>,不要随文档整理删除。</div>
<p class="note">本报告由 Hark 专家评审生成 · 2026-10-19 · 基线 af1f682。行号以该提交为准,后续提交可能漂移。</p>
</div></body></html>