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

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

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

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

748 lines
35 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:NET 套接字核心(net_socket)主机侧单元测试
* 模块功能:在 PC 上离线验证 Drivers/BSP/NET/net_socket.c 的**跨上下文决策面**。
* 被测对象:真实源文件 #include 原样纳入(同 TU 内可直接驱动它的 static)。
*
* 分片(同一 TU 内按片组织;一个目录=一个模块名,所以只有一个 runner):
* 分片 1 纯函数与 fd 表:inet_addr/inet_ntoa(E13 段序定案)、hton 与 ntoh 四件套、
* alloc_socket/net_get_sock/net_sock_raw 的边界、net_socket() 的"新 fd 不得继承"、
* net_bind 的动态端口与回绕、getsockname/getpeername、setblocking/set_event_cb/
* sock_state、net_poll 未初始化短路。
* 分片 2 每任务错误码槽 + 队列句柄绑定(adopt 幂等):内核未跑→[0] 槽退化、
* 不同任务不同槽位互不覆写、槽位用尽回退 [0]、临界区成对且不嵌套泄漏。
* 分片 3 跨上下文消息面:八个 msg.type 全分派 + 结果经**任务通知值**回传 +
* "登记挂起即不发即时回执"(do_notify=0)+ 失败时把 netTask 自己槽位的精确
* 错误码搭通知值带回来 + 调用方在自己的槽位里恢复 errno。
* 分片 4 状态机与闸门:监听门控 net_listener_armed 三支放行/其余挡下、net_can_read、
* net_poll 的"全局位命中才读本 Socket SINT、永不读全局"(B3;全局读即清会搁浅其它 Socket 事件,docs/网络/网络自愈架构设计.md §7)、
* 事件计数、每轮全量刷新驱动缓存 + sock_reset 步进(I1/头阻塞)。
* 分片 5 读写错误分支:send 分块上限/短写/挂起登记/末块不登记(NET_PENDING 拒收边界)、
* recv 快路径与 EOF vs WOULDBLOCK、connect 命令被拒的回滚、accept 的
* closed-with-readable 排空接受、close 的先结算再关芯片。
*
* ⚠ 断言只钉**真实契约**(AGENTS 模块约定 + docs/网络/网络自愈架构设计.md §4.10 +
* 源码注释里的定案编号),不钉假件规则:凡"因为假件这么做所以断言这么做"的一律不写。
*
* 入口:int main(void),返回 0=PASS / 1=FAIL。跑法见同目录 run_unit_test.py。
*
* 创建日期:2026-10-24
*/
#include <stdio.h>
#include <string.h>
#include "net_socket.h"
#include "net_types.h"
#include "net_pending.h"
#include "net_listener.h"
#include "net_events.h"
#include "ch395f.h"
/* dbg_log 桩的计数定义(桩头只 extern) */
int g_ns_info_count;
int g_ns_error_count;
int g_ns_debug_count;
/* —— 假件实现与生产源进同一个 TU(本仓主机单测既有做法)—— */
#include "ch395f_fake.c"
#include "ns_rtos.c"
#include "ns_peers.c"
/* —— 被测生产源 —— */
#include "net_socket.c"
/* UDP 数据面模块(2026-11-03 队列化改造):与 net_socket.c 同一 TU ——
* 它调 net_send_locked / net_get_sock / net_poll / net_dynamic_port_next 等
* net_socket.c 内部符号,分开编译会撞链接面(本套既有做法) */
#include "net_udp.c"
/* ===================== 断言框架 ===================== */
static int s_total;
static int s_passed;
static int s_failed;
#define CHECK(cond, name) do { s_total++; if (cond) { s_passed++; printf(" PASS %s\n", name); } else { s_failed++; printf(" FAIL %s (line %d)\n", name, __LINE__); } } while (0)
/* ===================== 槽位表只读小工具(断言用,不参与被测逻辑) ===================== */
static int ns_slot_owner_count(void)
{
int i;
int n = 0;
for (i = 0; i < (int)NET_ERRNO_TASK_SLOTS; i++) {
if (s_net_errno_slots[i].owner != NULL) {
n++;
}
}
return n;
}
static int ns_slot_index_of(TaskHandle_t h)
{
int i;
for (i = 0; i < (int)NET_ERRNO_TASK_SLOTS; i++) {
if (s_net_errno_slots[i].owner == (void *)h) {
return i;
}
}
return -1;
}
/* ===================== 场景复位 ===================== */
/* 生产模块的 static 在场景之间会残留,每个场景开头必须显式清干净(漏一次红一片) */
static void reset_all(void)
{
int i;
(void)memset(s_net_socks, 0, sizeof(s_net_socks));
(void)memset(s_net_errno_slots, 0, sizeof(s_net_errno_slots));
(void)memset(s_local_ip, 0, sizeof(s_local_ip));
s_dynamic_port = 49152;
s_net_generation = 0U;
s_net_initialized = 0U;
s_net_msg_queue = NULL;
/* UDP 上下文守卫的判据也是生产 static:不清就会把上一场景的"执行任务"带进本场景
* (表现为守卫判错上下文,且与任务句柄复用后的地址撞车) */
s_net_owner_task = NULL;
/* net_udp.c 的 per-fd 槽(挂起接收 + 源地址出参指针 + 武装态)同为生产 static:
* 残留一个 armed 槽,下一场景就会把源地址写进上一场景的栈内存 */
for (i = 0; i < NET_MAX_SOCKETS; i++) {
net_udp_clear(i);
}
g_ns_info_count = 0;
g_ns_error_count = 0;
g_ns_debug_count = 0;
ch395f_fake_reset();
ns_rtos_reset();
ns_peers_reset();
}
/* 把网络层拉到"已初始化 + 队列已绑定"的常态(绝大多数场景的前置) */
static void bring_up(void)
{
static int s_fake_queue; /* 句柄只是不透明标识,假内核不解引用 */
reset_all();
(void)net_msg_queue_adopt((osMessageQueueId_t)&s_fake_queue);
/* 装泵:调用方阻塞等待时以 netTask 身份跑一轮消息分派(= 跨上下文那条链) */
g_ns_pump = net_process_messages;
/* net_init 以 netTask 身份跑:生产里它就是 net_stack_bring_up() 在 netTask 内
* 调用的(App/task/net_task.c:92),而本层执行任务句柄(UDP 上下文守卫的判据)
* 正是在这里捕获——这里若不换身份,整个 UDP 守卫就只能测到"自己判自己" */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
g_peers.pending_capture_calls = 0; /* bring_up 内部的登记动作不该算进场景 */
g_ns_error_count = 0;
g_ns_info_count = 0;
}
/* UDP 两接口的场景前置:net_sendto / net_recvfrom 有 netTask 上下文守卫,
* 合法用法只能在 netTask 里调 —— 这里把"换身份"收进一个入口,避免每个场景各写
* 一遍换/还原而漏掉一次(漏了就是"守卫把正常路径判成拒绝"的假故障)。 */
static void bring_up_netctx(void)
{
bring_up();
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
}
/* UDP 场景前置:DGRAM + bind(2026-11-03 队列化改造后,UDP 两个数据面接口都要求
* 芯片侧 Socket 已打开,未 bind 一律 NOTCONN——所以"先 bind"从可选项变成每个
* UDP 场景的第一行)。**不在这里 bring_up**:当前上下文(netTask 还是 caller)
* 决定 bind 走直接执行还是入队,由场景自己选,本函数只做 socket + bind。
* 返回 fd,失败返回 -1(bind 失败=假芯片 open 被拒,场景必须显式失败而不是继续跑)。 */
static int udp_bind_ctx(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;
}
/* 直接造一个在用的控制块(多数场景不需要走完整的 socket()/bind()) */
static int mk_sock(int type, net_sock_state_t state)
{
int fd = net_socket(NET_AF_INET, type);
if (fd < 0) {
return fd;
}
s_net_socks[fd].state = state;
return fd;
}
/* ===================== 分片:纯函数 / 错误码槽 / 消息面 / 状态机 / 读写分支 ===================== */
/* 一个目录 = 一个模块名(check_test_coverage 的发现规则),所以只有一个 runner 与一个
* TU;分片以文件维度拆开,是为了让 1251 行口径的最大块能逐片推进、逐片可查。 */
#include "ns_s3_msg.c"
#include "ns_s4_state.c"
#include "ns_s5_io.c"
#include "ns_s6_edges.c"
/* 2026-11-03 UDP 队列化改造:数据面(模式/剥头/单包交付)+ 串行化 + 挂起收包 */
#include "ns_s7_udp.c"
/* = 分片 1 纯函数面与 fd 表 ===================== */
/* TC-NETSOCK-100 点分十进制 ↔ 32 位:**正向断言**(字节序统一修复后)
* 全仓唯一约定:s_addr 是网络字节序整数,&s_addr 的第 0 个内存字节 == 点分第一段;
* 小端主机上 "192.168.31.100" 的数值 = 0x641FA8C0。本组断言的落点是「互逆」与
* 「同族可比」,不是某个中间数值——数值只是这条约定的一个投影。 */
static void tc_100_inet_helpers(void)
{
/* 往返不变量的样本集:两个全零/全一边界 + 三个非回文地址(非回文才能考出顺位) */
static const char *const k_ips[5] = {
"0.0.0.0", "255.255.255.255", "1.1.1.1", "192.168.31.100", "10.0.0.0"
};
char buf[16];
int i;
reset_all();
CHECK(net_inet_addr(NULL) == 0U, "TC-NETSOCK-100a inet_addr(NULL) returns 0 (invalid)");
CHECK(net_inet_addr("192.168.31.100") == 0x641FA8C0U,
"TC-NETSOCK-100b inet_addr returns network-order int (LE value 0x641FA8C0 = bytes C0 A8 1F 64)");
CHECK(net_inet_ntoa(net_inet_addr("192.168.31.100"), buf) == buf &&
strcmp(buf, "192.168.31.100") == 0,
"TC-NETSOCK-100c ntoa(inet_addr(s)) == s: the two are exact inverses");
CHECK(net_inet_ntoa(0x641FA8C0U, buf) == buf && strcmp(buf, "192.168.31.100") == 0,
"TC-NETSOCK-100d ntoa segment order is MSB-first (fixed by E13/TRAP-NET-21, unchanged here)");
CHECK(net_inet_ntoa(0U, NULL) == NULL, "TC-NETSOCK-100e ntoa(NULL buf) returns NULL without crashing");
CHECK(net_inet_addr("10.0.0.1") == 0x0100000AU,
"TC-NETSOCK-100f non-palindrome address: 10.0.0.1 -> 0x0100000A (first octet in low byte)");
/* 正向断言 1:字符串 → 整数 → 字符串 必须回到原串(含 0.0.0.0 与 255.255.255.255) */
for (i = 0; i < 5; i++) {
(void)memset(buf, 0, sizeof(buf));
if (net_inet_ntoa(net_inet_addr(k_ips[i]), buf) == NULL || strcmp(buf, k_ips[i]) != 0) {
break;
}
}
CHECK(i == 5, "TC-NETSOCK-100g round trip ntoa(inet_addr(s)) == s over 5 addrs incl. 0.0.0.0 and 255.255.255.255");
/* 正向断言 2:整数 → 字符串 → 整数 必须回到原值(反方向也闭合) */
for (i = 0; i < 5; i++) {
uint32_t v = net_inet_addr(k_ips[i]);
(void)memset(buf, 0, sizeof(buf));
(void)net_inet_ntoa(v, buf);
if (net_inet_addr(buf) != v) {
break;
}
}
CHECK(i == 5, "TC-NETSOCK-100h round trip inet_addr(ntoa(v)) == v (inverse direction closes too)");
/* 边界语义别改坏:255.255.255.255 是**合法地址**,不得被当失败;
* 失败只有"参数为 NULL"一种,返回值 0 同时是 0.0.0.0 —— 这是 BSD inet_addr
* 的既有歧义,本轮刻意不改(改它要动全部调用者的判错方式,风险大于收益)。 */
CHECK(net_inet_addr("255.255.255.255") == 0xFFFFFFFFU,
"TC-NETSOCK-100i 255.255.255.255 parses to a valid value, never demoted to failure");
CHECK(net_inet_addr("0.0.0.0") == 0U,
"TC-NETSOCK-100j 0.0.0.0 -> 0 (BSD inet_addr ambiguity, not introduced by this fix)");
/* 段值溢出:按低 8 位截断(旧实现会把溢出位溅进相邻段,得到不可预期的混合值) */
CHECK(net_inet_addr("300.1.1.1") == net_inet_addr("44.1.1.1"),
"TC-NETSOCK-100k segment 300 truncates to 44 (300 and 0xFF) with no spill into neighbours");
}
static void tc_101_byteorder(void)
{
reset_all();
CHECK(net_htons(0x1234U) == 0x3412U, "TC-NETSOCK-101a htons byte swap");
CHECK(net_ntohs(net_htons(80U)) == 80U, "TC-NETSOCK-101b htons/ntohs are inverses");
CHECK(net_htonl(0x01020304U) == 0x04030201U, "TC-NETSOCK-101c htonl byte swap");
CHECK(net_ntohl(net_htonl(0xDEADBEEFU)) == 0xDEADBEEFU, "TC-NETSOCK-101d htonl/ntohl are inverses");
}
static void tc_102_parse_ip(void)
{
uint8_t a[4];
reset_all();
(void)memset(a, 0xEE, sizeof(a));
parse_ip_string(NULL, a);
CHECK(a[0] == 0xEEU, "TC-NETSOCK-102a parse_ip_string(NULL) returns at once, target buffer untouched");
parse_ip_string("192.168.31.100", a);
CHECK(a[0] == 192U && a[1] == 168U && a[2] == 31U && a[3] == 100U,
"TC-NETSOCK-102b parse_ip_string puts segment 0 at [0] (the order chip set_ip_addr wants)");
parse_ip_string("10.20.30.40", NULL); /* 只验不崩 */
CHECK(1, "TC-NETSOCK-102c parse_ip_string with NULL target does not crash");
}
static void tc_103_alloc_and_getsock(void)
{
int i;
int fd;
net_sock_t *p;
reset_all();
CHECK(net_get_sock(-1) == NULL && net_get_sock(NET_MAX_SOCKETS) == NULL,
"TC-NETSOCK-103a net_get_sock out of range returns NULL (incl. negative)");
CHECK(net_get_sock(0) == NULL, "TC-NETSOCK-103b unallocated slot returns NULL");
CHECK(net_sock_raw(3) == &s_net_socks[3],
"TC-NETSOCK-103c net_sock_raw skips the in_use check (reconciler must see CLOSED slots)");
CHECK(net_sock_state(0) == -1, "TC-NETSOCK-103d unused sock_state returns -1 and leaves errno alone");
/* 逐个占满:alloc_socket 线性扫描首个空闲槽 */
for (i = 0; i < NET_MAX_SOCKETS; i++) {
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (fd != i) {
break;
}
}
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-103e alloc_socket hands out 0..7 in order");
p = net_get_sock(0);
CHECK(p != NULL && p->in_use == 1U, "TC-NETSOCK-103f allocated slot has in_use=1");
reset_all();
}
static void tc_104_socket_no_inherit(void)
{
int fd;
net_sock_t *p;
reset_all();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(fd == 0, "TC-NETSOCK-104a net_socket returns the first free fd");
p = &s_net_socks[fd];
CHECK(p->ch395_sock == (uint8_t)fd, "TC-NETSOCK-104b chip socket number maps one-to-one to the descriptor");
CHECK(p->state == NET_SOCK_STATE_CREATED && p->type == NET_SOCK_STREAM,
"TC-NETSOCK-104c initial state CREATED with type recorded");
CHECK(p->send_ready == 1U && p->readable == 0U,
"TC-NETSOCK-104d initial state: send buffer seen as free, nothing readable");
/* 三条"新 fd 不得继承"的定案,逐条钉(都是真机上静默出错的形态) */
CHECK(p->blocking == 1U, "TC-NETSOCK-104e blocking must default to explicitly 1 (BSD semantics)");
CHECK(p->released_abnormal == 0U, "TC-NETSOCK-104f must not inherit the previous release reason (TRAP-NET-18)");
CHECK(p->session_dead == 0U, "TC-NETSOCK-104g must not inherit the previous session-dead level (listener lifecycle P1)");
CHECK(g_peers.listener_clear_calls == 1 && g_peers.pending_clear_calls == 1 &&
g_peers.send_clear_calls == 1,
"TC-NETSOCK-104h new fd clears listener retry and both pending tables (once each)");
/* 脏槽位复用:把上一条会话的残留写进去,再看新建是否清干净 */
p->blocking = 0U;
p->released_abnormal = 1U;
p->session_dead = 1U;
p->readable = 1U;
p->send_ready = 0U;
p->event_cb = (net_event_cb_t)0x1234;
/* 这里刻意用 netTask 直调版(net_close_sock)而不是公开 net_close:
* 本场景没绑消息队列,公开入口会先撞上"队列未创建"的 NET_ERR,
* 而那属于分片 3 的断言面(TC-NETSOCK-110h)。close 的清零主路径在 net_close_locked。 */
CHECK(net_close_sock(&s_net_socks[fd]) == 0,
"TC-NETSOCK-104i net_close_sock succeeds (its memset is the main reset path)");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
p = &s_net_socks[fd];
CHECK(fd == 0 && p->blocking == 1U && p->released_abnormal == 0U &&
p->session_dead == 0U && p->readable == 0U && p->send_ready == 1U &&
p->event_cb == NULL,
"TC-NETSOCK-104j reusing the slot: no leftover field may carry over");
/* 地址族不支持 → INVAL(在 alloc 之前,不占槽) */
CHECK(net_socket(NET_AF_INET6, NET_SOCK_STREAM) == -1 &&
net_get_errno() == NET_ERR_INVAL && s_net_socks[1].in_use == 0U,
"TC-NETSOCK-104k non-INET address family -> INVAL and no slot taken");
reset_all();
}
static void tc_105_bind(void)
{
struct net_sockaddr_in in;
int fd;
int rc;
reset_all();
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(2404);
in.sin_addr.s_addr = net_inet_addr("192.168.31.100");
CHECK(net_bind(-1, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105a out of range fd -> BADF");
CHECK(net_bind(0, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105b bind before socket -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_bind(fd, NULL, sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-105c addr NULL -> INVAL");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-105d addrlen too small -> INVAL");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == 0,
"TC-NETSOCK-105e TCP bind succeeds");
CHECK(s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].state == NET_SOCK_STATE_BOUND,
"TC-NETSOCK-105f TCP bind only stores host-order port and goes BOUND, no chip touch");
CHECK(g_chip.set_port_calls == 0 && g_chip.open_calls == 0,
"TC-NETSOCK-105g bind must not set source port or open the chip socket (open belongs to listen)");
/* port=0 → 动态端口 49152 起,并推进 */
reset_all();
(void)memset(&in, 0, sizeof(in));
in.sin_port = 0U;
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
rc = net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(rc == 0 && s_net_socks[fd].local_port == 49152U,
"TC-NETSOCK-105h port=0 allocates 49152");
CHECK(s_dynamic_port == 49153U,
"TC-NETSOCK-105h2 counter already advanced to 49153 (advance on allocate, never reuse)");
(void)net_socket(NET_AF_INET, NET_SOCK_STREAM); /* 只创建不 bind */
CHECK(s_dynamic_port == 49153U,
"TC-NETSOCK-105i3 creating a socket does NOT consume a dynamic port (only bind does)");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
rc = net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(rc == 0 && s_net_socks[fd].local_port == 49153U && s_dynamic_port == 49154U,
"TC-NETSOCK-105i second bind gets the next port (same port never issued twice)");
/* 回绕:把计数器压到 65535,分配后应回 49152(不是 0——0 会让芯片按"未绑定"处理) */
s_dynamic_port = 65535U;
reset_all();
s_dynamic_port = 65535U;
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
rc = net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(rc == 0 && s_net_socks[fd].local_port == 65535U && s_dynamic_port == 49152U,
"TC-NETSOCK-105j dynamic port wraps from 65535 back to 49152 (never to 0)");
}
static void tc_106_bind_udp(void)
{
struct net_sockaddr_in in;
int fd;
reset_all();
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(8888);
/* UDP:bind 就直接开芯片侧(无监听态),且必须配 DesIP=255.255.255.255 收任意源 */
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == 0, "TC-NETSOCK-106a UDP bind succeeds");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN, "TC-NETSOCK-106b UDP goes straight to UDP_OPEN");
CHECK(g_chip.set_proto_calls == 1 && g_chip.last_proto == CH395F_PROTO_TYPE_UDP,
"TC-NETSOCK-106c protocol set to UDP (not TCP)");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 8888U,
"TC-NETSOCK-106d source port delivered with bind");
CHECK(g_fx.des_ip[0][0] == 0xFFU && g_fx.des_ip[0][1] == 0xFFU &&
g_fx.des_ip[0][2] == 0xFFU && g_fx.des_ip[0][3] == 0xFFU &&
g_fx.des_port[0] == 0U,
"TC-NETSOCK-106e UDP server mode: DesIP all FF and dest port 0 (else only one peer is received)");
CHECK(g_chip.open_calls == 1, "TC-NETSOCK-106f UDP bind opens the chip socket once");
/* 槽位 >=4 必须补设收发缓冲(net_init 已配过,这里是兜底):布局 6 块/槽 */
reset_all();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(fd == 0, "TC-NETSOCK-106g take fd0 first (control case: no buffer reconfig)");
CHECK(g_fxchip.set_recv_buf_calls == 0, "TC-NETSOCK-106h UDP bind on fd<4 does not reconfigure buffers");
reset_all();
for (fd = 0; fd < 4; fd++) {
(void)net_socket(NET_AF_INET, NET_SOCK_DGRAM);
}
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM); /* fd4 */
(void)net_bind(fd, (struct net_sockaddr *)&in, sizeof(in));
CHECK(g_fx.recv_blk_start[4] == (uint8_t)(4 * 6) &&
g_fx.recv_blk_cnt[4] == (uint8_t)CH395F_RECV_BLOCKS &&
g_fx.send_blk_start[4] == (uint8_t)(4 * 6 + (int)CH395F_RECV_BLOCKS) &&
g_fx.send_blk_cnt[4] == (uint8_t)CH395F_SEND_BLOCKS,
"TC-NETSOCK-106i fd>=4 falls back to reconfiguring rx/tx buffers, same 6-block layout as net_init");
/* open 失败 → BUSY 且留在原状态(不得谎报 UDP_OPEN) */
reset_all();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY &&
s_net_socks[fd].state != NET_SOCK_STATE_UDP_OPEN &&
g_ns_error_count == 1,
"TC-NETSOCK-106j UDP open failure -> BUSY plus error log and state does not advance");
}
static void tc_107_peer_and_localname(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
reset_all();
(void)net_init("192.168.31.100", "255.255.255.0", "192.168.31.1", NULL);
CHECK(net_getsockname(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107a getsockname out of range -> BADF");
CHECK(net_getsockname(0, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107b unused CLOSED slot -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(7003);
CHECK(net_bind(fd, sa, (int)sizeof(in)) == 0, "TC-NETSOCK-107c bind precondition ok");
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_getsockname(fd, sa, &addrlen) == 0, "TC-NETSOCK-107d getsockname succeeds");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 7003U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-107e fills family, port (network order) and length");
/* 钉**具体顺位**而不是二选一:getsockname 走的是「首段在最低字节」这一族
* (与 net_inet_ntoa、net_events 的 remote_ip 同族),所以 192.168.31.100 =
* 0x641FA8C0。net_inet_addr 返回的是另一族(首段在最高字节,0xC0A81F64),
* 两族不互逆——已作为疑似缺陷报出,这里刻意不写成「两个都行」。 */
CHECK(in.sin_addr.s_addr == 0x641FA8C0U,
"TC-NETSOCK-107f local IP byte order = first octet in low byte (ntoa family)");
addrlen = 1;
CHECK(net_getsockname(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107g length too small -> INVAL");
CHECK(net_getsockname(fd, sa, NULL) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107h addrlen NULL -> INVAL");
/* getpeername:没有 CONNECT 回读过对端 → NOTCONN(不发 SPI 是它的卖点) */
(void)memset(&in, 0, sizeof(in));
addrlen = (int)sizeof(in);
CHECK(net_getpeername(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_NOTCONN,
"TC-NETSOCK-107i no peer yet -> NOTCONN (judged by remote_port==0)");
CHECK(g_chip.get_status_calls == 0 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-107j getpeername only reads the cache, issues no chip transactions");
s_net_socks[fd].remote_port = 51999U;
s_net_socks[fd].remote_ip = 0x0B01A8C0U;
(void)memset(&in, 0, sizeof(in));
addrlen = (int)sizeof(in);
CHECK(net_getpeername(fd, sa, &addrlen) == 0 &&
net_ntohs(in.sin_port) == 51999U && in.sin_addr.s_addr == 0x0B01A8C0U &&
in.sin_family == NET_AF_INET,
"TC-NETSOCK-107k getpeername fills the cached peer IP and port");
CHECK(net_getpeername(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107l getpeername out of range -> BADF");
CHECK(net_getpeername(5, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107m getpeername on unused fd -> BADF");
addrlen = 2;
CHECK(net_getpeername(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107n getpeername length too small -> INVAL");
reset_all();
}
static void tc_108_sockprops(void)
{
int fd;
int arg = 7;
reset_all();
CHECK(net_setblocking(-1, 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108a setblocking out of range -> BADF");
CHECK(net_setblocking(0, 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108b not in use -> BADF");
CHECK(net_set_event_cb(-1, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108c set_event_cb out of range -> BADF");
CHECK(net_set_event_cb(0, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108d set_event_cb not in use -> BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_setblocking(fd, 0) == 0 && s_net_socks[fd].blocking == 0U,
"TC-NETSOCK-108e setblocking(0) sets the attribute (mode lives in the control block, not per call)");
CHECK(net_setblocking(fd, 5) == 0 && s_net_socks[fd].blocking == 1U,
"TC-NETSOCK-108f any non-zero value counts as blocking");
CHECK(net_set_event_cb(fd, (net_event_cb_t)0x1234, &arg) == 0 &&
s_net_socks[fd].event_cb == (net_event_cb_t)0x1234 &&
s_net_socks[fd].event_arg == &arg,
"TC-NETSOCK-108g registers callback and user argument");
CHECK(net_set_event_cb(fd, NULL, NULL) == 0 && s_net_socks[fd].event_cb == NULL,
"TC-NETSOCK-108h cb=NULL unregisters");
s_net_socks[fd].state = NET_SOCK_STATE_ESTABLISHED;
CHECK(net_sock_state(fd) == (int)NET_SOCK_STATE_ESTABLISHED,
"TC-NETSOCK-108i sock_state returns the state snapshot");
CHECK(net_sock_state(NET_MAX_SOCKETS) == -1, "TC-NETSOCK-108j sock_state out of range -> -1");
s_net_socks[fd].in_use = 0U;
CHECK(net_sock_state(fd) == -1, "TC-NETSOCK-108k sock_state on unused fd -> -1");
reset_all();
}
static void tc_109_generation(void)
{
reset_all();
CHECK(net_get_generation() == 0U, "TC-NETSOCK-109a initial value 0");
net_generation_inc();
net_generation_inc();
CHECK(net_get_generation() == 2U, "TC-NETSOCK-109b each network rebuild adds 1 (services rebuild listeners from it)");
reset_all();
}
static void tc_110_queue_adopt(void)
{
static int q1;
static int q2;
reset_all();
CHECK(net_msg_queue_adopt(NULL) == -1, "TC-NETSOCK-110a NULL handle -> -1");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q1) == 0, "TC-NETSOCK-110b first binding succeeds");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q2) == -1,
"TC-NETSOCK-110c rebinding must be refused (no silent handle swap)");
CHECK(s_net_msg_queue == (osMessageQueueId_t)&q1,
"TC-NETSOCK-110d rebinding keeps the FIRST handle - swapping would strand queued requests forever");
/* 未绑定时所有公开 API 一律 NET_ERR,而不是崩在空句柄上 */
reset_all();
CHECK(net_listen(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110e without a bound queue: net_listen -> NET_ERR");
CHECK(net_recv(0, NULL, 0, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110f without a bound queue: net_recv -> NET_ERR (range check precedes queue check)");
CHECK(net_send(0, "x", 1, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110g without a bound queue: net_send -> NET_ERR");
CHECK(net_close(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110h without a bound queue: net_close -> NET_ERR");
CHECK(net_drop_conn(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110i without a bound queue: net_drop_conn -> NET_ERR");
CHECK(net_recv_to(0, NULL, 0, 10U) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110j without a bound queue: net_recv_to -> NET_ERR");
CHECK(net_accept(0, NULL, NULL) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110k without a bound queue: net_accept -> NET_ERR");
CHECK(net_connect(0, NULL, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110l without a bound queue: net_connect -> NET_ERR");
/* 越界优先于"未初始化":调用方拿到的码要能区分"我传错了"和"网还没起来" */
CHECK(net_send(NET_MAX_SOCKETS, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-110m range check before queue check -> BADF");
reset_all();
}
/* ===================== 分片 2 每任务错误码槽 ===================== */
static void tc_120_errno_slot_per_task(void)
{
reset_all();
/* 内核没跑起来:不做槽位分配,一律退到 [0] 槽(中断/内核前上下文) */
g_ns_kernel_state = osKernelInactive;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_set_errno(NET_ERR_BADF);
CHECK(net_get_errno() == NET_ERR_BADF && s_net_errno_slots[0].owner == NULL,
"TC-NETSOCK-120a kernel not running -> uses slot [0] and takes no slot (owner not written)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-120b with the kernel down all tasks share slot [0] (degraded semantics)");
/* 内核在跑:每个任务自己的槽位 */
g_ns_kernel_state = osKernelRunning;
reset_all();
g_ns_kernel_state = osKernelRunning;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_set_errno(NET_ERR_TIMEDOUT);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_OK,
"TC-NETSOCK-120c another task cannot read the caller code (positive wording)");
(void)net_set_errno(NET_ERR_WOULDBLOCK);
CHECK(net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-120d a task reads its own code");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_get_errno() == NET_ERR_TIMEDOUT,
"TC-NETSOCK-120e no cross overwrite: caller code survives the netTask write (old red line)");
/* 临界区:两个任务各占一次槽 → 两次进入、两次退出,绝不泄漏嵌套计数 */
CHECK(g_ns_crit_enter == 2 && g_ns_crit_exit == 2 && ns_os_crit_depth() == 0,
"TC-NETSOCK-120f each first slot take enters the critical section once and leaves in pairs");
/* 快路径只读不写:已有槽位的任务反复读写都不该再进临界区、也不该再占槽 */
{
int e0 = g_ns_crit_enter;
int owners0 = ns_slot_owner_count();
int i_caller = ns_slot_index_of((TaskHandle_t)&g_ns_task_caller);
int i_net = ns_slot_index_of((TaskHandle_t)&g_ns_task_net);
CHECK(i_caller >= 0 && i_net >= 0 && i_caller != i_net,
"TC-NETSOCK-120g two tasks land in DIFFERENT slots (table is inspectable, not convention)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
(void)net_set_errno(NET_ERR_BUSY);
(void)net_get_errno();
(void)net_set_errno(NET_ERR_CONNRESET);
CHECK(g_ns_crit_enter == e0, "TC-NETSOCK-120h existing slot hit: zero critical sections (fast path is read-only)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_ERR_WOULDBLOCK &&
ns_slot_owner_count() == owners0,
"TC-NETSOCK-120i repeated reads by another task add no slot and never see the caller code");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_get_errno() == NET_ERR_CONNRESET, "TC-NETSOCK-120j caller reads its own latest code");
}
/* 槽位用尽 → 回退 [0](刻意不重试,避免把竞态带回来) */
reset_all();
g_ns_kernel_state = osKernelRunning;
{
int i;
static int filler[10];
for (i = 0; i < (int)NET_ERRNO_TASK_SLOTS; i++) {
g_ns_cur_task = (TaskHandle_t)&filler[i];
(void)net_set_errno(NET_ERR_ISCONN);
}
g_ns_cur_task = (TaskHandle_t)&filler[0];
CHECK(net_get_errno() == NET_ERR_ISCONN, "TC-NETSOCK-120k 8 slots, one owner each");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller; /* 第 9 个任务 */
(void)net_set_errno(NET_ERR_NOMEM);
CHECK(net_get_errno() == NET_ERR_NOMEM,
"TC-NETSOCK-120l slots exhausted: a new task still reads its own code from slot [0]");
g_ns_cur_task = (TaskHandle_t)&filler[0];
CHECK(net_get_errno() == NET_ERR_NOMEM,
"TC-NETSOCK-120m after exhaustion it degrades to the shared slot [0] (only hurts debuggability)");
}
reset_all();
}
static void tc_121_errno_slot_isr(void)
{
reset_all();
g_ns_kernel_state = osKernelRunning;
g_ns_cur_task = NULL; /* 中断上下文/无句柄 */
(void)net_set_errno(NET_ERR_CONNRESET);
CHECK(net_get_errno() == NET_ERR_CONNRESET && s_net_errno_slots[0].owner == NULL,
"TC-NETSOCK-121 NULL handle (ISR context) -> falls back to slot [0] and leaves the table alone");
reset_all();
}
/* ===================== 主入口 ===================== */
int main(void)
{
printf("=== NET Socket Tests (net_socket.c host unit tests) ===\n");
printf("[shard1] pure functions and fd table\n");
tc_100_inet_helpers();
tc_101_byteorder();
tc_102_parse_ip();
tc_103_alloc_and_getsock();
tc_104_socket_no_inherit();
tc_105_bind();
tc_106_bind_udp();
tc_107_peer_and_localname();
tc_108_sockprops();
tc_109_generation();
tc_110_queue_adopt();
printf("[shard2] per-task errno slots and queue binding\n");
tc_120_errno_slot_per_task();
tc_121_errno_slot_isr();
printf("[shard3] cross-context messages and notify-value return\n");
ns_run_s3();
printf("[shard4] state machine and gates\n");
ns_run_s4();
printf("[shard5] stream setup and read/write branches\n");
ns_run_s5();
printf("[shard6] edge branches and remaining exits\n");
ns_run_s6();
printf("[shard7] UDP datagram plane: serialised chip work, modes, pending receive\n");
ns_run_s7();
printf("=== NET Socket Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}