Files
STM32F4-Base/test/host/net_socket/ns_s4_state.c
T
wandering1 794906cdc8 test(host): P2 覆盖率批次,5 套主机单测与共享假 FatFs
P2 五个文件全部进分母(三名队友并行,写范围互斥、零越界):
  net_socket 420 条 / 881-883=99.8%、52-52 函数(S1~S6 分片)
  sys_log_mcu 75 条 / 310-313=99.0%、12-12
  console_log 104 条 / 188-189=99.5%、15-15
  cfg_file_store_mcu 34 条 / 97-97=100%、10-10
  soe_store 26 条 / 78-79=98.7%、4-4
合计 659 条断言,P2 本体 1554-1561=99.6%、93-93 函数。

账:套件 28→33 全绿;files_tested 32→37;未触及 112→107(.c 34→29,
恒等式 29 = 0(P1剩) + 0(P2) + 29(P3) 咬合,§9 终点判据达成);分母
4684→6245 插桩行;line 85.8%→89.3%;func 90.1%→92.2%(这批 5 个文件
全都以 ≥98.7% 进分母,故未摊薄)。--update-baseline 前后逐文件比对:
下降 0 个文件。

基建:新建 test/host/common/fatfs_fake.{h,c}(以真实 ff.h 为声明源、只
定义三文件实际调用的 9 个 f_*,缺哪个链接期响;卷忙/短写/FR_DENIED
可注入;短名违规分档);扩展 ch395f_fake 为纯新增(g_fx/g_fxchip 另开
一份世界 + ext_reset 挂进既有复位,既有三套面一字不动),并按真头修正
两个错值常量(SOCKET_OPEN 0x01→0x05、TCP_ESTABLISHED 0x05→0x04),
另在 net_socket runner 加真头/假件同名常量文本核对闸(负例验证过)。
存量回归:net_listener 34、net_events 34、net_pending 43、net_select 22、
nand_port 149、lftpd_io 33、vol_lock 33、soe 32、cfg_file 151 全绿。

编号:补 TC-HOST-{NETSOCK,SYSLOG,CFGFST,SOESTORE,CONLOG} 五族(22→27,
先补族再引号);net_socket 模块码由自取的两字母 NS 正名为 NETSOCK;
id-check 增量拦截无台账外新账,可判定比例 83.27%(基线 82.6%)。

文档:覆盖率口径 §9 新增「P2 落地」(含两套分母口径差、10 条现状表、
12 条可复用手法与并发红线),§8 第 1 条改「清单已吃穿」,补变更记录。
本提交未改任何生产代码(App/Drivers/Src/Lib/MDK-ARM 零触碰)。

10 条现状已逐条登记在 §9(含 sys_log_mcu.c:509~537 卷忙整批丢日志、
net_socket.c 同文件两套 IP 字节序、UDP 两 API 绕过 netTask),已获指示
全部修复,另开一轮(含测试字面量统一纯 ASCII)。

gate: pre-commit run --all-files → 39 hooks, 38 Passed; only mem-numbers
Failed(本工作树从未 @build,无 .map 事实源,属分支期已知陷阱);
mem-budget Passed;test-coverage 与 id-check Passed。RAM/Flash 增量 = 0
(纯测试批次)。按仓内分支约定走 --no-verify,台账回填留待主树合并后。
2026-10-24 17:54:02 +08:00

