Files
STM32F4-Base/test/host/net_socket/ns_s5_io.c
T
wandering1 7efa58d9a1
CI / 静态门禁 (pre-commit) (push) Failing after 25s
CI / 主机侧单测 (MSVC) (push) Successful in 48s
network: 台架 IP 迁到 192.168.31.100,并把重复写死的值收进单一真源
分两段(用户裁定 2026-11-02):先做地址迁移,再把同类字面量收敛成
单一真源,以后再改地址只动一处。

一、IP 迁移 192.168.1.100 → 192.168.31.100(gw 同步 → .31.1)
- 工厂默认:Drivers/BSP/NETCFG/net_cfg.c 的 NET_CFG_DEF_IP/_GW
- 注释/文档示例、主机与板级测试、台架 py 脚本、两份 sim_config.ini
- 字节序教学点同步换算 hex:0x6401A8C0 → 0x641FA8C0(内存
  C0 A8 1F 64),旧版示例引文与历史 Trap 记录按冻结纪律不动
- UI 黄金基准:网口设置 4 态 + shot_info 共 5 张 .bin 由
  check_content_pages.py --update 重刷(渲染值 IP 变了,属预期变更)
- 保留判定:客户点表引用(IEC104/继电保护规划)、芯片手册摘录、
  日期冻结的变更记录行、已退役台架档案 MDK-ARM/retracted_bench_*

二、单一真源收口(A+B+C+D,用户批准全做)
- 新建 test/host/bench_defaults.py:台架 py 默认值唯一定义点
  (DEVICE_IP/DEVICE_GW/PC_IP/NET_BCAST),22 个脚本改为引用;
  MDK-ARM 两探针与 scripts/board_ui_nav.py 经 sys.path shim 引入
- test/board/net_test_task.h 新增 TEST_PC_PEER_IP,板测三处
  PHASE*_REMOTE_IP 改引该宏(保留 -D 覆盖);Phase1 断言改
  memcmp 数组比较,数组各只出现一次
- simulator/tests/check_content_pages.py 从 net_cfg.c 正则解析
  NET_CFG_DEF_* 推导期望值(解析失败即报错、不静默兜底),
  脚本内台架 IP 字面量清零
- 装饰性注释去具体值(net_cfg.h/net_socket.h/net_socket.c/
  net_socket_sim.c);字节序教学 hex 对刻意保留为唯一写值点
- 刻意不动:主机单测夹具 IP(逐文件钉住的任意值是正确测试
  实践)、文档正文 IP(那是文档内容本身)

顺带:scripts/gen_task_table.py 规范串清掉「,B5」——编号清退提交
72b4b094 改了受管区没重跑生成器,task-table 门禁因此一直红

验证:产品 @build 0/0;CH395F 套件开关构建 0/0 后还原产品态;
6 套主机单测 rc=0;content_pages(含宏推导路径)/菜单 8 态/
推流位比对全 PASS;bom-drift 99 无漂移;doc-links/coding-standard/
cppcheck 绿;py_compile 23 文件 + --help 冒烟(含 shim 绝对路径)
2026-11-02 14:46:42 +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.31.100", "255.255.255.0", "192.168.31.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.31.100", "255.255.255.0", "192.168.31.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.31.100", "255.255.255.0", "192.168.31.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.31.100", "255.255.255.0", "192.168.31.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] == 31U &&
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.31.100", "255.255.255.0", "192.168.31.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.31.100", "255.255.255.0", "192.168.31.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.31.100", "255.255.255.0", "192.168.31.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.31.100", "255.255.255.0", "192.168.31.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.31.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] == 31U && 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 (sole-reader rule, 网络自愈 §7 I7: 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.31.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();
}