Files
STM32F4-Base/test/host/net_socket/net_socket_test.c
T
wandering1 924ac3d428 test(host): 断言字面量统一纯 ASCII,30 文件 1535 行
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 21:00:17 +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 每任务错误码槽(E-03)+ 队列句柄绑定(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/E-02)。
* 分片 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 (N-32 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 每任务错误码槽(E-03) ===================== */
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 (the positive wording of E-03)");
(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;
}