test(host): P2 覆盖率批次,5 套主机单测与共享假 FatFs

P2 五个文件全部进分母(三名队友并行,写范围互斥、零越界):
  net_socket 420 条 / 881-883=99.8%、52-52 函数(S1~S6 分片)
  sys_log_mcu 75 条 / 310-313=99.0%、12-12
  console_log 104 条 / 188-189=99.5%、15-15
  cfg_file_store_mcu 34 条 / 97-97=100%、10-10
  soe_store 26 条 / 78-79=98.7%、4-4
合计 659 条断言,P2 本体 1554-1561=99.6%、93-93 函数。

账:套件 28→33 全绿;files_tested 32→37;未触及 112→107(.c 34→29,
恒等式 29 = 0(P1剩) + 0(P2) + 29(P3) 咬合,§9 终点判据达成);分母
4684→6245 插桩行;line 85.8%→89.3%;func 90.1%→92.2%(这批 5 个文件
全都以 ≥98.7% 进分母,故未摊薄)。--update-baseline 前后逐文件比对:
下降 0 个文件。

基建:新建 test/host/common/fatfs_fake.{h,c}(以真实 ff.h 为声明源、只
定义三文件实际调用的 9 个 f_*,缺哪个链接期响;卷忙/短写/FR_DENIED
可注入;短名违规分档);扩展 ch395f_fake 为纯新增(g_fx/g_fxchip 另开
一份世界 + ext_reset 挂进既有复位,既有三套面一字不动),并按真头修正
两个错值常量(SOCKET_OPEN 0x01→0x05、TCP_ESTABLISHED 0x05→0x04),
另在 net_socket runner 加真头/假件同名常量文本核对闸(负例验证过)。
存量回归:net_listener 34、net_events 34、net_pending 43、net_select 22、
nand_port 149、lftpd_io 33、vol_lock 33、soe 32、cfg_file 151 全绿。

编号:补 TC-HOST-{NETSOCK,SYSLOG,CFGFST,SOESTORE,CONLOG} 五族(22→27,
先补族再引号);net_socket 模块码由自取的两字母 NS 正名为 NETSOCK;
id-check 增量拦截无台账外新账,可判定比例 83.27%(基线 82.6%)。

文档:覆盖率口径 §9 新增「P2 落地」(含两套分母口径差、10 条现状表、
12 条可复用手法与并发红线),§8 第 1 条改「清单已吃穿」,补变更记录。
本提交未改任何生产代码(App/Drivers/Src/Lib/MDK-ARM 零触碰)。

10 条现状已逐条登记在 §9(含 sys_log_mcu.c:509~537 卷忙整批丢日志、
net_socket.c 同文件两套 IP 字节序、UDP 两 API 绕过 netTask),已获指示
全部修复,另开一轮(含测试字面量统一纯 ASCII)。

