0d469a835a
剥头判据改了,测试就得跟着改——但这一条提交里真正值钱的是"为什么会错到": UDP-4 当时写的是"客户端形态整包都是载荷、源地址取对端记账",UDP-24 的 240 轮 定种子随机长跑也照同一个模型脚本报假件。**随机长跑全绿恰恰不能证明模型对**: 它扫的是我自己写的那套模型,模型错了它一样绿。只有真机那次 48→56 的读数把这件事 钉死。 - UDP-4 改写为"客户端形态同样剥头",源地址来自信息头(脚本里特意让头里的源 `119.28.220.215` ≠ 记账对端 `119.28.220.214`,这样"取错地方"就必然失败), 并补 UDP-4h 断言 `addrlen`/family 与服务器形态一字不差——一条读路径就该一个契约。 - UDP-17h/m、UDP-22c、UDP-24 的客户端轮同样按定案重写(先 `reset_recv_len` 再 宣布长度,假件的收包脚本必须可重排,这是长跑教过的第二条)。 - 改完判据后覆盖率门禁照出 `net_udp_src_from_peer()` 一行没人走了。**没有把基线 调低**,而是补 UDP-26(9 条)钉住"`<8` 字节残包"那一侧:整包原样交付、一个字 节都不许剥、源地址退到对端记账;没记账时交出全零这个"我不知道",而不是包里的 字节——它是记账那本账如今唯一的读者。 全套 **677/677 绿**(改造前 470);`net_udp.c` 回到 237/239 行、14/14 函数, `net_socket.c` 966/969 行、59/59 函数,全仓 8372/9290(90.1 %)、47/47 套, `scripts/test_coverage_baseline.json` 与改动前一字未变(即无回退)。
1661 lines
81 KiB
C
1661 lines
81 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 客户端形态:DesIP 改写后**仍然剥头**(真机推翻的假设) ====== */
|
||
/*
|
||
* 这一组是 2026-11-04 真机回归改写的。设计时按手册 §9.2.4 读过一句"客户端模式不
|
||
* 带信息头",就以为运行时把 DesIP 从全 F 改成具体对端会让头消失;板子上 TC-NET-1603
|
||
* 实测 48 字节应答可读 56 字节、首字节 0x38 = 信息头的长度字段,证明**加不加头由
|
||
* 开 Socket 那一刻的 DesIP 决定,之后再改 DesIP 只改发送去向、不改收包帧格式**。
|
||
* 本层 bind 一律以全 F 开 Socket,所以头恒在、恒剥。旧断言(客户端形态整包都是
|
||
* 载荷、源地址取对端记账)因此是错的,留着就是把一个真机缺陷写进契约。
|
||
*/
|
||
static void tc_udp4_client_still_strips(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 字节信息头(真机 TC-NET-1603)。
|
||
* 头里写的源是应答真正的来源,未必等于我记账的对端——NAT 后的时间服务器就是这种。 */
|
||
(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 起手 */
|
||
}
|
||
udp_script_server_datagram((const char *)stream, 48, 51999U, "119.28.220.215");
|
||
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 form still delivers exactly the 48 payload bytes (the header is stripped: the chip keeps prepending it after a runtime DesIP change)");
|
||
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.215") &&
|
||
net_ntohs(in.sin_port) == 51999U,
|
||
"UDP-4g source address comes from the header even in client form - the peer bookkeeping would name the address I sent to, not the address the reply came from");
|
||
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
|
||
"UDP-4h addrlen/family filled as in server form (one read path, one contract)");
|
||
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 (a destination-less sendto needs it, and it is what the chip's DesIP registers mirror)");
|
||
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();
|
||
}
|
||
|
||
/* ---------- UDP-24:随机化 soak(纯软件,不需要板子) ---------- */
|
||
/*
|
||
* 函数功能:用固定种子的同余序列生成 240 轮随机 UDP 交互,每轮随机「载荷长度
|
||
* 1..112 / 源端口 / 源 IP / 本轮形态 SERVER 或 CLIENT / 回显是否带显式
|
||
* dest_addr / 是否顺带做一次 close+重开」,并断言一组**不变量**。
|
||
* 函数说明:UDP-1..23 是一条条钉死的具体契约,本组补的是另一件事——任意合法
|
||
* 序列跑完之后系统仍停在合法状态。这类断言最能暴露"某条出口忘了清槽 /
|
||
* 剥头判定被上一条命令带跑 / 长度取错源"的缺陷,而它们恰好在真机上最难
|
||
* 复现(本次改造修掉的 DesIP 形态缺陷就是这一类)。
|
||
* 种子固定 = 失败可原样复现(LCG 参数 k=1664525、c=1013904223)。
|
||
* 断言按"族"计数并打印第一个坏轮次,不逐轮 CHECK:240 轮 × 6 条会把
|
||
* 套件的用例计数彻底淹没,而失败信息一条也不会少。
|
||
* 112 = 假芯片可读流上限 128 B 减去服务器模式的 8 字节信息头。
|
||
*/
|
||
#define UDP_SOAK_ROUNDS 240
|
||
#define UDP_SOAK_MAX_DATA 112
|
||
|
||
static uint32_t udp_soak_rand(uint32_t *st)
|
||
{
|
||
*st = (*st * 1664525U) + 1013904223U;
|
||
return *st;
|
||
}
|
||
|
||
static void tc_udp24_random_soak(void)
|
||
{
|
||
uint32_t st = 20261104U;
|
||
uint8_t payload[UDP_SOAK_MAX_DATA];
|
||
uint8_t rx[UDP_SOAK_MAX_DATA + 8];
|
||
struct net_sockaddr_in peer;
|
||
struct net_sockaddr_in from;
|
||
const char *ips[2];
|
||
int bad_len = 0, bad_bytes = 0, bad_src = 0, bad_mode = 0, bad_slot = 0, bad_send = 0;
|
||
int first_bad[6];
|
||
int i;
|
||
int fd;
|
||
uint32_t w_bytes_before;
|
||
uint32_t w_calls_before;
|
||
int round_ok = 0;
|
||
char nm[192];
|
||
|
||
ips[0] = "10.20.30.40";
|
||
ips[1] = "192.168.7.9";
|
||
first_bad[0] = first_bad[1] = first_bad[2] = -1;
|
||
first_bad[3] = first_bad[4] = first_bad[5] = -1;
|
||
|
||
bring_up();
|
||
fd = udp_bind(6001);
|
||
if (fd < 0) {
|
||
CHECK(0, "UDP-24 soak could not bind a socket");
|
||
reset_all();
|
||
return;
|
||
}
|
||
ch395f_fake_enable_write_capture();
|
||
/* 真芯片每收完一包 WRITE_SEND_BUF 会拉 SENBUF_FREE(TRAP-NET-04:不等这个事件
|
||
* 就再写,写会被丢)。本套件的挂起发送是 ns_peers 的替身、不会自己结算,所以
|
||
* 按 440i/440j 的既有做法把它钉成电平:让每轮 sendto 都走同步写路径,
|
||
* "连续两包背靠背发"这件事交给阶段 16 的真机 TC-NET-1609 去证。 */
|
||
ch395f_fake_set_sint_sticky(fd, CH395F_SINT_STAT_SENBUF_FREE);
|
||
ch395f_fake_push_glob((uint32_t)1U << (fd + 4));
|
||
g_ns_int_pin_set = 0;
|
||
|
||
(void)memset(&peer, 0, sizeof(peer));
|
||
peer.sin_family = NET_AF_INET;
|
||
peer.sin_addr.s_addr = net_inet_addr("119.28.220.214");
|
||
peer.sin_port = net_htons(123U);
|
||
|
||
for (i = 0; i < UDP_SOAK_ROUNDS; i++) {
|
||
int len = 1 + (int)(udp_soak_rand(&st) % (uint32_t)UDP_SOAK_MAX_DATA);
|
||
uint16_t sport = (uint16_t)(1024U + (udp_soak_rand(&st) % 60000U));
|
||
const char *sip = ips[udp_soak_rand(&st) & 1U];
|
||
int as_client = ((int)(udp_soak_rand(&st) % 3U) == 0);
|
||
int j;
|
||
int n;
|
||
int alen;
|
||
int clean = 1;
|
||
|
||
for (j = 0; j < len; j++) {
|
||
payload[j] = (uint8_t)((j * 7 + i * 3 + 0x11) & 0xFF);
|
||
}
|
||
|
||
/* 形态由本轮决定,不沿用上轮遗留:SERVER 用 set_peer(NULL) 轻切回,
|
||
* CLIENT 显式指对端。剥不剥 8 字节必须只由这个判定决定。 */
|
||
if (net_udp_set_peer(fd, as_client ? &peer : NULL) != 0) {
|
||
if (first_bad[3] < 0) { first_bad[3] = i; }
|
||
bad_mode++;
|
||
continue;
|
||
}
|
||
if (net_udp_get_mode(fd) != (as_client ? NET_UDP_MODE_CLIENT : NET_UDP_MODE_SERVER)) {
|
||
if (first_bad[3] < 0) { first_bad[3] = i; }
|
||
bad_mode++;
|
||
continue;
|
||
}
|
||
|
||
(void)memset(rx, 0xA5, sizeof(rx));
|
||
alen = (int)sizeof(from);
|
||
/* 每轮重排长度脚本:假件的收包长度是"按调用序号取"的脚本,240 轮只追加
|
||
* 不清空会让序号撞满容量、之后每轮都读到同一个陈旧长度。 */
|
||
ch395f_fake_reset_recv_len(0);
|
||
/* 两种形态都按"8 字节信息头 + 载荷"脚本:本层 Socket 恒以全 F 开出,运行时
|
||
* 改 DesIP 不改收包帧格式(2026-11-04 真机 TC-NET-1603),所以 CLIENT 轮同样
|
||
* 带头、同样剥。形态在这条 soak 里考的是**发送去向**与寄存器状态,不是帧格式。 */
|
||
udp_script_server_datagram((const char *)payload, len, sport, sip);
|
||
|
||
n = net_recvfrom_to(fd, rx, len, 800U, (struct net_sockaddr *)&from, &alen);
|
||
if (n != len) {
|
||
if (first_bad[0] < 0) { first_bad[0] = i; }
|
||
bad_len++;
|
||
clean = 0;
|
||
} else if (memcmp(rx, payload, (size_t)len) != 0) {
|
||
if (first_bad[1] < 0) { first_bad[1] = i; }
|
||
bad_bytes++;
|
||
clean = 0;
|
||
} else if (alen != (int)sizeof(from) ||
|
||
from.sin_family != NET_AF_INET) {
|
||
if (first_bad[2] < 0) { first_bad[2] = i; }
|
||
bad_src++;
|
||
clean = 0;
|
||
} else {
|
||
/* 源地址恒取自信息头:记账的对端是"我往哪发",不是"谁在回我"。 */
|
||
uint32_t want_ip = net_inet_addr(sip);
|
||
uint16_t want_port = sport;
|
||
|
||
if (from.sin_addr.s_addr != want_ip || net_ntohs(from.sin_port) != want_port) {
|
||
if (first_bad[2] < 0) { first_bad[2] = i; }
|
||
bad_src++;
|
||
clean = 0;
|
||
}
|
||
}
|
||
|
||
/* 出参槽与挂起槽必须在每条出口后归零:泄漏 = 这个 fd 以后永久 BUSY */
|
||
if (net_udp_armed(fd) != 0 || net_udp_pending(fd) != 0) {
|
||
if (first_bad[4] < 0) { first_bad[4] = i; }
|
||
bad_slot++;
|
||
clean = 0;
|
||
}
|
||
|
||
/* 回显:SERVER 轮必须带显式 dest_addr(BSD 语义),CLIENT 轮走对端记账,
|
||
* 两条发送路径都被随机覆盖到。 */
|
||
w_bytes_before = g_fx.write_bytes;
|
||
w_calls_before = g_chip.write_calls;
|
||
if (as_client) {
|
||
n = net_sendto(fd, payload, len, 0, NULL, 0);
|
||
} else {
|
||
n = net_sendto(fd, payload, len, 0, (const struct net_sockaddr *)&peer,
|
||
(int)sizeof(peer));
|
||
}
|
||
if (n != len || g_fx.write_bytes - w_bytes_before != (uint32_t)len ||
|
||
g_chip.write_calls <= w_calls_before ||
|
||
net_udp_armed(fd) != 0 || net_udp_pending(fd) != 0) {
|
||
if (first_bad[5] < 0) { first_bad[5] = i; }
|
||
bad_send++;
|
||
clean = 0;
|
||
}
|
||
|
||
/* 每 17 轮做一次 close + 重开:把 net_close_locked / net_socket 上的
|
||
* net_udp_settle_close / net_udp_clear 挂进随机序列里,重开后必须还能收。 */
|
||
if ((i % 17) == 16) {
|
||
(void)net_close(fd);
|
||
fd = udp_bind(6001);
|
||
if (fd < 0 || net_udp_armed(fd) != 0 || net_udp_pending(fd) != 0 ||
|
||
net_udp_get_mode(fd) != NET_UDP_MODE_SERVER) {
|
||
if (first_bad[4] < 0) { first_bad[4] = i; }
|
||
bad_slot++;
|
||
clean = 0;
|
||
}
|
||
if (fd < 0) {
|
||
break;
|
||
}
|
||
}
|
||
if (clean != 0) {
|
||
round_ok++;
|
||
}
|
||
}
|
||
|
||
/* CHECK 只收 (条件, 名字):数字用 snprintf 拼进名字,失败时一眼看到第几轮坏的 */
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24a soak recv length always equals the chip-announced length (bad=%d, first round=%d, rounds=%d)",
|
||
bad_len, first_bad[0], UDP_SOAK_ROUNDS);
|
||
CHECK(bad_len == 0, nm);
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24b payload bytes neither lose the 8-byte info header nor lose real payload: the header is stripped in BOTH forms, since a runtime DesIP change does not change the chip's frame (bad=%d, first round=%d)",
|
||
bad_bytes, first_bad[1]);
|
||
CHECK(bad_bytes == 0, nm);
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24c source address is always header-derived, in both forms (bad=%d, first round=%d)",
|
||
bad_src, first_bad[2]);
|
||
CHECK(bad_src == 0, nm);
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24d set_peer keeps the mode exactly as commanded (bad=%d, first round=%d)",
|
||
bad_mode, first_bad[3]);
|
||
CHECK(bad_mode == 0, nm);
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24e neither the out-param slot nor the pending slot is ever left behind, including after close and rebind (bad=%d, first round=%d)",
|
||
bad_slot, first_bad[4]);
|
||
CHECK(bad_slot == 0, nm);
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24f every sendto hands the chip exactly len bytes, explicit-dest and peer-bookkeeping alike (bad=%d, first round=%d)",
|
||
bad_send, first_bad[5]);
|
||
CHECK(bad_send == 0, nm);
|
||
(void)snprintf(nm, sizeof(nm), "UDP-24g at least 90 percent of the random rounds are clean end to end (clean=%d/%d)",
|
||
round_ok, UDP_SOAK_ROUNDS);
|
||
CHECK(round_ok * 10 >= UDP_SOAK_ROUNDS * 9, nm);
|
||
net_close(fd);
|
||
reset_all();
|
||
}
|
||
|
||
/* ================= UDP-25 两条 UDP socket 并存:挂起不串门 ================= */
|
||
|
||
/* 前面 1~24 组全都只用一条 UDP socket,唯独漏了队列化真正要保的东西:
|
||
* **等待是一条 socket 自己的事**。两条 socket 里一条 parked 时,
|
||
* 另一条来包不得惊动它,另一条自己收发也不得被它拖住——
|
||
* 这是"串行执行、并行等待"这半句承诺的唯一形状。 */
|
||
static void tc_udp25_two_socket_park_isolation(void)
|
||
{
|
||
char buf_a[32];
|
||
char buf_b[32];
|
||
char buf_c[32];
|
||
struct net_sockaddr_in sa;
|
||
net_msg_t msg;
|
||
int fd_a;
|
||
int fd_b;
|
||
int notifies_before;
|
||
int reads_before;
|
||
int alen;
|
||
int rc;
|
||
char nm[200];
|
||
|
||
bring_up_netctx();
|
||
fd_a = udp_bind(123);
|
||
fd_b = udp_bind(456);
|
||
CHECK(fd_a >= 0 && fd_b >= 0 && fd_a != fd_b,
|
||
"UDP-25a two bound UDP sockets own different slots (per-fd state, not a module-wide flag)");
|
||
if (fd_a < 0 || fd_b < 0 || fd_a == fd_b) {
|
||
reset_all();
|
||
return;
|
||
}
|
||
|
||
/* fdA 的收包登记成挂起:此刻两条 socket 的芯片缓冲都是空的 */
|
||
(void)memset(buf_a, 0x5A, (int)sizeof(buf_a));
|
||
ch395f_fake_push_recv_len((uint8_t)fd_a, 0U);
|
||
ch395f_fake_push_recv_len((uint8_t)fd_b, 0U);
|
||
udp_arm_and_enqueue(fd_a, buf_a, (int)sizeof(buf_a), 0U, &msg);
|
||
net_process_messages();
|
||
CHECK(net_udp_pending(fd_a) == 1 && net_udp_pending(fd_b) == 0,
|
||
"UDP-25b only the socket someone actually waited on holds a pending slot");
|
||
|
||
/* fdB 来一个包:fdA 既不能被结算,也不能被通知,缓冲区一个字节都不能动 */
|
||
notifies_before = g_ns_notify_calls;
|
||
reads_before = g_chip.read_calls;
|
||
s_net_socks[fd_b].readable = 1U;
|
||
ch395f_fake_push_recv_len((uint8_t)fd_b, 16U);
|
||
net_process_messages();
|
||
CHECK(net_udp_pending(fd_a) == 1,
|
||
"UDP-25c another socket's datagram does not settle my parked receive");
|
||
CHECK(g_ns_notify_calls == notifies_before,
|
||
"UDP-25d and does not wake the waiter (a stray notify turns the next real packet into a stale -2)");
|
||
CHECK(buf_a[0] == (char)0x5A,
|
||
"UDP-25e the parked caller's buffer is untouched by the other socket's traffic");
|
||
|
||
/* 现在轮到 fdA 自己的包到:必须就地结算,源地址来自它自己那一包的头部。
|
||
* 长度先把该 fd 的脚本队列清空再给一条:假件的长度是"每次读消耗一格、最后一格
|
||
* 永远重复",本组前面给 fdA 留过 0/16 之类的旧值,不清就会拿旧长度配新内容。 */
|
||
udp_stage_server_datagram("TIME12345678", 12, 51999U, "192.168.31.9");
|
||
ch395f_fake_reset_recv_len((uint8_t)fd_a);
|
||
ch395f_fake_push_recv_len((uint8_t)fd_a, (uint16_t)(8 + 12));
|
||
s_net_socks[fd_a].readable = 1U;
|
||
net_process_messages();
|
||
CHECK(net_udp_pending(fd_a) == 0 && net_udp_armed(fd_a) == 0,
|
||
"UDP-25f the socket's own datagram settles its receive and frees both slots");
|
||
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)12,
|
||
"UDP-25g exactly one notify to the waiter, carrying the byte count");
|
||
CHECK(memcmp(buf_a, "TIME12345678", 12) == 0,
|
||
"UDP-25h the delivered bytes are this socket's own payload (header stripped, nothing off by eight)");
|
||
|
||
/* 先把 fdB 的芯片长度重新宣布成"空"(清掉上一段留的 16),否则它当场读走而不是挂起 */
|
||
ch395f_fake_reset_recv_len((uint8_t)fd_b);
|
||
ch395f_fake_push_recv_len((uint8_t)fd_b, 0U);
|
||
udp_arm_and_enqueue(fd_b, buf_b, (int)sizeof(buf_b), 0U, &msg);
|
||
net_process_messages();
|
||
CHECK(net_udp_pending(fd_b) == 1, "UDP-25i the second socket is now the parked one");
|
||
udp_stage_server_datagram("FD-A-OK", 7, 52000U, "192.168.31.8");
|
||
ch395f_fake_reset_recv_len((uint8_t)fd_a);
|
||
ch395f_fake_push_recv_len((uint8_t)fd_a, (uint16_t)(8 + 7));
|
||
s_net_socks[fd_a].readable = 1U;
|
||
alen = (int)sizeof(sa);
|
||
(void)memset(&sa, 0, sizeof(sa));
|
||
rc = net_recvfrom(fd_a, buf_c, (int)sizeof(buf_c), NET_MSG_DONTWAIT,
|
||
(struct net_sockaddr *)&sa, &alen);
|
||
(void)snprintf(nm, sizeof(nm),
|
||
"UDP-25j a parked socket never blocks another socket's own recvfrom (rc=%d want 7, parked still waiting=%d want 1)",
|
||
rc, net_udp_pending(fd_b));
|
||
CHECK(rc == 7 && net_udp_pending(fd_b) == 1, nm);
|
||
CHECK(net_ntohs(sa.sin_port) == 52000U,
|
||
"UDP-25k and the source address still comes from the packet it actually read");
|
||
CHECK(g_chip.read_calls > reads_before,
|
||
"UDP-25l the chip really was read for fdA (the group is not passing by doing nothing)");
|
||
|
||
(void)net_close_locked(&s_net_socks[fd_b]);
|
||
CHECK(net_udp_pending(fd_b) == 0 && net_udp_armed(fd_b) == 0,
|
||
"UDP-25m closing the parked socket settles it; nothing may outlive the fd");
|
||
reset_all();
|
||
}
|
||
|
||
/* ===== UDP-26 芯片宣布的长度不足 8 字节:无从判头的残包(剥头分支的另一侧) ===== */
|
||
/*
|
||
* 真机定案之后,"剥不剥头"只剩一个判据:芯片说这一包有几个字节。>= 8 就剥,
|
||
* < 8 就整包交出去、源地址退到对端记账(既有定案 TC-NETSOCK-440o)。UDP-4 把
|
||
* >= 8 那一侧钉死了,这一组钉 < 8 那一侧——否则这条兜底路径一行都没人走,
|
||
* 而它是唯一还会用 `net_udp_src_from_peer()` 的地方。
|
||
*/
|
||
static void tc_udp26_runt_no_header(void)
|
||
{
|
||
struct net_sockaddr_in in;
|
||
struct net_sockaddr *sa = (struct net_sockaddr *)∈
|
||
uint8_t runt[5];
|
||
char buf[16];
|
||
int addrlen;
|
||
int fd;
|
||
int rc;
|
||
int i;
|
||
|
||
bring_up();
|
||
fd = udp_bind(4321);
|
||
for (i = 0; i < 5; i++) {
|
||
runt[i] = (uint8_t)(0xC0 + i);
|
||
}
|
||
|
||
/* 第一轮:谁都没记账(bind 之后对端是零)——源地址就该是"无从得知" */
|
||
ch395f_fake_reset_recv_len((uint8_t)fd);
|
||
ch395f_fake_set_recv_stream(runt, 5);
|
||
ch395f_fake_push_recv_len((uint8_t)fd, 5);
|
||
s_net_socks[fd].readable = 1U;
|
||
addrlen = (int)sizeof(in);
|
||
(void)memset(&in, 0xAA, sizeof(in)); /* 出参必须被写满,不许留调用方旧值 */
|
||
rc = net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen);
|
||
CHECK(rc == 5,
|
||
"UDP-26a a runt the chip announced as 5 bytes is delivered whole - nothing is stripped from a packet too short to carry the 8-byte header");
|
||
CHECK((uint8_t)buf[0] == 0xC0U && (uint8_t)buf[4] == 0xC4U,
|
||
"UDP-26b the runt's own bytes are the payload (a strip here would eat the packet)");
|
||
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
|
||
"UDP-26c the runt still fills the address out-param properly (family + addrlen)");
|
||
CHECK(in.sin_addr.s_addr == 0U && net_ntohs(in.sin_port) == 0U,
|
||
"UDP-26d with nobody booked the runt's source is the zero peer - an honest \"I don't know\", never bytes from the packet");
|
||
CHECK(net_udp_armed(fd) == 0 && net_udp_pending(fd) == 0,
|
||
"UDP-26e the runt exit clears the out-param slot like every other exit");
|
||
|
||
/* 第二轮:记了账再残包——源地址退到记账的那个对端(芯片没告诉我们是谁发的) */
|
||
(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-26f book a peer before the second runt");
|
||
ch395f_fake_reset_recv_len((uint8_t)fd);
|
||
ch395f_fake_set_recv_stream(runt, 5);
|
||
ch395f_fake_push_recv_len((uint8_t)fd, 5);
|
||
s_net_socks[fd].readable = 1U;
|
||
addrlen = (int)sizeof(in);
|
||
(void)memset(&in, 0xAA, sizeof(in));
|
||
rc = net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen);
|
||
CHECK(rc == 5, "UDP-26g the booked-peer runt also delivers all 5 bytes");
|
||
CHECK(in.sin_addr.s_addr == net_inet_addr("119.28.220.214") &&
|
||
net_ntohs(in.sin_port) == 123U,
|
||
"UDP-26h an unhedrable runt falls back to the booked peer (TC-NETSOCK-440o's contract, and the only remaining reader of the bookkeeping)");
|
||
CHECK(net_udp_armed(fd) == 0 && net_udp_pending(fd) == 0,
|
||
"UDP-26i no slot left behind on this exit either");
|
||
reset_all();
|
||
}
|
||
|
||
/* ===================== 本分片入口 ===================== */
|
||
|
||
static void ns_run_s7(void)
|
||
{
|
||
tc_udp1_bind_sequence();
|
||
tc_udp2_requires_bind();
|
||
tc_udp3_server_strip();
|
||
tc_udp4_client_still_strips();
|
||
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();
|
||
tc_udp24_random_soak();
|
||
tc_udp25_two_socket_park_isolation();
|
||
tc_udp26_runt_no_header();
|
||
}
|