Files
STM32F4-Base/test/host/net_socket/ns_s5_io.c
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00

622 lines
32 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 (relisten settle 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 (relisten settle, 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 (listener lifecycle 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();
/* 探针窗口心跳缺口修复:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回
* CLOSED 也与本调用无关(排渣与等待归 net_listener_tick 的隔离机) */
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) <= 20U &&
g_ns_error_count == 0 && g_peers.listener_settle_calls == 1,
"TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (heartbeat gap fix)");
reset_all();
/* 非监听 client fd 保留旧超时线(同步、600ms 封顶、留痕) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
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-460f client-fd drop keeps the same bounded close-out as net_close 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();
}