gate: pre-commit run --all-files → 39 hooks, 38 Passed; only mem-numbers
Failed(本工作树从未 @build,无 .map 事实源,属分支期已知陷阱);
mem-budget Passed;test-coverage 与 id-check Passed。RAM/Flash 增量 = 0
(纯测试批次)。按仓内分支约定走 --no-verify,台账回填留待主树合并后。
This commit is contained in:
2026-10-24 17:54:02 +08:00
parent 4ac869058d
commit 794906cdc8
46 changed files with 10821 additions and 39 deletions
+657
View File
@@ -0,0 +1,657 @@
/*
* 模块名称: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;
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;
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
g_peers.pending_capture_calls = 0; /* bring_up 内部的登记动作不该算进场景 */
g_ns_error_count = 0;
g_ns_info_count = 0;
}
/* 直接造一个在用的控制块(多数场景不需要走完整的 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 位:钉**当前行为**并显式标注疑点(见报告)
* 现状:net_inet_addr 把首段放在**最高字节**(0xC0A80164),而 net_getsockname 组装
* s_addr 时把首段放在**最低字节**,net_inet_ntoa 只与后者互为逆——同一文件里
* 存在两套字节序约定。这里逐条钉住,不替生产"圆场"。 */
static void tc_100_inet_helpers(void)
{
char buf[16];
reset_all();
CHECK(net_inet_addr(NULL) == 0U, "TC-NETSOCK-100a inet_addr(NULL) 返 0(无效)");
CHECK(net_inet_addr("192.168.1.100") == 0xC0A80164U,
"TC-NETSOCK-100b inet_addr 首段落在最高字节(当前实现)");
CHECK(net_inet_ntoa(0xC0A80164U, buf) == buf && strcmp(buf, "100.1.168.192") == 0,
"TC-NETSOCK-100c ntoa(同一 32 位值) 与 inet_addr 不互逆(疑点已报,钉当前行为)");
CHECK(net_inet_ntoa(0x6401A8C0U, buf) == buf && strcmp(buf, "192.168.1.100") == 0,
"TC-NETSOCK-100d ntoa 与 getsockname 的 s_addr 约定互逆(E13 定案的段序:高段在前)");
CHECK(net_inet_ntoa(0U, NULL) == NULL, "TC-NETSOCK-100e ntoa(NULL 缓冲) 返 NULL 不崩");
/* 单段/缺分隔的容错:逐段解析,非数字段当 0(钉实现,不钉"应该是多少") */
CHECK(net_inet_addr("10.0.0.1") == 0x0A000001U, "TC-NETSOCK-100f inet_addr 10.0.0.1");
}
static void tc_101_byteorder(void)
{
reset_all();
CHECK(net_htons(0x1234U) == 0x3412U, "TC-NETSOCK-101a htons 换端");
CHECK(net_ntohs(net_htons(80U)) == 80U, "TC-NETSOCK-101b htons/ntohs 互逆");
CHECK(net_htonl(0x01020304U) == 0x04030201U, "TC-NETSOCK-101c htonl 换端");
CHECK(net_ntohl(net_htonl(0xDEADBEEFU)) == 0xDEADBEEFU, "TC-NETSOCK-101d htonl/ntohl 互逆");
}
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) 直接返回,不写目标缓冲");
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 段 0 落在 [0](芯片 set_ip_addr 要的顺位)");
parse_ip_string("10.20.30.40", NULL); /* 只验不崩 */
CHECK(1, "TC-NETSOCK-102c parse_ip_string 目标为 NULL 不崩");
}
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 越界返 NULL(含负数)");
CHECK(net_get_sock(0) == NULL, "TC-NETSOCK-103b 未分配的槽位返 NULL");
CHECK(net_sock_raw(3) == &s_net_socks[3],
"TC-NETSOCK-103c net_sock_raw 不判 in_use(对账必须看到 CLOSED 槽位)");
CHECK(net_sock_state(0) == -1, "TC-NETSOCK-103d 未占用 sock_state 返 -1 且不设 errno");
/* 逐个占满: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 按序分配 0..7");
p = net_get_sock(0);
CHECK(p != NULL && p->in_use == 1U, "TC-NETSOCK-103f 分配的槽位 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 返回首个空闲 fd");
p = &s_net_socks[fd];
CHECK(p->ch395_sock == (uint8_t)fd, "TC-NETSOCK-104b 芯片 Socket 号与描述符一一对应");
CHECK(p->state == NET_SOCK_STATE_CREATED && p->type == NET_SOCK_STREAM,
"TC-NETSOCK-104c 初态 CREATED + 类型记下");
CHECK(p->send_ready == 1U && p->readable == 0U,
"TC-NETSOCK-104d 初态:发送缓冲视为空闲、无可读");
/* 三条"新 fd 不得继承"的定案,逐条钉(都是真机上静默出错的形态) */
CHECK(p->blocking == 1U, "TC-NETSOCK-104e blocking 默认必须显式置 1(BSD 语义)");
CHECK(p->released_abnormal == 0U, "TC-NETSOCK-104f 不得继承上一条的释放原因(TRAP-NET-18)");
CHECK(p->session_dead == 0U, "TC-NETSOCK-104g 不得继承上一条的会话结束电平(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 新 fd 必须清监听重试 + 两张挂起表(各一次)");
/* 脏槽位复用:把上一条会话的残留写进去,再看新建是否清干净 */
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 成功(close 的 memset 是清零主路径)");
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 复用同一槽位:残留字段全部不得带走");
/* 地址族不支持 → 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 非 INET 地址族 → INVAL 且不占槽位");
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 越界 fd → BADF");
CHECK(net_bind(0, (struct net_sockaddr *)&in, sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-105b 未创建就 bind → 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 不足 → INVAL");
CHECK(net_bind(fd, (struct net_sockaddr *)&in, sizeof(in)) == 0,
"TC-NETSOCK-105e TCP bind 成功");
CHECK(s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].state == NET_SOCK_STATE_BOUND,
"TC-NETSOCK-105f TCP 只记端口转主机序 + 进 BOUND,不碰芯片");
CHECK(g_chip.set_port_calls == 0 && g_chip.open_calls == 0,
"TC-NETSOCK-105g TCP bind 期间不得下发源端口/开芯片 Socket(打开留给 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 → 分配 49152");
CHECK(s_dynamic_port == 49153U,
"TC-NETSOCK-105h2 计数器已推进到 49153(分配即推进,不留重复)");
(void)net_socket(NET_AF_INET, NET_SOCK_STREAM); /* 只创建不 bind */
CHECK(s_dynamic_port == 49153U,
"TC-NETSOCK-105i3 创建 socket **不**消耗动态端口(只有 bind 消耗)");
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 第二次 bind 拿下一个端口(不会把同一个端口发两次)");
/* 回绕:把计数器压到 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 动态端口越过 65535 回绕到 49152(不回 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 成功");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_UDP_OPEN, "TC-NETSOCK-106b UDP 直接进 UDP_OPEN");
CHECK(g_chip.set_proto_calls == 1 && g_chip.last_proto == CH395F_PROTO_TYPE_UDP,
"TC-NETSOCK-106c 协议下发为 UDP(不是 TCP)");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 8888U,
"TC-NETSOCK-106d 源端口随 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 模式:DesIP=全 FF + 目的端口 0(否则只收单一对端)");
CHECK(g_chip.open_calls == 1, "TC-NETSOCK-106f UDP bind 开一次芯片 Socket");
/* 槽位 >=4 必须补设收发缓冲(net_init 已配过,这里是兜底):布局 6 块/槽 */
reset_all();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(fd == 0, "TC-NETSOCK-106g 先拿 fd0(对照:不补缓冲)");
CHECK(g_fxchip.set_recv_buf_calls == 0, "TC-NETSOCK-106h fd<4 的 UDP bind 不重设缓冲");
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 兜底重设收发缓冲,块布局与 net_init 同构(6 块/槽)");
/* 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 失败 → BUSY + 留痕 + 状态不前进");
}
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 越界 → BADF");
CHECK(net_getsockname(0, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107b 未用且 CLOSED 的槽 → 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 前置成功");
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_getsockname(fd, sa, &addrlen) == 0, "TC-NETSOCK-107d getsockname 成功");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 7003U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-107e 回填族/端口(网络序)/长度");
/* 钉**具体顺位**而不是二选一: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 本机 IP 顺位=首段在最低字节(与 ntoa 同族;net_inet_addr 是另一族)");
addrlen = 1;
CHECK(net_getsockname(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107g 长度不足 → 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 尚无对端 → NOTCONN(remote_port==0 判据)");
CHECK(g_chip.get_status_calls == 0 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-107j getpeername 纯读缓存,不发任何芯片事务");
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 回填缓存的对端 IP:端口");
CHECK(net_getpeername(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107l getpeername 越界 → BADF");
CHECK(net_getpeername(5, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-107m getpeername 未在用 → BADF");
addrlen = 2;
CHECK(net_getpeername(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-107n getpeername 长度不足 → 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 越界 → BADF");
CHECK(net_setblocking(0, 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108b 未占用 → BADF");
CHECK(net_set_event_cb(-1, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108c set_event_cb 越界 → BADF");
CHECK(net_set_event_cb(0, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-108d set_event_cb 未占用 → 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) 落属性(模式挂控制块,不是单次参数)");
CHECK(net_setblocking(fd, 5) == 0 && s_net_socks[fd].blocking == 1U,
"TC-NETSOCK-108f 非 0 一律视为阻塞");
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 注册回调与用户参数");
CHECK(net_set_event_cb(fd, NULL, NULL) == 0 && s_net_socks[fd].event_cb == NULL,
"TC-NETSOCK-108h cb=NULL 取消注册");
s_net_socks[fd].state = NET_SOCK_STATE_ESTABLISHED;
CHECK(net_sock_state(fd) == (int)NET_SOCK_STATE_ESTABLISHED,
"TC-NETSOCK-108i sock_state 返状态快照");
CHECK(net_sock_state(NET_MAX_SOCKETS) == -1, "TC-NETSOCK-108j sock_state 越界 -1");
s_net_socks[fd].in_use = 0U;
CHECK(net_sock_state(fd) == -1, "TC-NETSOCK-108k sock_state 未占用 -1");
reset_all();
}
static void tc_109_generation(void)
{
reset_all();
CHECK(net_get_generation() == 0U, "TC-NETSOCK-109a 初值 0");
net_generation_inc();
net_generation_inc();
CHECK(net_get_generation() == 2U, "TC-NETSOCK-109b 每次网络重建 +1(服务据此重建监听)");
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 空句柄 → -1");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q1) == 0, "TC-NETSOCK-110b 首次绑定成功");
CHECK(net_msg_queue_adopt((osMessageQueueId_t)&q2) == -1,
"TC-NETSOCK-110c 重复绑定必须被拒(不静默换句柄)");
CHECK(s_net_msg_queue == (osMessageQueueId_t)&q1,
"TC-NETSOCK-110d 重复绑定**保留首次**句柄——换掉会让已入队请求永不处理");
/* 未绑定时所有公开 API 一律 NET_ERR,而不是崩在空句柄上 */
reset_all();
CHECK(net_listen(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110e 未绑定队列:net_listen → NET_ERR");
CHECK(net_recv(0, NULL, 0, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110f 未绑定队列:net_recv → NET_ERR(越界检查在队检查之前)");
CHECK(net_send(0, "x", 1, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110g 未绑定队列:net_send → NET_ERR");
CHECK(net_close(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110h 未绑定队列:net_close → NET_ERR");
CHECK(net_drop_conn(0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110i 未绑定队列:net_drop_conn → NET_ERR");
CHECK(net_recv_to(0, NULL, 0, 10U) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110j 未绑定队列:net_recv_to → NET_ERR");
CHECK(net_accept(0, NULL, NULL) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110k 未绑定队列:net_accept → NET_ERR");
CHECK(net_connect(0, NULL, 0) == -1 && net_get_errno() == NET_ERR,
"TC-NETSOCK-110l 未绑定队列:net_connect → NET_ERR");
/* 越界优先于"未初始化":调用方拿到的码要能区分"我传错了"和"网还没起来" */
CHECK(net_send(NET_MAX_SOCKETS, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-110m 越界检查先于队列检查 → 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 内核未运行 → 用 [0] 槽且**不占槽**(不写 owner)");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-120b 内核未运行时所有任务共享 [0] 槽(退化语义)");
/* 内核在跑:每个任务自己的槽位 */
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 另一个任务读不到 caller 的码(E-03 的正面表述)");
(void)net_set_errno(NET_ERR_WOULDBLOCK);
CHECK(net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-120d 本任务读自己的码");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_get_errno() == NET_ERR_TIMEDOUT,
"TC-NETSOCK-120e 互不覆写:caller 的码没被 netTask 抢走(旧实现的红线)");
/* 临界区:两个任务各占一次槽 → 两次进入、两次退出,绝不泄漏嵌套计数 */
CHECK(g_ns_crit_enter == 2 && g_ns_crit_exit == 2 && ns_os_crit_depth() == 0,
"TC-NETSOCK-120f 每次**首次占槽**进一次临界区且成对退出(无泄漏)");
/* 快路径只读不写:已有槽位的任务反复读写都不该再进临界区、也不该再占槽 */
{
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 两个任务落在**不同**槽位(槽位表可查,不是靠约定)");
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 命中已有槽位:全程零临界区(快路径只读)");
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 别的任务反复读也不新增槽位、也读不到 caller 的码");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_get_errno() == NET_ERR_CONNRESET, "TC-NETSOCK-120j caller 读到自己最新的码");
}
/* 槽位用尽 → 回退 [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 个槽各归各主");
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 槽位用尽:新任务落 [0] 槽仍读得到自己的码");
g_ns_cur_task = (TaskHandle_t)&filler[0];
CHECK(net_get_errno() == NET_ERR_NOMEM,
"TC-NETSOCK-120m 用尽后退化为共享 [0] 槽(只影响排障可读性,不新增共享槽位)");
}
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(中断上下文)→ 退 [0] 槽且不改槽位表");
reset_all();
}
/* ===================== 主入口 ===================== */
int main(void)
{
printf("=== NET Socket Tests (net_socket.c 主机单测) ===\n");
printf("[shard1] 纯函数面与 fd 表\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] 每任务错误码槽 + 队列绑定\n");
tc_120_errno_slot_per_task();
tc_121_errno_slot_isr();
printf("[shard3] 跨上下文消息与通知值回传\n");
ns_run_s3();
printf("[shard4] 状态机与闸门\n");
ns_run_s4();
printf("[shard5] 建流与读写分支\n");
ns_run_s5();
printf("[shard6] 边界残支与其余出口\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;
}
+379
View File
@@ -0,0 +1,379 @@
/*
* 模块名称:NET 层兄弟模块桩(net_socket 主机侧单测专用)
* 模块功能:net_socket.c 依赖的三个兄弟模块(net_listener / net_pending / net_events)
* 在这里**按边界契约打桩**,让本套单测只测 net_socket.c 自己那部分决策。
*
* ⚠ 仅供 test/host/net_socket/ 使用,不参与 MCU 构建。
*
* 为什么打桩而不是 #include 真实源(而不是"越真越好"):
* 1. 那三个模块**已各有自己的主机单测**(net_listener 34 条、net_events 34 条、
* net_pending 43 条),本套再跑一遍不增加任何对 net_socket.c 的判别力,
* 只会把"谁的红"变成糊账;
* 2. 本套真正要钉的是**边界契约**——net_socket.c 在什么条件下把什么 reason 登记给
* 监听层、什么条件下把挂起请求登记给 pending 层、以及"登记了就不能再自己发回执"。
* 桩把这些变成可断言的记录,比让真实 tick 在背后默默跑更有判别力;
* 3. 原型一律取**真实头**(net_listener.h / net_pending.h / net_events.h),
* 兄弟模块改签名 → 本套编译期就红。
*
* 桩的取值纪律(不自己发明语义,全部按真实头的函数说明):
* - net_pending_register / net_send_pending_register 返回 NET_PENDING_REGISTERED,
* 与真实实现一致(登记成功=哨兵,供分派跳过即时通知);可注入 -1 测登记被拒;
* - net_pending_tick 支持"下一轮把脚本里的结果当作数据到达,通知登记时的 caller",
* 复刻真实 tick 的"数据到达即通知"契约;
* - 其余全为纯记录(次数 + 最近一次入参),供"不该登记/必须登记"的断言。
*
* 创建日期:2026-10-24
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "net_listener.h"
#include "net_pending.h"
#include "net_events.h"
#include "net_socket.h"
#include "ns_peers.h"
#include "ns_rtos.h"
ns_peers_obs_t g_peers;
/* 桩内部"完成/取消"要发通知:转给假内核,这样测试能断言通知次数与通知值 */
void ns_peers_note_notify(TaskHandle_t to, uint32_t v)
{
ns_rtos_post_notify(to, v);
}
/* 挂起登记簿(只记一条,够本套断言用) */
static int s_pend_active[NET_MAX_SOCKETS];
static void *s_pend_buf[NET_MAX_SOCKETS];
static int s_pend_len[NET_MAX_SOCKETS];
static TaskHandle_t s_pend_caller[NET_MAX_SOCKETS];
static int s_snd_active[NET_MAX_SOCKETS];
static TaskHandle_t s_snd_caller[NET_MAX_SOCKETS];
/* tick 完成脚本:fd -> 结果值(一次生效) */
static int s_pend_done_armed[NET_MAX_SOCKETS];
static int s_pend_done_val[NET_MAX_SOCKETS];
static int s_snd_done_armed[NET_MAX_SOCKETS];
static int s_snd_done_val[NET_MAX_SOCKETS];
void ns_peers_reset(void)
{
(void)memset(&g_peers, 0, sizeof(g_peers));
(void)memset(s_pend_active, 0, sizeof(s_pend_active));
(void)memset(s_pend_buf, 0, sizeof(s_pend_buf));
(void)memset(s_pend_len, 0, sizeof(s_pend_len));
(void)memset(s_pend_caller, 0, sizeof(s_pend_caller));
(void)memset(s_snd_active, 0, sizeof(s_snd_active));
(void)memset(s_snd_caller, 0, sizeof(s_snd_caller));
(void)memset(s_pend_done_armed, 0, sizeof(s_pend_done_armed));
(void)memset(s_pend_done_val, 0, sizeof(s_pend_done_val));
(void)memset(s_snd_done_armed, 0, sizeof(s_snd_done_armed));
(void)memset(s_snd_done_val, 0, sizeof(s_snd_done_val));
g_peers.pend_register_rc = NET_PENDING_REGISTERED;
g_peers.send_register_rc = NET_PENDING_REGISTERED;
}
void ns_peers_arm_pending_done(int sockfd, int result)
{
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_done_armed[sockfd] = 1;
s_pend_done_val[sockfd] = result;
}
}
void ns_peers_arm_send_pending_done(int sockfd, int result)
{
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_snd_done_armed[sockfd] = 1;
s_snd_done_val[sockfd] = result;
}
}
/* ================= net_listener.h ================= */
void net_listener_request(int sockfd, net_listener_req_t reason)
{
g_peers.listener_req_calls++;
g_peers.last_req_fd = sockfd;
g_peers.last_req_reason = (int)reason;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
g_peers.req_armed[sockfd] = 1;
g_peers.req_reason[sockfd] = (int)reason;
}
}
void net_listener_clear(int sockfd)
{
g_peers.listener_clear_calls++;
g_peers.last_clear_fd = sockfd;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
g_peers.req_armed[sockfd] = 0;
}
}
void net_listener_tick(void)
{
g_peers.listener_tick_calls++;
}
void net_listener_settle(int sockfd, net_listener_settle_t level)
{
g_peers.listener_settle_calls++;
g_peers.last_settle_fd = sockfd;
g_peers.last_settle_level = (int)level;
}
/* ================= net_pending.h(recv 侧) ================= */
void net_pending_capture_owner(void)
{
g_peers.pending_capture_calls++;
}
int net_pending_register(int sockfd, void *buf, int len, uint32_t to_ms,
TaskHandle_t caller)
{
g_peers.pending_reg_calls++;
g_peers.last_pend_fd = sockfd;
g_peers.last_pend_to = to_ms;
g_peers.last_pend_caller = caller;
if (g_peers.pend_register_rc != NET_PENDING_REGISTERED) {
return g_peers.pend_register_rc; /* 测"登记被拒"分支 */
}
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_active[sockfd] = 1;
s_pend_buf[sockfd] = buf;
s_pend_len[sockfd] = len;
s_pend_caller[sockfd] = caller;
g_peers.pend_active_now[sockfd] = 1;
}
return NET_PENDING_REGISTERED;
}
void net_pending_tick(net_sock_t *p_socks)
{
int i;
g_peers.pending_tick_calls++;
(void)p_socks;
/* 复刻真实 tick 的主用例:数据到达 → 按登记时的 caller 通知 → 清槽 */
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if ((s_pend_active[i] != 0) && (s_pend_done_armed[i] != 0)) {
s_pend_done_armed[i] = 0;
s_pend_active[i] = 0;
g_peers.pend_active_now[i] = 0;
g_peers.pending_notified++;
ns_peers_note_notify(s_pend_caller[i], (uint32_t)s_pend_done_val[i]);
}
}
}
void net_pending_clear(int sockfd)
{
g_peers.pending_clear_calls++;
g_peers.last_pend_clear_fd = sockfd;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_active[sockfd] = 0;
g_peers.pend_active_now[sockfd] = 0;
}
}
void net_pending_cancel(int sockfd, int err)
{
g_peers.pending_cancel_calls++;
g_peers.last_pend_cancel_err = err;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_pend_active[sockfd] = 0;
g_peers.pend_active_now[sockfd] = 0;
}
}
void net_pending_cancel_all(int err)
{
int i;
g_peers.pending_cancel_all_calls++;
g_peers.last_pend_cancel_err = err;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_pend_active[i] != 0) {
g_peers.pending_notified++;
ns_peers_note_notify(s_pend_caller[i], (uint32_t)err);
}
s_pend_active[i] = 0;
g_peers.pend_active_now[i] = 0;
}
}
void net_pending_settle_close(int sockfd, net_sock_t *p_sock)
{
g_peers.pending_settle_calls++;
g_peers.last_pend_fd = sockfd;
(void)p_sock;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS) && (s_pend_active[sockfd] != 0)) {
/* 真实实现:有数据先送达,无数据按 CONNRESET 取消——两者都**必通知** */
s_pend_active[sockfd] = 0;
g_peers.pend_active_now[sockfd] = 0;
g_peers.pending_notified++;
ns_peers_note_notify(s_pend_caller[sockfd], (uint32_t)NET_ERR_CONNRESET);
}
}
int net_pending_active(int sockfd)
{
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return s_pend_active[sockfd];
}
/* ================= net_pending.h(send 侧) ================= */
int net_send_pending_register(int sockfd, const void *buf, int len, int sent,
TaskHandle_t caller)
{
g_peers.send_reg_calls++;
g_peers.last_snd_fd = sockfd;
g_peers.last_snd_sent = sent;
g_peers.last_snd_len = len;
(void)buf;
if (g_peers.send_register_rc != NET_PENDING_REGISTERED) {
return g_peers.send_register_rc;
}
/* 真实实现的硬前置:sent >= len 视为参数非法拒收(末块边界) */
if (sent >= len) {
return -1;
}
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_snd_active[sockfd] = 1;
s_snd_caller[sockfd] = caller;
}
return NET_PENDING_REGISTERED;
}
void net_send_pending_tick(net_sock_t *p_socks)
{
int i;
g_peers.send_tick_calls++;
(void)p_socks;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if ((s_snd_active[i] != 0) && (s_snd_done_armed[i] != 0)) {
s_snd_done_armed[i] = 0;
s_snd_active[i] = 0;
g_peers.send_notified++;
ns_peers_note_notify(s_snd_caller[i], (uint32_t)s_snd_done_val[i]);
}
}
}
void net_send_pending_clear(int sockfd)
{
g_peers.send_clear_calls++;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS)) {
s_snd_active[sockfd] = 0;
}
}
void net_send_pending_cancel(int sockfd, int err)
{
g_peers.send_cancel_calls++;
g_peers.last_snd_cancel_err = err;
if ((sockfd >= 0) && (sockfd < NET_MAX_SOCKETS) && (s_snd_active[sockfd] != 0)) {
s_snd_active[sockfd] = 0;
g_peers.send_notified++;
ns_peers_note_notify(s_snd_caller[sockfd], (uint32_t)err);
}
}
void net_send_pending_cancel_all(int err)
{
int i;
g_peers.send_cancel_all_calls++;
g_peers.last_snd_cancel_err = err;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (s_snd_active[i] != 0) {
g_peers.send_notified++;
ns_peers_note_notify(s_snd_caller[i], (uint32_t)err);
}
s_snd_active[i] = 0;
}
}
int net_send_pending_active(int sockfd)
{
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return 0;
}
return s_snd_active[sockfd];
}
/* ================= net_events.h(事件面与对账) ================= */
/* 事件处理桩:按真实头注释的效果改控制块(CONNECT→ESTABLISHED + 记对端等),
* 让 net_poll 的"中断 → 状态机"分派可断言;同时计数。 */
void handle_connect_event(net_sock_t *p_sock)
{
g_peers.evt_connect_calls++;
if (p_sock != NULL) {
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
p_sock->readable = 0U;
p_sock->released_abnormal = 0U;
p_sock->session_dead = 0U;
}
}
void handle_disconnect_event(net_sock_t *p_sock)
{
g_peers.evt_disconnect_calls++;
if (p_sock != NULL) {
p_sock->last_peer_port = p_sock->remote_port;
p_sock->state = NET_SOCK_STATE_CLOSED;
p_sock->session_dead = 1U;
}
}
void handle_recv_event(net_sock_t *p_sock)
{
g_peers.evt_recv_calls++;
if (p_sock != NULL) {
p_sock->readable = 1U;
}
}
void handle_send_event(net_sock_t *p_sock)
{
g_peers.evt_send_calls++;
if (p_sock != NULL) {
p_sock->send_ready = 1U;
}
}
void handle_timeout_event(net_sock_t *p_sock)
{
g_peers.evt_timeout_calls++;
if (p_sock != NULL) {
p_sock->last_peer_port = p_sock->remote_port;
p_sock->state = NET_SOCK_STATE_CLOSED;
p_sock->released_abnormal = 1U;
p_sock->session_dead = 1U;
}
}
void net_reconcile_tick(void)
{
g_peers.reconcile_calls++;
}
void net_lost_connect_probe(net_sock_t *p_sock)
{
g_peers.lost_probe_calls++;
g_peers.last_probe_state = (p_sock != NULL) ? (int)p_sock->state : -1;
if ((p_sock != NULL) && (g_peers.lost_probe_hit != 0)) {
/* 脚本:探针命中 → 直接转 ESTABLISHED(复刻补 accept 的净效果) */
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
p_sock->lost_conn_seen = 0U;
}
}
+82
View File
@@ -0,0 +1,82 @@
/*
* 模块名称:NET 层兄弟模块桩的观测面(net_socket 主机侧单测专用)
* 模块功能:声明 ns_peers.c 的记录结构与复位入口。原型层面的约束(必须与真实头逐字
* 一致)在 ns_peers.c 里由"include 真实头 + 直接实现同名函数"保证:
* 签名漂移 = 本套编译期红。
* ⚠ 仅供 test/host/net_socket/ 使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef NS_PEERS_H
#define NS_PEERS_H
#include <stdint.h>
#include "FreeRTOS.h"
#include "net_types.h"
typedef struct {
/* —— net_listener —— */
int listener_req_calls;
int last_req_fd;
int last_req_reason;
int listener_clear_calls;
int last_clear_fd;
int listener_tick_calls;
int listener_settle_calls;
int last_settle_fd;
int last_settle_level;
int req_armed[NET_MAX_SOCKETS]; /* 登记未清=1(clear/tick 后归 0) */
int req_reason[NET_MAX_SOCKETS];
/* —— net_pending(recv) —— */
int pending_capture_calls;
int pending_reg_calls;
int pend_register_rc; /* 注入登记结果(默认 NET_PENDING_REGISTERED) */
int last_pend_fd;
uint32_t last_pend_to;
TaskHandle_t last_pend_caller;
int pending_tick_calls;
int pending_clear_calls;
int last_pend_clear_fd;
int pending_cancel_calls;
int pending_cancel_all_calls;
int last_pend_cancel_err;
int pending_settle_calls;
int pending_notified; /* 经由桩真实发出通知的次数(必通知契约) */
int pend_active_now[NET_MAX_SOCKETS];
/* —— net_pending(send) —— */
int send_reg_calls;
int send_register_rc;
int last_snd_fd;
int last_snd_sent;
int last_snd_len;
int send_tick_calls;
int send_clear_calls;
int send_cancel_calls;
int send_cancel_all_calls;
int last_snd_cancel_err;
int send_notified;
/* —— net_events —— */
int evt_connect_calls;
int evt_disconnect_calls;
int evt_recv_calls;
int evt_send_calls;
int evt_timeout_calls;
int reconcile_calls;
int lost_probe_calls;
int lost_probe_hit; /* 1=让探针命中(补 accept) */
int last_probe_state;
} ns_peers_obs_t;
extern ns_peers_obs_t g_peers;
void ns_peers_reset(void);
void ns_peers_arm_pending_done(int sockfd, int result);
void ns_peers_arm_send_pending_done(int sockfd, int result);
/* 由 ns_peers.c 调用,把"桩内部完成的通知"接到假内核的通知面上 */
void ns_peers_note_notify(TaskHandle_t to, uint32_t v);
#endif /* NS_PEERS_H */
+334
View File
@@ -0,0 +1,334 @@
/*
* 模块名称:假内核实现(net_socket 主机侧单测专用)
* 模块功能:实现 ns_rtos.h 声明的假队列/假通知/假时基。由测试单元 #include 使用。
* ⚠ 仅供 test/host/net_socket/ 使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ns_rtos.h"
#include "net_socket.h"
int g_ns_task_caller;
int g_ns_task_net;
TaskHandle_t g_ns_cur_task;
ns_pump_fn g_ns_pump;
ns_poll_hook_fn g_ns_on_poll;
int g_ns_pump_calls;
int g_ns_block_waits;
int g_ns_notify_calls;
uint32_t g_ns_notify_last_val;
TaskHandle_t g_ns_notify_last_to;
int g_ns_put_calls;
int g_ns_get_calls;
int g_ns_crit_enter;
int g_ns_crit_exit;
uint32_t g_ns_tick;
osKernelState_t g_ns_kernel_state = osKernelRunning;
/* 通知槽:按句柄区分(只服务上面两个假任务,超出的句柄一律现场分配不了→报错) */
typedef struct {
TaskHandle_t h;
uint32_t val;
int pending;
} ns_notify_slot_t;
static ns_notify_slot_t s_slot[2];
/* 假队列:定长环形,存 net_msg_t 按值拷贝(与真队列语义一致) */
static net_msg_t s_q[NS_Q_CAP];
static int s_q_head;
static int s_q_count;
void ns_rtos_reset(void)
{
(void)memset(s_slot, 0, sizeof(s_slot));
s_slot[0].h = (TaskHandle_t)&g_ns_task_caller;
s_slot[1].h = (TaskHandle_t)&g_ns_task_net;
(void)memset(s_q, 0, sizeof(s_q));
s_q_head = 0;
s_q_count = 0;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
g_ns_pump = NULL;
g_ns_on_poll = NULL;
g_ns_pump_calls = 0;
g_ns_block_waits = 0;
g_ns_notify_calls = 0;
g_ns_notify_last_val = 0U;
g_ns_notify_last_to = NULL;
g_ns_put_calls = 0;
g_ns_get_calls = 0;
g_ns_crit_enter = 0;
g_ns_crit_exit = 0;
g_ns_tick = 0U;
g_ns_kernel_state = osKernelRunning;
g_ns_int_pin_set = 1;
g_ns_poll_tick_inc = 1U;
g_ns_poll_calls = 0U;
}
void ns_rtos_q_flush(void)
{
s_q_head = 0;
s_q_count = 0;
}
int ns_rtos_q_count(void)
{
return s_q_count;
}
void ns_rtos_q_fill(int n)
{
int i;
for (i = 0; (i < n) && ((uint32_t)s_q_count < NS_Q_CAP); i++) {
(void)memset(&s_q[(s_q_head + s_q_count) % (int)NS_Q_CAP], 0, sizeof(net_msg_t));
s_q_count++;
}
}
int ns_rtos_q_put_raw(const net_msg_t *msg)
{
if (msg == NULL || (uint32_t)s_q_count >= NS_Q_CAP) {
return -1;
}
(void)memcpy(&s_q[(s_q_head + s_q_count) % (int)NS_Q_CAP], msg, sizeof(net_msg_t));
s_q_count++;
return 0;
}
/* 假内核里的"另一次通知发出":与 xTaskNotify 共用同一套观测计数,
* 这样"谁发了几次通知"在测试里只有一个口径(挂起完成/取消走的就是这条) */
void ns_rtos_post_notify(TaskHandle_t to, uint32_t v)
{
int i;
g_ns_notify_calls++;
g_ns_notify_last_val = v;
g_ns_notify_last_to = to;
for (i = 0; i < 2; i++) {
if (s_slot[i].h == to) {
s_slot[i].val = v;
s_slot[i].pending = 1;
return;
}
}
printf("FATAL: ns_rtos_post_notify 收到未知任务句柄\n");
exit(2);
}
static ns_notify_slot_t *slot_of(TaskHandle_t h)
{
int i;
for (i = 0; i < 2; i++) {
if (s_slot[i].h == h) {
return &s_slot[i];
}
}
return NULL;
}
/* —— CMSIS-RTOS2 侧(原型逐字取自真实 cmsis_os2.h:689/697/298/459)—— */
osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id, const void *msg_ptr,
uint8_t msg_prio, uint32_t timeout)
{
(void)mq_id;
(void)msg_prio;
(void)timeout;
g_ns_put_calls++;
if (msg_ptr == NULL) {
return osErrorParameter;
}
if ((uint32_t)s_q_count >= NS_Q_CAP) {
return osErrorResource; /* 队列满(真机是 osWaitForever 阻塞,测试里用满队列测 BUSY 分支) */
}
(void)memcpy(&s_q[(s_q_head + s_q_count) % (int)NS_Q_CAP], msg_ptr, sizeof(net_msg_t));
s_q_count++;
return osOK;
}
osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void *msg_ptr,
uint8_t *msg_prio, uint32_t timeout)
{
(void)mq_id;
(void)msg_prio;
g_ns_get_calls++;
if (s_q_count == 0) {
(void)timeout;
return osErrorTimeout;
}
if (msg_ptr != NULL) {
(void)memcpy(msg_ptr, &s_q[s_q_head], sizeof(net_msg_t));
}
s_q_head = (s_q_head + 1) % (int)NS_Q_CAP;
s_q_count--;
return osOK;
}
osKernelState_t osKernelGetState(void)
{
return g_ns_kernel_state;
}
osStatus_t osDelay(uint32_t ticks)
{
static int guard;
g_ns_tick += ticks;
if (++guard > 20000) {
printf("FATAL: osDelay 跑飞(%d 次)—— 等待条件永不满足\n", guard);
exit(2);
}
if (guard > 0) {
/* 每次 osDelay 也让 HAL_GetTick 往前走,600ms 复位上限才量得到 */
g_ns_tick += 0U;
}
return osOK;
}
/* —— FreeRTOS 侧(桩 task.h 声明)—— */
TaskHandle_t xTaskGetCurrentTaskHandle(void)
{
return g_ns_cur_task;
}
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue,
eNotifyAction eAction)
{
ns_notify_slot_t *p = slot_of(xTaskToNotify);
(void)eAction; /* 本测试只用 eSetValueWithOverwrite,语义相同 */
g_ns_notify_calls++;
g_ns_notify_last_val = ulValue;
g_ns_notify_last_to = xTaskToNotify;
if (p == NULL) {
printf("FATAL: xTaskNotify 收到未知任务句柄\n");
exit(2);
}
p->val = ulValue;
p->pending = 1;
return pdTRUE;
}
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait)
{
ns_notify_slot_t *p = slot_of(g_ns_cur_task);
if (p == NULL) {
printf("FATAL: xTaskNotifyWait 当前任务句柄未知\n");
exit(2);
}
if (pulNotificationValue != NULL) {
*pulNotificationValue = p->val & (~ulBitsToClearOnEntry);
}
if (p->pending != 0) {
if (pulNotificationValue != NULL) {
*pulNotificationValue = p->val;
}
p->val = p->val & (~ulBitsToClearOnExit);
p->pending = 0;
return pdTRUE;
}
if (xTicksToWait == 0U) {
return pdFALSE; /* 零等待拾取:没有就是没有 */
}
/* 阻塞分支:这就是"睡到 netTask 回执"的调度点 → 驱动一轮 netTask 再拾取 */
g_ns_block_waits++;
if (g_ns_pump == NULL) {
printf("FATAL: 走到阻塞等待但测试没装 pump 钩子\n");
exit(2);
}
{
TaskHandle_t save = g_ns_cur_task;
int rounds = 0;
while (rounds < NS_PUMP_MAX) {
rounds++;
g_ns_pump_calls++;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net; /* 以 netTask 身份跑 */
g_ns_pump();
g_ns_cur_task = save;
if (p->pending != 0) {
break;
}
}
if (p->pending == 0) {
printf("FATAL: 阻塞等待 %d 轮仍无回执(该通知的没通知 = 真机必挂死)\n", rounds);
exit(2);
}
}
if (pulNotificationValue != NULL) {
*pulNotificationValue = p->val;
}
p->val = p->val & (~ulBitsToClearOnExit);
p->pending = 0;
return pdTRUE;
}
uint32_t xTaskGetTickCount(void)
{
return g_ns_tick;
}
int ns_os_crit_depth(void)
{
return g_ns_crit_enter - g_ns_crit_exit;
}
void ns_os_enter_critical(void)
{
g_ns_crit_enter++;
}
void ns_os_exit_critical(void)
{
g_ns_crit_exit++;
}
/* —— main.h 桩:时基 + INT# 引脚 —— */
uint32_t HAL_GetTick(void)
{
return g_ns_tick;
}
/* INT# 电平可注入:默认 SET(无中断)——net_poll 默认走"引脚高即退出"的短路分支 */
int g_ns_int_pin_set = 1;
uint32_t g_ns_poll_tick_inc = 1U;
uint32_t g_ns_poll_calls;
/* 读 INT# 就是"net_poll 又轮了一轮"的天然计数点:
* - 时基自走 g_ns_poll_tick_inc 毫秒(默认 1ms),NET_SEND_SPIN_MS=2 这种
* "有界自旋"预算才有可量测的时间轴;
* - 跑飞保险:轮询次数超阈值直接 exit(2)。net_send_locked / net_recvfrom 的
* 等待环里没有 osDelay,光靠 osDelay 的保险挡不住死循环(实测过一次挂门禁)。 */
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
(void)GPIOx;
(void)GPIO_Pin;
g_ns_poll_calls++;
g_ns_tick += g_ns_poll_tick_inc;
if (g_ns_on_poll != NULL) {
g_ns_on_poll(); /* 并发事件注入点(在时间推进之后,超时判定才量得到) */
}
if (g_ns_poll_calls > 400000U) {
printf("FATAL: net_poll 轮询 %lu 次仍未收敛(等待条件永不满足)\n",
(unsigned long)g_ns_poll_calls);
exit(2);
}
return (g_ns_int_pin_set != 0) ? GPIO_PIN_SET : GPIO_PIN_RESET;
}
+613
View File
@@ -0,0 +1,613 @@
/*
* 分片 3:跨上下文消息面(net_process_messages 全分派 + 结果经任务通知值回传)
* 前置:net_socket_test.c 已纳入生产源与假内核/假芯片/兄弟模块桩(同一 TU)。
*
* 这一片是整套里最值钱的部分,钉的是 AGENTS 模块约定的两条硬契约:
* A.「所有 net_* socket 操作经 netMsgQueue 在 netTask 串行执行」
* B.「net_* 公开 API 的错误码经任务通知值回传:net_msg_t 是按值拷入队列的,
* netTask 改的是自己那份副本,调用方永远看不到 → 错误码搭 xTaskNotify 通知值回来;
* 勿给 net_msg_t 加回传字段(加了只会让调用方读到栈垃圾)」
* 断言形态因此刻意是:先看**通知值本身**(32 位整体即结果,负值=精确错误码),
* 再看调用方在自己上下文恢复出的 errno——而不是去读队列里的 msg.result
* (那正是旧实现的错觉,也是 B 条明文禁止的方向)。
*
* 创建日期:2026-10-24
*/
/* close 场景的增量快照基准(前面场景也会 clear,必须按增量断言) */
static int s_close_base_clear;
static int s_close_base_settle;
/* 造一条已建立的 TCP 会话(多数消息场景的前置) */
static int mk_established(int type)
{
int fd = net_socket(NET_AF_INET, type);
net_sock_t *p;
if (fd < 0) {
return fd;
}
p = &s_net_socks[fd];
p->state = NET_SOCK_STATE_ESTABLISHED;
p->send_ready = 1U;
p->readable = 0U;
p->local_port = 2404U;
p->remote_port = 51999U;
p->remote_ip = 0x0B01A8C0U;
return fd;
}
/* TC-NETSOCK-200 未知消息类型:枚举闭合外的值也必须给出**本条消息自身**的确定结果
* (不许把上一条的回执残留当成这次的结果) */
static void tc_200_unknown_type(void)
{
net_msg_t msg;
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = (net_msg_type_t)999;
msg.sockfd = 0;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
msg.result = 12345; /* 脏值:分派必须覆写它 */
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-200a 原始消息入队");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-200b 入队本身不产生通知");
net_process_messages(); /* 以 netTask 身份跑一轮 */
CHECK(g_ns_notify_calls == 1, "TC-NETSOCK-200c 未知 type 仍必发回执(否则调用方永久悬挂)");
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR,
"TC-NETSOCK-200d 未知 type 的通知值 = 本条的精确错误码 NET_ERR(不是上一条残留)");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-200e 本轮队列被排空(非阻塞 while 语义)");
reset_all();
}
/* TC-NETSOCK-201 队列为空:四个自愈 tick 必跑,且不产生任何通知 */
static void tc_201_ticks_run_before_dispatch(void)
{
bring_up();
net_process_messages();
CHECK(g_peers.listener_tick_calls == 1 && g_peers.pending_tick_calls == 1 &&
g_peers.send_tick_calls == 1 && g_peers.reconcile_calls == 1,
"TC-NETSOCK-201a 空队列也要跑满四个自愈 tick(D10/pending×2/对账)");
CHECK(g_ns_notify_calls == 0 && g_ns_get_calls == 1,
"TC-NETSOCK-201b 空队列:取一次即退,不发通知");
reset_all();
}
/* TC-NETSOCK-202 队列句柄未绑定:直接返回,连 tick 都不该跑 */
static void tc_202_no_queue_bound(void)
{
reset_all();
g_ns_pump = net_process_messages;
net_process_messages();
CHECK(g_peers.listener_tick_calls == 0 && g_ns_get_calls == 0,
"TC-NETSOCK-202 未绑定队列就调 net_process_messages → 立即返回(不碰芯片也不跑 tick)");
reset_all();
}
/* TC-NETSOCK-210 SEND 快路径:端到端跑通"入队 → 分派 → 写芯片 → 通知值=字节数" */
static void tc_210_send_end_to_end(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)memset(buf, 'A', sizeof(buf));
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == (int)sizeof(buf), "TC-NETSOCK-210a net_send 返回实际写入字节数");
CHECK(g_chip.write_calls == 1 && g_chip.last_read_len == (uint16_t)sizeof(buf),
"TC-NETSOCK-210b 一次写满(健康链路不拆块、不进挂起)");
CHECK(s_net_socks[fd].send_bytes == sizeof(buf), "TC-NETSOCK-210c 发送字节统计累计");
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == (uint32_t)sizeof(buf),
"TC-NETSOCK-210d 结果经通知值回传(32 位整体即结果)");
CHECK(g_ns_notify_last_to == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-210e 通知发给**登记的那个 caller**,不是广播");
CHECK(g_ns_block_waits == 1,
"TC-NETSOCK-210f 零等待没拾到 → 走阻塞分支(两段式拾取的第二段)");
reset_all();
}
/* TC-NETSOCK-211 大帧分块:超过 NET_SEND_CHUNK_MAX 必须按硬件发送缓冲上界切块
* (TRAP-NET-16:单次写 >1KB 会写穿 1KB 发送 FIFO) */
static void tc_211_send_chunking(void)
{
static char buf[3000];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* 造"每写完一块芯片就报 SENBUF_FREE"的健康链路:让 net_poll 把 send_ready 置回来 */
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
/* SENBUF_FREE 是**条件位**:发送缓冲空着就还会再报。用假件的电平型掩码而不是
* 一次性位图,否则第二块起就再也等不到空闲 → 变成测假件的清零时机。 */
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
rc = net_send(fd, buf, 2500, 0);
CHECK(rc == 2500, "TC-NETSOCK-211a 2500 字节全部交付");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-211b 按 1024 上界切成 3 块(1024/1024/452)");
CHECK(g_chip.last_read_len == 452U, "TC-NETSOCK-211c 末块只写剩余量");
CHECK(NET_SEND_CHUNK_MAX == 1024,
"TC-NETSOCK-211d 分块粒度取芯片发送缓冲(不是假件自定的数)");
reset_all();
}
/* TC-NETSOCK-212 SEND 挂起登记:等发送缓冲超预算 → 登记挂起并**跳过即时回执** */
static void tc_212_send_pending_defers_notify(void)
{
static char buf[64];
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U; /* 芯片一直不腾缓冲 */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
CHECK(ns_rtos_q_put_raw(&msg) == 0, "TC-NETSOCK-212a SEND 消息入队");
net_process_messages();
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-212b 超自旋预算后登记挂起 send(而不是继续占着 netTask 忙等)");
CHECK(g_peers.last_snd_fd == fd && g_peers.last_snd_len == (int)sizeof(buf) &&
g_peers.last_snd_sent == 0,
"TC-NETSOCK-212c 登记的进度=已写芯片字节数(此处 0),总长与 caller 一并带走");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-212d 登记成功**绝不发即时回执**(否则「登记成功」被当成本次结果)");
CHECK(g_ns_notify_last_to == NULL,
"TC-NETSOCK-212e 一次通知都没发生(0x70000000 哨兵值也没可能漏进通知值)");
/* 完成时刻:由挂起 send 的 tick 结束(本套由桩脚本模拟"缓冲腾出来了") */
ns_peers_arm_send_pending_done(fd, 64);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 64U,
"TC-NETSOCK-212f 挂起完成后才发回执,通知值=实际写出字节数");
reset_all();
}
/* TC-NETSOCK-213 SEND 失败:未建立连接 → 通知值带**精确**错误码,不是笼统 -1 */
static void tc_213_send_error_code_roundtrip(void)
{
char buf[4];
net_msg_t msg;
int fd;
int rc;
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM); /* CREATED,未连接 */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_NOTCONN,
"TC-NETSOCK-213a 未连接的 SEND:通知值=NET_ERR_NOTCONN(精确码回传,供调用方分支)");
/* 端到端:公开 net_send 的 BADF 形态(fd 越界由**分派侧**再判一次) */
reset_all();
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_SEND;
msg.sockfd = 3; /* 合法范围内但未分配 */
msg.buf = buf;
msg.len = 4;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-213b 范围内但未分配的 fd:net_get_sock 判空 → BADF 搭通知值回来");
/* 调用方上下文恢复 errno:拿到的必须是"本任务的"精确码 */
reset_all();
bring_up();
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(6, buf, 4, 0); /* 6 未分配 → 分派侧 BADF → 通知值 -2 */
CHECK(rc == -1, "TC-NETSOCK-213c 对外保持 BSD 语义:失败一律 -1");
CHECK(net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-213d 调用方在自己的错误码槽里拿到精确码(E-03 + 通知值回传合力)");
reset_all();
}
/* TC-NETSOCK-220 RECV 快路径:芯片缓冲里已有数据 → 当场读出,不登记挂起 */
static void tc_220_recv_fast_path(void)
{
char buf[16];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd, 1400U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, 10, NET_MSG_DONTWAIT);
CHECK(rc == 10, "TC-NETSOCK-220a 缓冲 1400B、只给 10B 缓冲 → 读满 10(部分读合法)");
CHECK(g_chip.read_calls == 1 && g_chip.last_read_len == 10U,
"TC-NETSOCK-220b 只读调用方给的量(不得按芯片可用量越界写)");
CHECK(s_net_socks[fd].recv_bytes == 10U, "TC-NETSOCK-220c 接收字节统计累计");
CHECK(s_net_socks[fd].readable == 0U,
"TC-NETSOCK-220d 读过即清 readable(下一次 RECV_OK 中断再置)");
CHECK(g_peers.pending_reg_calls == 0, "TC-NETSOCK-220e 数据已在手,不该登记挂起");
reset_all();
}
/* TC-NETSOCK-221 RECV 非阻塞无数据 → WOULDBLOCK(且不许登记挂起) */
static void tc_221_recv_wouldblock(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221a DONTWAIT 无数据 → -1 + WOULDBLOCK");
CHECK(g_peers.pending_reg_calls == 0,
"TC-NETSOCK-221b 非阻塞绝不登记挂起(否则调用方以为自己在等,实际已被挂住)");
/* 模式属性(net_setblocking(0))与单次标志同判据 */
CHECK(net_setblocking(fd, 0) == 0, "TC-NETSOCK-221c 切非阻塞模式成功");
rc = net_recv(fd, buf, (int)sizeof(buf), 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221d blocking 属性=0 与 NET_MSG_DONTWAIT 等价生效");
CHECK(net_recv_nb(fd, buf, (int)sizeof(buf)) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-221e net_recv_nb 具名入口同路径");
reset_all();
}
/* TC-NETSOCK-222 RECV EOF:会话已结束 + 无数据 → 返 0 并登记重建(不是 -1) */
static void tc_222_recv_eof_relisten(void)
{
char buf[8];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0, "TC-NETSOCK-222a CLOSED+无数据 → EOF(0),不是错误");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_EOF,
"TC-NETSOCK-222b EOF 登记 REQ_EOF(重建动作交监听层唯一执行者,本层不动芯片)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0 && g_chip.close_calls == 0,
"TC-NETSOCK-222c 登记路径上**一个芯片事务都不发**(单一所有者不变量 II)");
/* 抢先 relisten 后 state 已回 LISTENING,但会话电平仍是"已结束" */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].session_dead = 1U;
s_net_socks[fd].auto_relisten = 1U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0,
"TC-NETSOCK-222d 已 relisten 回 LISTENING 仍判 EOF(N-32 P1 电平化:否则 EOF 永久失踪)");
/* auto_relisten=0(客户端 fd):EOF 不登记重建,交调用方决定重连 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].session_dead = 1U;
s_net_socks[fd].auto_relisten = 0U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_recv(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT);
CHECK(rc == 0 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-222e 非监听 fd 的 EOF 不许替调用方擅自重建");
reset_all();
}
/* TC-NETSOCK-223 RECV 阻塞无数据 → 登记挂起 + 跳过即时回执;到点由 tick 结束 */
static void tc_223_recv_pending_roundtrip(void)
{
char buf[16];
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = fd;
msg.buf = buf;
msg.len = (int)sizeof(buf);
msg.flags = 0;
msg.timeout_ms = 250U; /* 显式预算必须原样带到登记处 */
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_reg_calls == 1 && g_peers.last_pend_fd == fd,
"TC-NETSOCK-223a 阻塞无数据 → 登记挂起 recv(netTask 零占用返回)");
CHECK(g_peers.last_pend_to == 250U,
"TC-NETSOCK-223b 调用方的显式预算随登记带走(0=无限,这里必须不是 0)");
CHECK(g_peers.last_pend_caller == (TaskHandle_t)&g_ns_task_caller,
"TC-NETSOCK-223c 完成时要知道通知谁");
CHECK(g_ns_notify_calls == 0,
"TC-NETSOCK-223d 登记即发回执 = 把「登记成功」当结果,本条钉它没发生");
/* 数据到达:由挂起 recv 的 tick 结束(脚本模拟) */
ns_peers_arm_pending_done(fd, 7);
net_process_messages();
CHECK(g_ns_notify_calls == 1 && g_ns_notify_last_val == 7U,
"TC-NETSOCK-223e 数据到达后才回执,通知值=字节数");
reset_all();
}
/* TC-NETSOCK-224 net_recv_to:分片超时用独立返回码 -2(不靠 errno,避免跨任务竞态) */
static void tc_224_recv_to_timeout_code(void)
{
char buf[16];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
/* 先造一条"已登记"的挂起,再让 tick 判到点 */
ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT);
rc = net_recv_to(fd, buf, (int)sizeof(buf), 30U);
CHECK(rc == -2, "TC-NETSOCK-224a 本片超时 → 独立返回码 -2(与真错误 -1 区分)");
reset_all();
}
/* TC-NETSOCK-225 RECV 参数非法:不碰芯片、不登记 */
static void tc_225_recv_bad_args(void)
{
char buf[4];
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(0, buf, 4, 0U) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225a net_recv_to(timeout=0) 直接拒——0 在 netTask 侧是「无限等待」,"
"与本接口「有界占用」的存在理由相反");
CHECK(g_ns_put_calls == 0, "TC-NETSOCK-225b 拒绝发生在入队之前");
CHECK(net_recv(fd, NULL, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225c buf 为 NULL → INVAL");
CHECK(net_recv(fd, buf, 0, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-225d len<=0 → INVAL");
CHECK(net_send(fd, NULL, 4, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225e send 的 buf 为 NULL → INVAL");
CHECK(net_send(fd, buf, -1, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-225f send 的 len<0 → INVAL");
CHECK(g_chip.write_calls == 0 && g_chip.read_calls == 0 &&
g_peers.pending_reg_calls == 0 && g_peers.send_reg_calls == 0,
"TC-NETSOCK-225g 参数非法路径上零芯片事务、零挂起登记");
reset_all();
}
/* TC-NETSOCK-230 CLOSE:先结束挂起再关芯片,且 last_peer_port 跨释放保留 */
static void tc_230_close_order_and_peer(void)
{
net_msg_t msg;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
/* 登记一条挂起 recv,再看 close 是否先把它结算掉(排空/取消都必通知) */
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = fd;
msg.buf = (void *)"x";
msg.len = 1;
msg.caller = (TaskHandle_t)&g_ns_task_net; /* 另一个任务在等这条流 */
(void)ns_rtos_q_put_raw(&msg);
net_process_messages(); /* 走登记路径(不发回执) */
CHECK(g_peers.pending_reg_calls == 1 && g_ns_notify_calls == 0,
"TC-NETSOCK-230a 前置:已登记挂起且未回执");
/* 芯片侧此刻仍是 OPEN:close 才需要真去拆(已 CLOSED 的形态见 TC-NETSOCK-232) */
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_ns_notify_calls = 0;
{
s_close_base_clear = g_peers.listener_clear_calls;
s_close_base_settle = g_peers.pending_settle_calls;
}
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_CLOSE;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_settle_calls == s_close_base_settle + 1,
"TC-NETSOCK-230b close 必先结算本 fd 的挂起 recv(否则等待方永久悬挂)");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-230c 结算必通知(取消也要通知,不是静默清槽)");
CHECK(g_chip.close_calls >= 1 && g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-230d 芯片仍 OPEN → 走 sock_reset 确定性排渣把它拆掉");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-230e close 全程不得读全局中断寄存器(B3:唯一读者=net_poll)");
CHECK(s_net_socks[fd].in_use == 0U && s_net_socks[fd].last_peer_port == 51999U,
"TC-NETSOCK-230f 控制块清零但 last_peer_port 保留(幽灵甄别基准属芯片 Socket 级)");
CHECK(g_peers.listener_clear_calls == s_close_base_clear + 1,
"TC-NETSOCK-230g close 幂等清理监听重试(关闭即取消待重试,D10)");
CHECK(g_peers.pend_active_now[fd] == 0,
"TC-NETSOCK-230h close 后本 fd 不再有挂起 recv(两张表都归零)");
reset_all();
}
/* TC-NETSOCK-231 CLOSE 未在用:net_get_sock 判空 → BADF 通知值 */
static void tc_231_close_badfd(void)
{
net_msg_t msg;
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_CLOSE;
msg.sockfd = 5;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-231a close 未分配的 fd → BADF 经通知值回传");
CHECK(g_chip.close_calls == 0, "TC-NETSOCK-231b BADF 不碰芯片");
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_ACCEPT; /* 没有 in_use 的监听 */
msg.sockfd = 5;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-231c accept 未在用 → INVAL(控制块级校验,不是 fd 越界)");
reset_all();
}
/* TC-NETSOCK-240 DROP_CONN:四类前置各走各的分支(幂等语义是关键) */
static void tc_240_drop_conn_branches(void)
{
net_msg_t msg;
int fd;
/* 非在用 → BADF */
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = 2;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF, "TC-NETSOCK-240a 未分配 → BADF");
/* UDP → INVAL */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_INVAL,
"TC-NETSOCK-240b 非 TCP 流 → INVAL(本 API 只拆 TCP 会话)");
/* 监听态但无活动会话 → 幂等 0,不动芯片 */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].auto_relisten = 1U;
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == 0U, "TC-NETSOCK-240c 无活动会话 → 幂等成功 0");
CHECK(g_chip.close_calls == 0 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240d 幂等分支不得拆芯片(会把在听的监听毁掉)");
/* ESTABLISHED + 监听型 → QUARANTINE 拆除 + 登记重建 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = fd;
msg.caller = (TaskHandle_t)&g_ns_task_caller;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_peers.pending_settle_calls == 1 && g_peers.send_cancel_calls == 1,
"TC-NETSOCK-240e 拆会话前先结束两张表的挂起(必通知)");
CHECK(g_fxchip.rst_begin_calls == 1,
"TC-NETSOCK-240f 拆除走 sock_reset 确定性排渣(不是 close+盲等)");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-240g 监听型 fd 拆完登记 REQ_DROP(重建交监听层,本层不自己 open)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CLOSED &&
s_net_socks[fd].in_use == 1U && s_net_socks[fd].local_port == 2404U &&
s_net_socks[fd].auto_relisten == 1U,
"TC-NETSOCK-240h 只收口会话簿记:fd 保留、监听属性与端口都留下");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-240i 拆除路径也只碰本 Socket(F-NET-7:不读全局)");
reset_all();
}
/* TC-NETSOCK-250 入队失败(队列满)→ 调用方 BUSY,不留下"已提交"的假象 */
static void tc_250_put_fail_busy(void)
{
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
ns_rtos_q_fill((int)NS_Q_CAP); /* 把队列灌满 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
rc = net_send(fd, "abc", 3, 0);
CHECK(rc == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250a 队列满 → net_send 报 BUSY(不是静默丢弃)");
CHECK(g_chip.write_calls == 0 && g_ns_notify_calls == 0,
"TC-NETSOCK-250b 入队失败时零芯片事务、零通知(调用方不会误等回执)");
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250c 同一形态对 net_listen 成立");
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250d 同一形态对 net_close 成立");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250e 同一形态对 net_drop_conn 成立");
CHECK(net_recv_to(fd, (void *)"x", 1, 5U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-250f 同一形态对 net_recv_to 成立");
reset_all();
}
static void ns_run_s3(void)
{
tc_200_unknown_type();
tc_201_ticks_run_before_dispatch();
tc_202_no_queue_bound();
tc_210_send_end_to_end();
tc_211_send_chunking();
tc_212_send_pending_defers_notify();
tc_213_send_error_code_roundtrip();
tc_220_recv_fast_path();
tc_221_recv_wouldblock();
tc_222_recv_eof_relisten();
tc_223_recv_pending_roundtrip();
tc_224_recv_to_timeout_code();
tc_225_recv_bad_args();
tc_230_close_order_and_peer();
tc_231_close_badfd();
tc_240_drop_conn_branches();
tc_250_put_fail_busy();
}
+346
View File
@@ -0,0 +1,346 @@
/*
* 分片 4:net_poll 中断分派、监听/可读闸门、close 与 drop_conn 的拆除序列
*
* 这一片钉的是 AGENTS 模块约定里**没有文档就无法发现**的那条芯片约束:
* 所有 CH395F 中断在 net_poll() 内经 GET_GLOB_INT_STATUS 集中读取并清除,不得在
* ISR 或其他地方单独处理。全局寄存器整体读即清、芯片无按位清除手段,而 net_poll
* 只在全局位为 1 时才读该 Socket 的 SINT → 顺手全量清一遍等于把别的在用 Socket 的
* 门焊死(B3 实证,详见 docs/网络/网络自愈架构设计.md §4.10)。
*
* 断言手法(刻意不用「读了几次」这种弱口径):GET_SOCK_INT_STATUS 是**读即清**,
* 所以给每个 Socket 预置互不相同的挂起位图,跑一轮 net_poll 后看哪些位图被清掉了
* ——被清=被读过。这比数调用次数更硬:它直接照出「门有没有被焊死」。
*
* 创建日期:2026-10-24
*/
/* 预置:给每个 Socket 挂一个只有自己才有的 SINT 位图(只用低 6 位,避开保留位) */
static void prime_sint_masks(void)
{
int i;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
g_fake.int_status[i] = (uint8_t)(0x01U << (i % 6));
}
g_chip.int_status_calls = 0;
}
/* TC-NETSOCK-300 net_poll 的前置闸门 */
static void tc_300_poll_gates(void)
{
/* 1) 未初始化:直接 0,连引脚都不该读 */
reset_all();
g_ns_pump = net_process_messages;
CHECK(net_poll() == 0, "TC-NETSOCK-300a 未初始化 → 0 事件");
CHECK(g_ns_poll_calls == 0U && g_fxchip.glob_calls == 0,
"TC-NETSOCK-300b 未初始化不得读 INT# 引脚、不得读全局中断(省 SPI)");
/* 2) 初始化了但 INT# 为高(无中断待处理):不读全局寄存器 */
bring_up();
g_ns_int_pin_set = 1;
CHECK(net_poll() == 0, "TC-NETSOCK-300c INT# 高 → 0 事件");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-300d INT# 高时不得发全局中断读取(引脚轮询就是为省这一步)");
/* 3) INT# 低但全局状态为 0:读了全局一次,且不进 Socket 循环 */
bring_up();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob(0U);
CHECK(net_poll() == 0, "TC-NETSOCK-300e 全局位全 0 → 0 事件");
CHECK(g_fxchip.glob_calls == 1 && g_chip.int_status_calls == 0,
"TC-NETSOCK-300f 全局只读一次,读到 0 立即收手(不逐个 Socket 扫)");
reset_all();
}
/* TC-NETSOCK-301 门控精度:只读全局位置位的那个 Socket 的 SINT(B3 的正面表述) */
static void tc_301_poll_only_gated_sockets(void)
{
int i;
int a;
int b;
bring_up();
a = mk_established(NET_SOCK_STREAM);
b = mk_established(NET_SOCK_STREAM);
(void)mk_established(NET_SOCK_STREAM);
CHECK(a == 0 && b == 1, "TC-NETSOCK-301a 前置:三个在用 fd");
prime_sint_masks();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (1 + 4))); /* Socket n 的位 = bit(n+4) */
(void)net_poll();
CHECK(g_fake.int_status[0] == (uint8_t)(0x01U << 0) &&
g_fake.int_status[2] == (uint8_t)(0x01U << 2),
"TC-NETSOCK-301b 未点亮的 Socket 0/2 挂起位图没被清(读即清=读过,没读=还在)");
CHECK(g_fake.int_status[1] == 0U, "TC-NETSOCK-301c 点亮的 Socket 1 被读走(位图清零)");
CHECK(g_chip.int_status_calls == 1, "TC-NETSOCK-301d 整轮只发了一次 SINT 读取");
/* 缓存刷新环是另一回事:它对所有在用 fd 都要刷(单一事实源 I1) */
CHECK(g_fxchip.rst_poll_calls == 3,
"TC-NETSOCK-301e 每轮对全部 3 个在用 fd 步进 sock_reset(IDLE 槽零 SPI)");
(void)net_poll();
CHECK(g_fxchip.rst_poll_calls == 6,
"TC-NETSOCK-301f 再一轮再加 3 次:刷新环不受全局位门控(事件面与状态面分离)");
reset_all();
/* 高位字节:Socket 4~7 的全局位在 bit8~bit11 */
bring_up();
for (i = 0; i < 5; i++) {
(void)mk_established(NET_SOCK_STREAM);
}
prime_sint_masks();
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (4 + 4)));
(void)net_poll();
CHECK(g_fake.int_status[4] == 0U && g_fake.int_status[3] == (uint8_t)(0x01U << 3),
"TC-NETSOCK-301g Socket4~7 用高位字节 bit(n+4)=bit8..11 定位(算错就永远收不到 4~7 的事件)");
reset_all();
}
/* TC-NETSOCK-302 六种 SINT 位各自的分派与事件计数 */
static void tc_302_poll_event_dispatch(void)
{
static const uint8_t bits[6] = {
CH395F_SINT_STAT_CONNECT, CH395F_SINT_STAT_DISCONNECT, CH395F_SINT_STAT_RECV_OK,
CH395F_SINT_STAT_SEND_OK, CH395F_SINT_STAT_SOCK_TIMEOUT, CH395F_SINT_STAT_SENBUF_FREE
};
int k;
int fd;
for (k = 0; k < 6; k++) {
int expect = (bits[k] == CH395F_SINT_STAT_SENBUF_FREE) ? 0 : 1;
int n;
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = bits[k];
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
n = net_poll();
CHECK(n == expect, "TC-NETSOCK-302a 事件计数按位型区分(SENBUF_FREE 是状态位不是事件)");
switch (bits[k]) {
case CH395F_SINT_STAT_CONNECT:
CHECK(g_peers.evt_connect_calls == 1, "TC-NETSOCK-302b CONNECT → handle_connect_event");
break;
case CH395F_SINT_STAT_DISCONNECT:
CHECK(g_peers.evt_disconnect_calls == 1, "TC-NETSOCK-302c DISCONNECT → handle_disconnect_event");
break;
case CH395F_SINT_STAT_RECV_OK:
CHECK(g_peers.evt_recv_calls == 1 && s_net_socks[fd].readable == 1U,
"TC-NETSOCK-302d RECV_OK → handle_recv_event 置 readable 电平");
break;
case CH395F_SINT_STAT_SEND_OK:
CHECK(g_peers.evt_send_calls == 1, "TC-NETSOCK-302e SEND_OK → handle_send_event");
break;
case CH395F_SINT_STAT_SOCK_TIMEOUT:
CHECK(g_peers.evt_timeout_calls == 1, "TC-NETSOCK-302f TIMEOUT → handle_timeout_event");
break;
default:
CHECK(s_net_socks[fd].send_ready == 1U && g_peers.evt_send_calls == 0,
"TC-NETSOCK-302g SENBUF_FREE 只置 send_ready(不发事件、不误当 SEND_OK)");
break;
}
CHECK(g_peers.evt_connect_calls + g_peers.evt_disconnect_calls +
g_peers.evt_recv_calls + g_peers.evt_send_calls +
g_peers.evt_timeout_calls <= 1,
"TC-NETSOCK-302h 一位进来只敲一个 handler(不串味)");
}
/* 同一次 SINT 里多个位 → 多个 handler 都被叫到,事件数叠加 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_int_pin_set = 0;
g_fake.int_status[fd] = (uint8_t)(CH395F_SINT_STAT_RECV_OK | CH395F_SINT_STAT_DISCONNECT);
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
CHECK(net_poll() == 2, "TC-NETSOCK-302i 同批多事件位:各计一次(不吞后到的那位)");
CHECK(g_peers.evt_recv_calls == 1 && g_peers.evt_disconnect_calls == 1,
"TC-NETSOCK-302j 两位各归各的 handler");
reset_all();
}
/* TC-NETSOCK-303 PHY 变更位经驱动 feed 落到链路缓存(F-DRV-13 接线) */
static void tc_303_phy_feed(void)
{
bring_up();
g_ns_int_pin_set = 0;
g_fx.phy_status = CH395F_PHY_DISCONN; /* 下一次 feed 读到断开 */
ch395f_fake_push_glob(CH395F_GINT_STAT_PHY_CHANGE);
(void)net_poll();
CHECK(g_fake.link_up == 0U && g_fake.link_change == 1U,
"TC-NETSOCK-303 PHY_CHANGE → 驱动重读 PHY 并计一次链路变更(拔网线不再静默)");
reset_all();
}
/* TC-NETSOCK-310 监听门控 net_listener_armed 的三支放行与全部挡下形态 */
static void tc_310_listener_armed(void)
{
int fd;
reset_all();
CHECK(net_listener_armed(-1) == 0 && net_listener_armed(NET_MAX_SOCKETS) == 0,
"TC-NETSOCK-310a 越界 fd → 0(纯 RAM 读,不发 RPC)");
CHECK(net_listener_armed(0) == 0, "TC-NETSOCK-310b 未分配的 fd → 0");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310c ESTABLISHED(待 accept 电平)必须放行:i104 台架实证不放行则会话整场饿死");
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310d LISTENING 放行:补 accept 探针在 net_accept_locked 内部,挡住永远到不了");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 7003U;
s_net_socks[fd].readable = 1U;
CHECK(net_listener_armed(fd) == 1,
"TC-NETSOCK-310e CLOSED+auto_relisten+有残留数据 → 放行(保住 closed-with-readable 接受)");
s_net_socks[fd].readable = 0U;
CHECK(net_listener_armed(fd) == 0,
"TC-NETSOCK-310f CLOSED 但无残留 → 必须挡下(否则整个 CLOSED 窗每轮发 RPC → 喂不上狗)");
s_net_socks[fd].readable = 1U;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310g 非监听型 fd 的 CLOSED 不放行");
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 0U;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310h 还没绑端口的半成品监听不放行");
s_net_socks[fd].state = NET_SOCK_STATE_CONNECTING;
CHECK(net_listener_armed(fd) == 0, "TC-NETSOCK-310i CONNECTING 不是监听态 → 0");
reset_all();
}
/* TC-NETSOCK-311 recv 门控 net_can_read:非 ESTABLISHED 一律放行 */
static void tc_311_can_read(void)
{
int fd;
reset_all();
CHECK(net_can_read(0) == 1,
"TC-NETSOCK-311a 描述符失效 → 1:让 recv 去报 BADF,别把连接不存在藏住");
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 0, "TC-NETSOCK-311b ESTABLISHED 且无可读 → 0");
s_net_socks[fd].readable = 1U;
CHECK(net_can_read(fd) == 1, "TC-NETSOCK-311c readable 置位 → 1");
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].readable = 0U;
CHECK(net_can_read(fd) == 1,
"TC-NETSOCK-311d 连接已非 ESTABLISHED → 必须放行一次 recv 取回 EOF/错误");
reset_all();
}
/* TC-NETSOCK-320 close 的拆除序列:快路径(芯片已 CLOSED)与慢路径(走复位机) */
static void tc_320_close_two_paths(void)
{
int fd;
/* 快路径:芯片已 CLOSED → 只清本 Socket 残留事件,不发复位 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_CLOSED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320a close 成功(快路径)");
CHECK(g_fxchip.cmd_done_calls >= 1 && g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-320b 已 CLOSED → 跳过复位(省掉 100~600ms 的无占用)");
CHECK(g_fxchip.sock_events_calls >= 1,
"TC-NETSOCK-320c 快路径仍要清本 Socket 残留事件(防下一连接误判刚建立就断开)");
CHECK(g_fxchip.glob_calls == 0, "TC-NETSOCK-320d 全程零全局读取(F-NET-7)");
reset_all();
/* 慢路径:芯片还 OPEN → 走 sock_reset,且必须是 GRACEFUL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320e close 成功(慢路径)");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 0U,
"TC-NETSOCK-320f 慢路径用 GRACEFUL(正常释放 100ms 底),不是 QUARANTINE");
CHECK(g_fx.rst_state[fd] == (uint8_t)CH395F_FAKE_RST_IDLE,
"TC-NETSOCK-320g 复位机收口回 IDLE(槽位可复用,不搁浅)");
reset_all();
/* 复位启动被拒(槽位已在运行)→ close 仍放行,只留痕 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_state[fd] = (uint8_t)CH395F_FAKE_RST_DRAINED;
CHECK(net_close(fd) == 0, "TC-NETSOCK-320h 复位启动失败也放行 close(不卡调用方)");
CHECK(g_fxchip.rst_begin_reject == 1 && g_ns_error_count >= 1,
"TC-NETSOCK-320i 复位没成必须 [ERR] 留痕,交 reconciler/pump 对账接管");
reset_all();
}
/* TC-NETSOCK-321 drop_conn 用 QUARANTINE(异常释放语义),且不动监听簿记 */
static void tc_321_drop_conn_settle(void)
{
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].readable = 1U;
s_net_socks[fd].lost_conn_seen = 3U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn 成功");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 1U,
"TC-NETSOCK-321b 拆会话用 QUARANTINE 400ms 底(TRAP-NET-18 异常释放,与 close 分级)");
CHECK(s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-321c 新会话起点:事件未收到去抖计数清零(N-30)");
CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U &&
s_net_socks[fd].in_use == 1U,
"TC-NETSOCK-321d 端口/监听属性/fd 全部保留:这就是拆连接不拆监听");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-321e 登记 REQ_DROP,重建动作在 net_listener_tick 一处");
CHECK(s_net_socks[fd].readable == 0U && s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-321f 收口会话簿记:清 readable + 转 CLOSED");
reset_all();
}
/* TC-NETSOCK-322 close 前必先把挂起结算掉,再动芯片(顺序不可换) */
static void tc_322_close_before_chip(void)
{
int fd;
int close_at;
int settle_at;
char rxbuf[4];
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
/* 先登记一条挂起 recv:走 netTask 直调版 recv(无数据 + 阻塞 → 登记挂起) */
g_ns_pump = NULL; /* 本场景不靠泵,直接看登记结果 */
CHECK(net_recv_sock(&s_net_socks[fd], rxbuf, (int)sizeof(rxbuf), 0)
== NET_PENDING_REGISTERED,
"TC-NETSOCK-322p 前置:阻塞 recv 登记了挂起(返回哨兵)");
ch395f_fake_clear_log();
settle_at = g_peers.pending_settle_calls;
close_at = g_chip.close_calls;
CHECK(net_close_sock(&s_net_socks[fd]) == 0, "TC-NETSOCK-322a netTask 直调 close 成功");
CHECK(g_peers.pending_settle_calls == settle_at + 1,
"TC-NETSOCK-322b close 必先结算本 fd 的挂起 recv");
CHECK(g_chip.close_calls > close_at || g_fxchip.rst_close_calls > 0,
"TC-NETSOCK-322c 结算之后芯片侧才收到 CLOSE");
CHECK(g_peers.pending_notified == 1,
"TC-NETSOCK-322d 结算必通知(取消也要通知,不是静默清槽 → 否则等待方永久悬挂)");
reset_all();
}
static void ns_run_s4(void)
{
tc_300_poll_gates();
tc_301_poll_only_gated_sockets();
tc_302_poll_event_dispatch();
tc_303_phy_feed();
tc_310_listener_armed();
tc_311_can_read();
tc_320_close_two_paths();
tc_321_drop_conn_settle();
tc_322_close_before_chip();
}
+609
View File
@@ -0,0 +1,609 @@
/*
* 分片 5:建流三件套(listen / accept / connect)的锁定版决策 + net_init 下发面
* + UDP 数据报路径 + netTask 直调入口
*
* 为什么值得单独一片:这三条是「真机极难按需复现」的决策分支——open 被芯片拒
* (USE)、LISTEN 命令失败、TCP_CONNECT 被命令级拒绝、CONNECT 事件未收到的补 accept、
* 对端「发完就 FIN」的 closed-with-readable,在台架上要凑齐对端行为+时序+总线状态
* 才见得到一次;而每一条的错误形态都是「静默毁掉监听」或「数据被吞」(TRAP-NET-09/
* -12/-18/-20 与 E14 复核实验的原文结论)。
*
* 创建日期:2026-10-24
*/
/* 断线注入点:第二次轮询时把状态打回 CLOSED(模拟 net_poll 处理 DISCONNECT 之后的形态)。
* 用假内核现成的「每轮 net_poll 回调」注入并发事件,而不是自己发明时间或事件 API。 */
static int s_ns_poll_seen;
static void ns_drop_conn_on_second_poll(void)
{
s_ns_poll_seen++;
if (s_ns_poll_seen == 2) {
s_net_socks[0].state = NET_SOCK_STATE_CLOSED;
s_net_socks[0].session_dead = 1U;
}
}
/* TC-NETSOCK-400 net_init 的下发面与门控(一次调用钉十条契约) */
static void tc_400_net_init_downlink(void)
{
uint8_t mac_zero[6] = {0, 0, 0, 0, 0, 0};
uint8_t mac_real[6] = {0x02, 0x11, 0x22, 0x33, 0x44, 0x55};
int i;
/* 1) 芯片不在位:直接失败,且不得继续下发任何参数 */
reset_all();
g_fx.exist_rc = (int)CH395F_STATUS_NOT_DETECTED;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
"TC-NETSOCK-400a 芯片不在位 → -1");
CHECK(g_fxchip.reset_calls == 0 && g_fxchip.init_calls == 0,
"TC-NETSOCK-400b 不在位就不再复位/初始化(别把错误拖到协议栈里)");
CHECK(g_ns_error_count == 1, "TC-NETSOCK-400c 芯片不在位必留 [ERR](否则只剩端口不通的现象)");
/* 2) 协议栈初始化失败 */
reset_all();
g_fx.init_rc = (int)CH395F_STATUS_ERROR;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == -1,
"TC-NETSOCK-400d ch395f_init 失败 → -1");
CHECK(g_fxchip.ping_calls == 0, "TC-NETSOCK-400e 初始化失败后不得继续使能 PING(半配置状态)");
/* 3) 版本门控:低于 0x46 必须**拒置** bit3(SOCKET_CLOSE),退回芯片自动关断 */
reset_all();
g_fx.version = 0x45U;
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
"TC-NETSOCK-400f 版本不足不是致命错:继续起来");
CHECK(g_fx.fun_para == 0U && g_ns_error_count == 1,
"TC-NETSOCK-400g ver<0x46 → FUN_PARA 实发 0(bit3 拒置)+ [ERR] 留痕,不静默带病运行");
/* 4) 版本够新:FUN_PARA=0x08;bit1(TCP_SERVER 多连接) 与 bit0(保留) 都必须为 0 */
reset_all();
CHECK(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL) == 0,
"TC-NETSOCK-400h 正常初始化成功");
/* bit3=SOCKET_CLOSE 要置;bit1=TCP_SERVER 多连接(本项目单连接模型)与
* bit0=保留位都必须为 0——旧实现把 0x09 当「多 Socket 模式」下发,2026-10-11
* 定案为 0x08(bit0 是保留位,证据链 TRAP-NET-15)。所以掩码要考 bit0|bit1,
* 拿 0x09 考会把我们正想要的 bit3 一起算进去(第一版就这么写错了)。 */
CHECK(g_fx.fun_para == CH395F_FUN_PARA_SOCKET_CLOSE &&
(g_fx.fun_para & 0x03UL) == 0UL,
"TC-NETSOCK-400i FUN_PARA 只置 bit3,bit0/bit1 为 0(0x09 是历史误值,TRAP-NET-15)");
CHECK(g_fx.mss == (uint16_t)CH395F_TCP_MSS && g_fx.retrans_cnt == 8U &&
g_fx.retrans_period == 200U,
"TC-NETSOCK-400j MSS/重传参数取 net_config.h 真值(8 次 / 200ms,P0-a)");
CHECK(g_fx.ka_idle == 60000U && g_fx.ka_intvl == 5000U && g_fx.ka_cnt == 3U,
"TC-NETSOCK-400k KeepAlive 60s/5s/3(且必须在 ch395f_init 之前下发)");
CHECK(g_fx.mss != 0U || g_fx.ka_idle != 0U ? 1 : 0, "TC-NETSOCK-400k2 参数确有下发");
CHECK(g_chip.log_n == 0 ||
ch395f_fake_log_find('n') > ch395f_fake_log_find('Q'),
"TC-NETSOCK-400l MSS/KeepAlive 等 SET 类命令排在 INIT_CH395 之前(手册硬约束)");
/* 5) IP/掩码/网关三样都按点分顺位下发,并缓存本机 IP 供 getsockname 用 */
CHECK(g_fx.ip[0] == 192U && g_fx.ip[1] == 168U && g_fx.ip[2] == 1U &&
g_fx.ip[3] == 100U && g_fx.gw[3] == 1U && g_fx.mask[0] == 255U,
"TC-NETSOCK-400m IP/网关/掩码按 parse_ip_string 顺位下发(段 0 在 [0])");
CHECK(g_fxchip.set_dhcp_calls == 0 && g_fx.dhcp_enable == 0U,
"TC-NETSOCK-400n 指定了静态 IP 就绝不使能 DHCP");
/* 6) ip=NULL → 走 DHCP,不下发静态三样 */
reset_all();
(void)net_init(NULL, NULL, NULL, NULL);
CHECK(g_fxchip.set_dhcp_calls == 1 && g_fx.dhcp_enable == 1U,
"TC-NETSOCK-400o ip=NULL → 使能 DHCP");
CHECK(g_fxchip.set_ip_calls == 0 && g_fxchip.set_gw_calls == 0 &&
g_fxchip.set_mask_calls == 0, "TC-NETSOCK-400p DHCP 形态不下发静态地址");
/* 7) MAC:全零=用芯片内置,不下发;非零必须下发(写 EEPROM,须在 INIT 之前) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_zero);
CHECK(g_fxchip.set_mac_calls == 0, "TC-NETSOCK-400q MAC 全零 → 保持芯片内置,不下发");
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", mac_real);
CHECK(g_fxchip.set_mac_calls == 1 && g_fx.mac[0] == 0x02U && g_fx.mac[5] == 0x55U,
"TC-NETSOCK-400r 非零 MAC 下发(设备在线身份与 UI 显示一致)");
CHECK(ch395f_fake_log_find('M') < ch395f_fake_log_find('n') ||
ch395f_fake_log_find('M') < 0,
"TC-NETSOCK-400s SET_MAC 排在 INIT_CH395 **之前**(TRAP-NET-19:顺序反了本次上电不生效)");
/* 8) 收发缓冲块布局:6 块/Socket,先配数据 Socket 再配监听 Socket 0(TRAP-NET-01) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(g_fxchip.set_recv_buf_calls == NET_MAX_SOCKETS &&
g_fxchip.set_send_buf_calls == NET_MAX_SOCKETS,
"TC-NETSOCK-400t 8 个 Socket 全部显式重分配(上电默认只给 0~3)");
for (i = 0; i < NET_MAX_SOCKETS; i++) {
if (g_fx.recv_blk_start[i] != (uint8_t)(i * 6) ||
g_fx.recv_blk_cnt[i] != (uint8_t)CH395F_RECV_BLOCKS ||
g_fx.send_blk_start[i] != (uint8_t)(i * 6 + (int)CH395F_RECV_BLOCKS) ||
g_fx.send_blk_cnt[i] != (uint8_t)CH395F_SEND_BLOCKS) {
break;
}
}
CHECK(i == NET_MAX_SOCKETS,
"TC-NETSOCK-400u 块布局 recv=i*6 / send=recv+4,Socket7 恰好用尽 0~47(手册 §5.45)");
CHECK(g_fx.ping_enable == 1U && g_fxchip.ping_calls == 1, "TC-NETSOCK-400v 使能 PING 响应");
/* 9) net_init 的第一件事是取消挂起并通知调用方(顺序错了 = 调用方永久挂死) */
reset_all();
(void)net_init("192.168.1.100", "255.255.255.0", "192.168.1.1", NULL);
CHECK(g_peers.pending_cancel_all_calls == 1 &&
g_peers.send_cancel_all_calls == 1,
"TC-NETSOCK-400w 重建前两张挂起表都要取消(recv/send 各一张,漏一张就有人永远等)");
CHECK(g_peers.last_pend_cancel_err == NET_ERR_CONNRESET &&
g_peers.last_snd_cancel_err == NET_ERR_CONNRESET,
"TC-NETSOCK-400x 取消原因 = CONNRESET");
CHECK(g_peers.pending_capture_calls == 1,
"TC-NETSOCK-400y 登记执行任务(挂起登记的自等死锁守卫据此判「谁在执行」)");
reset_all();
}
/* TC-NETSOCK-410 net_listen_locked:open 重试、清事件时序、失败对称关断 */
static void tc_410_listen_locked(void)
{
struct net_sockaddr_in in;
int fd;
/* 非 TCP 流 → INVAL */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-410a UDP 不能 listen");
/* 状态不对(还没 bind 也没到 CREATED)→ INVAL */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-410b 已建立的 fd 再 listen → INVAL(不许把数据通道打回监听)");
/* 成功路径:BOUND → LISTENING,并启用 auto_relisten */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(2404);
CHECK(net_bind(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-410c bind 前置");
ch395f_fake_clear_log();
CHECK(net_listen_locked(fd) == 0, "TC-NETSOCK-410d listen 成功");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING &&
s_net_socks[fd].auto_relisten == 1U && s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-410e 进 LISTENING + auto_relisten=1(BSD 监听常驻)+ 去抖计数清零");
CHECK(g_chip.set_port_calls == 1 && g_chip.last_port == 2404U,
"TC-NETSOCK-410f 源端口取控制块的 local_port(端口不能漂)");
CHECK(ch395f_fake_log_find('i') >= 0 &&
ch395f_fake_log_find('i') < ch395f_fake_log_find('l'),
"TC-NETSOCK-410g 清本 Socket 事件排在 TCP_LISTEN **之前**(E14:反了会吞掉新 CONNECT)");
CHECK(g_fxchip.glob_calls == 0,
"TC-NETSOCK-410h listen 全程不得读全局中断寄存器(B3 审计掉的正是这个)");
/* open 一直失败:重试 3 次,每次都走确定性排渣,最后 BUSY */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-410i open 三次仍失败 → BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.rst_begin_calls == 3,
"TC-NETSOCK-410j 三次 open + 三次 sock_reset(TRAP-NET-09 的重试语义保留,不是盲等 500ms)");
CHECK(g_chip.listen_calls == 0, "TC-NETSOCK-410k open 没成绝不发 LISTEN 命令");
/* LISTEN 命令失败:open 已成的 Socket 必须关回去(U12 对称,否则卡 OPEN 无监听) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.listen_rc[fd] = CH395F_ERR_VAL;
CHECK(net_listen_locked(fd) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-410l TCP_LISTEN 失败 → BUSY");
CHECK(g_chip.close_calls == 1 && g_chip.open_calls == 1,
"TC-NETSOCK-410m LISTEN 失败必须 close 已开的 Socket(否则下次 open 恒返 USE)");
CHECK(s_net_socks[fd].state != NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-410n 失败不得谎报 LISTENING");
reset_all();
}
/* TC-NETSOCK-411 公开 net_listen:越界 / 队列 / 结果恢复 */
static void tc_411_listen_public(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
CHECK(net_listen(-1) == -1 && net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-411a 越界 → BADF");
CHECK(net_listen(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-411b 上界越界 → 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, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0, "TC-NETSOCK-411c bind");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_listen(fd) == 0, "TC-NETSOCK-411d 端到端 listen 成功(入队→分派→通知值 0)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_LISTENING,
"TC-NETSOCK-411e 公开入口确实驱动了芯片侧监听");
CHECK(g_ns_notify_last_val == 0U,
"TC-NETSOCK-411f 成功的 listen 通知值=0(哨兵 0x70000000 不许漏进来)");
/* 已监听的 fd 再 listen:netTask 里 INVAL,经通知值回传 */
CHECK(net_listen(fd) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-411g 重复 listen → -1 且 errno=INVAL(精确码,不是笼统 -1)");
reset_all();
}
/* TC-NETSOCK-420 accept:单连接模型的四种形态 */
static void tc_420_accept_shapes(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
/* 1) 已有可接受连接:返回的就是监听 fd 自身(单连接),并回填对端 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_accept(fd, sa, &addrlen) == fd, "TC-NETSOCK-420a 单连接:accept 返回监听 fd 自身");
CHECK(in.sin_family == NET_AF_INET && net_ntohs(in.sin_port) == 51999U &&
addrlen == (int)sizeof(in),
"TC-NETSOCK-420b 回填对端地址与长度(lftpd 打印「谁连进来」就靠这个)");
CHECK(g_peers.lost_probe_calls == 0,
"TC-NETSOCK-420c 非 LISTENING 不触发补 accept 探针(探针是监听态专用自救)");
/* 2) LISTENING 且探针命中 → 接受(CONNECT 事件未收到的自愈路径) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
s_net_socks[fd].local_port = 2404U;
s_net_socks[fd].remote_port = 55555U;
g_peers.lost_probe_hit = 1;
addrlen = 0;
CHECK(net_accept(fd, NULL, NULL) == fd,
"TC-NETSOCK-420d 探针把监听转 ESTABLISHED → accept 立即成功(N-30 主根因修复)");
CHECK(g_peers.lost_probe_calls == 1,
"TC-NETSOCK-420e LISTENING 才调探针(每轮 accept 一次,带去抖计数)");
/* 3) LISTENING 且无连接 → WOULDBLOCK,且不动监听重建 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_LISTENING;
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-420f 无连接 → WOULDBLOCK(不阻塞 netTask)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420g 监听还活着时绝不登记重建(那是 U12 事故的形态)");
/* 4) CLOSED + auto_relisten + RX 有残留 → 接受"已关但可读",先排空再 EOF */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 21U;
s_net_socks[fd].remote_port = 44444U;
ch395f_fake_push_recv_len((uint8_t)fd, 1027U);
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, sa, &addrlen) == fd,
"TC-NETSOCK-420h closed-with-readable:接受这条连接去排空(N-30 定案,反了就是 425)");
CHECK(g_peers.listener_req_calls == 0,
"TC-NETSOCK-420i 有残留数据时**不**登记重建(先 relisten 会把已到达的数据销毁)");
CHECK(g_chip.get_status_calls == 0,
"TC-NETSOCK-420j 排空判据只读 GET_RECV_LEN,不发状态查询");
/* 5) CLOSED + auto_relisten + RX 空 → 登记 REQ_ACCEPT 并 WOULDBLOCK */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 1U;
s_net_socks[fd].local_port = 21U;
CHECK(net_accept(fd, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_WOULDBLOCK,
"TC-NETSOCK-420k CLOSED 且无残留 → WOULDBLOCK");
CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_ACCEPT &&
g_peers.last_req_fd == fd,
"TC-NETSOCK-420l 消费者撞空才登记即时重建(N-32 P2:删掉它会拉长 CLOSED 窗)");
CHECK(g_chip.open_calls == 0 && g_chip.listen_calls == 0,
"TC-NETSOCK-420m 登记路径零芯片事务(重建动作只在 net_listener_tick 一处)");
/* 6) 非监听型 CLOSED(客户端)→ 不擅自登记 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
s_net_socks[fd].state = NET_SOCK_STATE_CLOSED;
s_net_socks[fd].auto_relisten = 0U;
CHECK(net_accept(fd, sa, &addrlen) == -1 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-420n 客户端 fd 的 CLOSED 不登记监听重建");
reset_all();
}
/* TC-NETSOCK-421 accept 的公开入口边界 */
static void tc_421_accept_public(void)
{
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
bring_up();
CHECK(net_accept(-1, sa, &addrlen) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-421a 越界 → BADF");
fd = mk_established(NET_SOCK_STREAM);
addrlen = 0;
CHECK(net_accept(fd, sa, NULL) == fd,
"TC-NETSOCK-421b addrlen 为 NULL 仍可 accept(addr 回填被跳过,不崩)");
addrlen = (int)sizeof(in);
CHECK(net_accept(fd, NULL, &addrlen) == fd,
"TC-NETSOCK-421c addr 为 NULL:只要 fd,不回填地址");
reset_all();
}
/* TC-NETSOCK-430 connect:地址簿记、open 重试、命令被拒回滚 */
static void tc_430_connect(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
CHECK(net_connect(-1, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-430a 越界 → BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_connect(fd, NULL, (int)sizeof(in)) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430b addr=NULL → INVAL(入队之前就把参数挡下)");
CHECK(net_connect(fd, (struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430c addrlen 不足 → INVAL");
CHECK(ns_rtos_q_count() == 0, "TC-NETSOCK-430d 参数非法不产生消息(不占 netTask)");
/* 成功:地址簿记 + 大端顺位 + 状态 CONNECTING */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
(void)memset(&in, 0, sizeof(in));
in.sin_family = NET_AF_INET;
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.200");
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430e connect 命令下发成功(异步,成败由中断裁定)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING,
"TC-NETSOCK-430f 本地状态转 CONNECTING");
CHECK(s_net_socks[fd].remote_port == 8888U,
"TC-NETSOCK-430g 对端端口转主机序存簿记");
CHECK(g_fx.des_ip[fd][0] == 192U && g_fx.des_ip[fd][1] == 168U &&
g_fx.des_ip[fd][2] == 1U && g_fx.des_ip[fd][3] == 200U,
"TC-NETSOCK-430h 目的 IP 按大端数组下发(set_des_ip 要的顺位,反了就连不上)");
CHECK(g_chip.set_port_calls == 1 && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-430i 源端口下发一次(SET_SOUR_PORT),目的端口另走 SET_DES_PORT=8888");
CHECK(g_fxchip.sock_events_calls == 1 && g_fxchip.glob_calls == 0,
"TC-NETSOCK-430j 清残留中断只清本 Socket(F-NET-7:原「再读清全局」会焊死别人)");
CHECK(ch395f_fake_log_find('v') < ch395f_fake_log_find('T') ||
ch395f_fake_log_find('v') < 0,
"TC-NETSOCK-430k 清事件排在 TCP_CONNECT 之前(TRAP-NET-12/-18 的两级预清)");
/* 命令级被拒(非 BUSY):必须回滚,不能停在 CONNECTING 干等 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fx.connect_rc[fd] = CH395F_ERR_VAL;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430l 芯片拒命令 → -1");
CHECK(g_chip.close_calls == 1,
"TC-NETSOCK-430m 被拒要按手册「再次连接需从 OPEN 重开」关掉 Socket");
CHECK(s_net_socks[fd].session_dead == 1U &&
s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-430n 命令级失败同样是本会话结束:置 session_dead+CLOSED(B2:否则挂起 recv 干等)");
CHECK(g_ns_error_count >= 1, "TC-NETSOCK-430o 被拒必留 [ERR]");
/* BUSY 只是命令仍在执行,不得判失败 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fx.connect_rc[fd] = CH395F_ERR_BUSY;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == 0,
"TC-NETSOCK-430p BUSY=芯片还在执行 → 维持原行为交给中断裁定(慢芯片不该被误杀)");
CHECK(s_net_socks[fd].state == NET_SOCK_STATE_CONNECTING && g_chip.close_calls == 0,
"TC-NETSOCK-430q BUSY 不回滚、不关 Socket");
/* open 三次失败 → BUSY,且不发 CONNECT */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
g_fake.open_rc[fd] = CH395F_ERR_USE;
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-430r open 三次失败 → BUSY");
CHECK(g_chip.open_calls == 3 && g_fxchip.connect_calls == 0,
"TC-NETSOCK-430s open 没成不发 TCP_CONNECT");
/* 非 TCP 流 / 未在用 → INVAL(netTask 侧再判一次) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-430t UDP 槽位不走 TCP connect");
CHECK(net_connect_locked(7, (const struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-430u 范围内但未在用 → INVAL(in_use 与控制块一起考;BADF 只考越界)");
CHECK(net_connect_locked(NET_MAX_SOCKETS, (const struct net_sockaddr *)&in,
(int)sizeof(in)) == -1, "TC-NETSOCK-430v 越界 → BADF");
reset_all();
}
/* TC-NETSOCK-440 UDP 数据报:sendto 分块 / recvfrom 剥 8 字节报文头 */
static void tc_440_udp_datagram(void)
{
static char big[2100];
char buf[64];
struct net_sockaddr_in in;
struct net_sockaddr *sa = (struct net_sockaddr *)&in;
int addrlen;
int fd;
bring_up();
CHECK(net_sendto(-1, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440a sendto 越界 → BADF");
CHECK(net_sendto(0, "x", 1, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-440b 未在用 → BADF");
CHECK(net_recvfrom(0, buf, sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-440c recvfrom 未在用 → BADF");
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_sendto(fd, NULL, 5, 0, NULL, 0) == -1 && net_get_errno() == NET_ERR_INVAL,
"TC-NETSOCK-440d sendto 参数非法 → INVAL");
CHECK(net_recvfrom(fd, NULL, 5, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-440e recvfrom buf 为空 → INVAL");
/* 带目的地址:先设 DesIP/DesPort 再写数据(不带上限的自旋实现,健康链路一次写完) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(8888);
in.sin_addr.s_addr = net_inet_addr("192.168.1.9");
CHECK(net_sendto(fd, "hello", 5, 0, sa, (int)sizeof(in)) == 5,
"TC-NETSOCK-440f sendto 成功返回字节数");
CHECK(g_fx.des_ip[fd][3] == 9U && g_fx.des_port[fd] == 8888U,
"TC-NETSOCK-440g 目的地址随本次发送下发(UDP 无连接,地址是每包带的)");
CHECK(g_chip.write_calls == 1 && s_net_socks[fd].send_bytes == 5U,
"TC-NETSOCK-440h 小包一次写完 + 统计");
/* 大报分块(仍受 NET_SEND_CHUNK_MAX 上界约束) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
ch395f_fake_set_sint_sticky((uint8_t)fd, CH395F_SINT_STAT_SENBUF_FREE);
CHECK(net_sendto(fd, big, 2100, 0, NULL, 0) == 2100, "TC-NETSOCK-440i 2100B 全部交付");
CHECK(g_chip.write_calls == 3, "TC-NETSOCK-440j UDP 也按 1024 上界分块(写穿 FIFO 的约束同 TCP)");
/* recvfrom:前 8 字节是芯片 UDP 头(长度/srcPort/srcIP),要剥掉再交出去 */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
ch395f_fake_push_recv_len((uint8_t)fd, 8U + 20U);
addrlen = (int)sizeof(in);
(void)memset(&in, 0, sizeof(in));
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, sa, &addrlen) == 20,
"TC-NETSOCK-440k 返回 20:报文头 8 字节被剥掉,不把头部当数据交给应用");
CHECK(addrlen == (int)sizeof(in) && in.sin_family == NET_AF_INET,
"TC-NETSOCK-440l 回填源地址结构与长度");
/* 无数据 + 非阻塞 → WOULDBLOCK(阻塞形态是自旋无预算,本套刻意不测,见交付说明) */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440m 非阻塞无数据 → WOULDBLOCK");
s_net_socks[fd].blocking = 0U;
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-440n 非阻塞属性同判据生效");
/* 短于 8 字节的残包:不剥头,整包交出去(钉当前实现的边界,不猜"应该怎么算") */
reset_all();
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
ch395f_fake_push_recv_len((uint8_t)fd, 5U);
CHECK(net_recvfrom(fd, buf, (int)sizeof(buf), 0, NULL, NULL) == 5,
"TC-NETSOCK-440o recv_len<8 时不剥头,整包返回(当前实现)");
reset_all();
}
/* TC-NETSOCK-450 netTask 直调入口的 NULL 守卫与委托 */
static void tc_450_direct_entries(void)
{
bring_up();
CHECK(net_send_sock(NULL, "x", 1) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450a net_send_sock(NULL) → BADF");
CHECK(net_recv_sock(NULL, (void *)"x", 1, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450b net_recv_sock(NULL) → BADF");
CHECK(net_close_sock(NULL) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-450c net_close_sock(NULL) → BADF");
/* netTask 内直调 send:挂起登记会被"执行者自等待"守卫拒 → 返回 -1(BUSY) */
reset_all();
bring_up();
{
int fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_cur_task = (TaskHandle_t)&g_ns_task_net;
g_peers.send_register_rc = -1; /* 复刻守卫:netTask 自己发的必被拒 */
CHECK(net_send_sock(&s_net_socks[fd], "hello", 5) == -1,
"TC-NETSOCK-450d netTask 内等不到缓冲时不登记挂起(没人能替它完成)→ -1");
CHECK(g_peers.send_reg_calls == 1,
"TC-NETSOCK-450e 确实走到了登记调用(被拒的是登记侧,不是本层没试)");
}
reset_all();
}
/* TC-NETSOCK-460 复位收口超时:芯片永远关不掉时上层必须有界退出(不得无限占用 netTask) */
static void tc_460_reset_sync_timeout_bound(void)
{
uint32_t t_before;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_keep_open[fd] = 1U; /* 复位机怎么努力芯片都不落回 CLOSED */
t_before = g_ns_tick;
CHECK(net_close(fd) == 0,
"TC-NETSOCK-460a 芯片关不掉也照样放行 close(fd 侧簿记必须继续收口)");
CHECK((g_ns_tick - t_before) <= 700U,
"TC-NETSOCK-460b 复位等待有 600ms 硬上限(无上限=netTask 被一颗坏芯片拖死)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-460c 复位超时必 [ERR]:这条 [ERR] 是唯一能发现它的信号");
CHECK(s_net_socks[fd].in_use == 0U,
"TC-NETSOCK-460d 超时也照常释放槽位(不因芯片状态卡住本地 fd 表)");
reset_all();
/* drop_conn 同一条超时线 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_keep_open[fd] = 1U;
t_before = g_ns_tick;
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 700U &&
g_ns_error_count >= 1,
"TC-NETSOCK-460e drop_conn 也走同一条有界收口并留痕");
reset_all();
}
/* TC-NETSOCK-470 发送途中的并发事件:断线要按短写交出已发量,不能假装全成功 */
static void tc_470_send_disconnect_midway(void)
{
static char big[2100];
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* 第一块写得进去,第二块等待期间对端断线(net_poll 处理 DISCONNECT 把状态打回 CLOSED) */
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_DISCONNECT;
g_ns_on_poll = ns_drop_conn_on_second_poll;
s_ns_poll_seen = 0;
rc = net_send(fd, big, 2100, 0);
CHECK(rc >= 0 && rc < 2100,
"TC-NETSOCK-470a 断线只截断剩余量:按已发数返回(短写),不是 -1 也不是谎报 2100");
CHECK((int)s_net_socks[fd].send_bytes == rc,
"TC-NETSOCK-470b 字节统计与实际交付一致(对端已收到的不能回收)");
CHECK(g_ns_error_count >= 1,
"TC-NETSOCK-470c 短写必 [ERR](静默截断曾让 UI 推流被对端误判为全程无字节)");
reset_all();
}
static void ns_run_s5(void)
{
tc_400_net_init_downlink();
tc_410_listen_locked();
tc_411_listen_public();
tc_420_accept_shapes();
tc_421_accept_public();
tc_430_connect();
tc_440_udp_datagram();
tc_450_direct_entries();
tc_460_reset_sync_timeout_bound();
tc_470_send_disconnect_midway();
}
+395
View File
@@ -0,0 +1,395 @@
/*
* 分片 6:边界残支(公开入口的越界/队满、直调入口的参数支、等待环里的其余出口)
*
* 这一片不为「覆盖率好看」而写:每条都对应一条**能被改坏**的分支——越界检查挪到
* 入队之后,调用方就会在 netTask 里等到一个没人处理的 BADF;短写留痕被删掉,
* 上层就会像 2026-09-21 那次 UI 推流一样「对端永久等待剩余字节」而日志全白。
* 唯一一条刻意**不是**真实时序的场景是 TC-NETSOCK-530(一次轮询之间被压了 1s),
* 它在真机上的对应形态是 netTask 被更高优先级任务压住——用大步进时基表达,
* 并在注释里写明它考的是「停滞超时先于自旋预算生效」这条判定顺序。
*
* 创建日期:2026-10-24
*/
/* TC-NETSOCK-500 六个公开入口的越界形态:BADF 必须在**入队之前**由调用方侧挡下 */
static void tc_500_public_bounds(void)
{
struct net_sockaddr_in in;
char buf[8];
int addrlen;
bring_up();
(void)memset(&in, 0, sizeof(in));
addrlen = (int)sizeof(in);
CHECK(net_send(NET_MAX_SOCKETS, buf, 4, 0) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500a net_send 越界 → BADF");
CHECK(net_recv(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500b net_recv 越界 → BADF");
CHECK(net_recv_to(NET_MAX_SOCKETS, buf, 4, 10U) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500c net_recv_to 越界 → BADF");
CHECK(net_close(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500d net_close 越界 → BADF");
CHECK(net_drop_conn(NET_MAX_SOCKETS) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-500e net_drop_conn 越界 → BADF");
CHECK(net_accept(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500f net_accept 越界 → BADF");
CHECK(net_connect(NET_MAX_SOCKETS, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500g net_connect 越界 → BADF");
CHECK(net_sendto(NET_MAX_SOCKETS, buf, 4, 0, NULL, 0) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500h net_sendto 越界 → BADF");
CHECK(net_recvfrom(NET_MAX_SOCKETS, buf, 4, NET_MSG_DONTWAIT, NULL, NULL) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-500i net_recvfrom 越界 → BADF");
CHECK(g_ns_put_calls == 0,
"TC-NETSOCK-500j 全部越界形态零入队(入队之后才挡 = 让 netTask 处理一个没主的请求)");
reset_all();
}
/* TC-NETSOCK-501 队满时每个公开入口都要报 BUSY(不能有的报、有的静默) */
static void tc_501_all_entries_busy_on_full_queue(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_accept(fd, NULL, NULL) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501a net_accept 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
(void)memset(&in, 0, sizeof(in));
CHECK(net_connect(fd, (struct net_sockaddr *)&in, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501b net_connect 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_recv_to(fd, (void *)"x", 1, 20U) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501c net_recv_to 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_recv(fd, (void *)"x", 1, NET_MSG_DONTWAIT) == -1 &&
net_get_errno() == NET_ERR_BUSY, "TC-NETSOCK-501d net_recv 队满 → BUSY");
ns_rtos_q_fill((int)NS_Q_CAP);
CHECK(net_send(fd, "x", 1, 0) == -1 && net_get_errno() == NET_ERR_BUSY,
"TC-NETSOCK-501e net_send 队满 → BUSY");
CHECK(g_ns_notify_calls == 0, "TC-NETSOCK-501f 队满路径零通知(调用方直接拿返回值走人)");
reset_all();
}
/* TC-NETSOCK-502 fd 表用尽:NOMEM 且不影响已建立的会话 */
static void tc_502_socket_table_full(void)
{
int i;
int fd;
bring_up();
for (i = 0; i < NET_MAX_SOCKETS; i++) {
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
if (fd < 0) {
break;
}
}
CHECK(i == NET_MAX_SOCKETS, "TC-NETSOCK-502a 8 个槽位全部可分配");
CHECK(net_socket(NET_AF_INET, NET_SOCK_STREAM) == -1 &&
net_get_errno() == NET_ERR_NOMEM, "TC-NETSOCK-502b 第 9 个 → NOMEM(不越界写表)");
s_net_socks[3].state = NET_SOCK_STATE_ESTABLISHED;
CHECK(net_get_sock(3) != NULL && net_listener_armed(3) == 1,
"TC-NETSOCK-502c 表满不影响已在用会话(alloc 失败只回 NOMEM,不动别人的槽位)");
reset_all();
}
/* TC-NETSOCK-503 net_wait_result 的两段式拾取:先零等待,已有通知就不必再睡 */
static void tc_503_zero_wait_pickup(void)
{
int fd;
int rc;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
/* 预置一条**已挂起**的通知(真机上=上一条消息的回执还没被读走),
* 公开入口应先零等待拾取它,而不是再睡一次(睡过头就是 实测踩过的坑) */
ns_rtos_post_notify((TaskHandle_t)&g_ns_task_caller, (uint32_t)(int)4);
g_ns_block_waits = 0;
rc = net_send(fd, "abcd", 4, 0);
CHECK(rc == 4, "TC-NETSOCK-503a 零等待就拾到通知值 → 直接返回结果");
CHECK(g_ns_block_waits == 0, "TC-NETSOCK-503b 已有通知时绝不进阻塞分支");
CHECK(g_peers.send_reg_calls == 0 && g_chip.write_calls == 0,
"TC-NETSOCK-503c 本次调用没有真的写芯片(拾到的是上一条消息的回执——这就是 实测踩过的形态)");
reset_all();
}
/* TC-NETSOCK-504 直调入口的参数支(这些分支从公开入口不可达,只能直接钉) */
static void tc_504_locked_entry_branches(void)
{
struct net_sockaddr_in in;
int fd;
bring_up();
(void)memset(&in, 0, sizeof(in));
in.sin_port = net_htons(80);
in.sin_addr.s_addr = net_inet_addr("10.1.1.1");
fd = net_socket(NET_AF_INET, NET_SOCK_STREAM);
CHECK(net_connect_locked(fd, NULL, (int)sizeof(in)) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504a connect_locked addr=NULL → INVAL");
CHECK(net_connect_locked(fd, (const struct net_sockaddr *)&in, 1) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504b connect_locked addrlen 不足 → INVAL");
s_net_socks[fd].in_use = 1U;
CHECK(net_send_locked(&s_net_socks[fd], "x", 1, NULL) == -1 &&
net_get_errno() == NET_ERR_NOTCONN, "TC-NETSOCK-504c 未建立的直调 send → NOTCONN");
CHECK(net_recv_locked(&s_net_socks[fd], (void *)"x", 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_WOULDBLOCK, "TC-NETSOCK-504d 直调 recv 无数据非阻塞 → WOULDBLOCK");
CHECK(net_recv_locked(&s_net_socks[fd], NULL, 1, NET_MSG_DONTWAIT, 0U, NULL) == -1 &&
net_get_errno() == NET_ERR_INVAL, "TC-NETSOCK-504e 直调 recv buf 空 → INVAL");
reset_all();
}
/* TC-NETSOCK-505 发送途中缓冲再也不腾出来:内层等待也要转挂起,并且末块不许登记 */
static void tc_505_inner_wait_and_last_chunk(void)
{
static char big[2100];
int fd;
int rc;
/* 第一块写得进、之后芯片不再报空闲 → 第二块的**内层**等待转挂起 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SENBUF_FREE; /* 只给一次空闲 */
rc = net_send_locked(&s_net_socks[fd], big, 2100,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == NET_PENDING_REGISTERED,
"TC-NETSOCK-505a 内层等待(写完一块后等发完)超预算也转挂起,不留 in-place 忙等");
CHECK(g_chip.write_calls == 2 && g_peers.last_snd_sent == 2048,
"TC-NETSOCK-505b 登记的进度=已写进芯片的字节数(1024+1024=2048),已发的绝不重发");
reset_all();
/* 末块:全部写完但芯片不回执 → 按完成返回,**不许**登记挂起
* (2026-10-14 台架回归 TC-NET-902 正是登记了却无人完成 → 永久挂死) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
rc = net_send_locked(&s_net_socks[fd], big, 1024,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 1024, "TC-NETSOCK-505c 单块恰好写完 → 返回 1024(完成)");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-505d 末块等待不得登记挂起(sent>=len 的守卫必须挡住)");
reset_all();
}
/* TC-NETSOCK-506 UDP sendto:等待腾出与短写留痕(与 TCP 同一套语义的另一份实现) */
static void tc_506_sendto_stall_and_short(void)
{
static char big[2100];
int fd;
int rc;
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
s_net_socks[fd].send_ready = 0U;
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
/* 每轮只走 1ms:先命中 NET_SEND_TIMEOUT_MS(1s) 之前不会有别出口 → 短写 */
g_ns_poll_tick_inc = 500U;
rc = net_sendto(fd, big, 2100, 0, NULL, 0);
CHECK(rc < 2100 && rc >= 0,
"TC-NETSOCK-506a sendto 停滞只拿到部分:按已发数返回(短写语义与 TCP 一致)");
CHECK(g_ns_error_count >= 2,
"TC-NETSOCK-506b 停滞 [ERR] + 短写 [ERR] 都要留(缺一就退化成静默截断)");
reset_all();
/* 写完一块后等发完的超时出口 */
bring_up();
fd = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SENBUF_FREE; /* 只给一次 */
g_ns_poll_tick_inc = 500U;
rc = net_sendto(fd, big, 2100, 0, NULL, 0);
CHECK(rc >= 1024 && rc < 2100,
"TC-NETSOCK-506c 第一块已交付、第二块等发完超时 → 返回 1024..2099 之间的实际值");
reset_all();
}
/* TC-NETSOCK-530 停滞超时优先于自旋预算:考判定顺序,不考真实时长 */
static void tc_530_stall_timeout_before_spin(void)
{
static char buf[64];
int fd;
int rc;
/* 一次轮询之间被压了 2s(真机形态:netTask 被更高优先级任务压住),
* 停滞超时(NET_SEND_TIMEOUT_MS=1s)必须**先于**自旋预算(2ms)命中:
* 生产代码里两条判定的先后顺序就是这条断言的内容。 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].send_ready = 0U;
g_ns_poll_tick_inc = 2000U;
rc = net_send_locked(&s_net_socks[fd], buf, (int)sizeof(buf),
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 0,
"TC-NETSOCK-530a 停滞超时按已发数 0 返回(短写形态,不是 -1)");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-530b 停滞超时优先:不进挂起登记(判定顺序被换掉的话这里会登记)");
CHECK(net_get_errno() == NET_ERR_TIMEDOUT,
"TC-NETSOCK-530c errno = TIMEDOUT(供调用方区分停滞与断线)");
reset_all();
}
/* TC-NETSOCK-535 已经写出去一块之后的超时收口(内层等待的另一条出口) */
static void tc_535_inner_timeout_after_chunk(void)
{
static char big[2100];
int fd;
int rc;
/* 第一块写得进(send_ready 初值 1),第二块的**内层**等待直接撞上停滞超时
* (判定顺序:先超时后自旋,所以这里收口在超时支而不是登记挂起) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_ns_poll_tick_inc = 2000U;
rc = net_send_locked(&s_net_socks[fd], big, 2100,
(TaskHandle_t)&g_ns_task_caller);
CHECK(rc == 1024, "TC-NETSOCK-535a 已交付的第一块照样交出(1024),剩余按短写处理");
CHECK(g_peers.send_reg_calls == 0,
"TC-NETSOCK-535b 停滞超时收口在登记挂起**之前**(换序就会登记一条永远完不成的挂起)");
CHECK(g_chip.write_calls == 1, "TC-NETSOCK-535c 只写了一次芯片:不得把已交付的块重发");
reset_all();
}
/* TC-NETSOCK-536 net_recv_to 的三条回传分支(-2 / -1+errno / >=0) */
static void tc_536_recv_to_three_exits(void)
{
char buf[16];
int fd;
/* >=0:正常拿到数据 */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].readable = 1U;
ch395f_fake_push_recv_len((uint8_t)fd, 12U);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 100U) == 12,
"TC-NETSOCK-536a 有数据 → 返回实际字节数(走 >=0 支)");
/* <0 且非 TIMEDOUT:分派侧 BADF → 恢复 errno 后返回 -1 */
reset_all();
bring_up();
(void)net_socket(NET_AF_INET, NET_SOCK_STREAM); /* 占掉 fd0,用未分配的 fd1 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(1, buf, (int)sizeof(buf), 100U) == -1 &&
net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-536b 真错误 → -1 且 errno 从通知值恢复(与 -2 严格区分)");
/* ==TIMEDOUT:已在 TC-NETSOCK-224 钉过 -2;这里补一条「-2 不改写 errno」 */
reset_all();
bring_up();
fd = mk_established(NET_SOCK_STREAM);
(void)net_set_errno(NET_OK);
ns_peers_arm_pending_done(fd, NET_ERR_TIMEDOUT);
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_recv_to(fd, buf, (int)sizeof(buf), 30U) == -2 &&
net_get_errno() == NET_OK,
"TC-NETSOCK-536c 超时只凭返回码判定,**不改写**错误码槽(本分支刻意不设 errno)");
reset_all();
}
/* TC-NETSOCK-537 fire_event:注册了回调才会被叫到(本层唯一的回调触发点在 SEND_OK) */
static void tc_537_event_callback_path(void)
{
int fd;
bring_up();
fd = mk_established(NET_SOCK_STREAM);
/* handle_send_event 是兄弟模块(net_events)的职责,本层只负责把中断交给它;
* 回调是否触发属 net_events 的断言面(其单测 TC-NETEV 已覆盖),此处只钉
* 「net_poll 不因回调未注册而跳过事件」。 */
s_net_socks[fd].event_cb = NULL;
g_ns_int_pin_set = 0;
ch395f_fake_push_glob((uint16_t)(1U << (fd + 4)));
g_fake.int_status[fd] = CH395F_SINT_STAT_SEND_OK;
CHECK(net_poll() == 1, "TC-NETSOCK-537 未注册回调也照常计事件(不因空指针跳过分发)");
reset_all();
}
/* TC-NETSOCK-540 close / drop_conn 的失败结果也要经通知值恢复精确 errno */
static void tc_540_negative_results(void)
{
net_msg_t msg;
int fd;
/* close 的负结果支:net_close_locked 自身恒返 0(复位没成也放行,见 TC-NETSOCK-320h),
* 所以 CLOSE 唯一的负值来自**分派侧**的 net_get_sock 判空 → 通知值 -2 →
* 调用方在自己的槽位里恢复成 BADF。范围合法但未分配的 fd 正好走这条。 */
bring_up();
fd = 3; /* 范围内、未分配 */
g_ns_cur_task = (TaskHandle_t)&g_ns_task_caller;
CHECK(net_close(fd) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-540a close 未分配的 fd → -1 且 errno 从通知值恢复成 BADF(不是笼统 NET_ERR)");
CHECK(net_drop_conn(fd) == -1 && net_get_errno() == NET_ERR_BADF,
"TC-NETSOCK-540a2 drop_conn 同一条负值恢复支");
reset_all();
/* drop_conn 同理:用未分配 fd 走分派侧 BADF 后再由调用方恢复 */
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_DROP_CONN;
msg.sockfd = 1;
msg.caller = (TaskHandle_t)&g_ns_task_net;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-540b drop_conn 分派侧 BADF 经通知值回传");
/* RECV 的 BADF 支(分派侧 net_get_sock 判空) */
reset_all();
bring_up();
(void)memset(&msg, 0, sizeof(msg));
msg.type = NET_MSG_RECV;
msg.sockfd = 4;
msg.buf = (void *)"x";
msg.len = 1;
msg.caller = (TaskHandle_t)&g_ns_task_net;
(void)ns_rtos_q_put_raw(&msg);
net_process_messages();
CHECK(g_ns_notify_last_val == (uint32_t)NET_ERR_BADF,
"TC-NETSOCK-540c recv 分派侧 BADF 同样经通知值回传");
reset_all();
}
/* TC-NETSOCK-545 未绑队列时的 sendto/recvfrom/getpeername 等纯本地入口 */
static void tc_545_local_entries_without_queue(void)
{
struct net_sockaddr_in in;
int addrlen = (int)sizeof(in);
reset_all();
CHECK(net_getsockname(0, (struct net_sockaddr *)&in, &addrlen) == -1 &&
net_get_errno() == NET_ERR_BADF, "TC-NETSOCK-545a 未初始化也能安全报 BADF(不崩)");
CHECK(net_getpeername(0, (struct net_sockaddr *)&in, &addrlen) == -1,
"TC-NETSOCK-545b getpeername 同上");
CHECK(net_inet_addr("1.2.3.4") != 0U, "TC-NETSOCK-545c 纯函数不依赖初始化");
reset_all();
}
static void ns_run_s6(void)
{
tc_500_public_bounds();
tc_501_all_entries_busy_on_full_queue();
tc_502_socket_table_full();
tc_503_zero_wait_pickup();
tc_504_locked_entry_branches();
tc_505_inner_wait_and_last_chunk();
tc_506_sendto_stall_and_short();
tc_530_stall_timeout_before_spin();
tc_535_inner_timeout_after_chunk();
tc_536_recv_to_three_exits();
tc_537_event_callback_path();
tc_540_negative_results();
tc_545_local_entries_without_queue();
}
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""NET 套接字核心(net_socket)的主机侧单元测试运行器(L0.5,纯标准库)。
做什么:
用本机 MSVC 编译 test/host/net_socket/net_socket_test.c(以 #include 纳入生产源
Drivers/BSP/NET/net_socket.c + 共享假芯片 test/host/common/ch395f_fake.c +
本目录的假内核 ns_rtos.c 与兄弟模块桩 ns_peers.c),在 PC 上离线验证:
跨上下文消息全分派、结果经任务通知值回传、每任务错误码槽(E-03)、
监听门控与 closed-with-readable、net_poll 的中断分派与"只读自己 SINT",
以及读写路径的短写 / 挂起登记 / EOF / 命令被拒回滚。
工具链与判定委托 test/host/common/unit_test_harness.py。
先跑常量闸(本 runner 独有的第二道检查,见 check_fake_chip_constants 注释),
不干净直接不编译——编译错误会把"假件值漂移"这件事埋得很深。
为什么要主机侧单测(而不是只用台架):
net_socket.c 是本产品**唯一的 SPI 串行化点**:所有 net_* 公开 API 把请求按值拷进
消息队列,由 netTask 串行执行,结果只能经 xTaskNotify 的 32 位通知值回来
(net_msg_t 是按值入队的,netTask 改自己那份副本,调用方永远看不到——AGENTS
模块约定明写"勿给 net_msg_t 加回传字段")。这条链的正确性取决于两个任务的
交错时序,真机上"通知丢了"表现为永久挂死并被看门狗复位,日志里只剩一条
[ERR];而错误码本身还按任务隔离(E-03),跨任务覆写只会让排障读到别人的码。
把队列/通知/时基做成显式量后,"登记了挂起却仍发即时回执"这种缺陷在 PC 上
一次就能钉死。
桩的边界(每一层为什么这么切):
- 真实头:net_socket.h / net_types.h / net_config.h / net_pending.h /
net_listener.h / net_events.h / cmsis_os2.h(只依赖 stdint+stddef,宿主可编)
→ 字段名、枚举取值、队列原型一改即编译期报错;
- 假芯片:test/host/common/ch395f_fake.h/.c(NET 域共享件,本批只做**纯新增**扩展:
net_socket.c 这一层才会调的初始化下发类 + 全局中断 0x19 + sock_reset 复位机 +
cmd_done 判据;既有宏/字段/函数一字未动);
- 假内核:本目录 ns_rtos.c(osMessageQueue 环形队列 + 任务通知 + 时基 +
临界区计数);**阻塞等待 = 调度点**:xTaskNotifyWait(portMAX_DELAY) 里以
netTask 身份跑一轮 net_process_messages(),于是单次公开 API 调用能跑完整条
入队→分派→通知值回传→调用方恢复 errno 的链;
- 兄弟模块桩:本目录 ns_peers.c(net_listener/net_pending/net_events 的边界契约
+ 调用记录)——这三个模块各有自己的主机单测(34/34/43 条),本套只测
net_socket.c 自己的决策,并把"该不该登记、登记哪个 reason"变成可断言面;
- main.h / FreeRTOS.h / task.h / dbg_log.h / test_config.h / net_latency_test.h
是本目录桩;刻意不定义 TEST_LATENCY_PROBE 与 NET_BENCH_CMD_LOG。
- 不需要 #undef s_addr:本套不 include ff.h(唯一会经 Windows 支路拉进 winsock.h
并把 s_addr 定义成 S_un.S_addr 的来源),真实 net_types.h 用的是自有的
struct net_in_addr 镜像类型,不受污染。
用法(仓库根目录):
python test/host/net_socket/run_unit_test.py
退出码:0=PASS / 1=测试失败(含常量闸不过)/ 2=环境错误。
设计依据:docs/网络/网络自愈架构设计.md §4.10、docs/测试说明/主机单测覆盖率口径.md §9
创建日期:2026-10-24
"""
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
# 顺序敏感:stub 必须最先(覆盖 ch395f.h / FreeRTOS.h / task.h / main.h / dbg_log.h);
# CMSIS_RTOS_V2 提供**真实** cmsis_os2.h;common 提供共享假芯片;NET 目录给真实
# net_socket.h / net_types.h / net_pending.h / net_listener.h / net_events.h;HERE 放本模块头。
BUILD = TestBuild(
name="NET Socket Tests",
src=os.path.join(HERE, "net_socket_test.c"),
include_dirs=[
os.path.join(HERE, "stub"),
os.path.join(ROOT, "Middlewares", "Third_Party", "FreeRTOS", "Source",
"CMSIS_RTOS_V2"),
os.path.join(ROOT, "test", "host", "common"),
os.path.join(ROOT, "Drivers", "BSP", "NET"),
HERE,
],
# /wd4244:生产源里 ch395f_set_*(sockfd, ...) 一律传 int fd 给 uint8_t 形参,
# ARMCC 不报、MSVC /W4 每条都报一遍(20+ 条)——那是宿主与原编译器的诊断差异,
# 不是本批要盯的问题,屏蔽以保持门禁输出可读(不改生产代码去"消警")。
extra_flags=["/wd4244", "/wd4100"],
summary_marker="=== NET Socket Tests:",
)
# ---------------------------------------------------------------------------
# 常量闸:真头与假件的**同名** CH395F_* 常量数值必须逐一对齐
# ---------------------------------------------------------------------------
#
# 为什么必须有这根闸(2026-10-24 实测漂移):假件头自称"值抄自真实头,勿顺手改",
# 实际 CH395F_SOCKET_OPEN 写成 0x01(真值 0x05)、CH395F_TCP_ESTABLISHED 写成 0x05
# (真值 0x04)。host 编译里生产码看到的也是假宏,宏对宏自洽 → 三套全绿也照不出来。
# 谁一旦写**数字**断言或从假件抄常量,就拿到了与真芯片不同的值。
#
# 为什么只能文本核对:两个头在 host 编译里是互斥的(stub/ch395f.h 转发到假件),
# 跨头 _Static_assert 不可能同时看到两份定义 → 编译期闸不存在。
#
# 只做同名常量的数值比对,且**只比对两边都是纯十六进制/十进制字面量的**:
# 宏链(CH395F_SEND_BUF_SIZE_BYTES 那种引用其它宏的)跳过——它们由结构而不是值约束。
_DEFINE_RE = re.compile(r"^\s*#\s*define\s+(CH395F_[A-Z0-9_]+)\s+([^\s/]+)")
_NUM_RE = re.compile(r"^(0[xX][0-9a-fA-F]+|[0-9]+)(U|UL|ULL)?$")
def parse_consts(path):
"""{CH395F_xxx: int},只收字面量取值的宏;文件不存在返回 None。"""
if not os.path.isfile(path):
return None
out = {}
try:
with open(path, "r", encoding="utf-8", errors="replace") as f:
for line in f:
m = _DEFINE_RE.match(line)
if not m:
continue
name, val = m.group(1), m.group(2)
if not _NUM_RE.match(val):
continue
digits = re.sub(r"[uUlL]+$", "", val)
try:
num = int(digits, 0)
except ValueError:
continue
out.setdefault(name, (num, line.rstrip("\n")))
except OSError:
return None
return out
def check_fake_chip_constants(verbose=True):
"""返回不一致的 (名字, 真头值, 假件值) 列表;两侧任一头读不到按环境问题报告。"""
real = parse_consts(os.path.join(ROOT, "Drivers", "BSP", "CH395F", "ch395f.h"))
fake = parse_consts(os.path.join(HERE, os.pardir, "common", "ch395f_fake.h"))
if real is None:
return None
if fake is None:
return None
drift = []
for name, (val, _line) in sorted(real.items()):
if name in fake and fake[name][0] != val:
drift.append((name, val, fake[name][0]))
if verbose and drift:
print("[DRIFT] 假件常量与真实 ch395f.h 不一致(host 编译里宏对宏自洽,照不出来):",
file=sys.stderr)
for name, rv, fv in drift:
print(" {} 真=0x{:02X} 假=0x{:02X}".format(name, rv, fv), file=sys.stderr)
return drift
if __name__ == "__main__":
drift = check_fake_chip_constants()
if drift is None:
print("[ENV] 常量闸无法读取真头或假件头(路径不对?)", file=sys.stderr)
sys.exit(2)
if drift:
print("[FAIL] 常量闸拦下 {} 处漂移:修 test/host/common/ch395f_fake.h 到真值"
"(出处见 Drivers/BSP/CH395F/ch395f.h 与 docs/芯片手册/CH395F.md)"
.format(len(drift)), file=sys.stderr)
sys.exit(1)
print("[OK] 常量闸:假件 CH395F_* 常量与真实 ch395f.h 同名项全对齐")
sys.exit(run_host_unit_test(BUILD))
+40
View File
@@ -0,0 +1,40 @@
/*
* 模块名称:主机侧单测桩 - FreeRTOS.h(net_socket 模块专用)
* 模块功能:给宿主一份与 FreeRTOS 语义等价的类型定义。net_socket.c 用到 TaskHandle_t /
* TickType_t / BaseType_t / eNotifyAction / pdTRUE / portMAX_DELAY,真实那份
* 经 portmacro.h 拉编译器与芯片寄存器,PC 上编不动。
* ⚠ 枚举成员与取值照抄 Middlewares/.../include/task.h:92-97 与 projdefs.h:44
* (eSetBits=0 起、pdTRUE=1、pdFALSE=0),改了就是换分母。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef FREERTOS_H
#define FREERTOS_H
#include <stddef.h>
#include <stdint.h>
typedef void *TaskHandle_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
typedef uint32_t TickType_t;
#define pdFALSE ((BaseType_t)0)
#define pdTRUE ((BaseType_t)1)
#define pdPASS pdTRUE
#define pdFAIL pdFALSE
#ifndef portMAX_DELAY
#define portMAX_DELAY ((TickType_t)0xFFFFFFFFU)
#endif
/* 与真实 task.h 同序(eSetBits..eSetValueWithoutOverwrite),取值不可重排 */
typedef enum {
eNoAction = 0,
eSetBits,
eIncrement,
eSetValueWithOverwrite,
eSetValueWithoutOverwrite
} eNotifyAction;
#endif /* FREERTOS_H */
+10
View File
@@ -0,0 +1,10 @@
/*
* 模块名称:主机侧单测桩 - ch395f.h(net_socket 模块入口)
* 模块功能:只做转发,真身在 test/host/common/ch395f_fake.h/.c(NET 域共享假芯片)。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef CH395F_H
#define CH395F_H
#include "ch395f_fake.h"
#endif /* CH395F_H */
+14
View File
@@ -0,0 +1,14 @@
/*
* 模块名称:主机侧单测桩 - cmsis_os.h(net_socket 模块专用)
* 模块功能:net_socket.c include 的是 CMSIS-RTOS **v1** 头,但它实际用的 API
* (osDelay / osMessageQueueGet/Put / osKernelGetState / osOK / osWaitForever)
* 全在 v2 头里。真实 v1 头会拉 FreeRTOS.h + task.h + cmsis_os2.h,
* 前两个由本目录桩接管,cmsis_os2.h 用**真实头**(只依赖 stdint/stddef,
* 宿主可编)——所以本桩就是一次转发,把 v2 的真实类型/枚举/原型接进来。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef CMSIS_OS_H
#define CMSIS_OS_H
#include "cmsis_os2.h"
#endif /* CMSIS_OS_H */
+23
View File
@@ -0,0 +1,23 @@
/*
* 模块名称:主机侧单测桩 - dbg_log.h(net_socket 模块专用)
* 模块功能:DBG_* 换成计数,风格与本目录既有 NET 域桩一致(net_listener / net_events
* 同名文件)。net_socket.c 有 11 条 DBG_ERROR + 6 条 DBG_INFO,其中
* 「短写必留痕」「版本门控拒置 bit3 必 [ERR]」「open 失败必 [ERR]」
* 「close 复位没成也放行仍要留痕」这四条本身就是可断言的契约。
* 本模块在 include 本头之前把 DBG_LINE_BUF_MAX 设为 128U(AGENTS 深调用链纪律),
* 桩不关心缓冲大小,只数条数。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef DBG_LOG_H
#define DBG_LOG_H
extern int g_ns_info_count;
extern int g_ns_error_count;
extern int g_ns_debug_count;
#define DBG_INFO(fmt, ...) do { (void)(fmt); g_ns_info_count++; } while (0)
#define DBG_DEBUG(fmt, ...) do { (void)(fmt); g_ns_debug_count++; } while (0)
#define DBG_ERROR(fmt, ...) do { (void)(fmt); g_ns_error_count++; } while (0)
#endif /* DBG_LOG_H */
+35
View File
@@ -0,0 +1,35 @@
/*
* 模块名称:主机侧单测桩 - main.h(net_socket 模块专用)
* 模块功能:net_socket.c 从真实 Inc/main.h 只要三样:HAL_GetTick(自旋/超时/挂起预算的
* 唯一致度源)、HAL_GPIO_ReadPin(轮询 CH395F INT#,低电平有效)、
* CH395_INT_GPIO_Port / CH395_INT_Pin(Inc/main.h:60-61,PE5)。
* 引脚名/号按真实头转抄(真值改了就编译期/断言期报错),GPIO_TypeDef 用不透明
* void*(本层不碰寄存器,只需类型能编)。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef MAIN_H
#define MAIN_H
#include <stdint.h>
typedef void GPIO_TypeDef;
typedef enum {
GPIO_PIN_RESET = 0,
GPIO_PIN_SET
} GPIO_PinState;
/* 转抄 Inc/main.h:60-61 */
#define CH395_INT_Pin 0x0020U /* GPIO_PIN_5 */
#define CH395_INT_GPIO_Port ((GPIO_TypeDef *)0)
uint32_t HAL_GetTick(void);
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
/* 每轮 net_poll 时基自走多少毫秒(默认 1ms:轮询本身就要花时间,自走才量得到
* NET_SEND_SPIN_MS 这类"有界自旋"预算;测试可置 0 造"时钟冻结"形态) */
extern uint32_t g_ns_poll_tick_inc;
extern uint32_t g_ns_poll_calls; /* net_poll 经 GPIO 读 INT# 的次数(也是跑飞保险计数) */
#endif /* MAIN_H */
@@ -0,0 +1,9 @@
/*
* 模块名称:主机侧单测桩 - net_latency_test.h(net_socket 模块专用)
* 模块功能:空壳,配合 TEST_LATENCY_PROBE 不定义即把 lat_* 调用全部条件编译掉。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef NET_LATENCY_TEST_H
#define NET_LATENCY_TEST_H
#endif /* NET_LATENCY_TEST_H */
+84
View File
@@ -0,0 +1,84 @@
/*
* 模块名称:假内核(net_socket 主机侧单测专用)
* 模块功能:给 net_socket.c 一份可在 PC 上跑的 FreeRTOS/CMSIS-RTOS2 薄壳——
* 消息队列(osMessageQueuePut/Get,真实 cmsis_os2.h 的原型)、
* 任务通知(xTaskNotify / xTaskNotifyWait)、当前任务句柄、时基、临界区计数。
*
* ⚠ 仅供 test/host/net_socket/ 使用,不参与 MCU 构建。
*
* 关键设计:**阻塞等待即调度点**。net_wait_result 的第二段
* xTaskNotifyWait(..., portMAX_DELAY) 在真机上是"睡到 netTask 回执";这里把这一次
* 等待实现成"调用测试装泵的 pump 钩子(= 以 netTask 身份跑一轮
* net_process_messages()),再拾取通知值"。于是单次 net_send()/net_recv() 调用就能
* 把"入队 → 分派 → 通知值回传 → 调用方恢复 errno"整条跨上下文链真实跑完,
* 而不是把中间环节假掉。
*
* 泵保险:pump 钩子最多跑 NS_PUMP_MAX 轮,超了直接 exit(2)——否则一条"该通知却没通知"
* 的缺陷会让门禁挂死而不是报错(本仓假 osDelay 的跑飞保险同一条纪律)。
*
* 创建日期:2026-10-24
*/
#ifndef NS_RTOS_H
#define NS_RTOS_H
#include "FreeRTOS.h"
#include "cmsis_os2.h"
#include "main.h" /* 桩:HAL_GetTick / HAL_GPIO_ReadPin / CH395_INT_* */
#define NS_Q_CAP 8u
#define NS_PUMP_MAX 50
/* 两个假任务:调用方任务 + netTask(句柄取地址即唯一) */
extern int g_ns_task_caller;
extern int g_ns_task_net;
/* 当前任务句柄(测试直接读写:切换"我是调用方"还是"我是 netTask") */
extern TaskHandle_t g_ns_cur_task;
/* 阻塞等待时跑的泵(测试装:通常 = 跑一轮 net_process_messages) */
typedef void (*ns_pump_fn)(void);
extern ns_pump_fn g_ns_pump;
extern int g_ns_pump_calls; /* 泵被驱动的次数 */
extern int g_ns_block_waits; /* 真正走到"阻塞分支"的次数 */
/* 通知/队列观测面 */
extern int g_ns_notify_calls; /* xTaskNotify 总次数 */
extern uint32_t g_ns_notify_last_val;
extern TaskHandle_t g_ns_notify_last_to;
extern int g_ns_put_calls;
extern int g_ns_get_calls;
extern int g_ns_crit_enter;
extern int g_ns_crit_exit;
extern uint32_t g_ns_tick;
void ns_rtos_reset(void);
void ns_rtos_q_flush(void);
int ns_rtos_q_count(void);
void ns_rtos_q_fill(int n); /* 预灌 n 条占位消息(把队列灌到满,测 BUSY 分支) */
/* 测试用的显式通知投递(不经队列,直接给某任务置通知值) */
void ns_rtos_post_notify(TaskHandle_t to, uint32_t v);
/* 手工投一条原始消息(绕过公开包装器:造未知 type、造 netTask 自调等异常形态) */
int ns_rtos_q_put_raw(const net_msg_t *msg);
/* —— 下面是给桩头对接的实现(原型与真实头一致) —— */
osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id, const void *msg_ptr,
uint8_t msg_prio, uint32_t timeout);
osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void *msg_ptr,
uint8_t *msg_prio, uint32_t timeout);
osKernelState_t osKernelGetState(void);
osStatus_t osDelay(uint32_t ticks);
/* 每轮 net_poll(= 一次 INT# 读取)都调一下测试回调:用来在"等待发送缓冲/等待数据"
* 的循环途中投递并发事件(对端断线、芯片突然可读),而不必自己发明时间 API */
typedef void (*ns_poll_hook_fn)(void);
extern ns_poll_hook_fn g_ns_on_poll;
/* CH395F INT# 电平(1=SET 无中断,net_poll 走短路分支) */
extern int g_ns_int_pin_set;
/* 内核状态可注入(net_errno_slot 靠它判"内核没跑起来"的退化分支) */
extern osKernelState_t g_ns_kernel_state;
#endif /* NS_RTOS_H */
+32
View File
@@ -0,0 +1,32 @@
/*
* 模块名称:主机侧单测桩 - task.h(net_socket 模块专用)
* 模块功能:声明 net_socket.c 用到的三件套 + 临界区宏。原型与真实
* Middlewares/.../include/task.h 逐字一致(返回值/参数顺序都不许改),
* 实现在 ns_rtos.c(假内核:任务通知值 + 当前任务句柄可切换)。
* taskENTER_CRITICAL/EXIT_CRITICAL 记计数——临界区只存在于本层槽位分配,
* 计数能断言"只在占槽时进临界区、且成对退出"。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef TASK_H
#define TASK_H
#include "FreeRTOS.h"
TaskHandle_t xTaskGetCurrentTaskHandle(void);
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue,
eNotifyAction eAction);
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait);
uint32_t xTaskGetTickCount(void);
int ns_os_crit_depth(void);
void ns_os_enter_critical(void);
void ns_os_exit_critical(void);
#define taskENTER_CRITICAL() ns_os_enter_critical()
#define taskEXIT_CRITICAL() ns_os_exit_critical()
#endif /* TASK_H */
+11
View File
@@ -0,0 +1,11 @@
/*
* 模块名称:主机侧单测桩 - test_config.h(net_socket 模块专用)
* 模块功能:空壳。net_socket.c 只在 TEST_LATENCY_PROBE 打开时才用延迟探针,
* 主机单测刻意**不开**(探针要写真实时间/真实周期计数,不是被测对象);
* 也不定义 NET_BENCH_CMD_LOG(压测命令日志同理,见 net_events 同名说明)。
* ⚠ 仅供主机侧单测使用,不参与 MCU 构建。
* 创建日期:2026-10-24
*/
#ifndef TEST_CONFIG_H
#define TEST_CONFIG_H
#endif /* TEST_CONFIG_H */