Files
STM32F4-Base/test/host/net_socket/ns_s3_msg.c
T
wandering1 924ac3d428 test(host): 断言字面量统一纯 ASCII,30 文件 1535 行
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 21:00:17 +08:00

614 lines
26 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 raw message enqueued");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-200b enqueue itself produces no notification");
net_process_messages(); /* 以 netTask 身份跑一轮 */
CHECK(g_ns_notify_calls == 1, "TC-NETSOCK-200c unknown type must still send a receipt (else the caller hangs forever)");
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR,
"TC-NETSOCK-200d notify value for unknown type = this message exact code NET_ERR, not a leftover");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-200e queue drained within this round (non-blocking while semantics)");
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 even with an empty queue all four self-heal ticks run (D10/pending x2/reconcile)");
CHECK(g_ns_notify_calls == 0 && g_ns_get_calls == 1,
"TC-NETSOCK-201b empty queue: one peek then exit, no notification");
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 without a bound queue returns at once (no chip, no ticks)");
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 returns the number of bytes actually written");
CHECK(g_chip.write_calls == 1 && g_chip.last_read_len == (uint16_t)sizeof(buf),
"TC-NETSOCK-210b one full write (a healthy path neither chunks nor goes pending)");
CHECK(s_net_socks[fd].send_bytes == sizeof(buf), "TC-NETSOCK-210c sent byte statistic accumulates");
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == (uint32_t)sizeof(buf),
"TC-NETSOCK-210d result travels back on the notify value (the whole 32 bits are the result)");
CHECK(g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-210e notify goes to the REGISTERED caller only, not a broadcast");
CHECK(g_ns_block_waits == 1,
"TC-NETSOCK-210f zero-wait found nothing -> blocking branch (second stage of the two-step peek)");
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 all 2500 bytes delivered");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-211b split into 3 chunks at the 1024 bound (1024/1024/452)");
CHECK(g_chip.last_read_len == 452U, "TC-NETSOCK-211c last chunk writes only the remainder");
CHECK(NET_SEND_CHUNK_MAX == 1024,
"TC-NETSOCK-211d chunk size comes from the chip send buffer, not a fake-defined number");
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 message enqueued");
net_process_messages();
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-212b past the spin budget it registers a pending send instead of busy-waiting in 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 registered progress = bytes already written to the chip (0 here), length and caller travel too");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-212d a successful registration NEVER sends an immediate receipt (else it reads as the result)");
CHECK(g_ns_notify_last_to == NULL,
"TC-NETSOCK-212e not a single notification (the 0x70000000 sentinel can not leak into a notify value)");
/* 完成时刻:由挂起 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 receipt only after the pending completes, notify value = bytes actually written");
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 on a not-connected socket: notify value = NET_ERR_NOTCONN (exact code for caller branches)");
/* 端到端:公开 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 in range but unallocated fd: net_get_sock null -> BADF travels on the notify value");
/* 调用方上下文恢复 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 semantics kept for callers: any failure is -1");
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-213d caller reads the exact code from its own errno slot (E-03 plus notify value together)");
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 chip has 1400B but the caller buffer is 10B -> reads 10 (a partial read is legal)");
CHECK(g_chip.read_calls == 1 && g_chip.last_read_len == 10U,
"TC-NETSOCK-220b reads only what the caller offered (never the chip available length)");
CHECK(s_net_socks[fd].recv_bytes == 10U, "TC-NETSOCK-220c received byte statistic accumulates");
CHECK(s_net_socks[fd].readable == 0U,
"TC-NETSOCK-220d reading clears readable (the next RECV_OK interrupt sets it again)");
CHECK(g_peers.pending_reg_calls == 0, "TC-NETSOCK-220e data is already in hand, no pending registration");
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 with no data -> -1 plus WOULDBLOCK");
CHECK(g_peers.pending_reg_calls == 0,
"TC-NETSOCK-221b non-blocking never registers pending (else the caller thinks it waits while it is parked)");
/* 模式属性(net_setblocking(0))与单次标志同判据 */
CHECK(net_setblocking(fd, 0) == 0, "TC-NETSOCK-221c switching to non-blocking mode succeeds");
rc = net_recv(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221d blocking attribute 0 behaves exactly like 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 named entry takes the same path");
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 with no data -> EOF(0), not an error");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_EOF,
"TC-NETSOCK-222b EOF registers REQ_EOF (the rebuild belongs to the listener layer, this layer never touches the chip)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0 && g_chip.close_calls == 0,
"TC-NETSOCK-222c the registration path issues NOT ONE chip transaction (single-owner invariant 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 still EOF after relisten moved it back to LISTENING (N-32 P1 level, else EOF vanishes forever)");
/* 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 EOF on a non-listener fd must not rebuild behind the caller back");
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 blocking with no data -> registers a pending recv (netTask returns with zero occupancy)");
CHECK(g_peers.last_pend_to == 250U,
"TC-NETSOCK-223b the caller explicit budget travels with the registration (0 means infinite, here it must not be 0)");
CHECK(g_peers.last_pend_caller == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-223c completion must know whom to notify");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-223d registering then immediately notifying would pass off registration as the result - pinned absent");
/* 数据到达:由挂起 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 receipt only once data arrives, notify value = byte count");
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 this slice timed out -> dedicated return code -2 (distinct from a real error -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) is refused up front - 0 means infinite wait on the netTask side,"
"which contradicts this API whole reason for having a bounded occupancy");
CHECK(g_ns_put_calls == 0, "TC-NETSOCK-225b the refusal happens before enqueue");
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 with buf NULL -> INVAL");
CHECK(net_send(fd, buf, -1, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225f send with 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 on the invalid-argument path: zero chip transactions and zero pending registrations");
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 precondition: pending registered and no receipt yet");
/* 芯片侧此刻仍是 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 must first settle this fd pending recv (else the waiter hangs forever)");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-230c settling always notifies (a cancel notifies too, not a silent slot clear)");
CHECK(g_chip.close_calls >= 1 && g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-230d chip still OPEN -> sock_reset deterministically drains and tears it down");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-230e close must never read the global interrupt register (B3: sole reader is net_poll)");
CHECK(s_net_socks[fd].in_use == 0U && s_net_socks[fd].last_peer_port == 51999U,
"TC-NETSOCK-230f control block cleared but last_peer_port kept (ghost discrimination is chip-socket level)");
CHECK(g_peers.listener_clear_calls == s_close_base_clear + 1,
"TC-NETSOCK-230g close idempotently clears the listener retry (closing cancels pending retries, D10)");
CHECK(g_peers.pend_active_now[fd] == 0,
"TC-NETSOCK-230h after close this fd has no pending recv (both tables zeroed)");
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 on an unallocated fd -> BADF travels back on the notify value");
CHECK(g_chip.close_calls == 0, "TC-NETSOCK-231b BADF does not touch the chip");
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 on an unused fd -> INVAL (control-block check, not fd range)");
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 unallocated -> 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 not a TCP stream -> INVAL (this API only tears down TCP sessions)");
/* 监听态但无活动会话 → 幂等 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 no active session -> idempotent success 0");
CHECK(g_chip.close_calls == 0 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240d the idempotent branch must not tear down the chip (it would destroy the live listener)");
/* 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 both pending tables are ended before dropping the session (with notification)");
CHECK(g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-240f teardown uses sock_reset deterministic draining, not close plus blind waiting");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)");
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 only session bookkeeping is closed out: fd, listener attribute and port all survive");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-240i teardown only touches its own socket (F-NET-7: never reads the global register)");
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 queue full -> net_send reports BUSY, it does not silently drop");
CHECK(g_chip.write_calls == 0 && g_ns_notify_calls == 0,
"TC-NETSOCK-250b on enqueue failure: zero chip transactions and zero notifications (caller will not wait in vain)");
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250c the same shape holds for net_listen");
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250d the same shape holds for net_close");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250e the same shape holds for net_drop_conn");
CHECK(net_recv_to(fd, (void *)"x", 1, 5U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250f the same shape holds for 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();
}