From 83a70721bd0f5f875836d44a171f3c386355f5fa Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Tue, 13 Oct 2026 12:43:56 +0800 Subject: [PATCH] =?UTF-8?q?docs(N34):=20E2/E3/E4=20=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E4=B8=8E=E5=AE=9A=E6=A1=88=20+=20bench=20?= =?UTF-8?q?=E5=AE=9E=E9=AA=8C=E5=8C=BA=E5=A4=8D=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E2:状态回读 18.1us(N=1000x2 逐值一致);LISTEN 可见延迟 100% <=10ms 无长尾(24 轮)-> "每 poll 全量刷新"成立、reconciler 去抖 可减为 1。 E3:GET_CMD_STATUS 对 OPEN/LISTEN/CLOSE 无判别力(残留假阳性), 定案 cmd_done 用状态回读判据;SET 类命令态永不更新。 E4:排渣序列定案=读空 RX -> CLOSE -> 排空事件(quiescent) -> 轮询 byte0=00H;C 变体(不排渣)0/20 且级联自持,Trap 23 残渣 恰等于单 Socket RX 缓冲整容(rxdrain=2048,19/20)。 E2 补充发现(Phase 15 板测):LISTEN 态 CLOSE_SOCKET 二段关闭 (一次只拆监听 byte0 恒 05,再发一次才落 00)——Trap 09 open 重试环的芯片级原因。 bench:ch395f_reuse_bench.c 复活 N30_CHIP_BENCH 门控实验区 (旧 N-30 矩阵 s_bench_legacy=0 停泊),ch395f_reuse_host.py 增 --mode e4。注册(uvprojx/main.c)实验完已还原不入库。 --- docs/网络/N34-自愈审计/实验方案.md | 126 ++++++++ test/board/ch395f_reuse_bench.c | 467 ++++++++++++++++++++++++++++- test/host/ch395f_reuse_host.py | 70 ++++- 3 files changed, 653 insertions(+), 10 deletions(-) diff --git a/docs/网络/N34-自愈审计/实验方案.md b/docs/网络/N34-自愈审计/实验方案.md index 842bed18..8d76bdf4 100644 --- a/docs/网络/N34-自愈审计/实验方案.md +++ b/docs/网络/N34-自愈审计/实验方案.md @@ -562,3 +562,129 @@ PASV 重监听被垂死僵尸 accept 的竞态——且可由网卡抖动**确 **工具与留痕**:`test/host/pasv_probe.py`(新增);pktmon 抓包留存 `%TEMP%\n30_cap.etl`(3.9MB,含网卡抖动窗口,设备 IP 过滤)。 + +### E2 结果(2026-10-13 11:43–11:50,bench 实验固件 Code=213412,跑两遍,台架锁 N34) + +**固件**:复活 `test/board/ch395f_reuse_bench.c`(N30_CHIP_BENCH 门控,内核前裸机; +旧 N-30 定案矩阵由 `s_bench_legacy=0` 停泊保留);注册 uvprojx Test 组 + `Src/main.c` +USER CODE 2 区(注册不入库,实验完 `git restore`)。DWT CYCCNT @168MHz(1 cycle≈5.95ns)。 +两遍独立运行,本节数字两遍一致(第二遍为第一遍 C 变体级联污染后的干净重跑,E2/E3 复验)。 + +**单次回读成本**(`ch395f_get_socket_status`,N=1000×2 遍,两遍逐值相同): + +| 统计 | cycles | µs | +|---|---|---| +| min | 3048 | 18.1 | +| avg | 3057 | 18.1 | +| max | 3481 | 20.7 | + +**LISTEN 可见性矩阵**(裸 TCP_LISTEN 不轮询命令态,发命令后 +0/10/50/100ms 回读; +100ms 仍不可见则 10ms 节奏续查到 1000ms;12 轮×2 遍=24,轮间换端口): + +| 首次观察到 TCP=01H | 计数 | +|---|---| +| +0ms | 14 | +| +10ms | 10 | +| >10ms | **0** | + +**判据结论**: +1. 单次回读 18.1µs ≪ 100µs → **"每 poll 全量刷新"成立**(4 监听 × ~18µs ≈ 72µs ≪ + 1ms 轮询周期;`docs/网络/N34-自愈审计/实施方案.md` §4 第 1 项预估 ~15µs 量级坐实)。 +2. LISTEN 可见延迟有界:100% 在 +10ms 内可见、无长尾 → **reconciler 去抖可减为 1 次** + (决策点后 ≥10ms 单次回读即可裁定 LISTEN 形态;现"连续 2 次"去抖对该形态无必要—— + 瞬态窗口仅存在于 0~10ms 内,且第二次回读必在窗口外)。 + +**E2 补充发现(2026-10-13 12:23,Phase 15 板测 TC1501/1502b 实证):LISTEN 态 +CLOSE_SOCKET 是二段关闭**。CLOSE 命令对 LISTEN 态 Socket 返回 SUCCESS(0x00), +但只拆监听(TCP 01H→00H),**byte0 恒留 05H(Socket 仍 OPEN),2s 内不自落**; +需再发一次 CLOSE(此时 TCP 已 00H)byte0 才落 00H(二段间隔实测 0ms 即落)。 +生产 net 层早已在**无意中吸收**该语义:`net_close_locked` 等 CLOSED 的 500ms 窗 +超时后照常放行,随后重 listen 时 `net_listen_locked` 的 Trap 09 open 重试 +(3 次 × 500ms 沉降)第二次 open 前的 close 恰好充当二段——这就是 Trap 09 +"关闭后需延时才能重开"重试环存在的芯片级原因之一。阶段 2 `sock_reset_poll` +的 WAIT_CLOSE 阶段已内置二段补发(标记=drain 字段 0xF,只补发一次), +TC1502b 实证 LISTEN 态复位 GRACEFUL ~180ms 落定。 +**对阶段 3 的含义**:任何以 `cmd_done(DONE_CLOSE)` 为判据的等待,若目标 Socket +可能处于 LISTEN,必须容忍"一次 CLOSE 不落 00H"——要么走 sock_reset(已内置), +要么保留 Trap 09 式重试,不得把 DONE_CLOSE 当单次 CLOSE 的充分条件。 + +### E3 结果(同场同固件,5 命令 × 5 轮 × 2 遍 = 50 样本,两遍签名逐条一致) + +**方法**:发命令前对照读 GET_CMD_STATUS(记录残留假阳性);发命令后按手册 +`docs/芯片手册/CH395F.md` §9.2.1 节奏(≥2ms 首查)以 ~1ms 轮询至终态(2s 上限)。 +`pre`=对照读值,`first`=首个非 BUSY 终值,`busy`=是否观察到 BUSY→终态转变。 + +| 命令 | pre(对照读) | busy | first | done | 判别力判定 | +|---|---|---|---|---|---| +| OPEN_SOCKET | 0x00(r0) / **0x17**(r1-4) | 否(0/10) | 0x00 | 3ms | **部分**:状态确被 OPEN 更新(0x17→0x00),但 pre=SUCCESS 时完成与残留不可分 | +| TCP_LISTEN | 0x00(残留 SUCCESS) | 否(0/10) | 0x00 | 3ms | **不成立**:无转变,残留 SUCCESS 不可甄别 | +| CLOSE_SOCKET | 0x00(残留 SUCCESS) | 否(0/10) | 0x00 | 3ms | **不成立**:同上 | +| SET_SOUR_PORT | **0x17** | 否 | **0x17(不变)** | — | **不成立**:SET 类根本不更新命令状态 | +| SET_PROTO_TYPE | **0x17** | 否 | **0x17(不变)** | — | **不成立**:同上 | + +**三条实证**: +1. **残留假阳性坐实**:陈旧错误码 0x17(ERR_VAL,源自"对已关闭 Socket 再 close") + 跨后续 SET 命令(不更新状态)持续存活——复核 `docs/问题记录/CH395F_Trap_Records.md` + Trap 25 定案轮的"陈旧残留"形态(RESET_ALL 陈旧 BUSY 变体本矩阵未复现,同类 + 陈旧值持续存活已证)。 +2. **OPEN/LISTEN/CLOSE 均在 <2ms 完成**,BUSY 从未观察到——"BUSY→终态"转变对这三条 + 命令不可观测;驱动 `poll_cmd_status` 20ms 粒度对它们恒白等 ≥20ms(无害但无判别价值)。 +3. **SET 类无判别力**(命令状态完全不被更新)——Trap 记录结论复核成立。 + +**判据结论(定案 `docs/网络/N34-自愈审计/实施方案.md` §4 第 4 项)**:五条命令均不满足 +"发后 ≤N ms 变终态 **且** 无残留假阳性" → **`ch395f_cmd_done` 完成判据改用状态回读**: +open 完成=byte0 变 05H;listen 完成=TCP=01H;close 完成=byte0=00H +(回读成本 18.1µs 由 E2 实测背书;listen 可见延迟 ≤10ms 由 E2 矩阵背书,成本与时效均成立)。 +GET_CMD_STATUS 仅保留两用途:INIT_CH395 类慢命令(内核前/测试路径)与**命令级被拒** +检测(非 SUCCESS 非 BUSY 错误码仍会被芯片写入命令状态,B2 判据仍有效——被拒码是 +"新值覆盖残留"方向,不受残留歧义影响)。`poll_cmd_status` 保留给内核前/测试路径, +产品 socket 路径弃用。 + +### E4 结果(同场同固件 + 生产路径基线;3 变体 ×10 轮 ×2 遍 = 60 轮) + +**方法**:每轮先造污染会话(主机 `test/host/ch395f_reuse_host.py --mode e4`:连上 → +发 3000B → RST,Trap 23 中止形态),芯片侧按变体执行复位序列,**立即**原端口重开 ++ LISTEN,主机立即上传 4096B 图案载荷(图案种子随全局轮次变,上一轮残渣必失配)。 +PASS 口径 = 消费者在 DISCONNECT/TIM_OUT 前实际读到的字节数=4096 且图案逐字节匹配 +(= 生产 net 层释放语义;事后读空仅作取证,不计入 PASS)。 + +| 变体 | 第 1 遍 | 第 2 遍 | 观察 | +|---|---|---|---| +| A 读空 RX→CLOSE→排事件→等 byte0=00H | **10/10** | **10/10** | 19/20 轮 rxdrain=**2048**(残渣恰等于芯片单 socket RX 缓冲整容 = Trap 23 机制直接证据) | +| B CLEAR_RECV_BUF→CLOSE→排事件→等 CLOSED | **10/10** | **10/10** | 与 A 等价,无差异 | +| C 什么都不做→CLOSE→等 CLOSED | **0/10** | **0/10** | 首 C 轮(rg20)两遍同签名,随后**自持级联** | + +**C 变体失败签名(两遍逐字节复现)**:重开后主机载荷连接 SYN 被吞(主机侧 +connect 超时);bench 首次 SINT 读 = **0x04**(陈旧 RECV 位粘滞——Trap 14 +"粘滞到被读",close/open 不清 SINT);后续轮污染连接亦 SYN 被吞(主机 +`contam CONNECT timeout`、bench `no death event`)——每轮 close 又携带未排空数据 +→ **级联自持**,open+listen 命令照常返回 SUCCESS 也无法恢复,唯芯片硬件复位 +(E13 RSTI←NRST)清除。 + +**机制定案**:决定性一步 = **close 前 RX 缓冲必须为空**(读空与 CLEAR_RECV_BUF +等价——Trap 25"缓冲重分配无效"指的是 set_send/recv_buf 重分配,不是 CLEAR)。 +带非空 RX 关闭违反 `docs/芯片手册/CH395F.md` §5.52 bit3=1"读出残留数据后再关闭" +契约 → 芯片拆链停滞 → 下一会话 SYN 被吞/CONNECT 丢失,且自持。排空事件(A/B 有、 +C 无)清的是粘滞 SINT 位——对**事件驱动 accept 的正确性**必需(0x04 陈旧 RECV 会 +误导消费者),但不是 TCP 层吞 SYN 的根因。所有轮 closed_ms=0(close 命令落定后 +byte0 已是 00H)——"等 CLOSED"几乎零成本,但为契约必需步。 + +**生产路径基线**(烧 bench 固件前,Phase 1.5 产品固件 Code=209780): +`MDK-ARM/ftp_abort_probe.py --check-abort --rounds 5` → **5/5**(中止 STOR→450; +设备日志 `sock4 TIMEOUT` → quarantine → `UPLOAD '/ab000N.bin' FAILED` → +followup `UPLOAD '/ab_ok.bin' 4096 bytes OK`)——现实现(隔离期持续排空 RX + +close + 排空事件 + 等 CLOSED)行为基线全绿,与变体 A 序列一致。 + +**判据结论(定案 `docs/网络/N34-自愈审计/实施方案.md` §4 第 3 项)**: +(a) 与现实现等价成立(A 20/20 载荷完整);(b) B 与 A 等价(无差异记录); +C 不等价(0/20 + 级联)→ **`ch395f_sock_reset` 内部序列定案**: + +1. 读空 RX(GET_RECV_LEN→READ_RECV_BUF 直到 0;CLEAR_RECV_BUF 等价可用,**取读空** + ——保 §5.52 契约且排空字节数可观测可留痕); +2. CLOSE_SOCKET; +3. 排空本 socket 事件(drain-until-quiescent,E8 M5 口径:连续两轮无新位); +4. 轮询 byte0=00H(非阻塞状态机步进,约束不变)。 + +GRACEFUL 与 QUARANTINE **都必须排 RX**——E4 假设"差异仅在是否排 RX + 最短等待"中 +"是否排 RX"一半被否(带脏缓冲关闭在任何释放形态下都危险),两模式差异只剩 +最短等待(100ms/400ms,Trap 23 二阶段实测值)。 diff --git a/test/board/ch395f_reuse_bench.c b/test/board/ch395f_reuse_bench.c index 1aa9540c..830b1ac2 100644 --- a/test/board/ch395f_reuse_bench.c +++ b/test/board/ch395f_reuse_bench.c @@ -10,7 +10,16 @@ * 运行方法:在实验分支(如 n30-chip-bench)把本 .c 注册进 uvprojx Test 组、 * Src/main.c 的 USER CODE 2 区在 app_main_init() 之前调 ch395f_reuse_bench() * (内核前裸机循环,永不返回,断电退出),烧录后跑主机脚本对照芯片日志。 - * 当前矩阵为定案轮(对照 vs "丢 CONNECT 捕获自 accept"修复组),见 s_vars[]。 + * 旧 N-30 定案矩阵(对照 vs "丢 CONNECT 捕获自 accept"修复组)保留在 s_vars[]/ + * bench_round(),由 s_bench_legacy=0 停泊不运行。 + * + * 2026-10-13 N34 阶段 2 前置实验(task-9):同一固件合并跑 E2/E3/E4—— + * E2 = GET_SOCKET_STATUS 单次 SPI 成本(DWT 周期计数)+ TCP_LISTEN 可见延迟矩阵; + * E3 = GET_CMD_STATUS 判别力矩阵(OPEN/LISTEN/CLOSE/SET_SOUR_PORT/SET_PROTO_TYPE, + * 含"命令未发时"对照读,复核 Trap 记录的 SET 类无判别力 + 陈旧 BUSY 残留); + * E4 = sock_reset 内部顺序 A/B/C 变体 ×10 轮(轮间换端口)+ 立即复连载荷完整性自证 + * (配套 test/host/ch395f_reuse_host.py --mode e4)。 + * 判据与结果 → docs/网络/N34-自愈审计/实验方案.md E2/E3/E4 条目与实验记录节。 * * close_mode:0=主机先关(芯片等 DISCONNECT);1=芯片先关;2=无数据连上即关 * verify:0=无修复;1=listen 后确认 LISTEN 重试;2=缓冲重分配; @@ -23,6 +32,7 @@ #include "ch395f.h" #include "dbg_log.h" +#include "main.h" #include "stm32f4xx_hal.h" /* 变体表:{sock, close 后复开延迟 ms, TX 字节数, close_mode, verify_listen} */ @@ -89,9 +99,10 @@ static int bench_chip_init(void) { return -1; } ch395f_reset(); - /* 等 PHY link(Trap 09:link up 前 open 必失败) */ + /* 等 PHY link(Trap 09:link up 前 open 必失败;B9 修正:判据是 != DISCONN, + * 旧写法 !=0 对 01H=DISCONN 也成立) */ for (int i = 0; i < 100; i++) { - if (ch395f_get_phy_status() != 0U) { + if (ch395f_get_phy_status() != CH395F_PHY_DISCONN) { break; } HAL_Delay(100); @@ -354,20 +365,460 @@ static void bench_round(unsigned idx) { HAL_Delay(v->delay_ms); } +/* ===================================================================== + * N34 阶段 2 前置实验区(2026-10-13):E2 / E3 / E4 一次固件合并跑 + * ===================================================================== */ + +/* 旧 N-30 定案矩阵停泊开关:0 = 不运行(保留代码备查,勿删) */ +static const int s_bench_legacy = 0; + +/* --- DWT 周期计数器:168 MHz,1 cycle ~= 5.95 ns(不依赖 HAL 时基/中断) --- */ +static void bench_dwt_enable(void) { + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0U; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; +} + +static uint32_t bench_cyc(void) { + return DWT->CYCCNT; +} + +/* cycles -> us(1 位小数,纯整数运算:MicroLIB 不支持 %f) */ +static void bench_log_cyc(const char *tag, uint32_t cyc) { + DBG_INFO("BENCH %s=%lu.%luus (%lucyc)", tag, + (unsigned long)(cyc / 168U), + (unsigned long)(((cyc % 168U) * 10U) / 168U), + (unsigned long)cyc); +} + +/* --- 裸命令下发:绕过驱动 poll_cmd_status(E2/E3 要观察命令自身状态轨迹)。 + * CS 包裹与驱动内部 ch395f_spi_begin/end 完全同构(该二函数为 static 未导出) --- */ +static void bench_spi_begin(void) { + HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_RESET); +} + +static void bench_spi_end(void) { + HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); +} + +static void bench_raw_cmd(uint8_t cmd, uint8_t sock, const uint8_t *args, uint8_t nargs) { + bench_spi_begin(); + (void)ch395f_write_cmd(cmd); + (void)ch395f_write_data(sock); + for (uint8_t i = 0U; i < nargs; i++) { + (void)ch395f_write_data(args[i]); + } + bench_spi_end(); +} + +/* 等 Socket byte0 回 00H(SOCKET_CLOSED);返回等待耗时 ms,-1=超时 */ +static int bench_wait_closed(uint8_t sock, uint32_t timeout_ms) { + uint32_t t0 = HAL_GetTick(); + uint8_t st[2] = { 0, 0 }; + for (;;) { + ch395f_get_socket_status(sock, st); + if (st[0] == CH395F_SOCKET_CLOSED) { + return (int)(HAL_GetTick() - t0); + } + if ((uint32_t)(HAL_GetTick() - t0) >= timeout_ms) { + return -1; + } + HAL_Delay(5); + } +} + +/* 排空本 Socket 事件:全局位图门控 + SINT 读(读即清),连续 2 轮(20ms 节奏)无新位即停; + * 返回排空期间见过的 SINT 位 OR(E8 M5 drain-until-quiescent 的 bench 版) */ +static uint8_t bench_drain_events(uint8_t sock, uint32_t max_ms) { + uint32_t t0 = HAL_GetTick(); + uint8_t seen = 0U; + int quiet = 0; + while ((uint32_t)(HAL_GetTick() - t0) < max_ms) { + uint16_t glob = ch395f_get_glob_int_status_all(); + uint8_t si = 0U; + if ((glob & (1U << (sock + 4U))) != 0U) { + si = ch395f_get_sock_int_status(sock); + } + if (si != 0U) { + seen |= si; + quiet = 0; + } else if (++quiet >= 2) { + break; + } + HAL_Delay(20); + } + return seen; +} + +/* 读空 RX(GET_RECV_LEN -> read_recv_buf 直到长度 0),返回总字节数 */ +static uint32_t bench_drain_rx(uint8_t sock) { + uint32_t total = 0U; + for (;;) { + uint16_t rl = ch395f_get_recv_len(sock); + if (rl == 0U) { + break; + } + uint16_t n = (rl > 2048U) ? 2048U : rl; + ch395f_read_recv_buf(sock, s_rx_buf, n); + total += n; + if (total > 65535U) { + break; + } + } + return total; +} + +/* 让命令落定:>=2ms 节奏轮询 GET_CMD_STATUS 直到非 BUSY;返回终值 */ +static uint8_t bench_cmd_settle(uint32_t max_ms) { + uint32_t t0 = HAL_GetTick(); + uint8_t s; + do { + HAL_Delay(2); + s = ch395f_get_cmd_status(); + } while (s == CH395F_ERR_BUSY && (uint32_t)(HAL_GetTick() - t0) < max_ms); + return s; +} + +/* ------------------------- E2:状态回读成本 + LISTEN 可见延迟 ------------------------- */ +static void bench_e2(void) { + static const uint16_t grid_ms[4] = { 0U, 10U, 50U, 100U }; + uint8_t st[2] = { 0, 0 }; + uint32_t min = 0xFFFFFFFFUL, max = 0U, sum = 0U; + + DBG_INFO("BENCH E2 ===== GET_SOCKET_STATUS cost + LISTEN visibility ====="); + /* 1) 单次回读成本 N=1000(socket 空闲态,与生产 net_poll 刷新同形) */ + for (uint32_t i = 0U; i < 1000U; i++) { + uint32_t c0 = bench_cyc(); + ch395f_get_socket_status(3U, st); + uint32_t d = bench_cyc() - c0; + if (d < min) { + min = d; + } + if (d > max) { + max = d; + } + sum += d; + } + bench_log_cyc("E2 gs_min", min); + bench_log_cyc("E2 gs_avg", sum / 1000U); + bench_log_cyc("E2 gs_max", max); + + /* 2) LISTEN 可见性矩阵:裸 TCP_LISTEN(不 poll 命令态)后 +0/10/50/100ms 回读; + * 100ms 仍不可见则 10ms 节奏续查到 1000ms,记录首次观察到 TCP=01H 的延迟 */ + for (unsigned r = 0U; r < 12U; r++) { + uint16_t port = (uint16_t)(5200U + r); + int grid[4] = { 0, 0, 0, 0 }; + int first_ms = -1; + (void)ch395f_close_socket(3U); + (void)bench_wait_closed(3U, 2000U); + ch395f_set_proto_type(3U, CH395F_PROTO_TYPE_TCP); + ch395f_set_sour_port(3U, port); + if (ch395f_open_socket(3U) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E2 v2 r%u open fail", r); + continue; + } + uint32_t t0 = HAL_GetTick(); + bench_raw_cmd(CH395F_CMD_TCP_LISTEN_SN, 3U, 0, 0U); + for (int g = 0; g < 4; g++) { + while ((uint32_t)(HAL_GetTick() - t0) < grid_ms[g]) { } + ch395f_get_socket_status(3U, st); + if (st[1] == CH395F_TCP_LISTEN) { + grid[g] = 1; + if (first_ms < 0) { + first_ms = (int)(HAL_GetTick() - t0); + } + } + } + while (first_ms < 0 && (uint32_t)(HAL_GetTick() - t0) < 1000U) { + HAL_Delay(10); + ch395f_get_socket_status(3U, st); + if (st[1] == CH395F_TCP_LISTEN) { + first_ms = (int)(HAL_GetTick() - t0); + } + } + DBG_INFO("BENCH E2 listen r%u port%u grid=%d%d%d%d first=%dms st=0x%02X/0x%02X", + r, (unsigned)port, grid[0], grid[1], grid[2], grid[3], + first_ms, st[0], st[1]); + (void)bench_cmd_settle(2000U); + (void)ch395f_close_socket(3U); + (void)bench_wait_closed(3U, 2000U); + } +} + +/* ------------------------- E3:GET_CMD_STATUS 判别力矩阵 ------------------------- */ +static void bench_e3_one(const char *name, uint8_t cmd, uint8_t sock, + uint16_t port0, int pre_open, int pre_listen, + const uint8_t *args, uint8_t nargs) { + for (int r = 0; r < 5; r++) { + uint16_t port = (uint16_t)(port0 + (uint16_t)r); + uint8_t largs[2]; + const uint8_t *pa = args; + uint8_t n = nargs; + (void)ch395f_close_socket(sock); + (void)bench_wait_closed(sock, 2000U); + ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP); + ch395f_set_sour_port(sock, port); + if (pre_open) { + if (ch395f_open_socket(sock) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E3 %s r%d pre-open fail", name, r); + continue; + } + if (pre_listen) { + if (ch395f_tcp_listen(sock) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E3 %s r%d pre-listen fail", name, r); + continue; + } + } + } + /* 对照读:命令未发时的残留状态(Trap 记录:陈旧 BUSY 残留即在此现形) */ + uint8_t pre = ch395f_get_cmd_status(); + if (cmd == CH395F_CMD_SET_SOUR_PORT_SN) { + largs[0] = (uint8_t)(port & 0xFFU); + largs[1] = (uint8_t)(port >> 8); + pa = largs; + n = 2U; + } + uint32_t t0 = HAL_GetTick(); + bench_raw_cmd(cmd, sock, pa, n); + int busy_seen = 0, polls = 0, done_ms = -1; + uint8_t firstv = 0xFFU; + HAL_Delay(2); /* 手册 §9.2.1:发命令后 >=2ms 才发首个 GET_CMD_STATUS */ + while ((uint32_t)(HAL_GetTick() - t0) < 2000U) { + uint8_t s = ch395f_get_cmd_status(); + polls++; + if (s == CH395F_ERR_BUSY) { + busy_seen = 1; + HAL_Delay(1); + continue; + } + firstv = s; + done_ms = (int)(HAL_GetTick() - t0); + break; + } + DBG_INFO("BENCH E3 %s r%d pre=0x%02X busy=%d first=0x%02X done=%dms polls=%d", + name, r, pre, busy_seen, firstv, done_ms, polls); + (void)bench_cmd_settle(1000U); + (void)ch395f_close_socket(sock); + (void)bench_wait_closed(sock, 2000U); + } +} + +static void bench_e3(void) { + static const uint8_t proto_tcp = CH395F_PROTO_TYPE_TCP; + DBG_INFO("BENCH E3 ===== GET_CMD_STATUS discriminative matrix ====="); + bench_e3_one("OPEN", CH395F_CMD_OPEN_SOCKET_SN, 3U, 5300U, 0, 0, 0, 0U); + bench_e3_one("LISTEN", CH395F_CMD_TCP_LISTEN_SN, 3U, 5310U, 1, 0, 0, 0U); + bench_e3_one("CLOSE", CH395F_CMD_CLOSE_SOCKET_SN, 3U, 5320U, 1, 1, 0, 0U); + bench_e3_one("SET_SOUR_PORT", CH395F_CMD_SET_SOUR_PORT_SN, 3U, 5330U, 0, 0, 0, 0U); + bench_e3_one("SET_PROTO_TYPE", CH395F_CMD_SET_PROTO_TYPE_SN, 3U, 5340U, 0, 0, &proto_tcp, 1U); +} + +/* ------------------------- E4:sock_reset 内部顺序 A/B/C ------------------------- + * A:读空 RX -> CLOSE -> 排空本 socket 事件 -> 轮询等 byte0=00H + * B:CLEAR_RECV_BUF -> CLOSE -> 排空事件 -> 等 CLOSED + * C:什么都不做 -> CLOSE -> 等 CLOSED + * 每轮先"污染会话"(主机连上发 3000B 后 RST,Trap 23 形态),复位后立即 + * open+listen,主机立即上传 4096B 图案载荷;芯片侧按图案自证完整性 + * (PASS 口径 = 消费者在 DISCONNECT/TIM_OUT 前实际读到的字节数与图案)。 */ +#define E4_SOCK 3U +#define E4_BASE_PORT 5100U +#define E4_ROUNDS 10U +#define E4_PAYLOAD 4096U +static uint8_t s_e4_acc[8192]; + +static uint8_t e4_seed(unsigned rg) { + return (uint8_t)(rg * 97U + 13U); +} + +/* 污染阶段收尾:等芯片 DISCONNECT/TIM_OUT(RST 中止的死亡事件),1=见到 */ +static int bench_e4_wait_death(uint8_t sock, uint32_t timeout_ms, uint8_t *out_bits) { + uint32_t t0 = HAL_GetTick(); + while ((uint32_t)(HAL_GetTick() - t0) < timeout_ms) { + uint16_t glob = ch395f_get_glob_int_status_all(); + if ((glob & (1U << (sock + 4U))) != 0U) { + uint8_t si = ch395f_get_sock_int_status(sock); + if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) { + if (out_bits != 0) { + *out_bits = si; + } + return 1; + } + } + HAL_Delay(10); + } + return 0; +} + +static void bench_e4_round(unsigned rg, uint8_t variant) { + uint16_t port = (uint16_t)(E4_BASE_PORT + rg); + uint8_t si = 0U; + uint32_t rx_drained = 0U; + int closed_ms; + + DBG_INFO("BENCH E4 rg%u var=%c port%u t=%u", rg, (char)('A' + variant), + (unsigned)port, (unsigned)HAL_GetTick()); + /* 轮前统一清洁(所有变体同起点) */ + (void)ch395f_close_socket(E4_SOCK); + (void)bench_wait_closed(E4_SOCK, 2000U); + (void)bench_drain_events(E4_SOCK, 300U); + ch395f_set_proto_type(E4_SOCK, CH395F_PROTO_TYPE_TCP); + ch395f_set_sour_port(E4_SOCK, port); + if (ch395f_open_socket(E4_SOCK) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E4 rg%u open fail", rg); + return; + } + if (ch395f_tcp_listen(E4_SOCK) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E4 rg%u listen fail", rg); + (void)ch395f_close_socket(E4_SOCK); + return; + } + /* 污染会话:主机 connect -> 发 3000B -> RST;芯片侧只等死亡事件(不读 RX, + * 残渣留给变体处置——A 的排空 / B 的 CLEAR / C 的什么都不做 正是被测对象) */ + if (!bench_e4_wait_death(E4_SOCK, 15000U, &si)) { + DBG_ERROR("BENCH E4 rg%u contam: no death event (host not synced?)", rg); + (void)ch395f_close_socket(E4_SOCK); + (void)bench_wait_closed(E4_SOCK, 2000U); + HAL_Delay(500); + return; + } + /* 被测复位序列 */ + if (variant == 0U) { + rx_drained = bench_drain_rx(E4_SOCK); + } else if (variant == 1U) { + ch395f_clear_recv_buf(E4_SOCK); + } + (void)ch395f_close_socket(E4_SOCK); + if (variant != 2U) { + (void)bench_drain_events(E4_SOCK, 500U); + } + closed_ms = bench_wait_closed(E4_SOCK, 2000U); + DBG_INFO("BENCH E4 rg%u var=%c reset done rxdrain=%lu closed_ms=%d t=%u", + rg, (char)('A' + variant), (unsigned long)rx_drained, closed_ms, + (unsigned)HAL_GetTick()); + /* 立即重开 + LISTEN(主机立即上传全载荷) */ + ch395f_set_sour_port(E4_SOCK, port); + if (ch395f_open_socket(E4_SOCK) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E4 rg%u reopen fail", rg); + return; + } + if (ch395f_tcp_listen(E4_SOCK) != CH395F_ERR_SUCCESS) { + DBG_ERROR("BENCH E4 rg%u relisten fail", rg); + (void)ch395f_close_socket(E4_SOCK); + return; + } + /* 等载荷连接 CONNECT(C 变体的陈旧 DISCONNECT 若在此同读出现 = 新连接被杀,如实计 FAIL) */ + if (!bench_wait_sock_int(E4_SOCK, 15000U, &si) || ((si & CH395F_SINT_STAT_CONNECT) == 0U)) { + DBG_ERROR("BENCH E4 rg%u var=%c VERIFY FAIL: no CONNECT (si=0x%02X)", rg, + (char)('A' + variant), si); + (void)ch395f_close_socket(E4_SOCK); + (void)bench_wait_closed(E4_SOCK, 2000U); + (void)bench_drain_events(E4_SOCK, 300U); + HAL_Delay(200); + return; + } + /* 消费循环:电平式 GET_RECV_LEN 收集(对丢 RECV 免疫),DISCONNECT/TIM_OUT 即止 + * (= 生产 net 层释放语义;closed-with-data 救回不在本判据内,事后读空仅作取证) */ + int death = ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) ? 1 : 0; + uint32_t total = 0U; + uint32_t tr = HAL_GetTick(); + while (!death && (uint32_t)(HAL_GetTick() - tr) < 6000U) { + uint16_t rl = ch395f_get_recv_len(E4_SOCK); + while (rl > 0U && total < (uint32_t)sizeof(s_e4_acc)) { + uint16_t n = (rl > 2048U) ? 2048U : rl; + uint32_t room = (uint32_t)sizeof(s_e4_acc) - total; + if ((uint32_t)n > room) { + n = (uint16_t)room; + } + ch395f_read_recv_buf(E4_SOCK, &s_e4_acc[total], n); + total += (uint32_t)n; + rl = ch395f_get_recv_len(E4_SOCK); + } + uint16_t glob = ch395f_get_glob_int_status_all(); + if ((glob & (1U << (E4_SOCK + 4U))) != 0U) { + si = ch395f_get_sock_int_status(E4_SOCK); + if ((si & (CH395F_SINT_STAT_DISCONNECT | CH395F_SINT_STAT_SOCK_TIMEOUT)) != 0U) { + death = 1; + } + } + if (!death) { + HAL_Delay(5); + } + } + uint32_t loop_total = total; + /* 事后读空(取证:数据是否其实还在芯片缓冲里) */ + for (int k = 0; k < 20; k++) { + uint16_t rl = ch395f_get_recv_len(E4_SOCK); + if (rl == 0U) { + break; + } + uint32_t room = (uint32_t)sizeof(s_e4_acc) - total; + if (room == 0U) { + break; + } + uint16_t n = (rl > 2048U) ? 2048U : rl; + if ((uint32_t)n > room) { + n = (uint16_t)room; + } + ch395f_read_recv_buf(E4_SOCK, &s_e4_acc[total], n); + total += (uint32_t)n; + } + /* 载荷自证:图案种子随全局轮次变,上一轮残渣必失配 */ + uint8_t seed = e4_seed(rg); + int mm = -1; + for (uint32_t i = 0U; i < loop_total; i++) { + if (s_e4_acc[i] != (uint8_t)(seed + i)) { + mm = (int)i; + break; + } + } + int ok = (loop_total == E4_PAYLOAD) && (mm < 0); + DBG_INFO("BENCH E4 rg%u var=%c VERIFY loop=%lu extra=%lu mm=%d death=%d head=%02X %02X %02X %02X => %s", + rg, (char)('A' + variant), (unsigned long)loop_total, + (unsigned long)(total - loop_total), mm, death, + s_e4_acc[0], s_e4_acc[1], s_e4_acc[2], s_e4_acc[3], ok ? "OK" : "FAIL"); + /* 轮后清洁 */ + (void)ch395f_close_socket(E4_SOCK); + (void)bench_wait_closed(E4_SOCK, 2000U); + (void)bench_drain_events(E4_SOCK, 300U); + HAL_Delay(200); +} + +static void bench_e4(void) { + DBG_INFO("BENCH E4 ===== sock_reset A/B/C x%u (host: ch395f_reuse_host.py --mode e4) =====", + (unsigned)E4_ROUNDS); + unsigned rg = 0U; + for (uint8_t v = 0U; v < 3U; v++) { + for (unsigned r = 0U; r < E4_ROUNDS; r++, rg++) { + bench_e4_round(rg, v); + } + } +} + void ch395f_reuse_bench(void) { - DBG_INFO("BENCH ===== N30 chip-level reuse bench START ====="); + DBG_INFO("BENCH ===== N34 E2/E3/E4 chip bench START (legacy matrix parked) ====="); if (bench_chip_init() != 0) { DBG_ERROR("BENCH init failed, giving up (normal boot continues)"); return; } - DBG_INFO("BENCH chip ready, cycling %u variants forever", (unsigned)BENCH_N_VARS); for (unsigned i = 0; i < 2048U; i++) { s_tx_pat[i] = (uint8_t)(0xA5U + (i % 251U)); } - unsigned idx = 0; + bench_dwt_enable(); + if (s_bench_legacy != 0) { + DBG_INFO("BENCH legacy N-30 matrix cycling %u variants", (unsigned)BENCH_N_VARS); + unsigned idx = 0; + for (;;) { + bench_round(idx); + idx = (idx + 1U) % (unsigned)BENCH_N_VARS; + } + } + bench_e2(); + bench_e3(); + bench_e4(); + DBG_INFO("BENCH ===== E2/E3/E4 ALL DONE (reflash or power-cycle to exit) ====="); for (;;) { - bench_round(idx); - idx = (idx + 1U) % (unsigned)BENCH_N_VARS; + HAL_Delay(1000); } } diff --git a/test/host/ch395f_reuse_host.py b/test/host/ch395f_reuse_host.py index 5dae2f31..7c0c8e63 100644 --- a/test/host/ch395f_reuse_host.py +++ b/test/host/ch395f_reuse_host.py @@ -1,8 +1,13 @@ # N-30 芯片级复用时序台架:主机端(与 test/board/ch395f_reuse_bench.c 共用变体表) -# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] -# 纯 TCP,无 FTP。按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。 +# 用法: python ch395f_reuse_host.py [--host IP] [--cycles N] [--mode legacy|e4] +# legacy: 按 idx 轮转连接 5000+idx 端口,记录收发结果与时刻。 +# e4: N34 E4 sock_reset A/B/C 矩阵配套(2026-10-13):每轮两次连接—— +# ①污染:连上发 3000B 后 RST(SO_LINGER 0,Trap 23 中止形态); +# ②载荷:立即重连发 4096B 图案(pat[i]=(seed+i)&0xFF, seed=(rg*97+13)&0xFF)后 FIN。 +# 端口 5100+rg,rg=0..(3*rounds-1),变体顺序 A/B/C(与固件 bench 表一致)。 import argparse import socket +import struct import sys import time @@ -70,12 +75,73 @@ def run_round(host, idx, tx_len, close_mode): return ok +def connect_retry(host, port, budget_s): + """connect with retry until budget_s elapses; None on give-up""" + t0 = time.time() + while time.time() - t0 < budget_s: + try: + return socket.create_connection((host, port), timeout=5) + except OSError: + time.sleep(0.2) + return None + + +def e4_round(host, rg, rounds_per_variant, first_budget): + """one E4 round: contaminate (send 3000B then RST) + payload (4096B pattern then FIN)""" + port = 5100 + rg + seed = (rg * 97 + 13) & 0xFF + budget = first_budget if rg == 0 else 30 + s = connect_retry(host, port, budget) + if s is None: + print("%s E4 r%2d port%5d <<<< contam CONNECT timeout" % (ts(), rg, port)) + return False + try: + s.sendall(b"\x33" * 3000) + except OSError as e: + print("%s E4 r%2d contam send err %r" % (ts(), rg, e)) + try: + s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0)) + except OSError: + pass + s.close() # RST: abort mid-session like ftp_abort_probe (Trap 23 form) + time.sleep(0.05) + + s = connect_retry(host, port, 20) + if s is None: + print("%s E4 r%2d port%5d <<<< payload CONNECT timeout" % (ts(), rg, port)) + return False + payload = bytes(((seed + i) & 0xFF) for i in range(4096)) + try: + s.sendall(payload) + except OSError as e: + print("%s E4 r%2d payload send err %r" % (ts(), rg, e)) + s.close() + return False + s.close() # FIN: full payload delivered, chip drains + print("%s E4 r%2d var=%s port%5d contam+payload sent (seed=%02X)" + % (ts(), rg, "ABC"[rg // rounds_per_variant], port, seed)) + return True + + def main(): ap = argparse.ArgumentParser() ap.add_argument("--host", default="192.168.1.100") ap.add_argument("--cycles", type=int, default=3) + ap.add_argument("--mode", choices=["legacy", "e4"], default="legacy") + ap.add_argument("--rounds", type=int, default=10, help="e4: rounds per variant") + ap.add_argument("--first-budget", type=int, default=300, + help="e4: connect budget for round 0 (bench runs E2/E3 first)") args = ap.parse_args() + if args.mode == "e4": + fails = [] + total = 3 * args.rounds + for rg in range(total): + if not e4_round(args.host, rg, args.rounds, args.first_budget): + fails.append(rg) + print("E4 RESULT: %d/%d host-side ok, failures %s" % (total - len(fails), total, fails)) + return 1 if fails else 0 + fails = [] for c in range(args.cycles): for idx in sorted(VARS):