fff961d803
前置来自 #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/任务规划/**。
1285 lines
62 KiB
C
1285 lines
62 KiB
C
/*
|
||
* 模块名称: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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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 *)∈
|
||
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();
|
||
}
|