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

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

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

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

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

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

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

531 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:NET 层事件分发与周期对账(net_events)主机侧单元测试
* 模块功能:在 PC 上离线验证 Drivers/BSP/NET/net_events.c 的五个事件入口与周期对账器:
* 对端六元组解析、**陈旧中断防误判**(芯片说还在连接就不许拆会话)、疑似
* 误报延后交给对账器、超时把会话标成异常释放(喂给 net_pending 收尾报
* CONNRESET 的那条输入)、掉线自愈自接,以及对账器的 100ms 周期闸门、
* 1000ms 冷却、五种不一致形态的稳定计数(死亡要连观 4 次、重开监听 2 次、
* 自接 1 次)与 U11 的 CLOSED->LISTENING 重同步。
*
* 被测对象:真实源文件 ../../../Drivers/BSP/NET/net_events.c(#include 原样纳入)。
* net_events.h / net_types.h / net_pending.h / net_listener.h 全用**真实头**
* (net_event_cb_t 回调签名、NET_EVENT_* / NET_SOCK_STATE_* /
* net_listener_settle_t / NET_LISTENER_REQ_* 一改即编译期报错)。假件:
* 共享假 CH395F(本批补齐对端六元组与链路变更计数)、可控 socket 表
* (net_sock_raw 对未在用槽位也返回项,与真实语义一致)、**可观测的事件
* 回调**(记 fd / 事件 / arg)、记录型 net_listener_settle/request 与
* net_pending_cancel / net_send_pending_cancel、可控时基、dbg_log 计数。
*
* 为什么要主机侧单测(而不是只靠台架):
* 1. CH395F 的中断是**整体读即清**且单连接 socket 复用同一个号,"迟到的 DISCONNECT"
* 与"丢了 CONNECT 的已连接"在真机上都会发生,却无法按需复现。误判的代价直接落在
* 现场设备上:把还活着的会话拆掉(104 主站看到莫名掉线),或让 socket 永远停在
* 已连接假象里不再重开。
* 2. 对账器的**稳定计数**(死亡 4 次、重开监听 2 次、自接 1 次)本质是"别拿一次
* 采样就把连接折腾死"。真机一次采样 100ms,凑够 4 次要 400ms,且必须一路稳定;
* 台架上没法按毫秒喂,也看不出为什么"偶尔不重开"。主机侧把时基做成显式量后,
* 第 3 次不该动、第 4 次该动、之后 1000ms 内不许再动,全部可精确断言。
* 3. handle_timeout_event 会置 released_abnormal=1,而 net_pending 收尾时正是靠
* 这个标志把挂起 recv 报成 CONNRESET 而不是 0(见 test/host/net_pending 的
* TC-NETPEND-014)。两个模块之间这条**跨文件因果**,只有两边都能离线跑时才可测。
*
* 覆盖点:TC-NETEV-001..006 CONNECT;010..014 DISCONNECT(含两条陈旧中断防误判);
* 020/021 RECV 与 SEND;030..032 TIMEOUT;040..042 掉线自愈;050..058 周期对账
* (周期闸门、链路变更留痕、五种不一致形态、稳定计数、冷却、in_use 兜底)。
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-23
*/
#include <stdio.h>
#include <string.h>
#include "net_events.h"
#include "net_listener.h"
#include "net_pending.h"
#include "net_socket.h"
#include "ch395f.h"
#include "ch395f_fake.c"
#include "net_events.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)
/* ===================== dbg_log 计数 ===================== */
int g_nev_info_count;
int g_nev_error_count;
int g_nev_debug_count;
/* ===================== 可观测的事件回调 ===================== */
static int g_ev_n;
static int g_ev_fd[16];
static int g_ev_kind[16];
static void *g_ev_arg[16];
static int g_ev_token;
static void ev_cb(int sockfd, net_event_t event, void *arg)
{
if (g_ev_n < (int)(sizeof(g_ev_fd) / sizeof(g_ev_fd[0]))) {
g_ev_fd[g_ev_n] = sockfd;
g_ev_kind[g_ev_n] = (int)event;
g_ev_arg[g_ev_n] = arg;
}
g_ev_n++;
}
/* ===================== 记录型监听与挂起取消 ===================== */
static int g_settle_n;
static int g_settle_sock;
static int g_settle_level;
static int g_req_n;
static int g_req_sock;
static int g_req_reason;
static int g_pcan_n;
static int g_scan_n;
static int g_pcan_err;
void net_listener_settle(int sockfd, net_listener_settle_t level)
{
g_settle_n++;
g_settle_sock = sockfd;
g_settle_level = (int)level;
}
void net_listener_request(int sockfd, net_listener_req_t reason)
{
g_req_n++;
g_req_sock = sockfd;
g_req_reason = (int)reason;
}
void net_listener_clear(int sockfd) { (void)sockfd; }
void net_listener_tick(void) { }
void net_pending_cancel(int sockfd, int err)
{
(void)sockfd;
g_pcan_n++;
g_pcan_err = err;
}
void net_send_pending_cancel(int sockfd, int err)
{
(void)sockfd;
(void)err;
g_scan_n++;
}
/* ===================== 可控时基、errno 与 socket 表 ===================== */
static uint32_t g_now;
static int g_errno_last;
uint32_t HAL_GetTick(void) { return g_now; }
int net_set_errno(int err) { g_errno_last = err; return err; }
uint32_t xTaskGetTickCount(void) { return g_now; }
static net_sock_t g_tbl[NET_MAX_SOCKETS];
net_sock_t *net_sock_raw(int sockfd)
{
if ((sockfd < 0) || (sockfd >= NET_MAX_SOCKETS)) {
return NULL;
}
/* 与真实语义一致:对账循环必须能读到 in_use == 0 的槽位,所以这里不给 NULL */
return &g_tbl[sockfd];
}
net_sock_t *net_get_sock(int sockfd)
{
net_sock_t *p = net_sock_raw(sockfd);
if ((p == NULL) || (p->in_use == 0u)) {
return NULL;
}
return p;
}
static void reset_all(void)
{
int i;
(void)memset(g_tbl, 0, sizeof(g_tbl));
g_now = 1000u;
g_errno_last = 0;
g_ev_n = 0;
g_settle_n = 0;
g_settle_sock = -1;
g_settle_level = -1;
g_req_n = 0;
g_req_sock = -1;
g_req_reason = -1;
g_pcan_n = 0;
g_scan_n = 0;
g_pcan_err = 0;
g_nev_info_count = 0;
g_nev_error_count = 0;
g_nev_debug_count = 0;
ch395f_fake_reset();
(void)i;
}
/* 一个"带应用回调的在用槽位",ch395_sock 与数组下标同号 */
static net_sock_t *mk(int fd, net_sock_state_t st, uint16_t local_port)
{
net_sock_t *p = &g_tbl[fd];
p->in_use = 1u;
p->ch395_sock = (uint8_t)fd;
p->type = NET_SOCK_STREAM;
p->state = st;
p->auto_relisten = 1u;
p->local_port = local_port;
p->event_cb = ev_cb;
p->event_arg = (void *)&g_ev_token;
return p;
}
/* ===================== CONNECT ===================== */
static void tc_connect(void)
{
net_sock_t *p;
printf("-- TC-NETEV-001..006 CONNECT --\n");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
ch395f_fake_set_peer(0, 192u, 168u, 1u, 50u, 51234u);
handle_connect_event(p);
/* remote_ip 是**低字节在前**拼的(remote[0] 落在最低 8 位),与网络序相反:
* 192.168.1.50 -> 0x3201A8C0。这是既有实现的字节序,钉住它是防止有人"顺手
* 改成大端"导致所有按 IP 比对/白名单的判断静默错位。 */
CHECK((p->remote_ip_arr[0] == 192u) && (p->remote_ip_arr[3] == 50u) &&
(p->remote_ip == ((192u) | (168u << 8) | (1u << 16) | (50u << 24))) &&
(p->remote_port == 51234u),
"TC-NETEV-001 peer tuple parsing: ip assembled little-endian into 32 bits, port from bytes 5/6");
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->session_dead == 0u) &&
(p->lost_conn_seen == 0u) && (p->send_ready == 1u),
"TC-NETEV-002 a listened socket becomes established after self-accept and is writable at once");
CHECK((g_chip.keepalive_calls == 1) && (g_chip.last_keepalive == 1u) &&
(g_chip.int_status_calls == 1),
"TC-NETEV-003 on connect, enable keepalive and clear the pending bit once (read clears)");
CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) &&
(g_ev_fd[0] == 0) && (g_ev_arg[0] == (void *)&g_ev_token),
"TC-NETEV-004 the event calls back into the app with fd and the caller arg");
reset_all();
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
ch395f_fake_set_peer(0, 10u, 0u, 0u, 1u, 2404u);
handle_connect_event(p);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->send_ready == 0u) &&
(g_chip.keepalive_calls == 1) && (g_ev_n == 1),
"TC-NETEV-005 the active-connect branch also becomes established but does NOT touch send_ready (differs from self-accept)");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
handle_connect_event(p);
CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED),
"TC-NETEV-006 a CONNECT interrupt in any other state is ignored (never invent a session)");
}
/* ===================== DISCONNECT ===================== */
static void tc_disconnect(void)
{
net_sock_t *p;
printf("-- TC-NETEV-010..014 DISCONNECT --\n");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
g_fake.status_byte1[0] = CH395F_TCP_ESTABLISHED;
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) &&
(g_nev_error_count == 1),
"TC-NETEV-010 the chip still says connecting: ignore this stale DISCONNECT (never tear down a live session)");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
g_fake.status_byte1[0] = CH395F_TCP_CLOSED;
ch395f_fake_push_recv_len(0, 9u); /* 还有 9 字节没读走 */
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->lost_conn_seen == 1u) &&
(g_ev_n == 0) && (g_settle_n == 0),
"TC-NETEV-011 suspected false alarm (dropped before any data): defer to the reconciler instead of tearing down");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
p->remote_port = 51234u;
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
g_fake.status_byte1[0] = CH395F_TCP_CLOSED;
handle_disconnect_event(p);
CHECK((g_settle_n == 1) && (g_settle_level == (int)NET_LISTENER_SETTLE_GRACEFUL) &&
(p->session_dead == 1u) && (p->released_abnormal == 0u) &&
(p->last_peer_port == 51234u) &&
(p->state == NET_SOCK_STATE_CLOSED) &&
(g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
"TC-NETEV-012 normal disconnect: graceful settle + mark session dead + remember old port + post event");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
g_fake.status_byte0[0] = CH395F_SOCKET_OPEN;
g_fake.status_byte1[0] = CH395F_TCP_LISTEN;
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) &&
(g_nev_info_count == 1) && (g_nev_error_count == 0),
"TC-NETEV-013 listen still alive: a stale listen DISCONNECT is only logged, not reported as ERROR");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
g_fake.status_byte1[0] = CH395F_TCP_CLOSED; /* 芯片说不在 LISTEN */
handle_disconnect_event(p);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_ev_n == 0) && (g_req_n == 0),
"TC-NETEV-013b even a lost listener is only logged: reopening belongs to the reconciler, the event layer must not overreach");
reset_all();
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
handle_disconnect_event(p);
CHECK((g_nev_error_count == 1) && (g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
"TC-NETEV-014 DISCONNECT in an unusual state: report ERROR but still notify the application");
}
/* ===================== RECV / SEND / TIMEOUT ===================== */
static void tc_recv_send_timeout(void)
{
net_sock_t *p;
printf("-- TC-NETEV-020/021/030..032 --\n");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
handle_recv_event(p);
CHECK((p->readable == 1u) && (g_ev_kind[0] == (int)NET_EVENT_DATA_RECEIVED),
"TC-NETEV-020 a RECV event sets readable and notifies the application");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
p->send_ready = 1u;
handle_send_event(p);
CHECK((g_ev_kind[0] == (int)NET_EVENT_SEND_COMPLETE) && (p->send_ready == 1u),
"TC-NETEV-021 SEND completion only posts the event; the fields stay owned by the write path");
reset_all();
p = mk(0, NET_SOCK_STATE_CONNECTING, 0u);
handle_timeout_event(p);
CHECK((p->state == NET_SOCK_STATE_CREATED) && (g_nev_error_count == 1) &&
(g_ev_kind[0] == (int)NET_EVENT_TIMEOUT),
"TC-NETEV-030 connect timeout: fall back to CREATED and notify the application");
reset_all();
p = mk(0, NET_SOCK_STATE_ESTABLISHED, 2404u);
p->remote_port = 777u;
handle_timeout_event(p);
CHECK((g_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE) &&
(p->released_abnormal == 1u) && (p->session_dead == 1u) &&
(p->last_peer_port == 777u) && (p->state == NET_SOCK_STATE_CLOSED),
"TC-NETEV-031 session timeout: quarantine-level settle + mark released abnormally (net_pending reports CONNRESET from it)");
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
handle_timeout_event(p);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_nev_error_count == 1) &&
(g_ev_kind[0] == (int)NET_EVENT_TIMEOUT),
"TC-NETEV-032 listen timeout is only logged: state unchanged but waiters are still notified");
}
/* ===================== 掉线自愈 ===================== */
static void tc_lost_probe(void)
{
net_sock_t *p;
printf("-- TC-NETEV-040..042 lost-CONNECT probe --\n");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
p->lost_conn_seen = 1u;
g_fake.cached_tcp[0] = CH395F_TCP_CLOSED;
net_lost_connect_probe(p);
CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0),
"TC-NETEV-040 the chip is not established: only clear the flag, do not create a session on our own");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
p->lost_conn_seen = 1u;
p->last_peer_port = 51234u;
g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED;
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 51234u);
net_lost_connect_probe(p);
CHECK((p->lost_conn_seen == 0u) && (g_ev_n == 0),
"TC-NETEV-041 peer port identical to last time: assume we missed CONNECT, do not accept a second time");
/* 自愈的适用面是软件还写着 LISTENING、芯片其实已经连上——accept 分支只认
* LISTENING/CONNECTING。若软件态已被别处改成 CLOSED,本函数打完 INFO 后什么都
* 不会发生(现状留痕,见第二断言)。 */
reset_all();
p = mk(0, NET_SOCK_STATE_LISTENING, 2404u);
p->lost_conn_seen = 1u;
p->last_peer_port = 1u;
g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED;
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u);
net_lost_connect_probe(p);
CHECK((g_ev_n == 1) && (g_ev_kind[0] == (int)NET_EVENT_CONNECTED) &&
(p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 55555u) &&
(p->lost_conn_seen == 0u),
"TC-NETEV-042 self-heal after a lost CONNECT interrupt: make up one accept so the session is not stranded forever");
reset_all();
p = mk(0, NET_SOCK_STATE_CLOSED, 2404u);
g_fake.cached_tcp[0] = CH395F_TCP_ESTABLISHED;
ch395f_fake_set_peer(0, 10u, 0u, 0u, 9u, 55555u);
net_lost_connect_probe(p);
CHECK((g_ev_n == 0) && (p->state == NET_SOCK_STATE_CLOSED),
"TC-NETEV-042b current state: self-heal cannot accept once the software side is CLOSED (logged only, no invented session)");
}
/* ===================== 周期对账 ===================== */
static void recon_step(uint32_t now)
{
g_now = now;
net_reconcile_tick();
}
static void tc_reconcile(void)
{
net_sock_t *p;
int infos;
int c;
printf("-- TC-NETEV-050..058 reconcile --\n");
/* 对账器里的 s_last / s_bad[] / s_cool[] 是函数内 static(外部够不着),
* 所以每个形态**各占一个 fd**、时间统一单调前进,互不串扰。 */
reset_all();
infos = g_nev_info_count;
recon_step(1000u); /* priming:链路计数记基线 */
CHECK(g_nev_info_count == infos,
"TC-NETEV-051a the first pass after power-up logs nothing for the link counter (the 0xFFFF sentinel only records the baseline)");
c = g_chip.cached_calls;
recon_step(1000u); /* 同一时刻再调一次 */
CHECK(g_chip.cached_calls == c, "TC-NETEV-050 100ms period gate: do not query the chip twice at the same instant");
reset_all();
p = mk(1, NET_SOCK_STATE_ESTABLISHED, 2404u);
g_fake.cached_open[1] = CH395F_SOCKET_CLOSED;
g_fake.cached_tcp[1] = CH395F_TCP_CLOSED;
infos = g_nev_info_count;
recon_step(1100u);
recon_step(1200u);
recon_step(1300u);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_ev_n == 0) &&
(g_nev_info_count == infos),
"TC-NETEV-052 a dead session needs 4 consecutive observations before acting: the first three change nothing");
recon_step(1400u);
CHECK((p->state == NET_SOCK_STATE_CLOSED) && (p->session_dead == 1u) &&
(g_ev_kind[0] == (int)NET_EVENT_DISCONNECTED),
"TC-NETEV-052b once the 4th observation is stable, run the disconnect teardown");
p->state = NET_SOCK_STATE_ESTABLISHED; /* 假装又活了,验证冷却 */
recon_step(1500u);
recon_step(1600u);
recon_step(1700u);
recon_step(1800u);
CHECK(g_ev_n == 1, "TC-NETEV-052c no repeat action inside the 1000ms cooldown after acting (even when the stable count is met)");
reset_all();
p = mk(2, NET_SOCK_STATE_ESTABLISHED, 2404u);
g_fake.cached_open[2] = CH395F_SOCKET_OPEN;
g_fake.cached_tcp[2] = CH395F_TCP_LISTEN; /* 芯片自己重开监听了,却没发 DISCONNECT */
recon_step(2900u);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (g_pcan_n == 0),
"TC-NETEV-053 the chip reopened the listener behind our back: needs 2 stable passes, the first one only accumulates");
recon_step(3000u);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (g_pcan_n == 1) &&
(g_scan_n == 1) && (g_pcan_err == NET_ERR_CONNRESET),
"TC-NETEV-053b second stable pass: resync to listen and cancel both pending waits (otherwise a task would wait forever)");
reset_all();
p = mk(3, NET_SOCK_STATE_LISTENING, 2404u);
p->last_peer_port = 1u;
g_fake.cached_tcp[3] = CH395F_TCP_ESTABLISHED; /* 连上了却没 CONNECT 中断 */
ch395f_fake_set_peer(3, 172u, 16u, 0u, 5u, 40000u);
recon_step(4100u);
CHECK((p->state == NET_SOCK_STATE_ESTABLISHED) && (p->remote_port == 40000u) &&
(g_ev_kind[0] == (int)NET_EVENT_CONNECTED),
"TC-NETEV-054 connect completed but the interrupt was lost: a single pass is enough to make up the accept");
reset_all();
p = mk(4, NET_SOCK_STATE_LISTENING, 2404u);
g_fake.cached_open[4] = CH395F_SOCKET_CLOSED; /* 监听没了 */
g_fake.cached_tcp[4] = CH395F_TCP_CLOSED;
recon_step(5200u);
recon_step(5300u);
CHECK((g_req_n == 1) && (g_req_sock == 4) &&
(g_req_reason == (int)NET_LISTENER_REQ_RECON),
"TC-NETEV-055 listener lost: request a reopen after 2 stable passes (the event layer does not open it itself)");
reset_all();
p = mk(5, NET_SOCK_STATE_CLOSED, 2404u);
g_fake.cached_open[5] = CH395F_SOCKET_OPEN; /* 芯片还在 LISTEN */
g_fake.cached_tcp[5] = CH395F_TCP_LISTEN;
p->remote_port = 999u;
p->lost_conn_seen = 1u;
recon_step(6400u);
CHECK((p->state == NET_SOCK_STATE_LISTENING) && (p->last_peer_port == 999u) &&
(p->lost_conn_seen == 0u) && (g_req_n == 0),
"TC-NETEV-056 U11 resync: software CLOSED while the chip is LISTEN -> follow the chip, no redundant reopen");
reset_all();
p = mk(6, NET_SOCK_STATE_CLOSED, 2404u);
g_fake.cached_open[6] = CH395F_SOCKET_CLOSED;
recon_step(7500u);
recon_step(7600u);
CHECK((g_req_n == 1) && (g_req_sock == 6),
"TC-NETEV-057 software CLOSED and the chip is not listening either: request a reopen (the listener layer rebuilds it)");
reset_all();
(void)memset(&g_tbl[7], 0, sizeof(g_tbl[7])); /* 槽位不在用 */
infos = g_nev_info_count;
recon_step(8700u);
CHECK(g_nev_info_count == infos, "TC-NETEV-058 unused slots are skipped outright (no action at all)");
/* 链路日志:s_link_last 初值 0xFFFF 是哨兵——第一次看到计数只记基线、不打日志
* (否则每次上电都会冒一条"链路 up"的假事件);此后计数一变就必须留痕。
* 本文件开头那次 priming 就是"基线"那一趟(TC-NETEV-051a),这里是"变化"那一趟。 */
reset_all();
g_fake.link_change = 7u;
infos = g_nev_info_count;
recon_step(9800u);
CHECK(g_nev_info_count == (infos + 1),
"TC-NETEV-051b any change of the link-change counter is logged (the baseline pass is already covered by 051a)");
}
int main(void)
{
printf("=== NET Events Tests ===\n");
tc_connect();
tc_disconnect();
tc_recv_send_timeout();
tc_lost_probe();
tc_reconcile();
printf("\n=== NET Events Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}