Files
STM32F4-Base/test/host/net_socket/net_socket_test.c
T
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00

713 lines
33 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/F-NET-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"
/* ===================== 断言框架 ===================== */
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)
{
(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;
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.1.100", "255.255.255.0", "192.168.1.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;
}
/* 直接造一个在用的控制块(多数场景不需要走完整的 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"
/* = 分片 1 纯函数面与 fd 表 ===================== */
/* TC-NETSOCK-100 点分十进制 ↔ 32 位:**正向断言**(字节序统一修复后)
* 全仓唯一约定:s_addr 是网络字节序整数,&s_addr 的第 0 个内存字节 == 点分第一段;
* 小端主机上 "192.168.1.100" 的数值 = 0x6401A8C0。本组断言的落点是「互逆」与
* 「同族可比」,不是某个中间数值——数值只是这条约定的一个投影。 */
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.1.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.1.100") == 0x6401A8C0U,
"TC-NETSOCK-100b inet_addr returns network-order int (LE value 0x6401A8C0 = bytes C0 A8 01 64)");
CHECK(net_inet_ntoa(net_inet_addr("192.168.1.100"), buf) == buf &&
strcmp(buf, "192.168.1.100") == 0,
"TC-NETSOCK-100c ntoa(inet_addr(s)) == s: the two are exact inverses");
CHECK(net_inet_ntoa(0x6401A8C0U, buf) == buf && strcmp(buf, "192.168.1.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.1.100", a);
CHECK(a[0] == 192U && a[1] == 168U && a[2] == 1U && 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.1.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.1.100", "255.255.255.0", "192.168.1.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.1.100 =
* 0x6401A8C0。net_inet_addr 返回的是另一族(首段在最高字节,0xC0A80164),
* 两族不互逆——已作为疑似缺陷报出,这里刻意不写成「两个都行」。 */
CHECK(in.sin_addr.s_addr == 0x6401A8C0U,
"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("=== NET Socket Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}