Files
STM32F4-Base/test/host/net_socket/ns_s7_udp.c
T
super_agent_bot fff961d803
CI / 静态门禁 (pre-commit) (push) Failing after 49s
CI / 主机侧单测 (MSVC) (push) Failing after 30s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 44s
feat(net/udp): UDP 数据面并入 netMsgQueue 串行执行(#73)
前置来自 #57 SNTP 调研:旧 net_sendto / net_recvfrom 由调用方直调
SPI 且无限自旋收包,既违约「netTask 是芯片唯一访问者」,又把 IWDG
预算吃在自旋里。

改造面
- 新增 Drivers/BSP/NET/net_udp.c/.h(真机专属,模拟器不编译):
  bind/set_peer/sendto/recvfrom 四条执行体 + 挂起收包表 + tick;
  net_msg_type_t 加四型,公开 API 一律「入队 → 睡在通知上」。
- 源地址出参不搭 net_msg_t(按值拷贝,AGENTS 禁加回传字段):改住
  net_udp.c 的 per-fd 槽,netTask 就地写回调用方内存,通知值只带
  结果码;每条出口都清槽,泄漏 = 该 fd 永久 BUSY。
- 发送复用 net_send_locked,不写第二份分块逻辑;「会话是否已死」
  收成一条判据 net_send_state_down()(TCP 看 ESTABLISHED、UDP 看
  UDP_OPEN),原先 4 处各写一遍。
- 顺带修掉真缺陷(新条 TRAP-NET-22):旧 sendto 把 DesIP 写成具体
  IP,芯片静默切 UDP Client,而旧 recvfrom 无条件剥 8 字节 → SNTP
  这类 48 B 应答丢载荷前 8 字节并交出垃圾源地址。加 udp_mode(塞进
  原有 padding,sizeof 不变)、bind 恒 SERVER、显式对端 sendto 切
  CLIENT、set_peer(fd, NULL) 轻量回 SERVER,仅 SERVER 且 avail >= 8
  才剥头。
- 截断定案:一次调用交付一个数据报,buf 装不下的尾巴用 64 B 栈上
  scratch 排空(不用 clear_recv_buf——它连后面已到达的包一起丢)。
- 取消点接齐:net_init 重建 cancel_all、net_socket 分配 fd clear、
  net_close settle_close + clear、重 bind 前先结算等包者,
  net_process_messages 第 5 个 tick 位挂 net_udp_tick。
- 收紧两处契约(原实现无生产调用者):UDP 未 bind 一律 -5;
  net_recv / _nb / _to 拒 DGRAM(绕过剥头就是静默吃 8 字节);
  NET_ERR_NOT_IN_NETTASK(-12) 退役为历史常量并写明原因。

验证
- 主机单测 test/host/net_socket 全套 616/616 绿(改造前 470):新
  分片 ns_s7_udp.c 20 组,改写 440 / 555 / 560 组;覆盖率
  net_udp.c 234/236(99.2%)、net_socket.c 956/969(98.7%),
  scripts/test_coverage_baseline.json 随本提交登记新文件。
- 测试抓到 4 个被测码真 bug:自等守卫误用调用侧谓词、芯片长度被读
  两次、槽指针取在 fd 合法性检查之前、重 bind 静默作废等待者。
- Keil ARMCC 0 Error(s) 0 Warning(s);net_udp.o Code 1,358 / ZI 224
  (8 fd × 28 B,其余模块零增长),全仓 RW_IRAM1 72.2%;内存台账
  受管区按门禁提示用 --write / --update 机器回填,未手改数字。
- pre-commit 全量钩子(含 doc-links、coding-standard、mem-budget、
  generated-fresh、bsp-err、registration、nested-comment)全绿。

限度
- 本轮无板子:真机回归 5 条(两端互通、set_peer(NULL) 能否恢复收
  广播、截断排空后下一包完整性、挂起 recv 期间 IWDG 余量、bind 写
  DesIP 与 open 先后的真芯片效果)已写进覆盖率口径「台架必验」。
- scripts/stack_report.py 退出码 1 是 10-26 起挂在账上的 iec104Task
  栈债(margin 1,016 < 1,024,见 docs/代码规范/pre-commit使用说明.md:
  122);本轮只让 net_bind 的 UDP 分支多一层静态帧(1,544 → 1,600),
  netTask margin 1,888 与 timeSyncTask 1,152 不受影响。抬栈要过
  mem-budget 台账,不在本分支自行处理。
- 登记簿销项按仓库约定留给主工作树,本分支不动 docs/任务规划/**。
2026-11-03 18:44:01 +08:00

1285 lines
62 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.
/*
* 模块名称:UDP 数据面分片(net_socket 主机侧单测,2026-11-03 UDP 队列化改造)
* 模块功能:钉 UDP 从"调用方上下文直调 SPI"改为"netMsgQueue 交 netTask 串行执行"
* 之后的全部契约,以及改造顺带修掉的**芯片模式缺陷**:
* - DesIP=0xFFFFFFFF(服务器模式)收包带 8 字节信息头;
* - DesIP=具体 IP(客户端模式)收包**没有**信息头。
* 改造前 recvfrom 恒剥 8 字节,客户端模式(SNTP 这类请求-应答)等于吃掉
* 载荷前 8 字节并回填一个假源地址——本分片用**逐字节内容**把两种模式钉死。
*
* 与既有分片的分工:
* - ns_s5/ns_s6 里 440/555/560 三组是"改造前形态"的历史钉子,已按新契约改写;
* - 本分片只测**队列化之后**新增或变化的决策:出参经 per-fd 槽回传、挂起 recv、
* 截断排空、模式切换、以及"任何一条出口都必须通知调用方"。
*
* ⚠ 断言只钉真实契约(AGENTS 模块约定 + docs/芯片手册/CH395F.md §9.2.4 +
* docs/问题记录/CH395F_Trap_Records.md),不钉假件规则。
*
* 创建日期:2026-11-03
*/
/* 造一个"已 bind 的 UDP fd"(bind 走队列,故调用前的上下文决定走哪条路) */
static int udp_bind(int port_host)
{
struct net_sockaddr_in in;
int fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons((uint16_t)port_host);
if (net_bind(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) != 0) {
return -1;
}
return fd;
}
/* 组一个"服务器模式数据报"的字节流:8 字节信息头 + n 字节载荷,写进假芯片的
* 可读流并返回总长。头与载荷是分两次读的,所以内容必须按芯片缓冲的真实字节序预置。 */
static int udp_build_server_datagram(uint8_t *buf, int cap, const char *payload, int n,
uint16_t src_port_host, const char *src_ip)
{
uint8_t ip[4];
int i;
if (n + 8 > cap) {
return -1;
}
(void)memset(buf, 0, (size_t)(8 + n));
parse_ip_string(src_ip, ip);
buf[0] = (uint8_t)(n & 0xFF); /* [0-1] 数据长度(本层不消费,仅占位) */
buf[1] = (uint8_t)((n >> 8) & 0xFF);
buf[2] = (uint8_t)(src_port_host & 0xFFU); /* [2-3] 源端口:芯片小端 */
buf[3] = (uint8_t)((src_port_host >> 8) & 0xFFU);
buf[4] = ip[0]; /* [4-7] 源 IP 线序:首段在低地址 */
buf[5] = ip[1];
buf[6] = ip[2];
buf[7] = ip[3];
for (i = 0; i < n; i++) {
buf[8 + i] = (uint8_t)payload[i];
}
return 8 + n;
}
/* "此刻就有这一个数据报可读":流 + 长度一起给(多数用例走这条) */
static void udp_script_server_datagram(const char *payload, int n,
uint16_t src_port_host, const char *src_ip)
{
uint8_t buf[128];
int total = udp_build_server_datagram(buf, (int)sizeof(buf), payload, n,
src_port_host, src_ip);
if (total < 0) {
return;
}
ch395f_fake_set_recv_stream(buf, (uint16_t)total);
ch395f_fake_push_recv_len(0, (uint16_t)total); /* 覆盖所有 fd:只有一个在用 */
}
/* "内容先备好、长度还没宣布":测"先没有、后面才到"必须这样分——
* 芯片长度才是"有没有包"的唯一依据,长度一宣布就是"现在就有"。 */
static void udp_stage_server_datagram(const char *payload, int n,
uint16_t src_port_host, const char *src_ip)
{
uint8_t buf[128];
int total = udp_build_server_datagram(buf, (int)sizeof(buf), payload, n,
src_port_host, src_ip);
if (total < 0) {
return;
}
ch395f_fake_set_recv_stream(buf, (uint16_t)total);
}
/* s_net_udp 是 net_udp.c 的生产 static:本分片直接读它的挂起标志,
* 不为了测试在生产里开一个只读 API(既有分片同样直读 s_net_socks)。 */
static int net_udp_pending(int fd)
{
return (s_net_udp[fd].pending != 0U) ? 1 : 0;
}
static int net_udp_armed(int fd)
{
return (s_net_udp[fd].armed != 0U) ? 1 : 0;
}
/* 手工投一条 UDP 收包消息(不经公开入口):公开入口在"挂起"这条出口上会睡在
* 任务通知里,测试拿不到"已登记、回执未发"这个中间态;这里自己装消息自己入队,
* 再自己以 netTask 身份泵一轮,断言登记与取消。与 net_recvfrom_impl 的入队段逐字同形。 */
static void udp_arm_and_enqueue(int fd, void *buf, int len, uint32_t to_ms, net_msg_t *msg)
{
(void)memset(msg, 0, sizeof(*msg));
msg->type = NET_MSG_UDP_RECVFROM;
msg->sockfd = fd;
msg->buf = buf;
msg->len = len;
msg->timeout_ms = to_ms;
msg->caller = (TaskHandle_t)&g_ns_task_caller;
(void)net_udp_outparam_arm(fd, NULL, NULL);
(void)ns_rtos_q_put_raw(msg);
}
/* ================= UDP-1 bind:命令序列与模式复位 ================= */
static void tc_udp1_bind_sequence(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(123U);
/* 队列化:调用方任务里 bind 不该碰芯片一下(TRAP-NET-14 的正面形态) */
ch395f_fake_clear_log();
CHECK(net_bind(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"UDP-1a caller-context bind succeeds (enqueued, executed in netTask, result via notify)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN,
"UDP-1b state UDP_OPEN (open succeeded, no listen state in UDP)");
CHECK(s_net_socks[fd].udp_mode == NET_UDP_MODE_SERVER,
"UDP-1c bind is always server mode (BSD bind names a local port, it never picks a peer)");
CHECK(g_fx.des_ip[fd][0] == 0xFFU && g_fx.des_ip[fd][1] == 0xFFU &&
g_fx.des_ip[fd][2] == 0xFFU && g_fx.des_ip[fd][3] == 0xFFU &&
g_fx.des_port[fd] == 0U,
"UDP-1d DesIP=0xFFFFFFFF + DesPort=0 (the hardware definition of server mode)");
CHECK(g_chip.last_proto == CH395F_PROTO_TYPE_UDP && g_chip.last_port == 123U,
"UDP-1e proto UDP then source port 123 (handbook §9.2.4 order)");
CHECK(ch395f_fake_log_find('P') >= 0 &&
ch395f_fake_log_find('P') < ch395f_fake_log_find('o'),
"UDP-1f DesIP/ports come BEFORE OPEN_SOCKET (open resets them: the ordering is the bug fix)");
CHECK(g_chip.open_calls == 1, "UDP-1g exactly one open");
/* 端口 0 = 动态取号,取号游标在 netTask 内推进(原来在调用方上下文改静态量) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
CHECK(net_bind(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"UDP-1h bind with port 0 succeeds");
CHECK(s_net_socks[fd].local_port == 49152U && g_chip.last_port == 49152U,
"UDP-1i dynamic port allocated inside netTask and written to the chip (no cross-context write to s_dynamic_port)");
reset_all();
}
/* ================= UDP-2 未 bind 的接口一律拒绝(新收紧) ================= */
static void tc_udp2_requires_bind(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[16];
int fd;
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(123U);
in.sin_addr.s_addr = net_inet_addr("192.168.31.9");
CHECK(net_sendto(fd, "hi", 2, 0, sa, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-2a sendto before bind -> NOTCONN (an unopened chip socket cannot send)");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-2b recvfrom before bind -> NOTCONN");
CHECK(net_udp_set_peer(fd, &in) == -1 && net_get_errno() == NET_ERR_NOTCONN,
"UDP-2c set_peer before bind -> NOTCONN (writing DesIP before open would be reset by open)");
CHECK(g_chip.write_calls == 0 && g_chip.open_calls == 0 && g_fxchip.set_des_ip_calls == 0,
"UDP-2d a refusal is a clean refusal: no write, no open, no DesIP");
reset_all();
}
/* ================= UDP-3 服务器模式:剥头 + 源地址逐字节 ================= */
static void tc_udp3_server_strip(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[64];
int addrlen;
int fd;
int rc;
bring_up();
fd = udp_bind(123);
CHECK(fd >= 0, "UDP-3a bind precondition");
/* 载荷 20 字节,头里写着 src 192.168.31.9:51999 */
udp_script_server_datagram("ABCDEFGHIJKLMNOPQRST", 20, 51999U, "192.168.31.9");
s_net_socks[fd].readable = 1U;
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
rc = net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen);
CHECK(rc == 20, "UDP-3b server mode returns the payload length only (20 of 28 chip bytes)");
CHECK((int)g_fx.recv_pos == 28,
"UDP-3c the whole datagram was read out (header + payload): the handbook requires draining in one go");
CHECK(memcmp(buf, "ABCDEFGHIJKLMNOPQRST", 20) == 0,
"UDP-3d delivered bytes are the payload, byte for byte (not the header, not shifted)");
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
"UDP-3e source address filled and addrlen reported");
CHECK(in.sin_addr.s_addr == net_inet_addr("192.168.31.9"),
"UDP-3f source IP matches the net_inet_addr family (line-order header -> network-order s_addr, no extra byteswap)");
CHECK(net_ntohs(in.sin_port) == 51999U,
"UDP-3g source port: chip little-endian in the header, network-order in the struct");
CHECK(s_net_socks[fd].readable == 0U,
"UDP-3h readable cleared after the read (a level-triggered event must not fire the same packet twice)");
CHECK(s_net_socks[fd].recv_bytes == 20U,
"UDP-3i statistics count delivered payload, not the chip header");
reset_all();
}
/* ================= UDP-4 客户端模式:不剥头(改造修的缺陷本体) ================= */
static void tc_udp4_client_no_strip(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
uint8_t stream[64];
char buf[64];
int addrlen;
int fd;
int rc;
int i;
bring_up();
fd = udp_bind(123);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(123U);
in.sin_addr.s_addr = net_inet_addr("119.28.220.214");
CHECK(net_udp_set_peer(fd, &in) == 0, "UDP-4a set_peer to an NTP-like server succeeds");
CHECK(s_net_socks[fd].udp_mode == NET_UDP_MODE_CLIENT, "UDP-4b mode is now client");
CHECK(g_fx.des_ip[fd][0] == 119U && g_fx.des_ip[fd][1] == 28U &&
g_fx.des_ip[fd][2] == 220U && g_fx.des_ip[fd][3] == 214U &&
g_fx.des_port[fd] == 123U,
"UDP-4c DesIP carries the peer octets in line order (the chip's own byte convention)");
CHECK(g_chip.open_calls == 1,
"UDP-4d switching mode does NOT reopen the socket (a reopen would drop buffered replies)");
/* 48 字节 SNTP 应答,前 8 字节故意长得像"信息头"——旧实现会把它们剥掉 */
(void)memset(stream, 0, sizeof(stream));
for (i = 0; i < 48; i++) {
stream[i] = (uint8_t)(0x24 + i); /* 0x24 = LI=0,VN=4,Mode=3 起手 */
}
ch395f_fake_set_recv_stream(stream, 48U);
ch395f_fake_push_recv_len((uint8_t)fd, 48U);
s_net_socks[fd].readable = 1U;
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
rc = net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen);
CHECK(rc == 48,
"UDP-4e client mode delivers ALL 48 bytes (the pre-refactor build returned 40 and silently ate the first 8)");
CHECK((uint8_t)buf[0] == 0x24U && (uint8_t)buf[7] == 0x2BU,
"UDP-4f the first delivered byte is the reply itself, not the byte after a stripped header");
CHECK(in.sin_addr.s_addr == net_inet_addr("119.28.220.214") &&
net_ntohs(in.sin_port) == 123U,
"UDP-4g source address comes from this socket's peer bookkeeping (client mode has no header to read)");
reset_all();
}
/* ================= UDP-5 带地址发送 = 切客户端模式,不带地址 = 不动 ================= */
static void tc_udp5_sendto_dest_switches_mode(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int fd;
bring_up();
fd = udp_bind(123);
ch395f_fake_enable_write_capture();
(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.9");
CHECK(net_sendto(fd, "ping", 4, 0, sa, (int)sizeof(in)) == 4, "UDP-5a sendto with a destination returns the byte count");
CHECK(s_net_socks[fd].udp_mode == NET_UDP_MODE_CLIENT,
"UDP-5b an explicit destination silently switches the chip to client mode: the software now books it (that is the whole point of udp_mode)");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
"UDP-5c the destination travels with this one send (address is per packet in UDP)");
CHECK(g_fx.write_cap_n == 4U && g_fx.write_bytes == 4U &&
memcmp(g_fx.write_cap, "ping", 4) == 0,
"UDP-5d the chip received exactly the caller's bytes, not a copy of something else");
CHECK(s_net_socks[fd].remote_port == 8888U &&
s_net_socks[fd].remote_ip == net_inet_addr("192.168.31.9"),
"UDP-5e peer bookkeeping written in the same family as net_connect (remote_ip is network order)");
/* 不带地址:DesIP 保持上次的对端,模式也不动;载荷按写入顺序续在捕获流后面。
* 上一块的 SENBUF_FREE 不在本场景要考的范围内(挂起续传见 TC-NETSOCK-506d),
* 这里直接把"缓冲又空了"这一步做掉,等价于 net_poll 收到 SENBUF_FREE。 */
s_net_socks[fd].send_ready = 1U;
(void)memset(&in, 0, sizeof(in));
CHECK(net_sendto(fd, "pong", 4, 0, NULL, 0) == 4, "UDP-5f sendto with NULL destination succeeds");
CHECK(g_fx.write_bytes == 8U && memcmp(&g_fx.write_cap[4], "pong", 4) == 0,
"UDP-5g the destination-less send still carries the caller's own bytes (the chunk pointer is advanced, not re-based)");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
"UDP-5h NULL destination means reuse, not reset (unchanged DesIP/DesPort)");
CHECK(s_net_socks[fd].udp_mode == NET_UDP_MODE_CLIENT, "UDP-5i mode unchanged by a destination-less send");
reset_all();
}
/* ================= UDP-6 挂起 recv:数据到达即结束(netTask 零占用) ================= */
/* 泵:以 netTask 身份跑一轮分派,并按脚本伪造"某一轮才有数据"。
* 真机上 netTask 是 1ms 主循环,HAL_GetTick 自己走;这里的假时钟必须由泵推进,
* 否则挂起 recv 的 deadline 永远不到点(表现为等满 50 轮被假件判成漏通知)。
* s_udp_pump_arrive_round < 0 → 纯推进时钟,不注入数据(测超时用)
* s_udp_pump_len > 0 → 到那一轮置 readable 并给出该长度的芯片数据 */
static int s_udp_pump_arrive_round = -1;
static int s_udp_pump_fd;
static int s_udp_pump_len;
static uint32_t s_udp_pump_tick_inc = 1U;
static int s_udp_pump_calls;
static void udp_pump(void)
{
if ((s_udp_pump_arrive_round >= 0) && (s_udp_pump_calls == s_udp_pump_arrive_round)) {
s_net_socks[s_udp_pump_fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)s_udp_pump_fd, (uint16_t)s_udp_pump_len);
}
s_udp_pump_calls++;
g_ns_tick += s_udp_pump_tick_inc;
net_process_messages();
}
static void udp_pump_stop(void)
{
g_ns_pump = net_process_messages;
s_udp_pump_arrive_round = -1;
s_udp_pump_len = 0;
s_udp_pump_tick_inc = 1U;
}
static void tc_udp6_pending_recv_arrival(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[32];
int addrlen;
int fd;
int rc;
bring_up();
fd = udp_bind(123);
s_udp_pump_fd = fd;
/* 内容先备好但**不宣布长度**:第 0 轮芯片缓冲是空的,数据报到第 1 轮才出现
* (8 字节头 + 12 字节载荷)——顺序反了就变成"一进来就有数据",测不到挂起 */
udp_stage_server_datagram("TIME12345678", 12, 51999U, "192.168.31.9");
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
s_udp_pump_arrive_round = 1;
s_udp_pump_len = 8 + 12;
s_udp_pump_calls = 0;
g_ns_pump = udp_pump;
g_ns_int_pin_set = 0; /* 有 INT# 电平但不注入 SINT:readable 由泵自己造 */
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
rc = net_recvfrom_to(fd, buf, (int)sizeof(buf), 5000U, sa, &addrlen);
udp_pump_stop();
CHECK(rc == 12, "UDP-6a a blocked recv ends with the datagram that arrives later (the caller slept on its own notification, not on a spin)");
CHECK(memcmp(buf, "TIME12345678", 12) == 0, "UDP-6b the late datagram's payload is delivered to the caller buffer");
CHECK(net_ntohs(in.sin_port) == 51999U, "UDP-6c the source address is filled by netTask into the caller's own memory (no return field on net_msg_t)");
CHECK(s_udp_pump_calls >= 2,
"UDP-6d the wait really slept and was woken by the completion notify (at least one pump round between)");
reset_all();
}
/* ================= UDP-7 挂起 recv:到点返回 -2 且不占 errno ================= */
static void tc_udp7_pending_timeout(void)
{
char buf[8];
int fd;
int rc;
int puts_before;
int reads_before;
bring_up();
fd = udp_bind(123);
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
g_ns_tick = 1000U;
/* 预算 10ms:不注入任何数据,泵每轮把假时钟推到 deadline 之后 */
s_udp_pump_arrive_round = -1;
s_udp_pump_tick_inc = 10U;
s_udp_pump_calls = 0;
g_ns_pump = udp_pump;
rc = net_recvfrom_to(fd, buf, (int)sizeof(buf), 10U, NULL, NULL);
udp_pump_stop();
CHECK(rc == -2, "UDP-7a the budget expired -> -2 (a timeout is a result, not an error - same contract as net_recv_to)");
CHECK(g_ns_error_count == 0,
"UDP-7b a timeout logs nothing and does not steal the caller's errno slot");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN, "UDP-7c the socket stays usable after a timeout");
CHECK(net_udp_pending(fd) == 0 && net_udp_armed(fd) == 0,
"UDP-7d the timeout hands both the pending flag and the out-param slot back (a leaked slot pins the fd as BUSY forever)");
/* 超时后立刻再来一次,数据到了必须能收到(槽位没泄漏的正面证据) */
udp_script_server_datagram("AFTER", 5, 7U, "10.0.0.1");
s_net_socks[fd].readable = 1U;
rc = net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL);
CHECK(rc == 5, "UDP-7e the same fd receives normally right after a timeout");
/* 预算 0 被这个入口拒绝:0 在本层一律是"无限等待",而本接口存在的理由就是
* 有界(与 net_recv_to 的分片长度检查同口径)——静默改成无限等会把 IWDG 咬死 */
puts_before = g_ns_put_calls;
reads_before = g_chip.read_calls;
rc = net_recvfrom_to(fd, buf, (int)sizeof(buf), 0U, NULL, NULL);
CHECK(rc == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-7f recvfrom_to with timeout_ms = 0 is refused as INVAL (0 means forever, which is what this entry must never do)");
CHECK(g_ns_put_calls == puts_before && g_chip.read_calls == reads_before,
"UDP-7g the refused budget never reaches the queue or the chip");
reset_all();
}
/* ================= UDP-8 挂起 recv 被取消:close / 重建 ================= */
static void tc_udp8_pending_cancel(void)
{
char buf[16];
net_msg_t msg;
int fd;
/* 关闭结算:无数据 -> CONNRESET 必通知 */
bring_up();
fd = udp_bind(123);
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
udp_arm_and_enqueue(fd, buf, (int)sizeof(buf), 0U, &msg);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
net_process_messages();
CHECK(net_udp_pending(fd) == 1, "UDP-8a the receive is registered (netTask occupancy is zero while waiting)");
(void)net_close_locked(&s_net_socks[fd]);
CHECK(net_udp_pending(fd) == 0, "UDP-8b close settles the pending slot (nothing may outlive the fd)");
CHECK(g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller &&
g_ns_notify_last_val == (uint32_t)(int)NET_ERR_CONNRESET,
"UDP-8c the waiter is woken with CONNRESET, not left sleeping forever");
reset_all();
/* 关闭结算:有数据 -> 先把这一包送达(与 net_pending_settle_close 同一契约) */
bring_up_netctx();
fd = udp_bind(123);
udp_script_server_datagram("LASTPKT", 7, 9U, "10.0.0.2");
ch395f_fake_push_recv_len((uint8_t)fd, 15U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
if (net_udp_outparam_arm(fd, NULL, NULL) != 0) {
CHECK(0, "UDP-8d arm the slot for the settle test");
}
s_net_udp[fd].pending = 1U;
s_net_udp[fd].buf = buf;
s_net_udp[fd].len = (int)sizeof(buf);
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
net_udp_settle_close(fd, &s_net_socks[fd]);
CHECK(g_ns_notify_last_val == 7U,
"UDP-8e closing with a datagram already in the chip buffer delivers it (7 bytes) instead of cancelling");
CHECK(memcmp(buf, "LASTPKT", 7) == 0, "UDP-8f the delivered payload is intact on the close path");
reset_all();
/* 网络重建:cancel_all 逐个通知 */
bring_up_netctx();
fd = udp_bind(123);
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_udp_outparam_arm(fd, NULL, NULL);
s_net_udp[fd].pending = 1U;
s_net_udp[fd].buf = buf;
s_net_udp[fd].len = (int)sizeof(buf);
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
net_udp_cancel_all(NET_ERR_CONNRESET);
CHECK(net_udp_pending(fd) == 0 && g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller,
"UDP-8g cancel_all wakes every waiter (the rebuild path cannot leave a task hanging)");
reset_all();
}
/* ================= UDP-9 出参互斥与泄漏(AGENTS 禁止的回传字段,本改造的替代面) ================= */
static void tc_udp9_outparam_slot(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[16];
int addrlen;
int fd;
int rc;
int puts_before;
int reads_before;
bring_up();
fd = udp_bind(123);
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
g_ns_int_pin_set = 0;
/* 基线:bind 自己投过一条消息,"这次拒绝有没有投递"只能跟调用前的计数比 */
puts_before = g_ns_put_calls;
reads_before = g_chip.read_calls;
/* 槽位被别的调用方占着:第二个并发收包必须被拒(数据报语义下无法判归属) */
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
addrlen = (int)sizeof(in);
if (net_udp_outparam_arm(fd, sa, &addrlen) != 0) {
CHECK(0, "UDP-9a arming a free slot succeeds");
}
rc = net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &addrlen);
CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY,
"UDP-9b a second receiver on the same fd gets BUSY (two datagrams cannot be attributed to two callers)");
CHECK(g_ns_put_calls == puts_before && g_chip.read_calls == reads_before,
"UDP-9c the refusal is clean: nothing enqueued, nothing read from the chip");
net_udp_outparam_disarm(fd);
/* 入队前失败(队列满)不许把槽位钉住 */
ns_rtos_q_fill(8);
rc = net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &addrlen);
CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY, "UDP-9d a full queue -> BUSY");
CHECK(s_net_udp[fd].armed == 0U,
"UDP-9e the slot is given back on the enqueue-failure path (a leaked slot would pin this fd as BUSY forever)");
ns_rtos_q_flush();
reset_all();
}
/* ================= UDP-10 截断排空(旧实现留半包,下一包从此错位) ================= */
static void tc_udp10_truncation_drains(void)
{
char buf[8];
int fd;
int rc;
bring_up_netctx();
fd = udp_bind(123);
udp_script_server_datagram("0123456789ABCDE", 17, 9U, "10.0.0.3");
ch395f_fake_push_recv_len((uint8_t)fd, 25U);
rc = net_recvfrom(fd, buf, 4, NET_MSG_DONTWAIT, NULL, NULL);
CHECK(rc == 4, "UDP-10a a short buffer yields exactly its capacity (BSD truncation)");
CHECK(memcmp(buf, "0123", 4) == 0, "UDP-10b the first four payload bytes, header excluded");
CHECK((int)g_fx.recv_pos == 25,
"UDP-10c the WHOLE datagram was drained (25 chip bytes consumed): a leftover tail would desync every later packet");
CHECK(g_ns_error_count >= 1,
"UDP-10d the truncated drop is logged (silent byte loss is the hardest thing to debug)");
CHECK(s_net_socks[fd].readable == 0U, "UDP-10e readable cleared after the full read");
reset_all();
}
/* ================= UDP-11 执行任务自等自通知被拒(同 net_pending 守卫) ================= */
static void tc_udp11_self_wait_refused(void)
{
char buf[16];
int fd;
bring_up_netctx();
fd = udp_bind(123);
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
g_ns_int_pin_set = 0;
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BUSY,
"UDP-11a a blocking UDP receive inside netTask is refused (the tick that would complete it runs in that same task: self-wait = deadlock)");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"UDP-11b the same call with NET_MSG_DONTWAIT is the legal netTask form -> WOULDBLOCK, no wait");
reset_all();
}
/* ================= UDP-12 net_init 重建:挂起全部结算 ================= */
static void tc_udp12_rebuild_cancels(void)
{
char buf[16];
int fd;
bring_up();
fd = udp_bind(123);
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_udp_outparam_arm(fd, NULL, NULL);
s_net_udp[fd].pending = 1U;
s_net_udp[fd].buf = buf;
s_net_udp[fd].len = (int)sizeof(buf);
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
(void)net_init("192.168.31.101", "255.255.255.0", "192.168.31.1", NULL);
CHECK(net_udp_pending(fd) == 0,
"UDP-12a a stack rebuild cancels every pending UDP receive (the same rule the TCP pending table follows)");
CHECK(g_ns_notify_last_val == (uint32_t)(int)NET_ERR_CONNRESET,
"UDP-12b the waiter is woken with CONNRESET on rebuild");
reset_all();
}
/* ================= UDP-13 公开流式接口不接受 DGRAM(剥头只有一条路) ================= */
static void tc_udp13_stream_api_rejects_dgram(void)
{
char buf[16];
int fd;
bring_up_netctx();
fd = udp_bind(123);
udp_script_server_datagram("XYZW", 4, 9U, "10.0.0.4");
ch395f_fake_push_recv_len((uint8_t)fd, 12U);
CHECK(net_recv(fd, buf, (int)sizeof(buf), 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-13a net_recv on a DGRAM socket is refused (it reads raw and would hand the chip header over as data)");
CHECK(net_recv_nb(fd, buf, (int)sizeof(buf)) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-13b net_recv_nb refused the same way (one UDP read path only: net_recvfrom)");
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 100U) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-13c net_recv_to refused the same way");
CHECK(g_chip.read_calls == 0, "UDP-13d refusal touched the chip zero times");
reset_all();
}
/* ================= UDP-14 队列化改造的存在理由:调用方上下文零 SPI ================= */
static void tc_udp14_caller_context_zero_spi(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[32];
int addrlen;
int fd;
int reads_before;
int writes_before;
bring_up();
fd = udp_bind(123);
/* 调用方上下文:日志里出现任何一个芯片事务短名都算违反约定 */
reads_before = g_chip.read_calls;
writes_before = g_chip.write_calls;
ch395f_fake_clear_log();
udp_script_server_datagram("HELLO", 5, 53U, "192.168.31.200");
ch395f_fake_push_recv_len((uint8_t)fd, 13U);
s_net_socks[fd].readable = 1U;
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen) == 5,
"UDP-14a caller-context recvfrom succeeds");
CHECK(g_chip.read_calls == reads_before + 2,
"UDP-14b exactly header + payload reads, all issued while netTask owned the pump (the caller blocked, it did not touch SPI)");
CHECK(g_chip.write_calls == writes_before, "UDP-14c a receive writes nothing to the send buffer");
CHECK(g_ns_block_waits >= 1,
"UDP-14d the caller waited on its own task notification (this is what replaces the old spin-poll in the caller task)");
reset_all();
}
/* ================= UDP-15 执行体与槽位的入参守卫(netTask 直调) ================= */
/* 这些守卫是"队列化之后仍然存在的第二条入口":板级用例与 netTask 内部直调 exec。
* 一旦入参检查松掉,轻则把越界 fd 当合法 fd 用,重则在 s_net_udp[] 上算出越界指针。
* 断言顺带钉住"拒绝对芯片零操作"——守卫失败不许留下半个芯片事务。 */
static void tc_udp15_exec_arg_guards(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[8];
int udp_fd;
int raw_fd;
int stream_fd;
int addrlen;
int reads_before;
int writes_before;
int desip_before;
bring_up_netctx();
udp_fd = udp_bind(123);
stream_fd = mk_sock(NET_SOCK_STREAM, NET_SOCK_STATE_ESTABLISHED);
raw_fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); /* 只 socket 不 bind */
reads_before = g_chip.read_calls;
writes_before = g_chip.write_calls;
desip_before = g_fxchip.set_des_ip_calls;
/* 越界 fd:四个执行体 + 槽位三件套 */
CHECK(net_udp_bind_exec(-1, 123U) == -1 && net_get_errno() == NET_ERR_BADF,
"UDP-15a bind_exec refuses a negative fd");
CHECK(net_udp_bind_exec(NET_MAX_SOCKETS, 123U) == -1 && net_get_errno() == NET_ERR_BADF,
"UDP-15b bind_exec refuses fd == NET_MAX_SOCKETS (the array has no such entry)");
CHECK(net_udp_set_peer_exec(99, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"UDP-15c set_peer_exec refuses an out-of-range fd");
CHECK(net_udp_sendto_exec(-1, buf, 4, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"UDP-15d sendto_exec refuses a negative fd");
CHECK(net_udp_recvfrom_exec(NET_MAX_SOCKETS, buf, (int)sizeof(buf),
NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF,
"UDP-15e recvfrom_exec refuses an out-of-range fd BEFORE it takes &s_net_udp[fd] (an out-of-range slot pointer is a wild write waiting to happen)");
CHECK(net_udp_outparam_arm(-1, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"UDP-15f the out-param slot refuses an out-of-range fd");
/* 越界的 disarm/clear 只许静默返回,且不许碰合法槽位 */
CHECK(net_udp_outparam_arm(udp_fd, NULL, NULL) == 0, "UDP-15g in-range arm succeeds");
net_udp_outparam_disarm(-1);
net_udp_clear(999);
CHECK(net_udp_armed(udp_fd) == 1,
"UDP-15h an out-of-range disarm/clear leaves the in-range slot exactly as it was");
net_udp_outparam_disarm(udp_fd);
CHECK(net_udp_armed(udp_fd) == 0, "UDP-15i the in-range disarm releases it");
/* 类型不符:UDP 执行体不碰 TCP fd(bind/set_peer/recvfrom 看 type,sendto 看状态) */
CHECK(net_udp_bind_exec(stream_fd, 123U) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-15j bind_exec on a STREAM fd is refused (a chip reopen would silently convert its protocol)");
CHECK(net_udp_set_peer_exec(stream_fd, NULL) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-15k set_peer_exec on a STREAM fd is refused");
CHECK(net_udp_recvfrom_exec(stream_fd, buf, (int)sizeof(buf),
NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"UDP-15l recvfrom_exec on a STREAM fd is refused (stream reads belong to net_recv)");
CHECK(net_udp_sendto_exec(stream_fd, buf, 4, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-15m sendto_exec on a STREAM fd fails on the state gate (it shares net_send_locked, so the UDP-open check is the guard - not a type check)");
/* 未 bind 的 UDP fd:芯片侧 Socket 还没开,三条执行体一律 NOTCONN */
CHECK(net_udp_sendto_exec(raw_fd, buf, 4, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-15n sendto_exec on an unbound UDP fd is refused (open would reset DesIP/DesPort written before it)");
CHECK(net_udp_recvfrom_exec(raw_fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-15n2 recvfrom_exec on an unbound UDP fd is refused");
CHECK(net_udp_set_peer_exec(raw_fd, NULL) == -1 && net_get_errno() == NET_ERR_NOTCONN,
"UDP-15n3 set_peer_exec on an unbound UDP fd is refused");
CHECK(net_udp_sendto_exec(raw_fd, buf, 0, NULL, NULL) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-15p a zero-length payload is refused before any state check");
CHECK(net_udp_sendto_exec(raw_fd, NULL, 4, NULL, NULL) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-15q a NULL payload is refused");
CHECK(net_udp_recvfrom_exec(raw_fd, NULL, (int)sizeof(buf), NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"UDP-15r recvfrom_exec refuses a NULL buffer");
CHECK(net_udp_recvfrom_exec(raw_fd, buf, 0, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"UDP-15s recvfrom_exec refuses a zero-capacity buffer");
/* 公开入口:给了地址就必须给够长度(静默忽略 = 悄悄发到上一个对端) */
(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.9");
CHECK(net_sendto(udp_fd, buf, 4, 0, sa, 4) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-15t net_sendto with a destination but a short addrlen is refused");
addrlen = 4;
CHECK(net_recvfrom(udp_fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &addrlen) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"UDP-15u net_recvfrom with a src_addr but *addrlen too small is refused (it would half-fill the caller's struct)");
CHECK(net_recvfrom(udp_fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"UDP-15v net_recvfrom with src_addr but NULL addrlen is refused");
CHECK(g_chip.read_calls == reads_before && g_chip.write_calls == writes_before &&
g_fxchip.set_des_ip_calls == desip_before,
"UDP-15w every one of these refusals left the chip untouched (a rejected call must not start a transaction)");
/* 最后才是正例:端口 0 = 请本层分配动态端口(取号游标只有一个,就在 netTask 里) */
CHECK(net_udp_bind_exec(raw_fd, 0U) == 0, "UDP-15x bind on a fresh UDP fd succeeds (port 0 = dynamic)");
CHECK(s_net_socks[raw_fd].local_port >= 49152U && s_net_socks[raw_fd].state == NET_SOCK_STATE_UDP_OPEN,
"UDP-15y the dynamic port came from the netTask-side cursor and the socket is open (the cursor used to be poked from whichever task called bind)");
reset_all();
}
/* ================= UDP-16 包装层的失败出口:队列未绑定 / 队列满 ================= */
/* 队列化接口最怕的一种泄漏:出参槽登记成功、消息没投出去。槽位留在原地,
* 这条 fd 之后每一次收包都被自己的旧登记判成 BUSY——而且永远不会自己好。 */
static void tc_udp16_wrapper_failure_exits(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
char buf[16];
int addrlen;
/* 队列句柄存档(清掉之后要还原,还原走的是同一条 adopt 路径) */
osMessageQueueId_t q_saved;
int fd;
int rc;
bring_up();
fd = udp_bind(123);
q_saved = s_net_msg_queue;
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(456);
in.sin_addr.s_addr = 0U;
addrlen = (int)sizeof(in);
/* 「执行任务已建档、队列还没绑上」这个窗口:net_init 早期,或 CubeMX 里
* 根本没建 netMsgQueue 的构建。手工把队列指针清掉才能走到这条出口——
* 未 net_init 时每个任务都被当成执行任务,走的是直调分支,到不了这里。
* ⚠ 这条出口按模块惯例是**裸 NET_ERR、不记 errno**(它表达的是"这套构建没配好
* 队列",不是某次调用的可分类失败),所以这里只钉 -1,不钉 net_get_errno()。 */
s_net_msg_queue = NULL;
CHECK(net_bind(fd, sa, (int)sizeof(in)) == -1,
"UDP-16a bind with no queue bound fails (never pretends success without an open)");
CHECK(net_sendto(fd, "ping", 4, 0, NULL, 0) == -1,
"UDP-16b sendto with no queue bound fails");
CHECK(net_udp_set_peer(fd, NULL) == -1,
"UDP-16c set_peer with no queue bound fails");
rc = net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen);
CHECK(rc == -1,
"UDP-16d recvfrom with no queue bound fails");
CHECK(net_udp_armed(fd) == 0,
"UDP-16e the failed send-back hands the out-param slot straight back");
rc = net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &addrlen);
CHECK(rc == -1,
"UDP-16f the NEXT call on the same fd fails the same way, not as BUSY - proof the slot did not leak");
/* 队列满:投不进去 → BUSY,且槽位同样当场归还 */
s_net_msg_queue = q_saved;
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_sendto(fd, "ping", 4, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BUSY,
"UDP-16g sendto on a full queue returns BUSY (osWaitForever on a queue nobody is draining would freeze the caller task)");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BUSY,
"UDP-16h recvfrom on a full queue returns BUSY");
CHECK(net_udp_armed(fd) == 0,
"UDP-16i the full-queue exit hands the slot back too (this is the exit that used to poison the fd forever)");
CHECK(net_udp_set_peer(fd, NULL) == -1 && net_get_errno() == NET_ERR_BUSY,
"UDP-16j set_peer on a full queue returns BUSY");
ns_rtos_q_flush();
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"UDP-16k once the queue drains, the same fd works normally again (the refusals above were stateless)");
reset_all();
}
/* ================= UDP-17 公开 set_peer/get_mode 与"回服务器模式" ================= */
static void tc_udp17_set_peer_and_mode(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
uint8_t stream[64];
char buf[64];
int addrlen;
int fd;
int raw_fd;
int stream_fd;
int rc;
int i;
bring_up();
fd = udp_bind(123);
raw_fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
stream_fd = mk_sock(NET_SOCK_STREAM, NET_SOCK_STATE_ESTABLISHED);
CHECK(net_udp_get_mode(fd) == NET_UDP_MODE_SERVER,
"UDP-17a a freshly bound UDP socket is in chip SERVER mode (that is what bind programs: DesIP=0xFFFFFFFF)");
CHECK(net_udp_get_mode(-1) == -1 && net_get_errno() == NET_ERR_BADF,
"UDP-17b get_mode refuses an out-of-range fd");
CHECK(net_udp_get_mode(stream_fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"UDP-17c get_mode on a STREAM fd is INVAL (there is no such thing as its chip mode)");
CHECK(net_udp_set_peer(raw_fd, NULL) == -1 && net_get_errno() == NET_ERR_NOTCONN,
"UDP-17d set_peer before bind is refused (open would reset what we just wrote)");
/* 切客户端模式(经队列,调用方上下文) */
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(123);
in.sin_addr.s_addr = net_inet_addr("119.28.220.214");
CHECK(net_udp_set_peer(fd, &in) == 0, "UDP-17e set_peer succeeds through the queue");
CHECK(net_udp_get_mode(fd) == NET_UDP_MODE_CLIENT, "UDP-17f the socket is now CLIENT mode");
CHECK(g_fx.des_ip[fd][0] == 119U && g_fx.des_ip[fd][3] == 214U &&
g_fx.des_port[fd] == 123U,
"UDP-17g the chip's destination registers carry the peer (line order, as the chip wants it)");
CHECK(s_net_socks[fd].remote_ip == net_inet_addr("119.28.220.214") &&
s_net_socks[fd].remote_port == 123U,
"UDP-17h peer bookkeeping is written on the switch, not only on send (client-mode receive has no header to read)");
CHECK(g_chip.open_calls == 1,
"UDP-17i switching to client mode does NOT reopen the socket (a reopen would drop the reply already buffered - the whole reason SNTP can use this)");
/* 回服务器模式:不重开,只把 DesIP/DesPort 复原并清对端账 */
CHECK(net_udp_set_peer(fd, NULL) == 0, "UDP-17j set_peer(NULL) returns to server mode");
CHECK(net_udp_get_mode(fd) == NET_UDP_MODE_SERVER, "UDP-17k mode is SERVER again");
CHECK(g_fx.des_ip[fd][0] == 0xFFU && g_fx.des_ip[fd][1] == 0xFFU &&
g_fx.des_ip[fd][2] == 0xFFU && g_fx.des_ip[fd][3] == 0xFFU &&
g_fx.des_port[fd] == 0U,
"UDP-17l the chip registers are back to the bind-time server values");
CHECK(s_net_socks[fd].remote_ip == 0U && s_net_socks[fd].remote_port == 0U &&
s_net_socks[fd].remote_ip_arr[0] == 0U,
"UDP-17m the peer bookkeeping is cleared with the mode (a stale peer would be used as the source address by a later client-mode read)");
CHECK(g_chip.open_calls == 1, "UDP-17n returning to server mode does not reopen either");
/* 端到端:回到服务器模式后,收包又必须剥掉 8 字节信息头 */
for (i = 0; i < 64; i++) {
stream[i] = (uint8_t)(0xC0 + i); /* 前 8 字节故意像信息头 */
}
ch395f_fake_set_recv_stream(stream, 20U);
ch395f_fake_push_recv_len((uint8_t)fd, 20U);
s_net_socks[fd].readable = 1U;
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
rc = net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen);
CHECK(rc == 12,
"UDP-17o after the mode was restored the header is stripped again (20 chip bytes in, 12 payload bytes out)");
CHECK((uint8_t)buf[0] == (uint8_t)0xC8U && (uint8_t)buf[11] == (uint8_t)0xC0U + 19U,
"UDP-17p delivery starts at the 9th chip byte: the header really is gone, not copied then skipped in the buffer");
reset_all();
}
/* ================= UDP-18 挂起 sendto:分派处不许发第二次回执 ================= */
/* UDP 发送与 TCP 共用 net_send_locked:发送缓冲没空出来时登记挂起 send,由
* net_send_pending_tick 续传。这条出口的正确性只有一句话:**回执只能有一份**,
* 而且必须是续传完成那一份。登记时就回执,调用方会拿到 0x70000000 当字节数。 */
static void tc_udp18_pending_send_completes(void)
{
static char payload[1400];
int fd;
int rc;
int writes_before;
bring_up();
fd = udp_bind(123);
writes_before = g_chip.write_calls;
/* 没有 SENBUF_FREE:发送缓冲始终没空,一块都写不进去 */
g_ns_int_pin_set = 0;
s_net_socks[fd].send_ready = 0U;
/* 假对端:续传由它下一轮的 tick 结算(真实实现在 net_pending 自己的单测里测) */
ns_peers_arm_send_pending_done(fd, 1400);
rc = net_sendto(fd, payload, (int)sizeof(payload), 0, NULL, 0);
CHECK(rc == 1400,
"UDP-18a the caller gets the byte count of the COMPLETED send (if the dispatch notified on registration, this would be 0x70000000 or an error)");
CHECK(g_peers.send_reg_calls == 1 && g_peers.last_snd_len == 1400 &&
g_peers.last_snd_sent == 0,
"UDP-18b nothing was sent when the pending send was registered (0 of 1400) - the spin budget expired without a free send buffer");
CHECK(g_chip.write_calls == writes_before,
"UDP-18c a stalled UDP send wrote nothing to the chip (writing into a full send buffer is silently dropped - TRAP-NET-04)");
CHECK(g_peers.state_down_calls >= 1,
"UDP-18d the shared state predicate was consulted (UDP and TCP share one chunking implementation, so TRAP-NET-16 cannot come back as a second path)");
reset_all();
}
/* ================= UDP-19 tick 的兜底:会话已死、陈旧电平、零长度 ================= */
static void tc_udp19_tick_fallbacks(void)
{
char buf[16];
int fd;
int rc;
int reads_before;
int notifies_before;
bring_up_netctx();
fd = udp_bind(123);
/* 挂起收包登记中,控制块被拆(close/复位/被别的任务抢走) */
CHECK(net_udp_outparam_arm(fd, NULL, NULL) == 0, "UDP-19a slot armed for the fabricated wait");
s_net_udp[fd].pending = 1U;
s_net_udp[fd].buf = buf;
s_net_udp[fd].len = (int)sizeof(buf);
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
notifies_before = g_ns_notify_calls;
net_udp_tick(s_net_socks);
CHECK(net_udp_pending(fd) == 0,
"UDP-19a2 a pending UDP receive whose socket died is settled in that same tick (nobody else will ever cancel it: reconcile ignores UDP)");
CHECK(g_ns_notify_calls == notifies_before + 1 &&
g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller &&
g_ns_notify_last_val == (uint32_t)(int)NET_ERR_CONNRESET,
"UDP-19b the settlement notifies the waiter with the error code (a lost notification = that task sleeps on a port that no longer exists)");
net_udp_tick(s_net_socks);
CHECK(g_ns_notify_calls == notifies_before + 1,
"UDP-19c the second tick settles nothing again (no double notification)");
/* 陈旧 readable:标志说有、芯片说没有 → 清标志,请求继续等 */
CHECK(net_udp_outparam_arm(fd, NULL, NULL) == 0, "UDP-19d re-armed for the stale-level case");
s_net_udp[fd].pending = 1U;
s_net_udp[fd].buf = buf;
s_net_udp[fd].len = (int)sizeof(buf);
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
s_net_socks[fd].state = NET_SOCK_STATE_UDP_OPEN;
s_net_socks[fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd, 0U);
reads_before = g_chip.read_calls;
net_udp_tick(s_net_socks);
CHECK(s_net_socks[fd].readable == 0U,
"UDP-19e a stale readable flag is cleared (leaving it set = one wasted SPI query every millisecond, forever)");
CHECK(net_udp_pending(fd) == 1, "UDP-19f a stale flag is not data - the request keeps waiting");
CHECK(g_chip.read_calls == reads_before, "UDP-19g the empty chip was not read past the length query");
net_udp_clear(fd);
/* 零长度不许进剥头逻辑:剥几字节、拷几字节、排空几字节必须同源 */
rc = net_udp_datagram_read(fd, &s_net_socks[fd], buf, (int)sizeof(buf), NULL, NULL, 0);
CHECK(rc == 0, "UDP-19h datagram_read with avail == 0 delivers nothing (it must not invent a header out of a zero-length report)");
rc = net_udp_datagram_read(fd, &s_net_socks[fd], buf, (int)sizeof(buf), NULL, NULL, -1);
CHECK(rc == 0, "UDP-19i a negative length report is treated the same way");
/* 越界的 settle_close 静默返回(幂等兜底,不崩、不误结算别的 fd) */
CHECK(net_udp_outparam_arm(fd, NULL, NULL) == 0, "UDP-19j armed for the range-guard check");
s_net_udp[fd].pending = 1U;
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
notifies_before = g_ns_notify_calls;
net_udp_settle_close(-1, &s_net_socks[fd]);
net_udp_settle_close(NET_MAX_SOCKETS, &s_net_socks[fd]);
CHECK(net_udp_pending(fd) == 1 && g_ns_notify_calls == notifies_before,
"UDP-19k out-of-range settle_close settles nothing (it must not walk into a neighbouring fd's waiter)");
reset_all();
}
/* ================= UDP-20 重绑必须给等待者一个了断 ================= */
static void tc_udp20_rebind_settles_waiter(void)
{
struct net_sockaddr_in in;
const struct net_sockaddr *sa = (const struct net_sockaddr *)&in;
char buf[16];
int fd;
int notifies_before;
bring_up_netctx();
fd = udp_bind(123);
/* 有人在等包(手工登记,理由同 UDP-12:公开入口一进挂起就睡在通知里) */
CHECK(net_udp_outparam_arm(fd, NULL, NULL) == 0, "UDP-20a slot armed");
s_net_udp[fd].pending = 1U;
s_net_udp[fd].buf = buf;
s_net_udp[fd].len = (int)sizeof(buf);
s_net_udp[fd].caller = (TaskHandle_t)&g_ns_task_caller;
notifies_before = g_ns_notify_calls;
/* 另一个任务重新 bind 同一 fd:芯片侧重开,缓冲里可能到的包一起作废 */
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(456);
CHECK(net_bind(fd, sa, (int)sizeof(in)) == 0, "UDP-20b the rebind itself succeeds");
CHECK(g_ns_notify_calls == notifies_before + 1 &&
g_ns_notify_last_val == (uint32_t)(int)NET_ERR_CONNRESET,
"UDP-20c the rebind hands the waiter an explicit CONNRESET before reopening (clearing the slot without notifying = that task sleeps forever on a socket that was reopened under it)");
CHECK(net_udp_pending(fd) == 0 && net_udp_armed(fd) == 0,
"UDP-20d no slot survives the rebind (neither pending nor armed)");
CHECK(s_net_socks[fd].local_port == 456U &&
s_net_socks[fd].udp_mode == NET_UDP_MODE_SERVER &&
s_net_socks[fd].readable == 0U,
"UDP-20e the reopened socket is a clean server-mode socket: new port, SERVER mode, no inherited level");
CHECK(g_chip.open_calls == 2, "UDP-20f a rebind is the HEAVY way back to server mode (it reopens; net_udp_set_peer(fd, NULL) is the light one)");
reset_all();
}
/* ===== UDP-21 两条"错误不是超时"的出口:bind 满队列 + recvfrom_to 真失败 ===== */
static void tc_udp21_bind_and_recvfrom_to_exits(void)
{
struct net_sockaddr_in in;
const struct net_sockaddr *sa = (const struct net_sockaddr *)&in;
char buf[8];
int fd;
int raw_fd;
int chip_fd;
bring_up(); /* 调用方上下文:公开入口一律走队列 */
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(1234);
/* 队列被别的请求占满:bind 只能"排队等空位",等不到就 BUSY,
* 绝不能把 fd 留在半个打开状态(芯片侧一条命令都没发)。 */
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_bind(fd, sa, (int)sizeof(in)) == NET_ERR &&
net_get_errno() == NET_ERR_BUSY,
"UDP-21a bind on a saturated queue is BUSY, and the code is the caller's own -11 (not a bare -1 the caller cannot diagnose)");
CHECK(s_net_socks[fd].state != NET_SOCK_STATE_UDP_OPEN && g_chip.open_calls == 0,
"UDP-21b a refused bind opens nothing on the chip: the fd is still just created, not half-bound");
ns_rtos_q_flush();
/* 同一 fd 在队列腾空后必须能正常 bind:BUSY 是"这次没排上",不是给 fd 投毒 */
CHECK(net_bind(fd, sa, (int)sizeof(in)) == 0 &&
s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN,
"UDP-21c the same fd binds fine once the queue drains (a BUSY refusal poisons nothing)");
/* net_recvfrom_to 的"真错误"出口:结果 <0 且不是超时 → 原样带精确码回调用方。
* 挑一个未 bind 的 DGRAM fd:芯片侧 Socket 还没开,执行体必返 NOTCONN。 */
raw_fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_recvfrom_to(raw_fd, buf, (int)sizeof(buf), 50U, NULL, NULL) == NET_ERR &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-21d net_recvfrom_to maps an execution failure to the precise code (NOTCONN here), and does not disguise it as a timeout");
CHECK(net_udp_armed(raw_fd) == 0,
"UDP-21e2 the refused attempt hands the out-param slot back (an armed slot left behind by a refusal turns this fd permanently BUSY, and the error only surfaces on the *next* caller)");
CHECK(net_recvfrom_to(raw_fd, buf, (int)sizeof(buf), 50U, NULL, NULL) == NET_ERR &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-21e the same refusal repeats with the same code (BUSY here would mean the fd was poisoned by its own last failure)");
/* 正向:预算内已有数据 → 返回字节数,不走 -2 */
udp_script_server_datagram("time", 4, 123U, "10.20.30.40");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == 4,
"UDP-21f sanity: the bound fd still delivers a queued datagram byte-for-byte");
/* 执行体在**执行阶段**失败(不是入队失败)时,精确码要能回到等待方:
* 真芯片 open 会回 ERR_BUSY,这条通道是整个"通知值即结果"设计的落点。
* 用一个新 fd,别拿已经 bind 成功的那个——那会看不出"失败没把 fd 变成已 bind"。 */
chip_fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_fake.open_rc[chip_fd] = CH395F_ERR_BUSY;
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(4567);
CHECK(net_bind(chip_fd, sa, (int)sizeof(in)) == NET_ERR &&
net_get_errno() == NET_ERR_BUSY,
"UDP-21g a bind the chip refuses (open → ERR_BUSY) comes back through the notify channel as -1 + the precise BUSY, not a bare -1 and not a timeout");
CHECK(s_net_socks[chip_fd].state != NET_SOCK_STATE_UDP_OPEN,
"UDP-21h a failed bind does not leave the fd looking bound");
g_fake.open_rc[chip_fd] = CH395F_ERR_SUCCESS;
CHECK(net_bind(chip_fd, sa, (int)sizeof(in)) == 0 &&
s_net_socks[chip_fd].state == NET_SOCK_STATE_UDP_OPEN &&
s_net_socks[chip_fd].local_port == 4567U,
"UDP-21i the same fd binds normally once the chip stops refusing");
/* 队列里有"别人的失败"排在我这条之前:两条在同一轮被排空,
* 覆盖式通知让我拿到的是**自己那条**的回执(后写覆盖先写)。
* 既有 TC-NETSOCK-545 在 TCP 侧钉过同一形态,这里补 UDP bind 这一条。 */
{
net_msg_t stale;
int ghost = 6;
(void)memset(&stale, 0, sizeof(stale));
stale.type = NET_MSG_UDP_BIND;
stale.sockfd = ghost; /* 没 net_socket() 过的 fd:执行体必拒 */
stale.len = 1234U; /* bind 的本地端口走 len(主机序) */
stale.addr = in;
stale.addrlen = (int)sizeof(in);
stale.caller = (TaskHandle_t)&g_ns_task_caller;
CHECK(ns_rtos_q_put_raw(&stale) == 0, "UDP-21j the ghost bind request is queued");
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(4567);
CHECK(net_bind(chip_fd, sa, (int)sizeof(in)) == 0,
"UDP-21k a stranger request settling first does not turn my own successful bind into an error (the completion that overwrites the notify value is mine)");
ns_rtos_q_flush();
}
reset_all();
}
/* ===== UDP-22 netTask 直调路径:set_peer 的三条出口(板级用例走这条) ===== */
static void tc_udp22_set_peer_direct_ctx(void)
{
struct net_sockaddr_in peer;
int fd;
int stream_fd;
int puts_before;
int set_ip_before;
bring_up_netctx(); /* 留在 netTask 上下文:owner → 直调,不经队列 */
fd = udp_bind(777);
puts_before = g_ns_put_calls;
(void)memset(&peer, 0, sizeof(peer));
peer.sin_family = NET_AF_INET;
peer.sin_port = net_htons(123);
peer.sin_addr.s_addr = net_inet_addr("119.28.220.214");
CHECK(net_udp_set_peer(fd, &peer) == 0,
"UDP-22a set_peer inside netTask runs directly (the board-test path) and succeeds");
CHECK(g_ns_put_calls == puts_before,
"UDP-22b the direct path does not enqueue a message (a self-addressed queue message would never be drained)");
CHECK(net_udp_get_mode(fd) == NET_UDP_MODE_CLIENT &&
g_fx.des_ip[fd][0] == 119U && g_fx.des_ip[fd][3] == 214U &&
g_fx.des_port[fd] == 123U && g_chip.open_calls == 1,
"UDP-22c direct set_peer writes DesIP/DesPort and flips to CLIENT without reopening the socket");
CHECK(net_udp_set_peer(fd, NULL) == 0 &&
net_udp_get_mode(fd) == NET_UDP_MODE_SERVER &&
g_fx.des_ip[fd][0] == 0xFFU && g_fx.des_port[fd] == 0U,
"UDP-22d NULL peer is the light way back to SERVER: registers restored, still no reopen");
/* 越界 / 未使用 / 面向流:三种拒绝都必须零芯片事务 */
set_ip_before = g_fxchip.set_des_ip_calls;
CHECK(net_udp_set_peer(-1, &peer) == NET_ERR && net_get_errno() == NET_ERR_BADF,
"UDP-22e negative fd is BADF on the direct path too");
CHECK(net_udp_set_peer(NET_MAX_SOCKETS, &peer) == NET_ERR &&
net_get_errno() == NET_ERR_BADF,
"UDP-22f fd >= NET_MAX_SOCKETS is BADF (the range guard is what keeps s_net_udp[] in bounds)");
CHECK(net_udp_set_peer(5, &peer) == NET_ERR && net_get_errno() == NET_ERR_BADF,
"UDP-22g an in-range but unused fd is BADF before anything is touched");
stream_fd = mk_sock(NET_SOCK_STREAM, NET_SOCK_STATE_ESTABLISHED);
CHECK(net_udp_set_peer(stream_fd, &peer) == NET_ERR &&
net_get_errno() == NET_ERR_INVAL,
"UDP-22h set_peer on a STREAM fd is refused as INVAL (the type check runs before the state check: a TCP socket has no UDP mode to switch at all)");
{
int unbound = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_udp_set_peer(unbound, &peer) == NET_ERR &&
net_get_errno() == NET_ERR_NOTCONN,
"UDP-22h2 an unbound DGRAM fd is refused as NOTCONN on the direct path too (DesIP written before open gets reset by open)");
}
CHECK(g_fxchip.set_des_ip_calls == set_ip_before,
"UDP-22i every refusal above wrote zero chip registers (a refusal that already moved DesIP would leave the socket pointing at a peer nobody asked for)");
reset_all();
}
/* ===== UDP-23 netTask 直调收包正例 + 槽位被占的 BUSY 出口 ===== */
static void tc_udp23_recvfrom_direct_ctx(void)
{
struct net_sockaddr_in from;
struct net_sockaddr *sa = (struct net_sockaddr *)&from;
int alen = (int)sizeof(from);
char buf[16];
int fd;
bring_up_netctx();
fd = udp_bind(888);
/* 数据已在芯片缓冲里:直调路径当场读出并就地经槽位写回源地址 */
udp_script_server_datagram("ABCDEFGH", 8, 5353U, "10.1.2.3");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &alen) == 8,
"UDP-23a netTask-context recvfrom reads the datagram on the spot (board tests call it this way)");
CHECK(memcmp(buf, "ABCDEFGH", 8) == 0,
"UDP-23b the 8-byte info header is stripped and the payload starts at byte 0");
CHECK(alen == (int)sizeof(from) && from.sin_addr.s_addr == net_inet_addr("10.1.2.3") &&
from.sin_port == net_htons(5353),
"UDP-23c the source address is written through the caller's pointer, on the direct path as well");
CHECK(net_udp_armed(fd) == 0,
"UDP-23d the slot is handed back by the execution body even on a synchronous hit");
/* 假件的脚本长度耗尽后会**重复最后一个值**(表达"一直有数据"的持续态),
* 这里显式宣布"没包了",否则下面两条会把上一个包再读一遍。 */
ch395f_fake_push_recv_len(0, 0U);
/* 槽位被别人占着:本 fd 上必须 BUSY,且不去碰芯片、也不许动别人的槽 */
CHECK(net_udp_outparam_arm(fd, NULL, NULL) == 0, "UDP-23e slot armed by somebody else");
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, sa, &alen) == NET_ERR &&
net_get_errno() == NET_ERR_BUSY,
"UDP-23f recvfrom while the out-param slot is taken is BUSY rather than writing through a pointer that belongs to another call (公开入口对外仍是 -1 + 精确码)");
CHECK(net_udp_armed(fd) != 0,
"UDP-23g the refusal does not steal the slot it did not own");
(void)net_udp_outparam_disarm(fd);
/* 直调 + 阻塞 + 无数据 = 自等,被守卫拒成 BUSY(tick 就在自己脚下跑,等谁都不会醒) */
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == NET_ERR &&
net_get_errno() == NET_ERR_BUSY,
"UDP-23h a blocking recvfrom executed inside netTask refuses to register a pending slot nobody can settle (self-wait guard)");
CHECK(net_udp_armed(fd) == 0 && net_udp_pending(fd) == 0,
"UDP-23i that refusal leaves neither an armed nor a pending slot behind");
reset_all();
}
/* ===================== 本分片入口 ===================== */
static void ns_run_s7(void)
{
tc_udp1_bind_sequence();
tc_udp2_requires_bind();
tc_udp3_server_strip();
tc_udp4_client_no_strip();
tc_udp5_sendto_dest_switches_mode();
tc_udp6_pending_recv_arrival();
tc_udp7_pending_timeout();
tc_udp8_pending_cancel();
tc_udp9_outparam_slot();
tc_udp10_truncation_drains();
tc_udp11_self_wait_refused();
tc_udp12_rebuild_cancels();
tc_udp13_stream_api_rejects_dgram();
tc_udp14_caller_context_zero_spi();
tc_udp15_exec_arg_guards();
tc_udp16_wrapper_failure_exits();
tc_udp17_set_peer_and_mode();
tc_udp18_pending_send_completes();
tc_udp19_tick_fallbacks();
tc_udp20_rebind_settles_waiter();
tc_udp21_bind_and_recvfrom_to_exits();
tc_udp22_set_peer_direct_ctx();
tc_udp23_recvfrom_direct_ctx();
}