Files
STM32F4-Base/test/host/net_socket/ns_s3_msg.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

614 lines
25 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.
/*
* 分片 3:跨上下文消息面(net_process_messages 全分派 + 结果经任务通知值回传)
* 前置:net_socket_test.c 已纳入生产源与假内核/假芯片/兄弟模块桩(同一 TU)。
*
* 这一片是整套里最值钱的部分,钉的是 AGENTS 模块约定的两条硬契约:
* A.「所有 net_* socket 操作经 netMsgQueue 在 netTask 串行执行」
* B.「net_* 公开 API 的错误码经任务通知值回传:net_msg_t 是按值拷入队列的,
* netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 xTaskNotify 通知值回来;
* 勿给 net_msg_t 加回传字段(加了只会让调用方读到栈垃圾)」
* 断言形态因此刻意是:先看**通知值本身**(32 位整体即结果,负值=精确错误码),
* 再看调用方在自己上下文恢复出的 errno——而不是去读队列里的 msg.result
* (那正是旧实现的错觉,也是 B 条明文禁止的方向)。
*
* 创建日期:2026-10-24
*/
/* close 场景的增量快照基准(前面场景也会 clear,必须按增量断言) */
static int s_close_base_clear;
static int s_close_base_settle;
/* 造一条已建立的 TCP 会话(多数消息场景的前置) */
static int mk_established(int type)
{
int fd = net_socket(NET_AF_INET, type);
net_sock_t *p;
if (fd < 0) {
return fd;
}
p = &s_net_socks[fd];
p->state = NET_SOCK_STATE_ESTABLISHED;
p->send_ready = 1U;
p->readable = 0U;
p->local_port = 2404U;
p->remote_port = 51999U;
p->remote_ip = 0x0B01A8C0U;
return fd;
}
/* TC-NETSOCK-200 未知消息类型:枚举闭合外的值也必须给出**本条消息自身**的确定结果
* (不许把上一条的回执残留当成这次的结果) */
static void tc_200_unknown_type(void)
{
net_msg_t msg;
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = (net_msg_type_t)999;
msg.sockfd = 0;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
msg.result = 12345; /* 脏值:分派必须覆写它 */
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-200a 原始消息入队");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-200b 入队本身不产生通知");
net_process_messages(); /* 以 netTask 身份跑一轮 */
CHECK(g_ns_notify_calls == 1, "TC-NETSOCK-200c 未知 type 仍必发回执(否则调用方永久悬挂)");
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR,
"TC-NETSOCK-200d 未知 type 的通知值 = 本条的精确错误码 NET_ERR(不是上一条残留)");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-200e 本轮队列被排空(非阻塞 while 语义)");
reset_all();
}
/* TC-NETSOCK-201 队列为空:四个自愈 tick 必跑,且不产生任何通知 */
static void tc_201_ticks_run_before_dispatch(void)
{
bring_up();
net_process_messages();
CHECK(g_peers.listener_tick_calls == 1 && g_peers.pending_tick_calls == 1 &&
g_peers.send_tick_calls == 1 && g_peers.reconcile_calls == 1,
"TC-NETSOCK-201a 空队列也要跑满四个自愈 tick(D10/pending×2/对账)");
CHECK(g_ns_notify_calls == 0 && g_ns_get_calls == 1,
"TC-NETSOCK-201b 空队列:取一次即退,不发通知");
reset_all();
}
/* TC-NETSOCK-202 队列句柄未绑定:直接返回,连 tick 都不该跑 */
static void tc_202_no_queue_bound(void)
{
reset_all();
g_ns_pump = net_process_messages;
net_process_messages();
CHECK(g_peers.listener_tick_calls == 0 && g_ns_get_calls == 0,
"TC-NETSOCK-202 未绑定队列就调 net_process_messages → 立即返回(不碰芯片也不跑 tick)");
reset_all();
}
/* TC-NETSOCK-210 SEND 快路径:端到端跑通"入队 → 分派 → 写芯片 → 通知值=字节数" */
static void tc_210_send_end_to_end(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)memset(buf, 'A', sizeof(buf));
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == (int)sizeof(buf), "TC-NETSOCK-210a net_send 返回实际写入字节数");
CHECK(g_chip.write_calls == 1 && g_chip.last_read_len == (uint16_t)sizeof(buf),
"TC-NETSOCK-210b 一次写满(健康链路不拆块、不进挂起)");
CHECK(s_net_socks[fd].send_bytes == sizeof(buf), "TC-NETSOCK-210c 发送字节统计累计");
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == (uint32_t)sizeof(buf),
"TC-NETSOCK-210d 结果经通知值回传(32 位整体即结果)");
CHECK(g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-210e 通知发给**登记的那个 caller**,不是广播");
CHECK(g_ns_block_waits == 1,
"TC-NETSOCK-210f 零等待没拾到 → 走阻塞分支(两段式拾取的第二段)");
reset_all();
}
/* TC-NETSOCK-211 大帧分块:超过 NET_SEND_CHUNK_MAX 必须按硬件发送缓冲上界切块
* (TRAP-NET-16:单次写 >1KB 会写穿 1KB 发送 FIFO) */
static void tc_211_send_chunking(void)
{
static char buf[3000];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* 造"每写完一块芯片就报 SENBUF_FREE"的健康链路:让 net_poll 把 send_ready 置回来 */
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
/* SENBUF_FREE 是**条件位**:发送缓冲空着就还会再报。用假件的电平型掩码而不是
* 一次性位图,否则第二块起就再也等不到空闲 → 变成测假件的清零时机。 */
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
rc = net_send(fd, buf, 2500, 0);
CHECK(rc == 2500, "TC-NETSOCK-211a 2500 字节全部交付");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-211b 按 1024 上界切成 3 块(1024/1024/452)");
CHECK(g_chip.last_read_len == 452U, "TC-NETSOCK-211c 末块只写剩余量");
CHECK(NET_SEND_CHUNK_MAX == 1024,
"TC-NETSOCK-211d 分块粒度取芯片发送缓冲(不是假件自定的数)");
reset_all();
}
/* TC-NETSOCK-212 SEND 挂起登记:等发送缓冲超预算 → 登记挂起并**跳过即时回执** */
static void tc_212_send_pending_defers_notify(void)
{
static char buf[64];
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U; /* 芯片一直不腾缓冲 */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-212a SEND 消息入队");
net_process_messages();
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-212b 超自旋预算后登记挂起 send(而不是继续占着 netTask 忙等)");
CHECK(g_peers.last_snd_fd == fd && g_peers.last_snd_len == (int)sizeof(buf) &&
g_peers.last_snd_sent == 0,
"TC-NETSOCK-212c 登记的进度=已写芯片字节数(此处 0),总长与 caller 一并带走");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-212d 登记成功**绝不发即时回执**(否则「登记成功」被当成本次结果)");
CHECK(g_ns_notify_last_to == NULL,
"TC-NETSOCK-212e 一次通知都没发生(0x70000000 哨兵值也没可能漏进通知值)");
/* 完成时刻:由挂起 send 的 tick 结束(本套由桩脚本模拟"缓冲腾出来了") */
ns_peers_arm_send_pending_done(fd, 64);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 64U,
"TC-NETSOCK-212f 挂起完成后才发回执,通知值=实际写出字节数");
reset_all();
}
/* TC-NETSOCK-213 SEND 失败:未建立连接 → 通知值带**精确**错误码,不是笼统 -1 */
static void tc_213_send_error_code_roundtrip(void)
{
char buf[4];
net_msg_t msg;
int fd;
int rc;
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM); /* CREATED,未连接 */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_NOTCONN,
"TC-NETSOCK-213a 未连接的 SEND:通知值=NET_ERR_NOTCONN(精确码回传,供调用方分支)");
/* 端到端:公开 net_send 的 BADF 形态(fd 越界由**分派侧**再判一次) */
reset_all();
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = 3; /* 合法范围内但未分配 */
msg.buf = buf;
msg.len = 4;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-213b 范围内但未分配的 fd:net_get_sock 判空 → BADF 搭通知值回来");
/* 调用方上下文恢复 errno:拿到的必须是"本任务的"精确码 */
reset_all();
bring_up();
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(6, buf, 4, 0); /* 6 未分配 → 分派侧 BADF → 通知值 -2 */
CHECK(rc == -1, "TC-NETSOCK-213c 对外保持 BSD 语义:失败一律 -1");
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-213d 调用方在自己的错误码槽里拿到精确码(E-03 + 通知值回传合力)");
reset_all();
}
/* TC-NETSOCK-220 RECV 快路径:芯片缓冲里已有数据 → 当场读出,不登记挂起 */
static void tc_220_recv_fast_path(void)
{
char buf[16];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd, 1400U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, 10, NET_MSG_DONTWAIT);
CHECK(rc == 10, "TC-NETSOCK-220a 缓冲 1400B、只给 10B 缓冲 → 读满 10(部分读合法)");
CHECK(g_chip.read_calls == 1 && g_chip.last_read_len == 10U,
"TC-NETSOCK-220b 只读调用方给的量(不得按芯片可用量越界写)");
CHECK(s_net_socks[fd].recv_bytes == 10U, "TC-NETSOCK-220c 接收字节统计累计");
CHECK(s_net_socks[fd].readable == 0U,
"TC-NETSOCK-220d 读过即清 readable(下一次 RECV_OK 中断再置)");
CHECK(g_peers.pending_reg_calls == 0, "TC-NETSOCK-220e 数据已在手,不该登记挂起");
reset_all();
}
/* TC-NETSOCK-221 RECV 非阻塞无数据 → WOULDBLOCK(且不许登记挂起) */
static void tc_221_recv_wouldblock(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221a DONTWAIT 无数据 → -1 + WOULDBLOCK");
CHECK(g_peers.pending_reg_calls == 0,
"TC-NETSOCK-221b 非阻塞绝不登记挂起(否则调用方以为自己在等,实际已被挂住)");
/* 模式属性(net_setblocking(0))与单次标志同判据 */
CHECK(net_setblocking(fd, 0) == 0, "TC-NETSOCK-221c 切非阻塞模式成功");
rc = net_recv(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221d blocking 属性=0 与 NET_MSG_DONTWAIT 等价生效");
CHECK(net_recv_nb(fd, buf, (int)sizeof(buf)) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221e net_recv_nb 具名入口同路径");
reset_all();
}
/* TC-NETSOCK-222 RECV EOF:会话已结束 + 无数据 → 返 0 并登记重建(不是 -1) */
static void tc_222_recv_eof_relisten(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0, "TC-NETSOCK-222a CLOSED+无数据 → EOF(0),不是错误");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_EOF,
"TC-NETSOCK-222b EOF 登记 REQ_EOF(重建动作交监听层唯一执行者,本层不动芯片)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0 && g_chip.close_calls == 0,
"TC-NETSOCK-222c 登记路径上**一个芯片事务都不发**(单一所有者不变量 II)");
/* 抢先 relisten 后 state 已回 LISTENING,但会话电平仍是"已结束" */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].session_dead = 1U;
s_net_socks[fd].auto_relisten = 1U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0,
"TC-NETSOCK-222d 已 relisten 回 LISTENING 仍判 EOF(N-32 P1 电平化:否则 EOF 永久失踪)");
/* auto_relisten=0(客户端 fd):EOF 不登记重建,交调用方决定重连 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].session_dead = 1U;
s_net_socks[fd].auto_relisten = 0U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-222e 非监听 fd 的 EOF 不许替调用方擅自重建");
reset_all();
}
/* TC-NETSOCK-223 RECV 阻塞无数据 → 登记挂起 + 跳过即时回执;到点由 tick 结束 */
static void tc_223_recv_pending_roundtrip(void)
{
char buf[16];
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.flags = 0;
msg.timeout_ms = 250U; /* 显式预算必须原样带到登记处 */
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_reg_calls == 1 && g_peers.last_pend_fd == fd,
"TC-NETSOCK-223a 阻塞无数据 → 登记挂起 recv(netTask 零占用返回)");
CHECK(g_peers.last_pend_to == 250U,
"TC-NETSOCK-223b 调用方的显式预算随登记带走(0=无限,这里必须不是 0)");
CHECK(g_peers.last_pend_caller == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-223c 完成时要知道通知谁");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-223d 登记即发回执 = 把「登记成功」当结果,本条钉它没发生");
/* 数据到达:由挂起 recv 的 tick 结束(脚本模拟) */
ns_peers_arm_pending_done(fd, 7);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 7U,
"TC-NETSOCK-223e 数据到达后才回执,通知值=字节数");
reset_all();
}
/* TC-NETSOCK-224 net_recv_to:分片超时用独立返回码 -2(不靠 errno,避免跨任务竞态) */
static void tc_224_recv_to_timeout_code(void)
{
char buf[16];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
/* 先造一条"已登记"的挂起,再让 tick 判到点 */
ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT);
rc = net_recv_to(fd, buf, (int)sizeof(buf), 30U);
CHECK(rc == -2, "TC-NETSOCK-224a 本片超时 → 独立返回码 -2(与真错误 -1 区分)");
reset_all();
}
/* TC-NETSOCK-225 RECV 参数非法:不碰芯片、不登记 */
static void tc_225_recv_bad_args(void)
{
char buf[4];
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(0, buf, 4, 0U) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225a net_recv_to(timeout=0) 直接拒——0 在 netTask 侧是「无限等待」,"
"与本接口「有界占用」的存在理由相反");
CHECK(g_ns_put_calls == 0, "TC-NETSOCK-225b 拒绝发生在入队之前");
CHECK(net_recv(fd, NULL, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225c buf 为 NULL → INVAL");
CHECK(net_recv(fd, buf, 0, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225d len<=0 → INVAL");
CHECK(net_send(fd, NULL, 4, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225e send 的 buf 为 NULL → INVAL");
CHECK(net_send(fd, buf, -1, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225f send 的 len<0 → INVAL");
CHECK(g_chip.write_calls == 0 && g_chip.read_calls == 0 &&
g_peers.pending_reg_calls == 0 && g_peers.send_reg_calls == 0,
"TC-NETSOCK-225g 参数非法路径上零芯片事务、零挂起登记");
reset_all();
}
/* TC-NETSOCK-230 CLOSE:先结束挂起再关芯片,且 last_peer_port 跨释放保留 */
static void tc_230_close_order_and_peer(void)
{
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
/* 登记一条挂起 recv,再看 close 是否先把它结算掉(排空/取消都必通知) */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = fd;
msg.buf = (void *)"x";
msg.len = 1;
msg.caller = (TaskHandle_t)&g_ns_task_net; /* 另一个任务在等这条流 */
(void)ns_rtos_q_put_raw(&msg);
net_process_messages(); /* 走登记路径(不发回执) */
CHECK(g_peers.pending_reg_calls == 1 && g_ns_notify_calls == 0,
"TC-NETSOCK-230a 前置:已登记挂起且未回执");
/* 芯片侧此刻仍是 OPEN:close 才需要真去拆(已 CLOSED 的形态见 TC-NETSOCK-232) */
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_ns_notify_calls = 0;
{
s_close_base_clear = g_peers.listener_clear_calls;
s_close_base_settle = g_peers.pending_settle_calls;
}
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_CLOSE;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_settle_calls == s_close_base_settle + 1,
"TC-NETSOCK-230b close 必先结算本 fd 的挂起 recv(否则等待方永久悬挂)");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-230c 结算必通知(取消也要通知,不是静默清槽)");
CHECK(g_chip.close_calls >= 1 && g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-230d 芯片仍 OPEN → 走 sock_reset 确定性排渣把它拆掉");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-230e close 全程不得读全局中断寄存器(B3:唯一读者=net_poll)");
CHECK(s_net_socks[fd].in_use == 0U && s_net_socks[fd].last_peer_port == 51999U,
"TC-NETSOCK-230f 控制块清零但 last_peer_port 保留(幽灵甄别基准属芯片 Socket 级)");
CHECK(g_peers.listener_clear_calls == s_close_base_clear + 1,
"TC-NETSOCK-230g close 幂等清理监听重试(关闭即取消待重试,D10)");
CHECK(g_peers.pend_active_now[fd] == 0,
"TC-NETSOCK-230h close 后本 fd 不再有挂起 recv(两张表都归零)");
reset_all();
}
/* TC-NETSOCK-231 CLOSE 未在用:net_get_sock 判空 → BADF 通知值 */
static void tc_231_close_badfd(void)
{
net_msg_t msg;
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_CLOSE;
msg.sockfd = 5;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-231a close 未分配的 fd → BADF 经通知值回传");
CHECK(g_chip.close_calls == 0, "TC-NETSOCK-231b BADF 不碰芯片");
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_ACCEPT; /* 没有 in_use 的监听 */
msg.sockfd = 5;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-231c accept 未在用 → INVAL(控制块级校验,不是 fd 越界)");
reset_all();
}
/* TC-NETSOCK-240 DROP_CONN:四类前置各走各的分支(幂等语义是关键) */
static void tc_240_drop_conn_branches(void)
{
net_msg_t msg;
int fd;
/* 非在用 → BADF */
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = 2;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-240a 未分配 → BADF");
/* UDP → INVAL */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-240b 非 TCP 流 → INVAL(本 API 只拆 TCP 会话)");
/* 监听态但无活动会话 → 幂等 0,不动芯片 */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].auto_relisten = 1U;
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == 0U, "TC-NETSOCK-240c 无活动会话 → 幂等成功 0");
CHECK(g_chip.close_calls == 0 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240d 幂等分支不得拆芯片(会把在听的监听毁掉)");
/* ESTABLISHED + 监听型 → QUARANTINE 拆除 + 登记重建 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_settle_calls == 1 && g_peers.send_cancel_calls == 1,
"TC-NETSOCK-240e 拆会话前先结束两张表的挂起(必通知)");
CHECK(g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-240f 拆除走 sock_reset 确定性排渣(不是 close+盲等)");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-240g 监听型 fd 拆完登记 REQ_DROP(重建交监听层,本层不自己 open)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CLOSED &&
s_net_socks[fd].in_use == 1U && s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].auto_relisten == 1U,
"TC-NETSOCK-240h 只收口会话簿记:fd 保留、监听属性与端口都留下");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-240i 拆除路径也只碰本 Socket(F-NET-7:不读全局)");
reset_all();
}
/* TC-NETSOCK-250 入队失败(队列满)→ 调用方 BUSY,不留下"已提交"的假象 */
static void tc_250_put_fail_busy(void)
{
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
ns_rtos_q_fill((int)NS_Q_CAP); /* 把队列灌满 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, "abc", 3, 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250a 队列满 → net_send 报 BUSY(不是静默丢弃)");
CHECK(g_chip.write_calls == 0 && g_ns_notify_calls == 0,
"TC-NETSOCK-250b 入队失败时零芯片事务、零通知(调用方不会误等回执)");
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250c 同一形态对 net_listen 成立");
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250d 同一形态对 net_close 成立");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250e 同一形态对 net_drop_conn 成立");
CHECK(net_recv_to(fd, (void *)"x", 1, 5U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250f 同一形态对 net_recv_to 成立");
reset_all();
}
static void ns_run_s3(void)
{
tc_200_unknown_type();
tc_201_ticks_run_before_dispatch();
tc_202_no_queue_bound();
tc_210_send_end_to_end();
tc_211_send_chunking();
tc_212_send_pending_defers_notify();
tc_213_send_error_code_roundtrip();
tc_220_recv_fast_path();
tc_221_recv_wouldblock();
tc_222_recv_eof_relisten();
tc_223_recv_pending_roundtrip();
tc_224_recv_to_timeout_code();
tc_225_recv_bad_args();
tc_230_close_order_and_peer();
tc_231_close_badfd();
tc_240_drop_conn_branches();
tc_250_put_fail_busy();
}