fix(net/udp): 收包剥头只看芯片宣布的字节数,与 udp_mode 无关(#73)
真机第一轮(2026-11-04 08:16,阶段 16 台架)把本轮最大的一条设计错误照了出来: TC-NET-1603 在客户端形态下发 48 字节应答,板子读到的是 `n=56`、首字节 `0x38` ——56 正是信息头的长度字段,也就是**头还在**,而我把它的 8 字节当成了载荷。 设计时按手册 §9.2.4 读出一句"客户端模式不带信息头",就以为运行时把 DesIP 从 全 F 改成具体对端会让头消失。真机给的定案是:**加不加 8 字节信息头,是在 OPEN_SOCKET 那一刻由当时的 DesIP 定死的**,之后再改 DesIP 只改发送去向与芯片 的收包源过滤,不改收包帧格式。本层 `net_bind` 一律以 DesIP=0xFFFFFFFF 开 Socket,所以对本层的 UDP socket 而言头恒在、恒剥。 - `net_udp_datagram_read` 的剥头条件由 `udp_mode == SERVER && avail >= 8` 改为 **只看 `avail >= 8`**;源地址一律从信息头里取——头里的源 IP/端口是本包的真实 来源,比"我把包发给了谁"这本账更准(NAT 后的时间服务器就是两种地址不一致的 那种)。 - 不足 8 字节的残包保持既有定案(TC-NETSOCK-440o):无从判头,整包按载荷交付, 源地址退到对端记账。这也是 `net_udp_src_from_peer()` 如今唯一的读者。 - `udp_mode` 语义收窄为"芯片的对端过滤 + 发送去向",**不描述收包帧格式**; `net_types.h` 的 UDP 形态块、两个模式宏的注释、`net_sendto` / `net_recvfrom` 的函数说明一并按定案改写。字段本身没动,仍复用原有的 2 字节 padding,RAM 零增长。 产品构型 Keil 构建 0 Error 0 Warning,`Code=249120 RO=29836 RW=2052 ZI=149996`, 与改造前的产品基线同尺寸。判据文字与真机证据见同批的测试、文档提交。
This commit is contained in:
+28
-19
@@ -598,14 +598,16 @@ static void net_udp_req_complete(int sockfd, int result) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:从 8 字节信息头回填源地址(服务器模式)
|
||||
* 函数功能:从 8 字节信息头回填源地址(本层收包的正常路径)
|
||||
* 入口参数:p_sock - 控制块(本函数不用,保留与 peer 版同形签名) net_sock_t *
|
||||
* hdr - 已读出的 8 字节信息头 const uint8_t *
|
||||
* src_addr - 调用方出参(可为 NULL) struct net_sockaddr *
|
||||
* addrlen - 调用方出参长度(可为 NULL) int *
|
||||
* 出口参数:src_addr - 填充发送方地址;*addrlen = sizeof(sockaddr_in)
|
||||
* 返回值:无
|
||||
* 限定条件:仅由 net_udp_datagram_read 在服务器模式分支调用
|
||||
* 限定条件:由 net_udp_datagram_read 在剥头分支(avail >= 8)调用——本层 Socket
|
||||
* 一律开在服务器形态,信息头与 udp_mode 无关,故 CLIENT 形态同样走这里,
|
||||
* 头里的源 IP/端口就是本包的真实来源
|
||||
* 函数说明:头布局(手册 §9.2.4)[0-1] 数据长度、[2-3] 源端口(芯片小端)、
|
||||
* [4-7] 源 IP(线序字节,[4] = 第一段)。s_addr 直接按"首段落在低字节"
|
||||
* 组装即得本层网络序约定(2026-10-24 定案:全仓 s_addr 只此一族,
|
||||
@@ -642,15 +644,16 @@ static void net_udp_src_from_header(net_sock_t *p_sock, const uint8_t *hdr,
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:从控制块固定对端回填源地址(客户端模式:芯片不加信息头)
|
||||
* 函数功能:从控制块固定对端回填源地址(不足 8 字节残包的兜底路径)
|
||||
* 入口参数:p_sock - 控制块(remote_ip_arr/remote_port = 本 Socket 的对端) net_sock_t *
|
||||
* src_addr - 调用方出参(可为 NULL) struct net_sockaddr *
|
||||
* addrlen - 调用方出参长度(可为 NULL) int *
|
||||
* 出口参数:src_addr - 填充对端地址;*addrlen = sizeof(sockaddr_in)
|
||||
* 返回值:无
|
||||
* 限定条件:仅由 net_udp_datagram_read 在客户端模式分支调用
|
||||
* 函数说明:客户端模式下芯片只收这个 IP 的包,源地址不需要从头里取——
|
||||
* 反过来说它也**没有**头可取,本函数是唯一的源地址来源。
|
||||
* 限定条件:仅由 net_udp_datagram_read 在残包分支(avail < 8)调用
|
||||
* 函数说明:本层 Socket 一律开在服务器形态、每个数据报都带信息头,正常包都从
|
||||
* 头里取源地址;只有无从判头的残包才退到"源地址=本 Socket 的对端记账",
|
||||
* 至少比报 NULL 或报上一个包的源更接近真相(TC-NETSOCK-440o 定案)。
|
||||
*/
|
||||
static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_addr,
|
||||
int *addrlen) {
|
||||
@@ -678,7 +681,7 @@ static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_a
|
||||
/*
|
||||
* 函数功能:从芯片缓冲读出**一个完整数据报**(剥头 + 截断排空 + 清可读标志)
|
||||
* 入口参数:sockfd - Socket 描述符 (0~7) int
|
||||
* p_sock - 控制块(读 udp_mode) net_sock_t *
|
||||
* p_sock - 控制块(只在不足 8 字节的残包上用来取对端记账当源地址) net_sock_t *
|
||||
* buf - 调用方缓冲 void *
|
||||
* len - 缓冲容量(> 0) int
|
||||
* src_addr - 源地址出参(可为 NULL) struct net_sockaddr *
|
||||
@@ -687,14 +690,21 @@ static void net_udp_src_from_peer(net_sock_t *p_sock, struct net_sockaddr *src_a
|
||||
* 出口参数:buf - 载荷(最多 len 字节,多余部分丢弃);src_addr - 发送方地址
|
||||
* 返回值:交付调用方的字节数(>= 0) int
|
||||
* 限定条件:netTask 上下文;avail 必须是本函数调用前刚读到的 ch395f_get_recv_len()
|
||||
* 函数说明:1. 服务器模式剥 8 字节信息头并从中取源地址;客户端模式整包都是
|
||||
* 载荷、源地址取固定对端。模式判错 = 吃掉载荷前 8 字节(本函数
|
||||
* 存在的理由,见 net_types.h UDP 模式注释)。
|
||||
* 2. 服务器模式手册要求"一次性将数据全部读出";且不论哪种模式,
|
||||
* 载荷超出 len 时必须把剩余部分读空——留半包在芯片缓冲里,
|
||||
* 下一次收包会从上一包的尾巴开始解析,源地址与载荷从此全部错位
|
||||
* (旧实现只读 min(可用, len),正是这个缺陷)。
|
||||
* 3. 服务器模式收到不足 8 字节的残包:无从判头,整包按载荷交付
|
||||
* 函数说明:1. **剥头与 udp_mode 无关,只看开 Socket 时的形态**:本层所有 DGRAM
|
||||
* Socket 都由 net_bind 在 `DesIP = 0xFFFFFFFF`(服务器形态)下开出来,
|
||||
* 芯片据此给**每一个**收到的数据报前置 8 字节信息头,此后无论把 DesIP
|
||||
* 改成具体对端(显式 dest_addr 的 net_sendto / net_udp_set_peer)都不
|
||||
* 再改变这一帧格式。2026-11-04 08:16 真机 TC-NET-1603 实测:客户端
|
||||
* 形态下发来 48 B,芯片给出 n=56、首字节 0x38(=56,正是信息头的长度
|
||||
* 字段)——运行时改 DesIP 不会让头消失。按 udp_mode 决定剥不剥,就会
|
||||
* 把 8 字节头当载荷交给应用(SNTP 48 B 应答只剩 40 B 且首 8 字节是头),
|
||||
* 这正是 TRAP-NET-22 记录的老缺陷;定案见该条 2026-11-04 追加段。
|
||||
* 源地址同理一律从头里取:头里的源 IP/端口是**本包**的真实来源,比
|
||||
* 对端记账更准(对端记账只用于发送与芯片收包过滤)。
|
||||
* 2. 手册要求"一次性将数据全部读出";且载荷超出 len 时必须把剩余部分
|
||||
* 读空——留半包在芯片缓冲里,下一次收包会从上一包的尾巴开始解析,
|
||||
* 源地址与载荷从此全部错位(旧实现只读 min(可用, len),正是这个缺陷)。
|
||||
* 3. 不足 8 字节的残包:无从判头,整包按载荷交付、源地址取对端记账
|
||||
* (保持既有定案 TC-NETSOCK-440o,不猜芯片行为)。
|
||||
* 4. 长度由调用方传入、本函数**不再自己读第二次**:一次收包的剥头量、
|
||||
* 载荷量、排空量必须由同一个数字算出。读两次等于多一次 SPI,而且
|
||||
@@ -715,9 +725,8 @@ static int net_udp_datagram_read(int sockfd, net_sock_t *p_sock, void *buf, int
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((p_sock->udp_mode == NET_UDP_MODE_SERVER) &&
|
||||
((uint32_t)avail >= NET_UDP_HDR_LEN)) {
|
||||
/* 信息头暂存(只在服务器形态用得到,放在本分支栈上) */
|
||||
if ((uint32_t)avail >= NET_UDP_HDR_LEN) {
|
||||
/* 信息头暂存(只在剥头这一支用得到,放在本分支栈上) */
|
||||
uint8_t hdr[NET_UDP_HDR_LEN];
|
||||
|
||||
/* 剥头:读即消费芯片缓冲 */
|
||||
@@ -725,7 +734,7 @@ static int net_udp_datagram_read(int sockfd, net_sock_t *p_sock, void *buf, int
|
||||
net_udp_src_from_header(p_sock, hdr, src_addr, addrlen);
|
||||
payload = avail - (int)NET_UDP_HDR_LEN;
|
||||
} else {
|
||||
/* 客户端模式(或服务器模式残包):无头可剥,源地址取固定对端 */
|
||||
/* 不足 8 字节的残包:无头可剥,源地址取固定对端 */
|
||||
net_udp_src_from_peer(p_sock, src_addr, addrlen);
|
||||
payload = avail;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user