Files
STM32F4-Base/test/host/net_events/net_events_test.c
T
wandering1 59e6d77224 test(NET): net_events 模块(34 条,覆盖 97.8%),NET 三块收官
假 CH395F 的第三批、也是 NET 域最后一块。net_events.c 覆盖 175/179 = 97.8%。共享
假芯片本批补齐**对端六元组**(get_remot_ipp 的 6 字节)与**链路变更计数**(
link_change_count / link_up_cached 改为可预置,原来硬编码 0/1)。

钉住的契约(都是真机上会发生、却不能按需复现的那类):
- 对端六元组解析:remote_ip 是**低字节在前**拼的(192.168.1.50 -> 0x3201A8C0,与
  网络序相反)。钉住它是防止有人"顺手改成大端",让所有按 IP 比对/白名单静默错位。
- 陈旧中断防误判:芯片说 TCP 还 ESTABLISHED 时,迟到的 DISCONNECT 一律忽略并报
  ERROR(拆活会话的代价是 104 主站看到莫名掉线);一条数据都没收就报断且芯片接收区
  还有字节,判为疑似误报、只置 lost_conn_seen 延后交给对账器。
- 正常断开:优雅级沉降 + session_dead + last_peer_port 记旧端口 + 转 CLOSED + 发事件。
- 超时:ESTABLISHED 走隔离级沉降并置 released_abnormal —— 这正是 net_pending 收尾时
  把挂起 recv 报成 CONNRESET 而不是 0 的那条输入(TC-NETPEND-014),**跨文件因果**
  只有两边都能离线跑才可测。
- 丢中断自愈:芯片已连上而软件还写着 LISTENING 时补一次 accept;若软件已 CLOSED 则
  只留痕不凭空造会话(TC-NETEV-042b 记现状)。
- 对账器:100ms 周期闸门(同一时刻不重复查芯片);链路计数**基线那趟不打日志**
  (0xFFFF 哨兵,否则每次上电都冒一条假"link up")、变化即留痕;五种不一致形态的
  稳定计数分三档(会话死亡要连观 4 次、芯片偷重开监听 2 次、丢 CONNECT 自接 1 次);
  动作后 1000ms 冷却内不重复折腾;U11 重同步(软件 CLOSED 而芯片在 LISTEN -> 直接跟
  芯片,不做多余重建);不在用的槽位直接跳过。

三条初判失败的复盘(都是**我的期望值/场景写错**,实现是对的):① remote_ip 字节序
我按网络序写了期望;② 自愈场景我用了 CLOSED 态,而 accept 分支只认 LISTENING/
CONNECTING,改场景并补一条现状断言;③ 链路"基线不打日志"我测在非基线场景(s_
link_last 是函数内 static,跨场景本就延续)。对账器的 s_last/s_bad[]/s_cool[] 都是
函数内 static(外部够不着),因此每个形态**各占一个 fd**、时基单调前进,互不串扰。

覆盖率与台账:套件 24 → 25 全绿;files_tested 28 → 29;未触及 .c 38 → 37;分母
3,766 → 3,945 行;line=3472/3945(**88.0%**,连涨两批)、func=263/290(90.7%)。
恒等式 37 = 3(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-NETEV(游标 058),
族数 18 → 19。

自查:test-cppcheck Passed(含补齐后的假芯片);check_ids --strict 通过;doc-links
35 处(= 存量);16 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑
@build。--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后
统一 --write 回填)与 ui-golden(simulator/tests/ref/*.bin 不入库,新 worktree 没有
黄金参考)三条失败,与本分支改动无关;提交按并行分支约定走 --no-verify。
2026-10-23 23:44:51 +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 对端六元组解析:ip 低字节在前拼 32 位、端口取第 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 监听口自接后转已连接并立刻可写");
CHECK((g_chip.keepalive_calls == 1) && (g_chip.last_keepalive == 1u) &&
(g_chip.int_status_calls == 1),
"TC-NETEV-003 建链即开 keepalive 并清一次挂起位(读即清)");
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 事件带 fd 与调用方 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 主动连接分支同样转已连接,但**不动 send_ready**(与自接有别)");
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 其它状态收到 CONNECT 中断一律忽略(不许凭空造会话)");
}
/* ===================== 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 芯片说还在连接:忽略这个陈旧 DISCONNECT(不许拆活会话)");
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 疑似误报(一条数据都没收就断):延后交给对账器,不立即拆");
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 正常断开:优雅沉降 + 会话标死 + 记旧端口 + 发事件");
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 监听还在:陈旧监听 DISCONNECT 只留痕,不报 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 监听丢了也只留痕:重开交给对账器,事件层不越权");
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:报 ERROR 但仍要通知应用");
}
/* ===================== 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 RECV 事件置可读并通知应用");
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 完成只发事件,字段留给写路径自己管");
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 连接超时:退回 CREATED 并通知应用");
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 会话超时:隔离级沉降 + 标异常释放(net_pending 收尾据此报 CONNRESET)");
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 监听超时只留痕:不改状态,但仍要通知等待方");
}
/* ===================== 掉线自愈 ===================== */
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 芯片不在已连接态:只清标记,不自作主张建会话");
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 对端口与上次相同:认定是自己没看到 CONNECT,不自接第二遍");
/* 自愈的适用面是软件还写着 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 丢了 CONNECT 中断时自愈:补一次 accept,会话不至于永久失联");
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 现状:软件已 CLOSED 时自愈接不上(只留痕,不凭空造会话)");
}
/* ===================== 周期对账 ===================== */
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 上电第一趟不因链路计数打日志(0xFFFF 哨兵只记基线)");
c = g_chip.cached_calls;
recon_step(1000u); /* 同一时刻再调一次 */
CHECK(g_chip.cached_calls == c, "TC-NETEV-050 100ms 周期闸门:同一时刻不重复查芯片");
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 会话死亡要**连观 4 次**才动手:前三次保持原状");
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 第 4 次稳定后走断开收尾");
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 动作后 1000ms 冷却内不再重复折腾(哪怕稳定计数够了)");
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 芯片偷偷重开监听:需要 2 次稳定,第一次只累计");
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 第 2 次稳定:重同步成监听并**取消两条挂起**(否则任务永远等不到回)");
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 连接完成但中断丢了:1 次即补 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 监听丢了:2 次稳定后请求重开监听(不自已在事件层里开)");
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 重同步:软件 CLOSED 而芯片在 LISTEN -> 直接跟芯片,不多余重开");
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 软件 CLOSED 且芯片也不在监听:请求重开(重建交回监听层)");
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 不在用的槽位直接跳过(不产生任何动作)");
/* 链路日志: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 链路变更计数一变就留痕(基线那趟已由 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;
}