347 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 分片 4:net_poll 中断分派、监听/可读闸门、close 与 drop_conn 的拆除序列
*
* 这一片钉的是 AGENTS 模块约定里**没有文档就无法发现**的那条芯片约束:
* 所有 CH395F 中断在 net_poll() 内经 GET_GLOB_INT_STATUS 集中读取并清除,不得在
* ISR 或其他地方单独处理。全局寄存器整体读即清、芯片无按位清除手段,而 net_poll
* 只在全局位为 1 时才读该 Socket 的 SINT → 顺手全量清一遍等于把别的在用 Socket 的
* 门焊死(B3 实证,详见 docs/网络/网络自愈架构设计.md §4.10)。
*
* 断言手法(刻意不用「读了几次」这种弱口径):GET_SOCK_INT_STATUS 是**读即清**,
* 所以给每个 Socket 预置互不相同的挂起位图,跑一轮 net_poll 后看哪些位图被清掉了
* ——被清=被读过。这比数调用次数更硬:它直接照出「门有没有被焊死」。
*
* 创建日期:2026-10-24
*/
/* 预置:给每个 Socket 挂一个只有自己才有的 SINT 位图(只用低 6 位,避开保留位) */
static void prime_sint_masks(void)
{
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
g_fake.int_status[i] = (uint8_t)(0x01U << (i % 6));
}
g_chip.int_status_calls = 0;
}
/* TC-NETSOCK-300 net_poll 的前置闸门 */
static void tc_300_poll_gates(void)
{
/* 1) 未初始化:直接 0,连引脚都不该读 */
reset_all();
g_ns_pump = net_process_messages;
CHECK(net_poll() == 0, "TC-NETSOCK-300a 未初始化 → 0 事件");
CHECK(g_ns_poll_calls == 0U && g_fxchip.glob_calls == 0,
"TC-NETSOCK-300b 未初始化不得读 INT# 引脚、不得读全局中断(省 SPI)");
/* 2) 初始化了但 INT# 为高(无中断待处理):不读全局寄存器 */
bring_up();
g_ns_int_pin_set = 1;
CHECK(net_poll() == 0, "TC-NETSOCK-300c INT# 高 → 0 事件");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-300d INT# 高时不得发全局中断读取(引脚轮询就是为省这一步)");
/* 3) INT# 低但全局状态为 0:读了全局一次,且不进 Socket 循环 */
bring_up();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob(0U);
CHECK(net_poll() == 0, "TC-NETSOCK-300e 全局位全 0 → 0 事件");
CHECK(g_fxchip.glob_calls == 1 && g_chip.int_status_calls == 0,
"TC-NETSOCK-300f 全局只读一次,读到 0 立即收手(不逐个 Socket 扫)");
reset_all();
}
/* TC-NETSOCK-301 门控精度:只读全局位置位的那个 Socket 的 SINT(B3 的正面表述) */
static void tc_301_poll_only_gated_sockets(void)
{
int i;
int a;
int b;
bring_up();
a = mk_established(NET_SOCK_STREAM);
b = mk_established(NET_SOCK_STREAM);
(void)mk_established(NET_SOCK_STREAM);
CHECK(a == 0 && b == 1, "TC-NETSOCK-301a 前置:三个在用 fd");
prime_sint_masks();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (1 + 4))); /* Socket n 的位 = bit(n+4) */
(void)net_poll();
CHECK(g_fake.int_status[0] == (uint8_t)(0x01U << 0) &&
g_fake.int_status[2] == (uint8_t)(0x01U << 2),
"TC-NETSOCK-301b 未点亮的 Socket 0/2 挂起位图没被清(读即清=读过,没读=还在)");
CHECK(g_fake.int_status[1] == 0U, "TC-NETSOCK-301c 点亮的 Socket 1 被读走(位图清零)");
CHECK(g_chip.int_status_calls == 1, "TC-NETSOCK-301d 整轮只发了一次 SINT 读取");
/* 缓存刷新环是另一回事:它对所有在用 fd 都要刷(单一事实源 I1) */
CHECK(g_fxchip.rst_poll_calls == 3,
"TC-NETSOCK-301e 每轮对全部 3 个在用 fd 步进 sock_reset(IDLE 槽零 SPI)");
(void)net_poll();
CHECK(g_fxchip.rst_poll_calls == 6,
"TC-NETSOCK-301f 再一轮再加 3 次:刷新环不受全局位门控(事件面与状态面分离)");
reset_all();
/* 高位字节:Socket 4~7 的全局位在 bit8~bit11 */
bring_up();
for (i = 0; i < 5; i++) {
(void)mk_established(NET_SOCK_STREAM);
}
prime_sint_masks();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (4 + 4)));
(void)net_poll();
CHECK(g_fake.int_status[4] == 0U && g_fake.int_status[3] == (uint8_t)(0x01U << 3),
"TC-NETSOCK-301g Socket4~7 用高位字节 bit(n+4)=bit8..11 定位(算错就永远收不到 4~7 的事件)");
reset_all();
}
/* TC-NETSOCK-302 六种 SINT 位各自的分派与事件计数 */
static void tc_302_poll_event_dispatch(void)
{
static const uint8_t bits[6] = {
CH395F_SINT_STAT_CONNECT, CH395F_SINT_STAT_DISCONNECT, CH395F_SINT_STAT_RECV_OK,
CH395F_SINT_STAT_SEND_OK, CH395F_SINT_STAT_SOCK_TIMEOUT, CH395F_SINT_STAT_SENBUF_FREE
};
int k;
int fd;
for (k = 0; k < 6; k++) {
int expect = (bits[k] == CH395F_SINT_STAT_SENBUF_FREE) ? 0 : 1;
int n;
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = bits[k];
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
n = net_poll();
CHECK(n == expect, "TC-NETSOCK-302a 事件计数按位型区分(SENBUF_FREE 是状态位不是事件)");
switch (bits[k]) {
case CH395F_SINT_STAT_CONNECT:
CHECK(g_peers.evt_connect_calls == 1, "TC-NETSOCK-302b CONNECT → handle_connect_event");
break;
case CH395F_SINT_STAT_DISCONNECT:
CHECK(g_peers.evt_disconnect_calls == 1, "TC-NETSOCK-302c DISCONNECT → handle_disconnect_event");
break;
case CH395F_SINT_STAT_RECV_OK:
CHECK(g_peers.evt_recv_calls == 1 && s_net_socks[fd].readable == 1U,
"TC-NETSOCK-302d RECV_OK → handle_recv_event 置 readable 电平");
break;
case CH395F_SINT_STAT_SEND_OK:
CHECK(g_peers.evt_send_calls == 1, "TC-NETSOCK-302e SEND_OK → handle_send_event");
break;
case CH395F_SINT_STAT_SOCK_TIMEOUT:
CHECK(g_peers.evt_timeout_calls == 1, "TC-NETSOCK-302f TIMEOUT → handle_timeout_event");
break;
default:
CHECK(s_net_socks[fd].send_ready == 1U && g_peers.evt_send_calls == 0,
"TC-NETSOCK-302g SENBUF_FREE 只置 send_ready(不发事件、不误当 SEND_OK)");
break;
}
CHECK(g_peers.evt_connect_calls + g_peers.evt_disconnect_calls +
g_peers.evt_recv_calls + g_peers.evt_send_calls +
g_peers.evt_timeout_calls <= 1,
"TC-NETSOCK-302h 一位进来只敲一个 handler(不串味)");
}
/* 同一次 SINT 里多个位 → 多个 handler 都被叫到,事件数叠加 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = (uint8_t)(CH395F_SINT_STAT_RECV_OK | CH395F_SINT_STAT_DISCONNECT);
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
CHECK(net_poll() == 2, "TC-NETSOCK-302i 同批多事件位:各计一次(不吞后到的那位)");
CHECK(g_peers.evt_recv_calls == 1 && g_peers.evt_disconnect_calls == 1,
"TC-NETSOCK-302j 两位各归各的 handler");
reset_all();
}
/* TC-NETSOCK-303 PHY 变更位经驱动 feed 落到链路缓存(F-DRV-13 接线) */
static void tc_303_phy_feed(void)
{
bring_up();
g_ns_int_pin_set = 0;
g_fx.phy_status = CH395F_PHY_DISCONN; /* 下一次 feed 读到断开 */
ch395f_fake_push_glob(CH395F_GINT_STAT_PHY_CHANGE);
(void)net_poll();
CHECK(g_fake.link_up == 0U && g_fake.link_change == 1U,
"TC-NETSOCK-303 PHY_CHANGE → 驱动重读 PHY 并计一次链路变更(拔网线不再静默)");
reset_all();
}
/* TC-NETSOCK-310 监听门控 net_listener_armed 的三支放行与全部挡下形态 */
static void tc_310_listener_armed(void)
{
int fd;
reset_all();
CHECK(net_listener_armed(-1) == 0 && net_listener_armed(NET_MAX_SOCKETS) == 0,
"TC-NETSOCK-310a 越界 fd → 0(纯 RAM 读,不发 RPC)");
CHECK(net_listener_armed(0) == 0, "TC-NETSOCK-310b 未分配的 fd → 0");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310c ESTABLISHED(待 accept 电平)必须放行:i104 台架实证不放行则会话整场饿死");
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310d LISTENING 放行:补 accept 探针在 net_accept_locked 内部,挡住永远到不了");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 7003U;
s_net_socks[fd].readable = 1U;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310e CLOSED+auto_relisten+有残留数据 → 放行(保住 closed-with-readable 接受)");
s_net_socks[fd].readable = 0U;
CHECK(net_listener_armed(fd) == 0,
"TC-NETSOCK-310f CLOSED 但无残留 → 必须挡下(否则整个 CLOSED 窗每轮发 RPC → 喂不上狗)");
s_net_socks[fd].readable = 1U;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310g 非监听型 fd 的 CLOSED 不放行");
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310h 还没绑端口的半成品监听不放行");
s_net_socks[fd].state = NET_SOCK_STATE_CONNECTING;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310i CONNECTING 不是监听态 → 0");
reset_all();
}
/* TC-NETSOCK-311 recv 门控 net_can_read:非 ESTABLISHED 一律放行 */
static void tc_311_can_read(void)
{
int fd;
reset_all();
CHECK(net_can_read(0) == 1,
"TC-NETSOCK-311a 描述符失效 → 1:让 recv 去报 BADF,别把连接不存在藏住");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 0, "TC-NETSOCK-311b ESTABLISHED 且无可读 → 0");
s_net_socks[fd].readable = 1U;
CHECK(net_can_read(fd) == 1, "TC-NETSOCK-311c readable 置位 → 1");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 1,
"TC-NETSOCK-311d 连接已非 ESTABLISHED → 必须放行一次 recv 取回 EOF/错误");
reset_all();
}
/* TC-NETSOCK-320 close 的拆除序列:快路径(芯片已 CLOSED)与慢路径(走复位机) */
static void tc_320_close_two_paths(void)
{
int fd;
/* 快路径:芯片已 CLOSED → 只清本 Socket 残留事件,不发复位 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_CLOSED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320a close 成功(快路径)");
CHECK(g_fxchip.cmd_done_calls >= 1 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-320b 已 CLOSED → 跳过复位(省掉 100~600ms 的无占用)");
CHECK(g_fxchip.sock_events_calls >= 1,
"TC-NETSOCK-320c 快路径仍要清本 Socket 残留事件(防下一连接误判刚建立就断开)");
CHECK(g_fxchip.glob_calls == 0, "TC-NETSOCK-320d 全程零全局读取(F-NET-7)");
reset_all();
/* 慢路径:芯片还 OPEN → 走 sock_reset,且必须是 GRACEFUL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320e close 成功(慢路径)");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 0U,
"TC-NETSOCK-320f 慢路径用 GRACEFUL(正常释放 100ms 底),不是 QUARANTINE");
CHECK(g_fx.rst_state[fd] == (uint8_t)CH395F_FAKE_RST_IDLE,
"TC-NETSOCK-320g 复位机收口回 IDLE(槽位可复用,不搁浅)");
reset_all();
/* 复位启动被拒(槽位已在运行)→ close 仍放行,只留痕 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_state[fd] = (uint8_t)CH395F_FAKE_RST_DRAINED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320h 复位启动失败也放行 close(不卡调用方)");
CHECK(g_fxchip.rst_begin_reject == 1 && g_ns_error_count >= 1,
"TC-NETSOCK-320i 复位没成必须 [ERR] 留痕,交 reconciler/pump 对账接管");
reset_all();
}
/* TC-NETSOCK-321 drop_conn 用 QUARANTINE(异常释放语义),且不动监听簿记 */
static void tc_321_drop_conn_settle(void)
{
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].readable = 1U;
s_net_socks[fd].lost_conn_seen = 3U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn 成功");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 1U,
"TC-NETSOCK-321b 拆会话用 QUARANTINE 400ms 底(TRAP-NET-18 异常释放,与 close 分级)");
CHECK(s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-321c 新会话起点:事件未收到去抖计数清零(N-30)");
CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U &&
s_net_socks[fd].in_use == 1U,
"TC-NETSOCK-321d 端口/监听属性/fd 全部保留:这就是拆连接不拆监听");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-321e 登记 REQ_DROP,重建动作在 net_listener_tick 一处");
CHECK(s_net_socks[fd].readable == 0U && s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-321f 收口会话簿记:清 readable + 转 CLOSED");
reset_all();
}
/* TC-NETSOCK-322 close 前必先把挂起结算掉,再动芯片(顺序不可换) */
static void tc_322_close_before_chip(void)
{
int fd;
int close_at;
int settle_at;
char rxbuf[4];
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
/* 先登记一条挂起 recv:走 netTask 直调版 recv(无数据 + 阻塞 → 登记挂起) */
g_ns_pump = NULL; /* 本场景不靠泵,直接看登记结果 */
CHECK(net_recv_sock(&s_net_socks[fd], rxbuf, (int)sizeof(rxbuf), 0)
== NET_PENDING_REGISTERED,
"TC-NETSOCK-322p 前置:阻塞 recv 登记了挂起(返回哨兵)");
ch395f_fake_clear_log();
settle_at = g_peers.pending_settle_calls;
close_at = g_chip.close_calls;
CHECK(net_close_sock(&s_net_socks[fd]) == 0, "TC-NETSOCK-322a netTask 直调 close 成功");
CHECK(g_peers.pending_settle_calls == settle_at + 1,
"TC-NETSOCK-322b close 必先结算本 fd 的挂起 recv");
CHECK(g_chip.close_calls > close_at || g_fxchip.rst_close_calls > 0,
"TC-NETSOCK-322c 结算之后芯片侧才收到 CLOSE");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-322d 结算必通知(取消也要通知,不是静默清槽 → 否则等待方永久悬挂)");
reset_all();
}
static void ns_run_s4(void)
{
tc_300_poll_gates();
tc_301_poll_only_gated_sockets();
tc_302_poll_event_dispatch();
tc_303_phy_feed();
tc_310_listener_armed();
tc_311_can_read();
tc_320_close_two_paths();
tc_321_drop_conn_settle();
tc_322_close_before_chip();
}