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