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

577 lines
27 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.
/*
* 分片 6:边界残支(公开入口的越界/队满、直调入口的参数支、等待环里的其余出口)
*
* 这一片不为「覆盖率好看」而写:每条都对应一条**能被改坏**的分支——越界检查挪到
* 入队之后,调用方就会在 netTask 里等到一个没人处理的 BADF;短写留痕被删掉,
* 上层就会像 2026-09-21 那次 UI 推流一样「对端永久等待剩余字节」而日志全白。
* 唯一一条刻意**不是**真实时序的场景是 TC-NETSOCK-530(一次轮询之间被压了 1s),
* 它在真机上的对应形态是 netTask 被更高优先级任务压住——用大步进时基表达,
* 并在注释里写明它考的是「停滞超时先于自旋预算生效」这条判定顺序。
*
* 创建日期:2026-10-24
*/
/* TC-NETSOCK-500 六个公开入口的越界形态:BADF 必须在**入队之前**由调用方侧挡下 */
static void tc_500_public_bounds(void)
{
struct net_sockaddr_in in;
char buf[8];
int addrlen;
bring_up();
(void)memset(&in, 0, sizeof(in));
addrlen = (int)sizeof(in);
CHECK(net_send(NET_MAX_SOCKETS, buf, 4, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500a net_send 越界 → BADF");
CHECK(net_recv(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500b net_recv 越界 → BADF");
CHECK(net_recv_to(NET_MAX_SOCKETS, buf, 4, 10U) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500c net_recv_to 越界 → BADF");
CHECK(net_close(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500d net_close 越界 → BADF");
CHECK(net_drop_conn(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500e net_drop_conn 越界 → BADF");
CHECK(net_accept(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500f net_accept 越界 → BADF");
CHECK(net_connect(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500g net_connect 越界 → BADF");
CHECK(net_sendto(NET_MAX_SOCKETS, buf, 4, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500h net_sendto 越界 → BADF");
CHECK(net_recvfrom(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500i net_recvfrom 越界 → BADF");
CHECK(g_ns_put_calls == 0,
"TC-NETSOCK-500j 全部越界形态零入队(入队之后才挡 = 让 netTask 处理一个没主的请求)");
reset_all();
}
/* TC-NETSOCK-501 队满时每个公开入口都要报 BUSY(不能有的报、有的静默) */
static void tc_501_all_entries_busy_on_full_queue(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_accept(fd, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501a net_accept 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
(void)memset(&in, 0, sizeof(in));
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501b net_connect 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_recv_to(fd, (void *)"x", 1, 20U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501c net_recv_to 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_recv(fd, (void *)"x", 1, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501d net_recv 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_send(fd, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501e net_send 队满 → BUSY");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-501f 队满路径零通知(调用方直接拿返回值走人)");
reset_all();
}
/* TC-NETSOCK-502 fd 表用尽:NOMEM 且不影响已建立的会话 */
static void tc_502_socket_table_full(void)
{
int i;
int fd;
bring_up();
for (i = 0; i < NET_MAX_SOCKETS; i++) {
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (fd < 0) {
break;
}
}
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-502a 8 个槽位全部可分配");
CHECK(net_socket(NET_AF_INET, NET_SOCK_STREAM) == -1 &&
net_get_errno() == NET_ERR_NOMEM, "TC-NETSOCK-502b 第 9 个 → NOMEM(不越界写表)");
s_net_socks[3].state = NET_SOCK_STATE_ESTABLISHED;
CHECK(net_get_sock(3) != NULL && net_listener_armed(3) == 1,
"TC-NETSOCK-502c 表满不影响已在用会话(alloc 失败只回 NOMEM,不动别人的槽位)");
reset_all();
}
/* TC-NETSOCK-503 net_wait_result 的两段式拾取:先零等待,已有通知就不必再睡 */
static void tc_503_zero_wait_pickup(void)
{
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
/* 预置一条**已挂起**的通知(真机上=上一条消息的回执还没被读走),
* 公开入口应先零等待拾取它,而不是再睡一次(睡过头就是 实测踩过的坑) */
ns_rtos_post_notify((TaskHandle_t)&g_ns_task_caller, (uint32_t)(int)4);
g_ns_block_waits = 0;
rc = net_send(fd, "abcd", 4, 0);
CHECK(rc == 4, "TC-NETSOCK-503a 零等待就拾到通知值 → 直接返回结果");
CHECK(g_ns_block_waits == 0, "TC-NETSOCK-503b 已有通知时绝不进阻塞分支");
CHECK(g_peers.send_reg_calls == 0 && g_chip.write_calls == 0,
"TC-NETSOCK-503c 本次调用没有真的写芯片(拾到的是上一条消息的回执——这就是 实测踩过的形态)");
reset_all();
}
/* TC-NETSOCK-504 直调入口的参数支(这些分支从公开入口不可达,只能直接钉) */
static void tc_504_locked_entry_branches(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(80);
in.sin_addr.s_addr = net_inet_addr("10.1.1.1");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_connect_locked(fd, NULL, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504a connect_locked addr=NULL → INVAL");
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504b connect_locked addrlen 不足 → INVAL");
s_net_socks[fd].in_use = 1U;
CHECK(net_send_locked(&s_net_socks[fd], "x", 1, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN, "TC-NETSOCK-504c 未建立的直调 send → NOTCONN");
CHECK(net_recv_locked(&s_net_socks[fd], (void *)"x", 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-504d 直调 recv 无数据非阻塞 → WOULDBLOCK");
CHECK(net_recv_locked(&s_net_socks[fd], NULL, 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504e 直调 recv buf 空 → INVAL");
reset_all();
}
/* TC-NETSOCK-505 发送途中缓冲再也不腾出来:内层等待也要转挂起,并且末块不许登记 */
static void tc_505_inner_wait_and_last_chunk(void)
{
static char big[2100];
int fd;
int rc;
/* 第一块写得进、之后芯片不再报空闲 → 第二块的**内层**等待转挂起 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SENBUF_FREE; /* 只给一次空闲 */
rc = net_send_locked(&s_net_socks[fd], big, 2100,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == NET_PENDING_REGISTERED,
"TC-NETSOCK-505a 内层等待(写完一块后等发完)超预算也转挂起,不留 in-place 忙等");
CHECK(g_chip.write_calls == 2 && g_peers.last_snd_sent == 2048,
"TC-NETSOCK-505b 登记的进度=已写进芯片的字节数(1024+1024=2048),已发的绝不重发");
reset_all();
/* 末块:全部写完但芯片不回执 → 按完成返回,**不许**登记挂起
* (2026-10-14 台架回归 TC-NET-902 正是登记了却无人完成 → 永久挂死) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
rc = net_send_locked(&s_net_socks[fd], big, 1024,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 1024, "TC-NETSOCK-505c 单块恰好写完 → 返回 1024(完成)");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-505d 末块等待不得登记挂起(sent>=len 的守卫必须挡住)");
reset_all();
}
/* TC-NETSOCK-506 UDP sendto:等待腾出与短写留痕(与 TCP 同一套语义的另一份实现) */
static void tc_506_sendto_stall_and_short(void)
{
static char big[2100];
int fd;
int rc;
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
s_net_socks[fd].send_ready = 0U;
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
/* 每轮只走 1ms:先命中 NET_SEND_TIMEOUT_MS(1s) 之前不会有别出口 → 短写 */
g_ns_poll_tick_inc = 500U;
rc = net_sendto(fd, big, 2100, 0, NULL, 0);
CHECK(rc < 2100 && rc >= 0,
"TC-NETSOCK-506a sendto 停滞只拿到部分:按已发数返回(短写语义与 TCP 一致)");
CHECK(g_ns_error_count >= 2,
"TC-NETSOCK-506b 停滞 [ERR] + 短写 [ERR] 都要留(缺一就退化成静默截断)");
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)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SENBUF_FREE; /* 只给一次 */
g_ns_poll_tick_inc = 500U;
rc = net_sendto(fd, big, 2100, 0, NULL, 0);
CHECK(rc >= 1024 && rc < 2100,
"TC-NETSOCK-506c 第一块已交付、第二块等发完超时 → 返回 1024..2099 之间的实际值");
reset_all();
}
/* TC-NETSOCK-530 停滞超时优先于自旋预算:考判定顺序,不考真实时长 */
static void tc_530_stall_timeout_before_spin(void)
{
static char buf[64];
int fd;
int rc;
/* 一次轮询之间被压了 2s(真机形态:netTask 被更高优先级任务压住),
* 停滞超时(NET_SEND_TIMEOUT_MS=1s)必须**先于**自旋预算(2ms)命中:
* 生产代码里两条判定的先后顺序就是这条断言的内容。 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_poll_tick_inc = 2000U;
rc = net_send_locked(&s_net_socks[fd], buf, (int)sizeof(buf),
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 0,
"TC-NETSOCK-530a 停滞超时按已发数 0 返回(短写形态,不是 -1)");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-530b 停滞超时优先:不进挂起登记(判定顺序被换掉的话这里会登记)");
CHECK(net_get_errno() == NET_ERR_TIMEDOUT,
"TC-NETSOCK-530c errno = TIMEDOUT(供调用方区分停滞与断线)");
reset_all();
}
/* TC-NETSOCK-535 已经写出去一块之后的超时收口(内层等待的另一条出口) */
static void tc_535_inner_timeout_after_chunk(void)
{
static char big[2100];
int fd;
int rc;
/* 第一块写得进(send_ready 初值 1),第二块的**内层**等待直接撞上停滞超时
* (判定顺序:先超时后自旋,所以这里收口在超时支而不是登记挂起) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_poll_tick_inc = 2000U;
rc = net_send_locked(&s_net_socks[fd], big, 2100,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 1024, "TC-NETSOCK-535a 已交付的第一块照样交出(1024),剩余按短写处理");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-535b 停滞超时收口在登记挂起**之前**(换序就会登记一条永远完不成的挂起)");
CHECK(g_chip.write_calls == 1, "TC-NETSOCK-535c 只写了一次芯片:不得把已交付的块重发");
reset_all();
}
/* TC-NETSOCK-536 net_recv_to 的三条回传分支(-2 / -1+errno / >=0) */
static void tc_536_recv_to_three_exits(void)
{
char buf[16];
int fd;
/* >=0:正常拿到数据 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd, 12U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 100U) == 12,
"TC-NETSOCK-536a 有数据 → 返回实际字节数(走 >=0 支)");
/* <0 且非 TIMEDOUT:分派侧 BADF → 恢复 errno 后返回 -1 */
reset_all();
bring_up();
(void)net_socket(NET_AF_INET, NET_SOCK_STREAM); /* 占掉 fd0,用未分配的 fd1 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(1, buf, (int)sizeof(buf), 100U) == -1 &&
net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-536b 真错误 → -1 且 errno 从通知值恢复(与 -2 严格区分)");
/* ==TIMEDOUT:已在 TC-NETSOCK-224 钉过 -2;这里补一条「-2 不改写 errno」 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)net_set_errno(NET_OK);
ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 30U) == -2 &&
net_get_errno() == NET_OK,
"TC-NETSOCK-536c 超时只凭返回码判定,**不改写**错误码槽(本分支刻意不设 errno)");
reset_all();
}
/* TC-NETSOCK-537 fire_event:注册了回调才会被叫到(本层唯一的回调触发点在 SEND_OK) */
static void tc_537_event_callback_path(void)
{
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* handle_send_event 是兄弟模块(net_events)的职责,本层只负责把中断交给它;
* 回调是否触发属 net_events 的断言面(其单测 TC-NETEV 已覆盖),此处只钉
* 「net_poll 不因回调未注册而跳过事件」。 */
s_net_socks[fd].event_cb = NULL;
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SEND_OK;
CHECK(net_poll() == 1, "TC-NETSOCK-537 未注册回调也照常计事件(不因空指针跳过分发)");
reset_all();
}
/* TC-NETSOCK-540 close / drop_conn 的失败结果也要经通知值恢复精确 errno */
static void tc_540_negative_results(void)
{
net_msg_t msg;
int fd;
/* close 的负结果支:net_close_locked 自身恒返 0(复位没成也放行,见 TC-NETSOCK-320h),
* 所以 CLOSE 唯一的负值来自**分派侧**的 net_get_sock 判空 → 通知值 -2 →
* 调用方在自己的槽位里恢复成 BADF。范围合法但未分配的 fd 正好走这条。 */
bring_up();
fd = 3; /* 范围内、未分配 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-540a close 未分配的 fd → -1 且 errno 从通知值恢复成 BADF(不是笼统 NET_ERR)");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-540a2 drop_conn 同一条负值恢复支");
reset_all();
/* drop_conn 同理:用未分配 fd 走分派侧 BADF 后再由调用方恢复 */
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = 1;
msg.caller = (TaskHandle_t)&g_ns_task_net;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-540b drop_conn 分派侧 BADF 经通知值回传");
/* RECV 的 BADF 支(分派侧 net_get_sock 判空) */
reset_all();
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = 4;
msg.buf = (void *)"x";
msg.len = 1;
msg.caller = (TaskHandle_t)&g_ns_task_net;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-540c recv 分派侧 BADF 同样经通知值回传");
reset_all();
}
/* TC-NETSOCK-545 未绑队列时的 sendto/recvfrom/getpeername 等纯本地入口 */
static void tc_545_local_entries_without_queue(void)
{
struct net_sockaddr_in in;
int addrlen = (int)sizeof(in);
reset_all();
CHECK(net_getsockname(0, (struct net_sockaddr *)&in, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-545a 未初始化也能安全报 BADF(不崩)");
CHECK(net_getpeername(0, (struct net_sockaddr *)&in, &addrlen) == -1,
"TC-NETSOCK-545b getpeername 同上");
CHECK(net_inet_addr("1.2.3.4") != 0U, "TC-NETSOCK-545c 纯函数不依赖初始化");
reset_all();
}
/* ============= TC-NETSOCK-550 s_addr 同族一致性(字节序统一后的收口断言) =============
*
* 这次字节序统一的实质不是「给 inet_addr 换个数值」,而是「全仓只剩一套 s_addr 约定」。
* 所以这组断言全部写在**跨函数的接缝**上:解析→存储→芯片下发→回读→打印,
* 每一环都拿同一句点分十进制做基准。任何一处又引入第二套顺位,这里必红。
*/
/* TC-NETSOCK-550 解析 → 存储 → 打印 闭合(getsockname / getpeername 同族可比对) */
static void tc_550_family_roundtrip(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char txt[16];
int addrlen;
int fd;
bring_up();
/* 1) getsockname 的 s_addr 与 inet_addr 解析结果**直接相等**(旧实现差一次换端) */
(void)mk_established(NET_SOCK_STREAM); /* getsockname 要 in_use 的描述符 */
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_getsockname(0, sa, &addrlen) == 0,
"TC-NETSOCK-550a getsockname precondition ok");
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.1.100"),
"TC-NETSOCK-550b getsockname s_addr is the same family as inet_addr (direct equality)");
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.1.100") == 0,
"TC-NETSOCK-550c local address closes parse-store-print (old code printed 100.1.168.192)");
/* 2) connect 的对端簿记同样闭合(lftpd:864 与 104 显示走的就是这条链) */
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(2404U);
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_connect_locked(fd, sa, (int)sizeof(in)) == 0,
"TC-NETSOCK-550d connect precondition ok");
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_getpeername(fd, sa, &addrlen) == 0,
"TC-NETSOCK-550e getpeername ok (reads event cache, zero SPI)");
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.1.200"),
"TC-NETSOCK-550f getpeername peer equals the s_addr fed into net_connect");
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "192.168.1.200") == 0,
"TC-NETSOCK-550g peer address prints right (lftpd connection-received log depends on it)");
CHECK(s_net_socks[fd].remote_ip_arr[0] == 192U &&
s_net_socks[fd].remote_ip_arr[3] == 200U,
"TC-NETSOCK-550h chip des_ip array stays wire order [192,168,1,200]: on-wire behaviour unchanged");
reset_all();
}
/* TC-NETSOCK-555 UDP 报文头里的源地址拆装(同族的第四个生产者:net_recvfrom) */
static void tc_555_udp_src_addr(void)
{
/* CH395F UDP 头(§9.2.4):[0-1] 总长(LE) [2-3] 源端口(LE) [4-7] 源 IP(线序) */
static const uint8_t hdr[8] = {
11U, 0U, /* 总长 = 8 头 + 3 数据 */
0x1FU, 0xCBU, /* 源端口 51999(小端 1F CB) */
239U, 1U, 2U, 3U /* 源 IP 线序:239.1.2.3 */
};
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[16];
char txt[16];
int addrlen;
int fd;
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
ch395f_fake_push_recv_len((uint8_t)fd, (uint16_t)(8U + 3U));
ch395f_fake_set_recv_bytes(hdr, (uint16_t)sizeof(hdr));
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &addrlen) == 3,
"TC-NETSOCK-555a strips the 8-byte chip header and delivers only the 3-byte payload");
CHECK(in.sin_addr.s_addr == net_inet_addr("239.1.2.3"),
"TC-NETSOCK-555b source IP unpack matches the inet_addr family");
CHECK(in.sin_port == net_htons(51999U),
"TC-NETSOCK-555c source port read chip little-endian then stored in network order");
CHECK(addrlen == (int)sizeof(in) && net_ntohs(in.sin_port) == 51999U,
"TC-NETSOCK-555d addrlen filled and port round-trips to 51999");
CHECK(strcmp(net_inet_ntoa(in.sin_addr.s_addr, txt), "239.1.2.3") == 0,
"TC-NETSOCK-555e multicast source address prints correctly");
reset_all();
}
/* TC-NETSOCK-560 UDP 上下文守卫:非执行任务不得直调芯片
* 三条一起才成立,缺一条都不算把危险面关掉:
* ① 执行任务内照常成功(守卫不是"把 UDP 判死")
* ② 非执行任务被拒且**一次芯片事务都没有**(拒了却已经抢过 SPI = 白拒)
* ③ 正常错误路径不被守卫吞掉,且**走消息队列的接口完全不受影响**
* (那些接口生来就是给非 netTask 用的,误伤它们才是大事故)
*/
static void tc_560_udp_context_guard(void)
{
char buf[8];
int fd;
/* ① 执行任务上下文:与修复前逐字相同 */
bring_up_netctx();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == 3,
"TC-NETSOCK-560a net_sendto unchanged inside the executing task");
reset_all();
/* ② 非执行任务:拒绝 + 零芯片事务(bring_up 里 net_init 是以 netTask 身份跑的,
* 此刻 g_ns_cur_task 已还原成 caller,正是"别的任务") */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_chip.write_calls = 0;
g_chip.read_calls = 0;
g_fxchip.set_des_ip_calls = 0;
g_fxchip.glob_calls = 0;
g_ns_poll_calls = 0;
g_ns_error_count = 0;
CHECK(net_sendto(fd, "abc", 3, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_NOT_IN_NETTASK,
"TC-NETSOCK-560b non-executing task gets NET_ERR_NOT_IN_NETTASK (exact code, not -1)");
CHECK(g_chip.write_calls == 0 && g_fxchip.set_des_ip_calls == 0 &&
g_ns_poll_calls == 0,
"TC-NETSOCK-560c clean refusal: zero writes, zero set_des_ip, zero net_poll rounds");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_NOT_IN_NETTASK,
"TC-NETSOCK-560d net_recvfrom guarded the same way");
CHECK(g_chip.read_calls == 0 && g_fxchip.glob_calls == 0 && g_ns_poll_calls == 0,
"TC-NETSOCK-560e recvfrom touched no chip and no global interrupt register either");
CHECK(g_ns_error_count == 2,
"TC-NETSOCK-560f each refusal logs one ERR line (silence hides the wrong task)");
/* ③ 正常错误路径优先:守卫排在参数校验之后 */
CHECK(net_sendto(-1, "abc", 3, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-560g out-of-range fd still reports BADF first");
CHECK(net_sendto(fd, NULL, 3, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-560h NULL buf still reports INVAL, the guard does not swallow it");
CHECK(net_recvfrom(fd, NULL, 3, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-560i recvfrom keeps the same precedence");
/* ④ 走消息队列的接口不受守卫影响:同一个非 netTask 上下文照常能把活干完 */
CHECK(net_close(fd) == 0,
"TC-NETSOCK-560j net_close from a non-executing task still works (queue path untouched)");
/* 执行完的证据用"收到回执 + 槽位释放",不看 close_calls:芯片此刻已是
* CLOSED,走的是快路径(不再补发 CLOSE,见 TC-NETSOCK-320a/b),拿它当证据
* 会把一条正确的快路径判成失败 */
CHECK(s_net_socks[fd].in_use == 0U && g_ns_notify_calls >= 1,
"TC-NETSOCK-560k queue path really completed (receipt seen and slot released)");
reset_all();
/* ⑤ 未捕获执行任务(net_init 没跑):守卫不抢先,仍由参数/状态检查给确定错误 */
reset_all();
g_ns_pump = net_process_messages;
CHECK(net_sendto(0, "abc", 3, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-560l before net_init sendto reports BADF, no invented context error");
/* 未捕获执行任务、但 socket 确实在用时,守卫**选择放行**:宁可不做判定,也不给
* 一个无从判定的场景编造错误码。放行后走的是完全正常的执行路径(这里 DONTWAIT
* 无数据 → WOULDBLOCK),证明守卫不是"没初始化就一律拒绝"的粗暴实现。
* 刻意用 recvfrom 而不是 sendto:未初始化时 net_poll 直接短路返回、时间基准
* 不前进,sendto 的等待环永远等不到超时(真机上不可达:net_socket 之后还有
* NET_READY 屏障,但这条差异值得记一笔,见交付报告)。 */
reset_all();
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; /* net_init 没跑:判据恒 NULL */
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_fxchip.glob_calls = 0;
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-560m guard stays open when the executing task was never captured");
CHECK(net_get_errno() != NET_ERR_NOT_IN_NETTASK && g_fxchip.glob_calls == 0,
"TC-NETSOCK-560n no context error invented and no global register read");
reset_all();
}
static void ns_run_s6(void)
{
tc_500_public_bounds();
tc_501_all_entries_busy_on_full_queue();
tc_502_socket_table_full();
tc_503_zero_wait_pickup();
tc_504_locked_entry_branches();
tc_505_inner_wait_and_last_chunk();
tc_506_sendto_stall_and_short();
tc_530_stall_timeout_before_spin();
tc_535_inner_timeout_after_chunk();
tc_536_recv_to_three_exits();
tc_537_event_callback_path();
tc_540_negative_results();
tc_545_local_entries_without_queue();
tc_550_family_roundtrip();
tc_555_udp_src_addr();
tc_560_udp_context_guard();
}