diff --git a/test/host/net_socket/ns_s7_udp.c b/test/host/net_socket/ns_s7_udp.c index 75555bae..a5f0c2a4 100644 --- a/test/host/net_socket/ns_s7_udp.c +++ b/test/host/net_socket/ns_s7_udp.c @@ -231,9 +231,16 @@ static void tc_udp3_server_strip(void) reset_all(); } -/* ================= UDP-4 客户端模式:不剥头(改造修的缺陷本体) ================= */ - -static void tc_udp4_client_no_strip(void) +/* ====== 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 *)∈ @@ -259,24 +266,26 @@ static void tc_udp4_client_no_strip(void) CHECK(g_chip.open_calls == 1, "UDP-4d switching mode does NOT reopen the socket (a reopen would drop buffered replies)"); - /* 48 字节 SNTP 应答,前 8 字节故意长得像"信息头"——旧实现会把它们剥掉 */ + /* 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 起手 */ } - ch395f_fake_set_recv_stream(stream, 48U); - ch395f_fake_push_recv_len((uint8_t)fd, 48U); + 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 mode delivers ALL 48 bytes (the pre-refactor build returned 40 and silently ate the first 8)"); + "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.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)"); + 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(); } @@ -879,7 +888,7 @@ static void tc_udp17_set_peer_and_mode(void) "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)"); + "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)"); @@ -1352,13 +1361,10 @@ static void tc_udp24_random_soak(void) /* 每轮重排长度脚本:假件的收包长度是"按调用序号取"的脚本,240 轮只追加 * 不清空会让序号撞满容量、之后每轮都读到同一个陈旧长度。 */ ch395f_fake_reset_recv_len(0); - if (as_client) { - /* 客户端模式:芯片不再前置信息头,整段就是载荷 */ - ch395f_fake_set_recv_stream(payload, (uint16_t)len); - ch395f_fake_push_recv_len(0, (uint16_t)len); - } else { - udp_script_server_datagram((const char *)payload, len, sport, sip); - } + /* 两种形态都按"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) { @@ -1375,8 +1381,9 @@ static void tc_udp24_random_soak(void) bad_src++; clean = 0; } else { - uint32_t want_ip = as_client ? peer.sin_addr.s_addr : net_inet_addr(sip); - uint16_t want_port = as_client ? net_ntohs(peer.sin_port) : sport; + /* 源地址恒取自信息头:记账的对端是"我往哪发",不是"谁在回我"。 */ + 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; } @@ -1434,10 +1441,10 @@ static void tc_udp24_random_soak(void) (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: SERVER strips, CLIENT never strips (bad=%d, first round=%d)", + (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 right in both forms - header-derived in SERVER, peer-derived in CLIENT (bad=%d, first round=%d)", + (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)", @@ -1555,6 +1562,71 @@ static void tc_udp25_two_socket_park_isolation(void) 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) @@ -1562,7 +1634,7 @@ static void ns_run_s7(void) tc_udp1_bind_sequence(); tc_udp2_requires_bind(); tc_udp3_server_strip(); - tc_udp4_client_no_strip(); + tc_udp4_client_still_strips(); tc_udp5_sendto_dest_switches_mode(); tc_udp6_pending_recv_arrival(); tc_udp7_pending_timeout(); @@ -1584,4 +1656,5 @@ static void ns_run_s7(void) tc_udp23_recvfrom_direct_ctx(); tc_udp24_random_soak(); tc_udp25_two_socket_park_isolation(); + tc_udp26_runt_no_header(); }