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

610 lines
31 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.
/*
* 分片 5:建流三件套(listen / accept / connect)的锁定版决策 + net_init 下发面
* + UDP 数据报路径 + netTask 直调入口
*
* 为什么值得单独一片:这三条是「真机极难按需复现」的决策分支——open 被芯片拒
* (USE)、LISTEN 命令失败、TCP_CONNECT 被命令级拒绝、CONNECT 事件未收到的补 accept、
* 对端「发完就 FIN」的 closed-with-readable,在台架上要凑齐对端行为+时序+总线状态
* 才见得到一次;而每一条的错误形态都是「静默毁掉监听」或「数据被吞」(TRAP-NET-09/
* -12/-18/-20 与 E14 复核实验的原文结论)。
*
* 创建日期:2026-10-24
*/
/* 断线注入点:第二次轮询时把状态打回 CLOSED(模拟 net_poll 处理 DISCONNECT 之后的形态)。
* 用假内核现成的「每轮 net_poll 回调」注入并发事件,而不是自己发明时间或事件 API。 */
static int s_ns_poll_seen;
static void ns_drop_conn_on_second_poll(void)
{
s_ns_poll_seen++;
if (s_ns_poll_seen == 2) {
s_net_socks[0].state = NET_SOCK_STATE_CLOSED;
s_net_socks[0].session_dead = 1U;
}
}
/* TC-NETSOCK-400 net_init 的下发面与门控(一次调用钉十条契约) */
static void tc_400_net_init_downlink(void)
{
uint8_t mac_zero[6] = {0, 0, 0, 0, 0, 0};
uint8_t mac_real[6] = {0x02, 0x11, 0x22, 0x33, 0x44, 0x55};
int i;
/* 1) 芯片不在位:直接失败,且不得继续下发任何参数 */
reset_all();
g_fx.exist_rc = (int)CH395F_STATUS_NOT_DETECTED;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
"TC-NETSOCK-400a chip not present -> -1");
CHECK(g_fxchip.reset_calls == 0 && g_fxchip.init_calls == 0,
"TC-NETSOCK-400b when absent it must not reset or initialise (do not drag the error into the stack)");
CHECK(g_ns_error_count == 1, "TC-NETSOCK-400c an absent chip must leave an [ERR] line (else only a dead port is left to observe)");
/* 2) 协议栈初始化失败 */
reset_all();
g_fx.init_rc = (int)CH395F_STATUS_ERROR;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
"TC-NETSOCK-400d ch395f_init failure -> -1");
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e after an init failure PING must not be enabled (half-configured state)");
/* 3) 版本门控:低于 0x46 必须**拒置** bit3(SOCKET_CLOSE),退回芯片自动关断 */
reset_all();
g_fx.version = 0x45U;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
"TC-NETSOCK-400f a too-old version is not fatal: bring-up continues");
CHECK(g_fx.fun_para == 0U && g_ns_error_count == 1,
"TC-NETSOCK-400g ver<0x46 -> FUN_PARA actually sends 0 (bit3 refused) plus [ERR], never silently degraded");
/* 4) 版本够新:FUN_PARA=0x08;bit1(TCP_SERVER 多连接) 与 bit0(保留) 都必须为 0 */
reset_all();
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
"TC-NETSOCK-400h normal initialisation succeeds");
/* bit3=SOCKET_CLOSE 要置;bit1=TCP_SERVER 多连接(本项目单连接模型)与
* bit0=保留位都必须为 0——旧实现把 0x09 当「多 Socket 模式」下发,2026-10-11
* 定案为 0x08(bit0 是保留位,证据链 TRAP-NET-15)。所以掩码要考 bit0|bit1,
* 拿 0x09 考会把我们正想要的 bit3 一起算进去(第一版就这么写错了)。 */
CHECK(g_fx.fun_para == CH395F_FUN_PARA_SOCKET_CLOSE &&
(g_fx.fun_para & 0x03UL) == 0UL,
"TC-NETSOCK-400i FUN_PARA sets bit3 only, bit0/bit1 are 0 (0x09 was a historical mistake, TRAP-NET-15)");
CHECK(g_fx.mss == (uint16_t)CH395F_TCP_MSS && g_fx.retrans_cnt == 8U &&
g_fx.retrans_period == 200U,
"TC-NETSOCK-400j MSS and retransmit parameters come from net_config.h truth (8 tries / 200ms, P0-a)");
CHECK(g_fx.ka_idle == 60000U && g_fx.ka_intvl == 5000U && g_fx.ka_cnt == 3U,
"TC-NETSOCK-400k KeepAlive 60s/5s/3 and it must be issued before ch395f_init");
CHECK(g_fx.mss != 0U || g_fx.ka_idle != 0U ? 1 : 0, "TC-NETSOCK-400k2 parameters really were issued");
CHECK(g_chip.log_n == 0 ||
ch395f_fake_log_find('n') > ch395f_fake_log_find('Q'),
"TC-NETSOCK-400l SET-class commands (MSS/KeepAlive) come before INIT_CH395 (manual hard requirement)");
/* 5) IP/掩码/网关三样都按点分顺位下发,并缓存本机 IP 供 getsockname 用 */
CHECK(g_fx.ip[0] == 192U && g_fx.ip[1] == 168U && g_fx.ip[2] == 1U &&
g_fx.ip[3] == 100U && g_fx.gw[3] == 1U && g_fx.mask[0] == 255U,
"TC-NETSOCK-400m IP, gateway and mask follow the parse_ip_string order (segment 0 lands at [0])");
CHECK(g_fxchip.set_dhcp_calls == 0 && g_fx.dhcp_enable == 0U,
"TC-NETSOCK-400n with a static IP given, DHCP is never enabled");
/* 6) ip=NULL → 走 DHCP,不下发静态三样 */
reset_all();
(void)net_init(NULL, NULL, NULL, NULL);
CHECK(g_fxchip.set_dhcp_calls == 1 && g_fx.dhcp_enable == 1U,
"TC-NETSOCK-400o ip=NULL -> enables DHCP");
CHECK(g_fxchip.set_ip_calls == 0 && g_fxchip.set_gw_calls == 0 &&
g_fxchip.set_mask_calls == 0, "TC-NETSOCK-400p the DHCP form issues no static address at all");
/* 7) MAC:全零=用芯片内置,不下发;非零必须下发(写 EEPROM,须在 INIT 之前) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_zero);
CHECK(g_fxchip.set_mac_calls == 0, "TC-NETSOCK-400q all-zero MAC -> keep the chip built-in one, do not issue it");
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_real);
CHECK(g_fxchip.set_mac_calls == 1 && g_fx.mac[0] == 0x02U && g_fx.mac[5] == 0x55U,
"TC-NETSOCK-400r a non-zero MAC is issued (device online identity stays consistent with the UI)");
CHECK(ch395f_fake_log_find('M') < ch395f_fake_log_find('n') ||
ch395f_fake_log_find('M') < 0,
"TC-NETSOCK-400s SET_MAC comes BEFORE INIT_CH395 (TRAP-NET-19: reversed order does not take effect this power cycle)");
/* 8) 收发缓冲块布局:6 块/Socket,先配数据 Socket 再配监听 Socket 0(TRAP-NET-01) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(g_fxchip.set_recv_buf_calls == NET_MAX_SOCKETS &&
g_fxchip.set_send_buf_calls == NET_MAX_SOCKETS,
"TC-NETSOCK-400t all 8 sockets are explicitly reallocated (power-on defaults only cover 0-3)");
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (g_fx.recv_blk_start[i] != (uint8_t)(i * 6) ||
g_fx.recv_blk_cnt[i] != (uint8_t)CH395F_RECV_BLOCKS ||
g_fx.send_blk_start[i] != (uint8_t)(i * 6 + (int)CH395F_RECV_BLOCKS) ||
g_fx.send_blk_cnt[i] != (uint8_t)CH395F_SEND_BLOCKS) {
break;
}
}
CHECK(i == NET_MAX_SOCKETS,
"TC-NETSOCK-400u layout recv=i*6 / send=recv+4, socket 7 uses exactly blocks 0..47 (manual 5.45)");
CHECK(g_fx.ping_enable == 1U && g_fxchip.ping_calls == 1, "TC-NETSOCK-400v PING response enabled");
/* 9) net_init 的第一件事是取消挂起并通知调用方(顺序错了 = 调用方永久挂死) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(g_peers.pending_cancel_all_calls == 1 &&
g_peers.send_cancel_all_calls == 1,
"TC-NETSOCK-400w both pending tables are cancelled before a rebuild (recv and send each one, missing one strands a task)");
CHECK(g_peers.last_pend_cancel_err == NET_ERR_CONNRESET &&
g_peers.last_snd_cancel_err == NET_ERR_CONNRESET,
"TC-NETSOCK-400x cancellation reason = CONNRESET");
CHECK(g_peers.pending_capture_calls == 1,
"TC-NETSOCK-400y registers the executing task (the pending self-wait guard judges who is executing from it)");
reset_all();
}
/* TC-NETSOCK-410 net_listen_locked:open 重试、清事件时序、失败对称关断 */
static void tc_410_listen_locked(void)
{
struct net_sockaddr_in in;
int fd;
/* 非 TCP 流 → INVAL */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-410a UDP cannot listen");
/* 状态不对(还没 bind 也没到 CREATED)→ INVAL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-410b listening again on an established fd -> INVAL (never demote a data channel to a listener)");
/* 成功路径:BOUND → LISTENING,并启用 auto_relisten */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(2404);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-410c bind precondition");
ch395f_fake_clear_log();
CHECK(net_listen_locked(fd) == 0, "TC-NETSOCK-410d listen succeeds");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING &&
s_net_socks[fd].auto_relisten == 1U && s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-410e enters LISTENING with auto_relisten=1 (BSD listener stays resident) and debounce cleared");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 2404U,
"TC-NETSOCK-410f source port comes from the control block local_port (the port must not drift)");
CHECK(ch395f_fake_log_find('i') >= 0 &&
ch395f_fake_log_find('i') < ch395f_fake_log_find('l'),
"TC-NETSOCK-410g clearing this socket events comes BEFORE TCP_LISTEN (E14: reversed swallows the new CONNECT)");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-410h listen must never read the global interrupt register (exactly what the B3 audit removed)");
/* open 一直失败:重试 3 次,每次都走确定性排渣,最后 BUSY */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-410i open still failing after 3 tries -> BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.rst_begin_calls == 3,
"TC-NETSOCK-410j three opens plus three sock_reset (TRAP-NET-09 retry semantics kept, not a blind 500ms wait)");
CHECK(g_chip.listen_calls == 0, "TC-NETSOCK-410k with no successful open, never issue the LISTEN command");
/* LISTEN 命令失败:open 已成的 Socket 必须关回去(U12 对称,否则卡 OPEN 无监听) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.listen_rc[fd] = CH395F_ERR_VAL;
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-410l TCP_LISTEN failure -> BUSY");
CHECK(g_chip.close_calls == 1 && g_chip.open_calls == 1,
"TC-NETSOCK-410m a failed LISTEN must close the opened socket (else every later open returns USE)");
CHECK(s_net_socks[fd].state != NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-410n failure must not falsely report LISTENING");
reset_all();
}
/* TC-NETSOCK-411 公开 net_listen:越界 / 队列 / 结果恢复 */
static void tc_411_listen_public(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
CHECK(net_listen(-1) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-411a out of range -> BADF");
CHECK(net_listen(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-411b upper bound out of range -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(7003);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-411c bind");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_listen(fd) == 0, "TC-NETSOCK-411d end-to-end listen succeeds (enqueue, dispatch, notify value 0)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-411e the public entry really drove the chip-side listener");
CHECK(g_ns_notify_last_val == 0U,
"TC-NETSOCK-411f a successful listen notify value is 0 (the 0x70000000 sentinel must not leak)");
/* 已监听的 fd 再 listen:netTask 里 INVAL,经通知值回传 */
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-411g listening twice -> -1 with errno INVAL (exact code, not a generic -1)");
reset_all();
}
/* TC-NETSOCK-420 accept:单连接模型的四种形态 */
static void tc_420_accept_shapes(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
/* 1) 已有可接受连接:返回的就是监听 fd 自身(单连接),并回填对端 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_accept(fd, sa, &addrlen) == fd, "TC-NETSOCK-420a single connection: accept returns the listening fd itself");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 51999U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-420b fills the peer address and length (lftpd prints who connected from this)");
CHECK(g_peers.lost_probe_calls == 0,
"TC-NETSOCK-420c non-LISTENING never calls the missed-accept probe (the probe is listener-only self-rescue)");
/* 2) LISTENING 且探针命中 → 接受(CONNECT 事件未收到的自愈路径) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].local_port = 2404U;
s_net_socks[fd].remote_port = 55555U;
g_peers.lost_probe_hit = 1;
addrlen = 0;
CHECK(net_accept(fd, NULL, NULL) == fd,
"TC-NETSOCK-420d the probe moved the listener to ESTABLISHED -> accept succeeds at once (N-30 root fix)");
CHECK(g_peers.lost_probe_calls == 1,
"TC-NETSOCK-420e only LISTENING calls the probe (once per accept round, with debounce)");
/* 3) LISTENING 且无连接 → WOULDBLOCK,且不动监听重建 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-420f no connection -> WOULDBLOCK (never blocks netTask)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420g while the listener is alive, never register a rebuild (that is the U12 accident shape)");
/* 4) CLOSED + auto_relisten + RX 有残留 → 接受"已关但可读",先排空再 EOF */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 21U;
s_net_socks[fd].remote_port = 44444U;
ch395f_fake_push_recv_len((uint8_t)fd, 1027U);
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, sa, &addrlen) == fd,
"TC-NETSOCK-420h closed-with-readable: accept this connection to drain it (N-30, reversed yields 425)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420i with leftover data present, do NOT register a rebuild (relisting first destroys arrived data)");
CHECK(g_chip.get_status_calls == 0,
"TC-NETSOCK-420j the drain decision only reads GET_RECV_LEN, no status query");
/* 5) CLOSED + auto_relisten + RX 空 → 登记 REQ_ACCEPT 并 WOULDBLOCK */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 21U;
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-420k CLOSED with no leftover -> WOULDBLOCK");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_ACCEPT &&
g_peers.last_req_fd == fd,
"TC-NETSOCK-420l only a consumer hitting empty registers an immediate rebuild (N-32 P2: removing it widens the CLOSED window)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0,
"TC-NETSOCK-420m the registration path costs zero chip transactions (rebuild lives only in net_listener_tick)");
/* 6) 非监听型 CLOSED(客户端)→ 不擅自登记 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_accept(fd, sa, &addrlen) == -1 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-420n CLOSED on a client fd registers no listener rebuild");
reset_all();
}
/* TC-NETSOCK-421 accept 的公开入口边界 */
static void tc_421_accept_public(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
bring_up();
CHECK(net_accept(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-421a out of range -> BADF");
fd = mk_established(NET_SOCK_STREAM);
addrlen = 0;
CHECK(net_accept(fd, sa, NULL) == fd,
"TC-NETSOCK-421b accept still works with addrlen NULL (address fill skipped, no crash)");
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, NULL, &addrlen) == fd,
"TC-NETSOCK-421c addr NULL: returns just the fd and fills no address");
reset_all();
}
/* TC-NETSOCK-430 connect:地址簿记、open 重试、命令被拒回滚 */
static void tc_430_connect(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
CHECK(net_connect(-1, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-430a out of range -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_connect(fd, NULL, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430b addr=NULL -> INVAL (arguments blocked before enqueue)");
CHECK(net_connect(fd, (struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430c addrlen too small -> INVAL");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-430d invalid arguments create no message (netTask is not occupied)");
/* 成功:地址簿记 + 大端顺位 + 状态 CONNECTING */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430e connect command issued (asynchronous, the interrupt decides success)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING,
"TC-NETSOCK-430f local state moves to CONNECTING");
CHECK(s_net_socks[fd].remote_port == 8888U,
"TC-NETSOCK-430g peer port stored in bookkeeping as host order");
CHECK(g_fx.des_ip[fd][0] == 192U && g_fx.des_ip[fd][1] == 168U &&
g_fx.des_ip[fd][2] == 1U && g_fx.des_ip[fd][3] == 200U,
"TC-NETSOCK-430h destination IP issued as a big-endian array (the order set_des_ip needs, reversed means no link)");
CHECK(g_chip.set_port_calls == 1 && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-430i source port issued once (SET_SOUR_PORT), the peer port goes via SET_DES_PORT=8888");
CHECK(g_fxchip.sock_events_calls == 1 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-430j clearing stale interrupts only clears this socket (F-NET-7: the old extra global read welded others shut)");
CHECK(ch395f_fake_log_find('v') < ch395f_fake_log_find('T') ||
ch395f_fake_log_find('v') < 0,
"TC-NETSOCK-430k clearing events precedes TCP_CONNECT (the two-stage pre-clear of TRAP-NET-12/-18)");
/* 命令级被拒(非 BUSY):必须回滚,不能停在 CONNECTING 干等 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fx.connect_rc[fd] = CH395F_ERR_VAL;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430l chip refused the command -> -1");
CHECK(g_chip.close_calls == 1,
"TC-NETSOCK-430m after a refusal the manual requires reopening from OPEN, so the socket is closed");
CHECK(s_net_socks[fd].session_dead == 1U &&
s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-430n a command-level failure also ends the session: sets session_dead and CLOSED (B2, else pending recv waits forever)");
CHECK(g_ns_error_count >= 1, "TC-NETSOCK-430o a refusal must leave an [ERR] line");
/* BUSY 只是命令仍在执行,不得判失败 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fx.connect_rc[fd] = CH395F_ERR_BUSY;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430p BUSY means the chip is still executing -> keep the old behaviour and let the interrupt decide (do not kill a slow chip)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING && g_chip.close_calls == 0,
"TC-NETSOCK-430q BUSY neither rolls back nor closes the socket");
/* open 三次失败 → BUSY,且不发 CONNECT */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-430r open failing 3 times -> BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.connect_calls == 0,
"TC-NETSOCK-430s with no successful open, never issue TCP_CONNECT");
/* 非 TCP 流 / 未在用 → INVAL(netTask 侧再判一次) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430t a UDP slot never takes the TCP connect path");
CHECK(net_connect_locked(7, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430u in range but not in use -> INVAL (in_use is checked with the control block; BADF is only for range)");
CHECK(net_connect_locked(NET_MAX_SOCKETS, (const struct net_sockaddr *)&in,
(int)sizeof(in)) == -1, "TC-NETSOCK-430v out of range -> BADF");
reset_all();
}
/* TC-NETSOCK-440 UDP 数据报:sendto 分块 / recvfrom 剥 8 字节报文头 */
static void tc_440_udp_datagram(void)
{
static char big[2100];
char buf[64];
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
bring_up_netctx();
CHECK(net_sendto(-1, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440a sendto out of range -> BADF");
CHECK(net_sendto(0, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440b not in use -> BADF");
CHECK(net_recvfrom(0, buf, sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-440c recvfrom on an unused fd -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_sendto(fd, NULL, 5, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-440d sendto invalid arguments -> INVAL");
CHECK(net_recvfrom(fd, NULL, 5, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-440e recvfrom with a NULL buf -> INVAL");
/* 带目的地址:先设 DesIP/DesPort 再写数据(不带上限的自旋实现,健康链路一次写完) */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.9");
CHECK(net_sendto(fd, "hello", 5, 0, sa, (int)sizeof(in)) == 5,
"TC-NETSOCK-440f sendto returns the number of bytes sent");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-440g the destination address travels with this send (UDP is connectionless, address is per packet)");
CHECK(g_chip.write_calls == 1 && s_net_socks[fd].send_bytes == 5U,
"TC-NETSOCK-440h small packet written in one go plus statistics");
/* 大报分块(仍受 NET_SEND_CHUNK_MAX 上界约束) */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
CHECK(net_sendto(fd, big, 2100, 0, NULL, 0) == 2100, "TC-NETSOCK-440i all 2100 bytes delivered");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-440j UDP also chunks at the 1024 bound (the FIFO overrun constraint matches TCP)");
/* recvfrom:前 8 字节是芯片 UDP 头(长度/srcPort/srcIP),要剥掉再交出去 */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
ch395f_fake_push_recv_len((uint8_t)fd, 8U + 20U);
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen) == 20,
"TC-NETSOCK-440k returns 20: the 8-byte report header was stripped, the header is not handed over as data");
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
"TC-NETSOCK-440l fills the source address structure and length");
/* 无数据 + 非阻塞 → WOULDBLOCK(阻塞形态是自旋无预算,本套刻意不测,见交付说明) */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440m non-blocking with no data -> WOULDBLOCK");
s_net_socks[fd].blocking = 0U;
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440n the non-blocking attribute follows the same decision");
/* 短于 8 字节的残包:不剥头,整包交出去(钉当前实现的边界,不猜"应该怎么算") */
reset_all();
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
ch395f_fake_push_recv_len((uint8_t)fd, 5U);
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == 5,
"TC-NETSOCK-440o with recv_len<8 no header is stripped, the whole packet is returned (current implementation)");
reset_all();
}
/* TC-NETSOCK-450 netTask 直调入口的 NULL 守卫与委托 */
static void tc_450_direct_entries(void)
{
bring_up();
CHECK(net_send_sock(NULL, "x", 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450a net_send_sock(NULL) -> BADF");
CHECK(net_recv_sock(NULL, (void *)"x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450b net_recv_sock(NULL) -> BADF");
CHECK(net_close_sock(NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450c net_close_sock(NULL) -> BADF");
/* netTask 内直调 send:挂起登记会被"执行者自等待"守卫拒 → 返回 -1(BUSY) */
reset_all();
bring_up();
{
int fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
g_peers.send_register_rc = -1; /* 复刻守卫:netTask 自己发的必被拒 */
CHECK(net_send_sock(&s_net_socks[fd], "hello", 5) == -1,
"TC-NETSOCK-450d inside netTask a stalled buffer never registers pending (nobody can complete it) -> -1");
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-450e the registration call really happened (the refusal came from the register side, not from us not trying)");
}
reset_all();
}
/* TC-NETSOCK-460 复位收口超时:芯片永远关不掉时上层必须有界退出(不得无限占用 netTask) */
static void tc_460_reset_sync_timeout_bound(void)
{
uint32_t t_before;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_keep_open[fd] = 1U; /* 复位机怎么努力芯片都不落回 CLOSED */
t_before = g_ns_tick;
CHECK(net_close(fd) == 0,
"TC-NETSOCK-460a even when the chip cannot be closed, close proceeds (fd-side bookkeeping must still close out)");
CHECK((g_ns_tick - t_before) <= 700U,
"TC-NETSOCK-460b the reset wait has a hard 600ms ceiling (without one a bad chip drags netTask down)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-460c a reset timeout must leave [ERR]: that line is the only way to ever notice it");
CHECK(s_net_socks[fd].in_use == 0U,
"TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)");
reset_all();
/* drop_conn 同一条超时线 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_keep_open[fd] = 1U;
t_before = g_ns_tick;
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 700U &&
g_ns_error_count >= 1,
"TC-NETSOCK-460e drop_conn takes the same bounded close-out and logs it too");
reset_all();
}
/* TC-NETSOCK-470 发送途中的并发事件:断线要按短写交出已发量,不能假装全成功 */
static void tc_470_send_disconnect_midway(void)
{
static char big[2100];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* 第一块写得进去,第二块等待期间对端断线(net_poll 处理 DISCONNECT 把状态打回 CLOSED) */
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_DISCONNECT;
g_ns_on_poll = ns_drop_conn_on_second_poll;
s_ns_poll_seen = 0;
rc = net_send(fd, big, 2100, 0);
CHECK(rc >= 0 && rc < 2100,
"TC-NETSOCK-470a a mid-send disconnect only truncates: returns bytes already sent (a short write), not -1 and not a false 2100");
CHECK((int)s_net_socks[fd].send_bytes == rc,
"TC-NETSOCK-470b byte statistic matches what was actually delivered (what the peer got cannot be clawed back)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-470c a short write must log [ERR] (a silent truncation once made the peer see zero bytes from a UI push)");
reset_all();
}
static void ns_run_s5(void)
{
tc_400_net_init_downlink();
tc_410_listen_locked();
tc_411_listen_public();
tc_420_accept_shapes();
tc_421_accept_public();
tc_430_connect();
tc_440_udp_datagram();
tc_450_direct_entries();
tc_460_reset_sync_timeout_bound();
tc_470_send_disconnect_midway();
}