Files
STM32F4-Base/test/host/net_socket/ns_s3_msg.c
T
wandering1 a3c6e89378 fix(网络): 主动拆会话登记即返——drop 路径不再占用 netTask(登记簿 N-17 收口)
道F 批 2 / task-17。定位 + 单点收口,不开 net 层跨上下文大口子。

【定位(链路,全部代码可复核)】
探针挂起 30s → time_sync.c:149 预算耗尽 → :187 net_drop_conn()。NET_MSG_
DROP_CONN 处理器(net_drop_conn_locked)内同步跑 net_sock_reset_sync
(QUARANTINE)(2ms 节奏、400ms 底/600ms 顶,自家注释原记「等待期 netTask
有界占用 ~400-600ms」):netTask 的 1ms 主循环冻结在同一迭代 → NET 位
停摆;串行总线整窗停摆 → UI 推流/FTP/104 在途消息回执顺延;对时调用方
睡在 net_wait_result 等回执期间无分片心跳 → 五位同窗缺席 → 连续 8 个
100ms 抽样停喂 → 声明 [WD] task stall(V5 时代 req=0x37、快照只剩 bit0,
即日志 mask=0x00000001)。被动断连/EOF 走 net_listener_tick 异步沉降机
(D10 条令)故纯 FTP 负载 0 stall——只有主动 drop 留在同步原语里。

【修复】监听型 fd 的芯片拆除改为 net_listener_settle(QUARANTINE) +
net_listener_request(REQ_DROP) 登记即返,拆链/排渣/等 CLOSED/重建全部
交 net_listener_tick 30ms 节奏——与被动释放同一台机器(E16 台架实证该
自建序列 RST 10/10、FIN 5/5)。非监听 client fd 无监听可保,同步形态
原样保留(全仓零生产调用者)。返回值语义显式改为「拆除与重建已登记」
(net_socket.h 注释按 lead 要求写明,防未来调用方按旧语义写)。
APP_ASSERT(S12 试点)分派门与断言位置未动,登记时序下仍成立。

【评估否决留档(lead 要求论证入文档)】「缩短重建重试 5×2s」不采:
该退避在 time_sync_open_listener() bind/listen 失败路径、已被
time_sync_delay_fed() 分片喂保护、不在缺口链上;缩短反撞 TRAP-NET-09
沉降窗。全文见 docs/网络/网络自愈架构设计.md §4.6(已改写)。

【竞态与信源核对】① 主路径置 CLOSED 后迟到的 DISCONNECT/TIMEOUT 被
net_events 状态门拦截(只认活会话态),不会把刚登记的 QUARANTINE 降级;
② 零新增读清点——全局寄存器唯一读者仍 net_poll,隔离窗两处本 Socket
读清是 net_listener_tick 原位,§4.10 三条准入判据不触发;③ E13 物理
边界(对端不配合 FIN_WAIT_2 长尾)逐字不变,只移动等待发生的位置。

【测试】net_socket 主机套件 457/457:TC-NETSOCK-240f/321b/321b2/460e
重锚到登记式(settle+REQ_DROP、rst 机零启动、tick 预算 ≤20、无 [ERR]),
新增 321g/h/i + 460f 钉住 client fd 旧同步形态。全量 --all-files 40 绿。

【体积增量(vs scripts/ram_baseline.tsv 2026-10-25 行)】
ER_IROM1 237328→237344(+16 B);RW_IRAM1 75792(+0);ZI 0。
@build 0 错误 0 警告(Code=215948 RO=21396 RW=1232 ZI=131984)。

gate: only mem-budget/mem-numbers pending lead ledger backfill
test-coverage 本条**绿**(已覆盖行净增,未动用裁决③的覆盖率例外——
旧同步路径行经 client fd 新用例继续被覆盖,净差非负)。其余含
id-check/doc-links/全部主机单测绿。仅 mem-numbers/mem-budget 两红 =
台账 +16B 漂移待回填(lead 单写者禁区)。

