Files
STM32F4-Base/test/host/net_socket/ns_s5_io.c
T
wandering1 5a806d8a26 fix(log,net): 处置主机单测照出的 10 条现状,撤销 1 条误报
用户裁定「全部修」。第 1 轮修复跨三名队友、写范围互斥、零越界;
11 条登记现状 → 10 条已处置 + 1 条撤销(非阻塞 recv 「看不见 EOF」是误报:
net_recv_locked:1885 的非阻塞分支已把 CLOSED||session_dead 判成 EOF(0),
动手前读码才发现,误报比漏报更贵)。

存储与日志(App/log/sys_log_mcu.c、soe_store.c、cfg_file_store_mcu.c、sys_log.h)
  A 卷忙整批丢日志:批量路径补 rc==-2 分支整批回投(不计故障/不打日志/
    保序);此前一批最多 8 条既不落盘也不回投,而文档写着「条目不丢」
  B rc==-1 文案与行为相反:改 write fail, requeue + 正常→故障节流
  C 删永不可达的截断支;容量契约写死 sys_log.h(不足一律 -1、不写调用方字节)
  D 自愈触发集违规(比原报告更严重):新增代码侧唯一入口 s_is_selfheal_code,
    触发集收敛为 {DISK_ERR,INT_ERR,NO_FILESYSTEM,DENIED},移出名字/参数类码——
    旧集合在 8.3 同名同因下每轮删一次自己的历史日志且永不自愈,正是存储架构
    §10.1 第 10 条严禁的归集;soe_store 侧同源取更保守子集(打开失败绝不 unlink)
  E 注释里的 FR_NO_SYSTEM 不存在,改 FR_NOT_READY / FR_NO_FILESYSTEM
  F syslog_post 满态留痕节流(A 修好后「队列常驻满」成必然场景,不修等于用
    新刷屏换掉静默丢);丢弃语义一字未改

NET(net_socket.c/.h、net_types.h、net_socket_sim.c、lftpd.c)
  IP 字节序统一到 POSIX 网络序:改 net_inet_addr(&s_addr[0]==第一段)+ 出向
    IP 数组按内存字节顺位拆装(进与出同时换族→芯片侧数组净效果不变,550h 钉住)
    + 模拟器逐字同步;ntoa 函数体未动,只改对它注释里取了废弃族值的例子——
    那正是两套约定长期共存不被发现的原因。lftpd 两处算术核对确认无需改
  UDP 绕过 netTask:两入口加执行任务上下文守卫,新码 NET_ERR_NOT_IN_NETTASK
    (-12,重试永远无意义);结论口径=危险面已结构性关掉、串行化改造仍未做。
    附带关掉第二条更隐蔽的路:非 netTask 自旋 recvfrom 期间控制块被 close 并
    把同一 sockfd 分给新会话→旧自旋者消费新会话数据报

控制台(console_log.c/.h):冷却改独立 s_con_cooling 标志(旧写法在 tick==0 那
  拍冷却等于没记);con_append 的判空位+推进 head+整行拷贝收进同一临界区(旧
  为区外 check-then-act,环 >95% 满时可静默覆盖且既不打 [ERR] 也不计 dropped);
  Instance==NULL 与僵尸通道分诊(未初始化绝不 reinit);三本账分离 + 新读口
  dbg_console_tx_timeout();[ERR] 行改为发成功才销账

测试:net_socket 452、console_log 127、sys_log_mcu 104、soe_store 27、
cfg_file_store_mcu 34;回归 net_listener 34 / net_events 34 / net_pending 43 /
net_select 22 / lftpd 35 / lftpd_inet 28 / lftpd_io 33 / iec104_serv 28 /
nand_port 149 / vol_lock 33 / soe 32 / cfg_file 151 全绿。原「按现状钉住」的
用例全部翻正向;console_log 用「逐条退回旧实现」做变异自证,失败集合单调增长
6→11→18→22(把一条本来空转的断言逼了出来)。
游标抬升:TC-NETSOCK-545→560、TC-SYSLOG-100→107、TC-CONLOG-135→167。

文档:控制台设计说明 §4.1 口径与 §13-O2 销项 + v1.5;运行与自检记录 §4.4/§4.5
四条;存储架构 §10.1 第 10 条推论(指向 s_is_selfheal_code);TRAP-NET-21 后续
条目;BSD 指南新增 §9.0 s_addr 唯一约定;覆盖率口径 §9「第 1 轮修复处置」表 +
后续项 5 条 + 台架复验 7 条 + 新坑 8 条 + 变更记录。