【越界申报】① test/host/net_socket/{ns_s3_msg,ns_s4_state,ns_s5_io}.c
(批准③同类:只动本任务相关用例,未碰他人行);② docs/网络/
网络自愈架构设计.md §4.6 改写(lead 本任务指令明示)。登记簿 N-17
销项与 README 波次行归 lead;docs/内存管理 数字未碰(禁区)。

台架:V5 复跑判据(e02_probe + FTP ≥30min 零 stall / 零复位)属网络
对端类——待 lead 排窗,步骤清单随 done 消息发出。
2026-10-25 10:08:46 +08:00

616 lines
26 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.
/*
* 分片 3:跨上下文消息面(net_process_messages 全分派 + 结果经任务通知值回传)
* 前置:net_socket_test.c 已纳入生产源与假内核/假芯片/兄弟模块桩(同一 TU)。
*
* 这一片是整套里最值钱的部分,钉的是 AGENTS 模块约定的两条硬契约:
* A.「所有 net_* socket 操作经 netMsgQueue 在 netTask 串行执行」
* B.「net_* 公开 API 的错误码经任务通知值回传:net_msg_t 是按值拷入队列的,
* netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 xTaskNotify 通知值回来;
* 勿给 net_msg_t 加回传字段(加了只会让调用方读到栈垃圾)」
* 断言形态因此刻意是:先看**通知值本身**(32 位整体即结果,负值=精确错误码),
* 再看调用方在自己上下文恢复出的 errno——而不是去读队列里的 msg.result
* (那正是旧实现的错觉,也是 B 条明文禁止的方向)。
*
* 创建日期:2026-10-24
*/
/* close 场景的增量快照基准(前面场景也会 clear,必须按增量断言) */
static int s_close_base_clear;
static int s_close_base_settle;
/* 造一条已建立的 TCP 会话(多数消息场景的前置) */
static int mk_established(int type)
{
int fd = net_socket(NET_AF_INET, type);
net_sock_t *p;
if (fd < 0) {
return fd;
}
p = &s_net_socks[fd];
p->state = NET_SOCK_STATE_ESTABLISHED;
p->send_ready = 1U;
p->readable = 0U;
p->local_port = 2404U;
p->remote_port = 51999U;
p->remote_ip = 0x0B01A8C0U;
return fd;
}
/* TC-NETSOCK-200 未知消息类型:枚举闭合外的值也必须给出**本条消息自身**的确定结果
* (不许把上一条的回执残留当成这次的结果) */
static void tc_200_unknown_type(void)
{
net_msg_t msg;
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = (net_msg_type_t)999;
msg.sockfd = 0;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
msg.result = 12345; /* 脏值:分派必须覆写它 */
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-200a raw message enqueued");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-200b enqueue itself produces no notification");
net_process_messages(); /* 以 netTask 身份跑一轮 */
CHECK(g_ns_notify_calls == 1, "TC-NETSOCK-200c unknown type must still send a receipt (else the caller hangs forever)");
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR,
"TC-NETSOCK-200d notify value for unknown type = this message exact code NET_ERR, not a leftover");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-200e queue drained within this round (non-blocking while semantics)");
reset_all();
}
/* TC-NETSOCK-201 队列为空:四个自愈 tick 必跑,且不产生任何通知 */
static void tc_201_ticks_run_before_dispatch(void)
{
bring_up();
net_process_messages();
CHECK(g_peers.listener_tick_calls == 1 && g_peers.pending_tick_calls == 1 &&
g_peers.send_tick_calls == 1 && g_peers.reconcile_calls == 1,
"TC-NETSOCK-201a even with an empty queue all four self-heal ticks run (D10/pending x2/reconcile)");
CHECK(g_ns_notify_calls == 0 && g_ns_get_calls == 1,
"TC-NETSOCK-201b empty queue: one peek then exit, no notification");
reset_all();
}
/* TC-NETSOCK-202 队列句柄未绑定:直接返回,连 tick 都不该跑 */
static void tc_202_no_queue_bound(void)
{
reset_all();
g_ns_pump = net_process_messages;
net_process_messages();
CHECK(g_peers.listener_tick_calls == 0 && g_ns_get_calls == 0,
"TC-NETSOCK-202 net_process_messages without a bound queue returns at once (no chip, no ticks)");
reset_all();
}
/* TC-NETSOCK-210 SEND 快路径:端到端跑通"入队 → 分派 → 写芯片 → 通知值=字节数" */
static void tc_210_send_end_to_end(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)memset(buf, 'A', sizeof(buf));
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == (int)sizeof(buf), "TC-NETSOCK-210a net_send returns the number of bytes actually written");
CHECK(g_chip.write_calls == 1 && g_chip.last_read_len == (uint16_t)sizeof(buf),
"TC-NETSOCK-210b one full write (a healthy path neither chunks nor goes pending)");
CHECK(s_net_socks[fd].send_bytes == sizeof(buf), "TC-NETSOCK-210c sent byte statistic accumulates");
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == (uint32_t)sizeof(buf),
"TC-NETSOCK-210d result travels back on the notify value (the whole 32 bits are the result)");
CHECK(g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-210e notify goes to the REGISTERED caller only, not a broadcast");
CHECK(g_ns_block_waits == 1,
"TC-NETSOCK-210f zero-wait found nothing -> blocking branch (second stage of the two-step peek)");
reset_all();
}
/* TC-NETSOCK-211 大帧分块:超过 NET_SEND_CHUNK_MAX 必须按硬件发送缓冲上界切块
* (TRAP-NET-16:单次写 >1KB 会写穿 1KB 发送 FIFO) */
static void tc_211_send_chunking(void)
{
static char buf[3000];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* 造"每写完一块芯片就报 SENBUF_FREE"的健康链路:让 net_poll 把 send_ready 置回来 */
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
/* SENBUF_FREE 是**条件位**:发送缓冲空着就还会再报。用假件的电平型掩码而不是
* 一次性位图,否则第二块起就再也等不到空闲 → 变成测假件的清零时机。 */
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
rc = net_send(fd, buf, 2500, 0);
CHECK(rc == 2500, "TC-NETSOCK-211a all 2500 bytes delivered");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-211b split into 3 chunks at the 1024 bound (1024/1024/452)");
CHECK(g_chip.last_read_len == 452U, "TC-NETSOCK-211c last chunk writes only the remainder");
CHECK(NET_SEND_CHUNK_MAX == 1024,
"TC-NETSOCK-211d chunk size comes from the chip send buffer, not a fake-defined number");
reset_all();
}
/* TC-NETSOCK-212 SEND 挂起登记:等发送缓冲超预算 → 登记挂起并**跳过即时回执** */
static void tc_212_send_pending_defers_notify(void)
{
static char buf[64];
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U; /* 芯片一直不腾缓冲 */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-212a SEND message enqueued");
net_process_messages();
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-212b past the spin budget it registers a pending send instead of busy-waiting in netTask");
CHECK(g_peers.last_snd_fd == fd && g_peers.last_snd_len == (int)sizeof(buf) &&
g_peers.last_snd_sent == 0,
"TC-NETSOCK-212c registered progress = bytes already written to the chip (0 here), length and caller travel too");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-212d a successful registration NEVER sends an immediate receipt (else it reads as the result)");
CHECK(g_ns_notify_last_to == NULL,
"TC-NETSOCK-212e not a single notification (the 0x70000000 sentinel can not leak into a notify value)");
/* 完成时刻:由挂起 send 的 tick 结束(本套由桩脚本模拟"缓冲腾出来了") */
ns_peers_arm_send_pending_done(fd, 64);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 64U,
"TC-NETSOCK-212f receipt only after the pending completes, notify value = bytes actually written");
reset_all();
}
/* TC-NETSOCK-213 SEND 失败:未建立连接 → 通知值带**精确**错误码,不是笼统 -1 */
static void tc_213_send_error_code_roundtrip(void)
{
char buf[4];
net_msg_t msg;
int fd;
int rc;
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM); /* CREATED,未连接 */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_NOTCONN,
"TC-NETSOCK-213a SEND on a not-connected socket: notify value = NET_ERR_NOTCONN (exact code for caller branches)");
/* 端到端:公开 net_send 的 BADF 形态(fd 越界由**分派侧**再判一次) */
reset_all();
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = 3; /* 合法范围内但未分配 */
msg.buf = buf;
msg.len = 4;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-213b in range but unallocated fd: net_get_sock null -> BADF travels on the notify value");
/* 调用方上下文恢复 errno:拿到的必须是"本任务的"精确码 */
reset_all();
bring_up();
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(6, buf, 4, 0); /* 6 未分配 → 分派侧 BADF → 通知值 -2 */
CHECK(rc == -1, "TC-NETSOCK-213c BSD semantics kept for callers: any failure is -1");
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-213d caller reads the exact code from its own errno slot (E-03 plus notify value together)");
reset_all();
}
/* TC-NETSOCK-220 RECV 快路径:芯片缓冲里已有数据 → 当场读出,不登记挂起 */
static void tc_220_recv_fast_path(void)
{
char buf[16];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd, 1400U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, 10, NET_MSG_DONTWAIT);
CHECK(rc == 10, "TC-NETSOCK-220a chip has 1400B but the caller buffer is 10B -> reads 10 (a partial read is legal)");
CHECK(g_chip.read_calls == 1 && g_chip.last_read_len == 10U,
"TC-NETSOCK-220b reads only what the caller offered (never the chip available length)");
CHECK(s_net_socks[fd].recv_bytes == 10U, "TC-NETSOCK-220c received byte statistic accumulates");
CHECK(s_net_socks[fd].readable == 0U,
"TC-NETSOCK-220d reading clears readable (the next RECV_OK interrupt sets it again)");
CHECK(g_peers.pending_reg_calls == 0, "TC-NETSOCK-220e data is already in hand, no pending registration");
reset_all();
}
/* TC-NETSOCK-221 RECV 非阻塞无数据 → WOULDBLOCK(且不许登记挂起) */
static void tc_221_recv_wouldblock(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221a DONTWAIT with no data -> -1 plus WOULDBLOCK");
CHECK(g_peers.pending_reg_calls == 0,
"TC-NETSOCK-221b non-blocking never registers pending (else the caller thinks it waits while it is parked)");
/* 模式属性(net_setblocking(0))与单次标志同判据 */
CHECK(net_setblocking(fd, 0) == 0, "TC-NETSOCK-221c switching to non-blocking mode succeeds");
rc = net_recv(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221d blocking attribute 0 behaves exactly like NET_MSG_DONTWAIT");
CHECK(net_recv_nb(fd, buf, (int)sizeof(buf)) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221e net_recv_nb named entry takes the same path");
reset_all();
}
/* TC-NETSOCK-222 RECV EOF:会话已结束 + 无数据 → 返 0 并登记重建(不是 -1) */
static void tc_222_recv_eof_relisten(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0, "TC-NETSOCK-222a CLOSED with no data -> EOF(0), not an error");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_EOF,
"TC-NETSOCK-222b EOF registers REQ_EOF (the rebuild belongs to the listener layer, this layer never touches the chip)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0 && g_chip.close_calls == 0,
"TC-NETSOCK-222c the registration path issues NOT ONE chip transaction (single-owner invariant II)");
/* 抢先 relisten 后 state 已回 LISTENING,但会话电平仍是"已结束" */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].session_dead = 1U;
s_net_socks[fd].auto_relisten = 1U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0,
"TC-NETSOCK-222d still EOF after relisten moved it back to LISTENING (N-32 P1 level, else EOF vanishes forever)");
/* auto_relisten=0(客户端 fd):EOF 不登记重建,交调用方决定重连 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].session_dead = 1U;
s_net_socks[fd].auto_relisten = 0U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-222e EOF on a non-listener fd must not rebuild behind the caller back");
reset_all();
}
/* TC-NETSOCK-223 RECV 阻塞无数据 → 登记挂起 + 跳过即时回执;到点由 tick 结束 */
static void tc_223_recv_pending_roundtrip(void)
{
char buf[16];
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.flags = 0;
msg.timeout_ms = 250U; /* 显式预算必须原样带到登记处 */
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_reg_calls == 1 && g_peers.last_pend_fd == fd,
"TC-NETSOCK-223a blocking with no data -> registers a pending recv (netTask returns with zero occupancy)");
CHECK(g_peers.last_pend_to == 250U,
"TC-NETSOCK-223b the caller explicit budget travels with the registration (0 means infinite, here it must not be 0)");
CHECK(g_peers.last_pend_caller == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-223c completion must know whom to notify");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-223d registering then immediately notifying would pass off registration as the result - pinned absent");
/* 数据到达:由挂起 recv 的 tick 结束(脚本模拟) */
ns_peers_arm_pending_done(fd, 7);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 7U,
"TC-NETSOCK-223e receipt only once data arrives, notify value = byte count");
reset_all();
}
/* TC-NETSOCK-224 net_recv_to:分片超时用独立返回码 -2(不靠 errno,避免跨任务竞态) */
static void tc_224_recv_to_timeout_code(void)
{
char buf[16];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
/* 先造一条"已登记"的挂起,再让 tick 判到点 */
ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT);
rc = net_recv_to(fd, buf, (int)sizeof(buf), 30U);
CHECK(rc == -2, "TC-NETSOCK-224a this slice timed out -> dedicated return code -2 (distinct from a real error -1)");
reset_all();
}
/* TC-NETSOCK-225 RECV 参数非法:不碰芯片、不登记 */
static void tc_225_recv_bad_args(void)
{
char buf[4];
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(0, buf, 4, 0U) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225a net_recv_to(timeout=0) is refused up front - 0 means infinite wait on the netTask side,"
"which contradicts this API whole reason for having a bounded occupancy");
CHECK(g_ns_put_calls == 0, "TC-NETSOCK-225b the refusal happens before enqueue");
CHECK(net_recv(fd, NULL, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225c buf NULL -> INVAL");
CHECK(net_recv(fd, buf, 0, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225d len <= 0 -> INVAL");
CHECK(net_send(fd, NULL, 4, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225e send with buf NULL -> INVAL");
CHECK(net_send(fd, buf, -1, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225f send with len < 0 -> INVAL");
CHECK(g_chip.write_calls == 0 && g_chip.read_calls == 0 &&
g_peers.pending_reg_calls == 0 && g_peers.send_reg_calls == 0,
"TC-NETSOCK-225g on the invalid-argument path: zero chip transactions and zero pending registrations");
reset_all();
}
/* TC-NETSOCK-230 CLOSE:先结束挂起再关芯片,且 last_peer_port 跨释放保留 */
static void tc_230_close_order_and_peer(void)
{
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
/* 登记一条挂起 recv,再看 close 是否先把它结算掉(排空/取消都必通知) */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = fd;
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_peers.pending_reg_calls == 1 && g_ns_notify_calls == 0,
"TC-NETSOCK-230a precondition: pending registered and no receipt yet");
/* 芯片侧此刻仍是 OPEN:close 才需要真去拆(已 CLOSED 的形态见 TC-NETSOCK-232) */
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_ns_notify_calls = 0;
{
s_close_base_clear = g_peers.listener_clear_calls;
s_close_base_settle = g_peers.pending_settle_calls;
}
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_CLOSE;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_settle_calls == s_close_base_settle + 1,
"TC-NETSOCK-230b close must first settle this fd pending recv (else the waiter hangs forever)");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-230c settling always notifies (a cancel notifies too, not a silent slot clear)");
CHECK(g_chip.close_calls >= 1 && g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-230d chip still OPEN -> sock_reset deterministically drains and tears it down");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-230e close must never read the global interrupt register (B3: sole reader is net_poll)");
CHECK(s_net_socks[fd].in_use == 0U && s_net_socks[fd].last_peer_port == 51999U,
"TC-NETSOCK-230f control block cleared but last_peer_port kept (ghost discrimination is chip-socket level)");
CHECK(g_peers.listener_clear_calls == s_close_base_clear + 1,
"TC-NETSOCK-230g close idempotently clears the listener retry (closing cancels pending retries, D10)");
CHECK(g_peers.pend_active_now[fd] == 0,
"TC-NETSOCK-230h after close this fd has no pending recv (both tables zeroed)");
reset_all();
}
/* TC-NETSOCK-231 CLOSE 未在用:net_get_sock 判空 → BADF 通知值 */
static void tc_231_close_badfd(void)
{
net_msg_t msg;
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_CLOSE;
msg.sockfd = 5;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-231a close on an unallocated fd -> BADF travels back on the notify value");
CHECK(g_chip.close_calls == 0, "TC-NETSOCK-231b BADF does not touch the chip");
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_ACCEPT; /* 没有 in_use 的监听 */
msg.sockfd = 5;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-231c accept on an unused fd -> INVAL (control-block check, not fd range)");
reset_all();
}
/* TC-NETSOCK-240 DROP_CONN:四类前置各走各的分支(幂等语义是关键) */
static void tc_240_drop_conn_branches(void)
{
net_msg_t msg;
int fd;
/* 非在用 → BADF */
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = 2;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-240a unallocated -> BADF");
/* UDP → INVAL */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-240b not a TCP stream -> INVAL (this API only tears down TCP sessions)");
/* 监听态但无活动会话 → 幂等 0,不动芯片 */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].auto_relisten = 1U;
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == 0U, "TC-NETSOCK-240c no active session -> idempotent success 0");
CHECK(g_chip.close_calls == 0 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240d the idempotent branch must not tear down the chip (it would destroy the live listener)");
/* ESTABLISHED + 监听型 → QUARANTINE 拆除 + 登记重建 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_settle_calls == 1 && g_peers.send_cancel_calls == 1,
"TC-NETSOCK-240e both pending tables are ended before dropping the session (with notification)");
CHECK(g_peers.listener_settle_calls == 1 &&
g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE &&
g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240f listener teardown is REGISTERED for the tick machine (QUARANTINE settle + REQ_DROP) - the handler starts no reset machine and never waits (N-17)");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CLOSED &&
s_net_socks[fd].in_use == 1U && s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].auto_relisten == 1U,
"TC-NETSOCK-240h only session bookkeeping is closed out: fd, listener attribute and port all survive");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-240i teardown only touches its own socket (F-NET-7: never reads the global register)");
reset_all();
}
/* TC-NETSOCK-250 入队失败(队列满)→ 调用方 BUSY,不留下"已提交"的假象 */
static void tc_250_put_fail_busy(void)
{
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
ns_rtos_q_fill((int)NS_Q_CAP); /* 把队列灌满 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, "abc", 3, 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250a queue full -> net_send reports BUSY, it does not silently drop");
CHECK(g_chip.write_calls == 0 && g_ns_notify_calls == 0,
"TC-NETSOCK-250b on enqueue failure: zero chip transactions and zero notifications (caller will not wait in vain)");
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250c the same shape holds for net_listen");
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250d the same shape holds for net_close");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250e the same shape holds for net_drop_conn");
CHECK(net_recv_to(fd, (void *)"x", 1, 5U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250f the same shape holds for net_recv_to");
reset_all();
}
static void ns_run_s3(void)
{
tc_200_unknown_type();
tc_201_ticks_run_before_dispatch();
tc_202_no_queue_bound();
tc_210_send_end_to_end();
tc_211_send_chunking();
tc_212_send_pending_defers_notify();
tc_213_send_error_code_roundtrip();
tc_220_recv_fast_path();
tc_221_recv_wouldblock();
tc_222_recv_eof_relisten();
tc_223_recv_pending_roundtrip();
tc_224_recv_to_timeout_code();
tc_225_recv_bad_args();
tc_230_close_order_and_peer();
tc_231_close_badfd();
tc_240_drop_conn_branches();
tc_250_put_fail_busy();
}