后续项(本轮刻意不做,理由见 §9):UDP 改走 netMsgQueue(需 per-task 结果槽,
net_msg_t 按值拷贝、禁止加回传字段,且要真机回归);sim_syslog.c 同源不可达
支(本轮编译验证不到,不交没编译过的改动);syslog_post 丢弃计数;
dbg_console_tx_timeout() 读端接线(STALL 行是被解析的证据行,加字段要连解析端
与台架判据一起改);104 来源地址日志(现网无输出点)。

gate: @build 0 errors 0 warnings(Code=213720 RO=18860 RW=1204 ZI=131500)。
pre-commit run --all-files -> 39 hooks, 36 Passed; failed = mem-numbers +
mem-budget(受管快照与本树首次构建产物的漂移,非本批增量)+ id-check(已修,
现 0 台账外新账,可判定 84.63% > 基线 82.6%)。实测水位 RW_IRAM1 57.4%、
RW_CCM 87.9%,均低于 92% 红线;RAM/Flash 本批增量为数个 static 变量。
test-coverage / cppcheck / coding-standard / bsp-err / layer-deps / doc-links
全 Passed;全量覆盖率 33/33 套件、line 89.3%、func 92.3%、逐文件下降 0。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 19:43:56 +08:00

610 lines
30 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 芯片不在位 → -1");
CHECK(g_fxchip.reset_calls == 0 && g_fxchip.init_calls == 0,
"TC-NETSOCK-400b 不在位就不再复位/初始化(别把错误拖到协议栈里)");
CHECK(g_ns_error_count == 1, "TC-NETSOCK-400c 芯片不在位必留 [ERR](否则只剩端口不通的现象)");
/* 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 失败 → -1");
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e 初始化失败后不得继续使能 PING(半配置状态)");
/* 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 版本不足不是致命错:继续起来");
CHECK(g_fx.fun_para == 0U && g_ns_error_count == 1,
"TC-NETSOCK-400g ver<0x46 → FUN_PARA 实发 0(bit3 拒置)+ [ERR] 留痕,不静默带病运行");
/* 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 正常初始化成功");
/* 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 只置 bit3,bit0/bit1 为 0(0x09 是历史误值,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/重传参数取 net_config.h 真值(8 次 / 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(且必须在 ch395f_init 之前下发)");
CHECK(g_fx.mss != 0U || g_fx.ka_idle != 0U ? 1 : 0, "TC-NETSOCK-400k2 参数确有下发");
CHECK(g_chip.log_n == 0 ||
ch395f_fake_log_find('n') > ch395f_fake_log_find('Q'),
"TC-NETSOCK-400l MSS/KeepAlive 等 SET 类命令排在 INIT_CH395 之前(手册硬约束)");
/* 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/网关/掩码按 parse_ip_string 顺位下发(段 0 在 [0])");
CHECK(g_fxchip.set_dhcp_calls == 0 && g_fx.dhcp_enable == 0U,
"TC-NETSOCK-400n 指定了静态 IP 就绝不使能 DHCP");
/* 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 → 使能 DHCP");
CHECK(g_fxchip.set_ip_calls == 0 && g_fxchip.set_gw_calls == 0 &&
g_fxchip.set_mask_calls == 0, "TC-NETSOCK-400p DHCP 形态不下发静态地址");
/* 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 MAC 全零 → 保持芯片内置,不下发");
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 非零 MAC 下发(设备在线身份与 UI 显示一致)");
CHECK(ch395f_fake_log_find('M') < ch395f_fake_log_find('n') ||
ch395f_fake_log_find('M') < 0,
"TC-NETSOCK-400s SET_MAC 排在 INIT_CH395 **之前**(TRAP-NET-19:顺序反了本次上电不生效)");
/* 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 8 个 Socket 全部显式重分配(上电默认只给 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 块布局 recv=i*6 / send=recv+4,Socket7 恰好用尽 0~47(手册 §5.45)");
CHECK(g_fx.ping_enable == 1U && g_fxchip.ping_calls == 1, "TC-NETSOCK-400v 使能 PING 响应");
/* 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 重建前两张挂起表都要取消(recv/send 各一张,漏一张就有人永远等)");
CHECK(g_peers.last_pend_cancel_err == NET_ERR_CONNRESET &&
g_peers.last_snd_cancel_err == NET_ERR_CONNRESET,
"TC-NETSOCK-400x 取消原因 = CONNRESET");
CHECK(g_peers.pending_capture_calls == 1,
"TC-NETSOCK-400y 登记执行任务(挂起登记的自等死锁守卫据此判「谁在执行」)");
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 不能 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 已建立的 fd 再 listen → INVAL(不许把数据通道打回监听)");
/* 成功路径: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 前置");
ch395f_fake_clear_log();
CHECK(net_listen_locked(fd) == 0, "TC-NETSOCK-410d listen 成功");
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 进 LISTENING + auto_relisten=1(BSD 监听常驻)+ 去抖计数清零");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 2404U,
"TC-NETSOCK-410f 源端口取控制块的 local_port(端口不能漂)");
CHECK(ch395f_fake_log_find('i') >= 0 &&
ch395f_fake_log_find('i') < ch395f_fake_log_find('l'),
"TC-NETSOCK-410g 清本 Socket 事件排在 TCP_LISTEN **之前**(E14:反了会吞掉新 CONNECT)");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-410h listen 全程不得读全局中断寄存器(B3 审计掉的正是这个)");
/* 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 三次仍失败 → BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.rst_begin_calls == 3,
"TC-NETSOCK-410j 三次 open + 三次 sock_reset(TRAP-NET-09 的重试语义保留,不是盲等 500ms)");
CHECK(g_chip.listen_calls == 0, "TC-NETSOCK-410k open 没成绝不发 LISTEN 命令");
/* 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 失败 → BUSY");
CHECK(g_chip.close_calls == 1 && g_chip.open_calls == 1,
"TC-NETSOCK-410m LISTEN 失败必须 close 已开的 Socket(否则下次 open 恒返 USE)");
CHECK(s_net_socks[fd].state != NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-410n 失败不得谎报 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 越界 → BADF");
CHECK(net_listen(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-411b 上界越界 → 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 端到端 listen 成功(入队→分派→通知值 0)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-411e 公开入口确实驱动了芯片侧监听");
CHECK(g_ns_notify_last_val == 0U,
"TC-NETSOCK-411f 成功的 listen 通知值=0(哨兵 0x70000000 不许漏进来)");
/* 已监听的 fd 再 listen:netTask 里 INVAL,经通知值回传 */
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-411g 重复 listen → -1 且 errno=INVAL(精确码,不是笼统 -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 单连接:accept 返回监听 fd 自身");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 51999U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-420b 回填对端地址与长度(lftpd 打印「谁连进来」就靠这个)");
CHECK(g_peers.lost_probe_calls == 0,
"TC-NETSOCK-420c 非 LISTENING 不触发补 accept 探针(探针是监听态专用自救)");
/* 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 探针把监听转 ESTABLISHED → accept 立即成功(N-30 主根因修复)");
CHECK(g_peers.lost_probe_calls == 1,
"TC-NETSOCK-420e LISTENING 才调探针(每轮 accept 一次,带去抖计数)");
/* 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 无连接 → WOULDBLOCK(不阻塞 netTask)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420g 监听还活着时绝不登记重建(那是 U12 事故的形态)");
/* 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:接受这条连接去排空(N-30 定案,反了就是 425)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420i 有残留数据时**不**登记重建(先 relisten 会把已到达的数据销毁)");
CHECK(g_chip.get_status_calls == 0,
"TC-NETSOCK-420j 排空判据只读 GET_RECV_LEN,不发状态查询");
/* 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 且无残留 → 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 消费者撞空才登记即时重建(N-32 P2:删掉它会拉长 CLOSED 窗)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0,
"TC-NETSOCK-420m 登记路径零芯片事务(重建动作只在 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 客户端 fd 的 CLOSED 不登记监听重建");
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 越界 → BADF");
fd = mk_established(NET_SOCK_STREAM);
addrlen = 0;
CHECK(net_accept(fd, sa, NULL) == fd,
"TC-NETSOCK-421b addrlen 为 NULL 仍可 accept(addr 回填被跳过,不崩)");
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, NULL, &addrlen) == fd,
"TC-NETSOCK-421c addr 为 NULL:只要 fd,不回填地址");
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 越界 → 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(入队之前就把参数挡下)");
CHECK(net_connect(fd, (struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430c addrlen 不足 → INVAL");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-430d 参数非法不产生消息(不占 netTask)");
/* 成功:地址簿记 + 大端顺位 + 状态 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 命令下发成功(异步,成败由中断裁定)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING,
"TC-NETSOCK-430f 本地状态转 CONNECTING");
CHECK(s_net_socks[fd].remote_port == 8888U,
"TC-NETSOCK-430g 对端端口转主机序存簿记");
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 目的 IP 按大端数组下发(set_des_ip 要的顺位,反了就连不上)");
CHECK(g_chip.set_port_calls == 1 && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-430i 源端口下发一次(SET_SOUR_PORT),目的端口另走 SET_DES_PORT=8888");
CHECK(g_fxchip.sock_events_calls == 1 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-430j 清残留中断只清本 Socket(F-NET-7:原「再读清全局」会焊死别人)");
CHECK(ch395f_fake_log_find('v') < ch395f_fake_log_find('T') ||
ch395f_fake_log_find('v') < 0,
"TC-NETSOCK-430k 清事件排在 TCP_CONNECT 之前(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 芯片拒命令 → -1");
CHECK(g_chip.close_calls == 1,
"TC-NETSOCK-430m 被拒要按手册「再次连接需从 OPEN 重开」关掉 Socket");
CHECK(s_net_socks[fd].session_dead == 1U &&
s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-430n 命令级失败同样是本会话结束:置 session_dead+CLOSED(B2:否则挂起 recv 干等)");
CHECK(g_ns_error_count >= 1, "TC-NETSOCK-430o 被拒必留 [ERR]");
/* 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=芯片还在执行 → 维持原行为交给中断裁定(慢芯片不该被误杀)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING && g_chip.close_calls == 0,
"TC-NETSOCK-430q BUSY 不回滚、不关 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 三次失败 → BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.connect_calls == 0,
"TC-NETSOCK-430s open 没成不发 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 UDP 槽位不走 TCP connect");
CHECK(net_connect_locked(7, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430u 范围内但未在用 → INVAL(in_use 与控制块一起考;BADF 只考越界)");
CHECK(net_connect_locked(NET_MAX_SOCKETS, (const struct net_sockaddr *)&in,
(int)sizeof(in)) == -1, "TC-NETSOCK-430v 越界 → 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 越界 → BADF");
CHECK(net_sendto(0, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440b 未在用 → BADF");
CHECK(net_recvfrom(0, buf, sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-440c recvfrom 未在用 → 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 参数非法 → INVAL");
CHECK(net_recvfrom(fd, NULL, 5, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-440e recvfrom 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 成功返回字节数");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-440g 目的地址随本次发送下发(UDP 无连接,地址是每包带的)");
CHECK(g_chip.write_calls == 1 && s_net_socks[fd].send_bytes == 5U,
"TC-NETSOCK-440h 小包一次写完 + 统计");
/* 大报分块(仍受 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 2100B 全部交付");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-440j UDP 也按 1024 上界分块(写穿 FIFO 的约束同 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 返回 20:报文头 8 字节被剥掉,不把头部当数据交给应用");
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
"TC-NETSOCK-440l 回填源地址结构与长度");
/* 无数据 + 非阻塞 → 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 非阻塞无数据 → 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 非阻塞属性同判据生效");
/* 短于 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 recv_len<8 时不剥头,整包返回(当前实现)");
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 netTask 内等不到缓冲时不登记挂起(没人能替它完成)→ -1");
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-450e 确实走到了登记调用(被拒的是登记侧,不是本层没试)");
}
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 芯片关不掉也照样放行 close(fd 侧簿记必须继续收口)");
CHECK((g_ns_tick - t_before) <= 700U,
"TC-NETSOCK-460b 复位等待有 600ms 硬上限(无上限=netTask 被一颗坏芯片拖死)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-460c 复位超时必 [ERR]:这条 [ERR] 是唯一能发现它的信号");
CHECK(s_net_socks[fd].in_use == 0U,
"TC-NETSOCK-460d 超时也照常释放槽位(不因芯片状态卡住本地 fd 表)");
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 也走同一条有界收口并留痕");
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 断线只截断剩余量:按已发数返回(短写),不是 -1 也不是谎报 2100");
CHECK((int)s_net_socks[fd].send_bytes == rc,
"TC-NETSOCK-470b 字节统计与实际交付一致(对端已收到的不能回收)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-470c 短写必 [ERR](静默截断曾让 UI 推流被对端误判为全程无字节)");
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();
}