Files
STM32F4-Base/test/host/iec104_serv/iec104_serv_test.c
T
wandering1 957ec64645 test(APP/104): iec104_serv 模块(28 条,84.2%)——P1 十三块全部进分母
P1 收官批次。新增 test/host/iec104_serv/,iec104_serv.c 覆盖 117/139 = 84.2%。
做法:把生产源整个 #include 进 TU,直接驱动它的 7 个 static 辅助函数——**不跑
iec104_serv_task 的无限主循环**(那是 never-returning 的任务体,不适合单测;事件组
相关支路用 sys_evtHandle=NULL 走"不可用"分支)。链路层与 pts 层给假件:链路协议本身
有 test/host/iec104 的 97 条专测,本模块只管服务怎么活、怎么死、怎么重生。

钉住的契约:
- 建监听:2404 端口过 htons、绑 NET_INADDR_ANY;socket/bind/listen 任一失败都要把
  已经拿到的号关掉(漏关就是每次重试漏一个 socket 号,CH395F 只有 8 个)。
- **网络代次一变就拆掉旧连接与旧监听并重建**:现场改 IP 后端口必须在新地址上继续
  服务,这条不重建就是"设备换了 IP 之后 104 主站再也连不上"。
- accept 门控:监听未就绪不 accept;表项已失效时把监听号丢掉、下一轮重建。
- 单连接 socket 的断线语义:对端 FIN 时只关数据口,**监听必须留着**;数据号与监听
  号同一个时绝不关(关了就端口失联)。recv 返 -1 且 errno 是 WOULDBLOCK 是正常空转,
  不能当成错误丢连接;真错误则丢弃并**当场重建监听**,不留空窗。
- 判死用**连续**短写计数:一次成功即复位。写成累计就会把偶发拥塞误判成断线,主站
  看到的就是莫名掉线(连到 3 次才判死,I104_SEND_FAIL_MAX)。
- 32 分频探活:无可读数据时不是每轮都去收(省 CPU 预算),到点才收一次;对端 FIN 后
  复位计数器。协议状态掉回 IDLE 也要丢并重生(否则服务挂着但永不服务)。
- 时钟桥接两个溢出边界:epoch 秒超过 32 位一律拒绝且不碰时钟(截断写进 RTC 就是乱值);
  未对时(sys_clock_get 返回 UINT32_MAX)回 UINT64_MAX 哨兵而不是 0(否则对端会以为
  设备时间是 1970-01-01)。

主机侧兼容处理(写进桩注释):真实 App/util/sys_clock.h 会 include sd2506.h(RTC 芯片
驱动)一路拉到 HAL,PC 上编不动,故桩掉 sys_clock.h 但**两个签名与真实头逐字一致**;
104 的头散在 App 与 Drivers/BSP 的多个子目录(iec104_serv.h/iec104_link.h/iec104_pts.h/
task_watchdog.h/sys_events.h/prot_set.h/sd2506.h…),逐个手写 include 目录必漏且报错会
落在被测源上、极易误读成生产代码坏了——运行器改为**按目录实际是否含 .h 自动收集**。

自查:初跑红 2 条,都是我的场景问题(分频到点那轮 recv 返回 0 会走对端 FIN 支路,
应给 -1 + WOULDBLOCK;"重建失败丢监听号"必须同时让表项失效),改正后 28/28。

P1 收官合计:12 个新模块、**308 条断言**;套件 16 → **28** 全绿;files_tested 19 → **32**;
未触及 .c 47 → **34**;恒等式 34 = 0(P1 剩) + 5(P2) + 29(P3);分母 2,893 → **4,684** 行;
line=4020/4684(85.8%)、func=300/333(90.1%)。百分比"降"是摊薄不是回退(lftpd.c 64.3%、
lftpd_io.c 53.5% 这类只测主体的文件也进了分母),按 §5 口径该盯 files_tested/files_untested。
亮点:lftpd_inet.c **127/127 = 100%**、net_listener.c 99.2%、net_events.c 97.8%。
编号补建 TC-HOST-I104SRV(游标 041),TC-HOST 族数 20 → 22。§9 的落地进度表补齐并
统一为 NET 拆出的四块 ①②③④。

自查门禁:test-cppcheck Passed;check_ids --strict 通过;doc-links 35 处(= 存量);
10 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。
--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填)
与 ui-golden(ref/*.bin 不入库)三条失败,与本分支改动无关。
2026-10-23 23:44:53 +08:00

528 lines
19 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.
/*
* 模块名称:IEC 60870-5-104 上行服务生命周期(iec104_serv)主机侧单元测试
* 模块功能:在 PC 上离线验证 App/task/iec104_serv.c 的服务侧骨架:监听建立与失败
* 回收、**网络代次一变就重建监听**(改了 IP 端口必须在新地址上继续服务)、
* accept 门控与表项失效自愈、单连接 socket 的断线语义(对端 FIN 只关数据
* 口、绝不关监听)、连续短写判死、活性分频探活、协议状态掉回 IDLE 时丢
* 连接并立即重建,以及时钟桥接的两个溢出边界(>4G 秒拒绝、未对时返回哨兵
* 而不是把 1970 当真实时间)。
*
* 被测对象:真实源文件 ../../../App/task/iec104_serv.c(#include 纳入,故它的 static
* s_bind_listen / s_drop_conn / s_service_link / s_serv_poll / s_send_apdu /
* s_clock_sync / s_epoch_ms 都能直接驱动——**不跑 iec104_serv_task 的无限
* 主循环**,那是 never-returning 的任务体,不适合单测)。
* iec104_link.h / iec104_pts.h / prot_set.h / sys_clock.h / sys_events.h /
* task_watchdog.h / net_types.h 全用**真实头**(iec104_state_t 枚举、
* iec104_link_cfg_t 函数指针签名、TASK_WD_IEC104=7、SYS_EVT_* 位值一改即
* 编译期报错);链路层(iec104_link_*)与 pts 层给假件——链路协议本身有
* test/host/iec104 的 97 条专测,本模块只管"服务怎么活、怎么死、怎么重生"。
*
* 为什么要主机侧单测(而不是只靠台架):
* 1. 104 主站掉线重连是**现场最常见的故障形态**,但台架上没法按需制造"改 IP 时
* 正好有连接""对端 FIN 与 RST 的区别""连续三次短写"这些时序;而这里每一条
* 处理错的后果都是端口永久失联或会话卡死,只能靠主站侧超时才发现。
* 2. 判死用**连续**短写计数(一次成功即复位),写成"累计"就会把偶发拥塞误判成
* 断线,主站看到的就是莫名掉线——这条只有能精确注入短写/成功序列才可测。
* 3. 时钟桥接的两个边界:epoch 秒超过 32 位必须拒绝(否则截断成 2106 年前的某个
* 乱值写进 RTC),未对时(sys_clock_get 返回 UINT32_MAX)必须回哨兵而不是 0,
* 否则对端会以为设备时间是 1970-01-01。
* 4. 活性分频(每 32 轮才探一次)是 CPU 预算的取舍:写错成每轮都探,就把 100ms
* 轮询变成了每轮两次系统调用;写错成分频相位,TESTFR 会迟迟不发。
*
* 覆盖点:TC-I104SRV-001..004 建监听;010..013 发送与连续短写判死;020..023 时钟
* 桥接;030..033 代次重建与 accept 门控;034..036 活性分频与对端 FIN;037..041
* 错误丢弃与协议掉线重生。
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-23
*/
#include <stdio.h>
#include <string.h>
#include "iec104_serv.h"
#include "net_socket.h"
#include "iec104_serv.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)
int g_i4_info_count;
int g_i4_error_count;
int g_i4_debug_count;
/* 真实 sys_events.h 把它声明成 extern,这里给定义(本模块只在主循环里用,
测试设成 NULL 走"事件组不可用"支路,不阻塞静态辅助函数的驱动)。 */
osEventFlagsId_t sys_evtHandle;
/* ===================== 可控 socket 面 ===================== */
typedef struct {
int socket_rc;
int bind_rc;
int listen_rc;
int accept_rc;
int send_rc;
int recv_rc;
int can_read;
int armed;
int errno_val;
uint32_t gen;
int sock_exists;
int socket_calls;
int bind_calls;
int listen_calls;
int accept_calls;
int close_calls;
int last_closed_fd;
int last_accept_sock;
int recv_calls;
uint16_t last_bind_port_net;
uint16_t last_bind_family;
uint32_t last_bind_addr;
int delay_calls;
} i4_net_t;
static i4_net_t g_n;
/* net_get_sock 要返回一个真 net_sock_t(结构来自真实 net_types.h),先声明再用 */
static net_sock_t g_sock_probe;
int net_socket(int domain, int type) { (void)domain; (void)type; g_n.socket_calls++; return g_n.socket_rc; }
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen)
{
const struct net_sockaddr_in *a = (const struct net_sockaddr_in *)addr;
(void)sockfd;
(void)addrlen;
g_n.bind_calls++;
g_n.last_bind_port_net = a->sin_port;
g_n.last_bind_family = a->sin_family;
g_n.last_bind_addr = a->sin_addr.s_addr;
return g_n.bind_rc;
}
int net_listen(int sockfd) { (void)sockfd; g_n.listen_calls++; return g_n.listen_rc; }
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen)
{
(void)addr;
(void)addrlen;
g_n.accept_calls++;
g_n.last_accept_sock = sockfd;
return g_n.accept_rc;
}
int net_close(int sockfd) { g_n.close_calls++; g_n.last_closed_fd = sockfd; return 0; }
int net_send(int sockfd, const void *buf, int len, int flags)
{
(void)sockfd;
(void)buf;
(void)flags;
return g_n.send_rc;
}
int net_recv_nb(int sockfd, void *buf, int len)
{
(void)sockfd;
(void)buf;
(void)len;
g_n.recv_calls++;
return g_n.recv_rc;
}
int net_can_read(int sockfd) { (void)sockfd; return g_n.can_read; }
int net_get_errno(void) { return g_n.errno_val; }
int net_listener_armed(int sockfd) { (void)sockfd; return g_n.armed; }
uint32_t net_get_generation(void) { return g_n.gen; }
net_sock_t *net_get_sock(int sockfd) { (void)sockfd; return (g_n.sock_exists != 0) ? &g_sock_probe : NULL; }
uint16_t net_htons(uint16_t v) { return (uint16_t)((v >> 8) | (v << 8)); }
/* ===================== 假链路层与假 pts ===================== */
static int g_lk_init;
static int g_lk_open;
static int g_lk_close;
static int g_lk_poll;
static int g_lk_onrx;
static uint32_t g_lk_last_now;
static iec104_state_t g_lk_state;
void iec104_link_init(iec104_link_t *lk, const iec104_link_cfg_t *cfg, void *ctx)
{
(void)lk; (void)cfg; (void)ctx;
g_lk_init++;
}
void iec104_link_open(iec104_link_t *lk, uint32_t now_ms)
{
(void)lk;
g_lk_open++;
g_lk_last_now = now_ms;
}
void iec104_link_close(iec104_link_t *lk) { (void)lk; g_lk_close++; }
void iec104_link_poll(iec104_link_t *lk, uint32_t now_ms)
{
(void)lk;
g_lk_poll++;
g_lk_last_now = now_ms;
}
void iec104_link_on_rx(iec104_link_t *lk, const uint8_t *data, uint16_t len, uint32_t now_ms)
{
(void)lk; (void)data; (void)len; (void)now_ms;
g_lk_onrx++;
}
iec104_state_t iec104_link_state(const iec104_link_t *lk) { (void)lk; return g_lk_state; }
static int g_pts_init;
void iec104_pts_init(void) { g_pts_init++; }
/* ===================== 假时钟 / 定值 / 看门狗 / RTOS ===================== */
static uint32_t g_clock_val;
static int g_clock_set_rc;
static uint32_t g_clock_last_secs;
static int g_clock_set_calls;
static uint32_t g_dirty;
static int g_evt_set_calls;
static uint32_t g_evt_last;
static int g_wd_beats;
static int g_wd_last_id;
static uint32_t g_now;
uint32_t sys_clock_get(void) { return g_clock_val; }
int sys_clock_set_unix(uint32_t unix_secs)
{
g_clock_set_calls++;
g_clock_last_secs = unix_secs;
return g_clock_set_rc;
}
uint8_t prot_set_dirty_peek(void) { return (uint8_t)g_dirty; }
uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags)
{
(void)ef_id;
g_evt_set_calls++;
g_evt_last = flags;
return flags;
}
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout)
{
(void)ef_id; (void)flags; (void)options; (void)timeout;
return 0u;
}
void task_wd_beat(task_wd_id_t id) { g_wd_beats++; g_wd_last_id = (int)id; }
uint32_t osKernelGetTickCount(void) { return g_now; }
void osDelay(uint32_t ticks)
{
(void)ticks;
g_n.delay_calls++;
if (g_n.delay_calls > 500) {
printf(" ABORT 跑飞(>500 轮 osDelay)\n");
fflush(stdout);
exit(2);
}
}
/* ===================== 复位 ===================== */
static void reset_all(void)
{
(void)memset(&g_n, 0, sizeof(g_n));
g_n.socket_rc = 5;
g_n.bind_rc = 0;
g_n.listen_rc = 0;
g_n.accept_rc = -1;
g_n.send_rc = 0;
g_n.armed = 1;
g_n.sock_exists = 1;
g_n.errno_val = NET_ERR_WOULDBLOCK;
g_n.gen = 100u;
(void)memset(&g_sock_probe, 0, sizeof(g_sock_probe));
g_lk_init = 0; g_lk_open = 0; g_lk_close = 0; g_lk_poll = 0; g_lk_onrx = 0;
g_lk_last_now = 0u;
g_lk_state = IEC104_ST_RUN;
g_pts_init = 0;
g_clock_val = 1700000000u;
g_clock_set_rc = 0;
g_clock_last_secs = 0u;
g_clock_set_calls = 0;
g_dirty = 0u;
g_evt_set_calls = 0;
g_evt_last = 0u;
g_wd_beats = 0;
g_wd_last_id = -1;
g_now = 5000u;
g_i4_info_count = 0;
g_i4_error_count = 0;
g_i4_debug_count = 0;
sys_evtHandle = NULL;
s_listen = -1;
s_client = -1;
s_gen = 100u;
s_liveness_ctr = 0u;
s_send_fail_run = 0u;
s_conn_dead = 0u;
}
/* ===================== 建监听 ===================== */
static void tc_bind_listen(void)
{
printf("-- TC-I104SRV-001..004 bind/listen --\n");
reset_all();
CHECK((s_bind_listen() == 0) && (s_listen == 5) && (g_n.listen_calls == 1) &&
(g_n.last_bind_family == NET_AF_INET) &&
(g_n.last_bind_port_net == net_htons(IEC104_SERV_PORT)) &&
(g_n.last_bind_addr == NET_INADDR_ANY),
"TC-I104SRV-001 建监听:2404 端口过 htons、绑任意地址、记下监听号");
reset_all();
g_n.socket_rc = -1;
CHECK((s_bind_listen() == -1) && (g_n.bind_calls == 0) && (g_n.close_calls == 0),
"TC-I104SRV-002 建不出 socket 就返回,不去 bind 也不关不存在的口");
reset_all();
g_n.bind_rc = -1;
CHECK((s_bind_listen() == -1) && (g_n.close_calls == 1) && (g_n.listen_calls == 0),
"TC-I104SRV-003 bind 失败要把刚建的口关掉(否则每次重试漏一个 socket 号)");
reset_all();
g_n.listen_rc = -1;
CHECK((s_bind_listen() == -1) && (g_n.close_calls == 1) && (s_listen == -1),
"TC-I104SRV-004 listen 失败同样关口且不留下半个监听");
}
/* ===================== 发送与连续短写判死 ===================== */
static void tc_send(void)
{
const uint8_t apdu[4] = { 0x68u, 0x02u, 0x00u, 0x00u };
printf("-- TC-I104SRV-010..013 send & fail streak --\n");
reset_all();
s_client = -1;
s_send_apdu(NULL, apdu, 4u);
CHECK((g_n.send_rc == 0) && (g_n.close_calls == 0) && (g_i4_error_count == 0),
"TC-I104SRV-010 没连接时直接返回(不发 net_send、不报错)");
reset_all();
s_client = 7;
g_n.send_rc = 4;
s_send_apdu(NULL, apdu, 4u);
CHECK((s_send_fail_run == 0u) && (s_conn_dead == 0u),
"TC-I104SRV-011 完整发出把连败计数清零");
reset_all();
s_client = 7;
g_n.send_rc = 2; /* 短写:只发了 2/4 */
s_send_apdu(NULL, apdu, 4u);
CHECK((s_send_fail_run == 1u) && (s_conn_dead == 0u) && (g_i4_error_count == 1),
"TC-I104SRV-012 第一次短写:留痕但不判死(拥塞很正常)");
s_send_apdu(NULL, apdu, 4u);
s_send_apdu(NULL, apdu, 4u);
CHECK((s_send_fail_run == 3u) && (s_conn_dead == 1u),
"TC-I104SRV-012b 连到 3 次才判死(IEC104_SEND_FAIL_MAX)");
reset_all();
s_client = 7;
g_n.send_rc = 1;
s_send_apdu(NULL, apdu, 4u);
s_send_apdu(NULL, apdu, 4u);
g_n.send_rc = 4;
s_send_apdu(NULL, apdu, 4u); /* 中间成功一次 */
g_n.send_rc = 1;
s_send_apdu(NULL, apdu, 4u);
s_send_apdu(NULL, apdu, 4u);
CHECK((s_conn_dead == 0u) && (s_send_fail_run == 2u),
"TC-I104SRV-013 **成功即复位**:偶发拥塞不会被累计成误杀");
}
/* ===================== 时钟桥接边界 ===================== */
static void tc_clock(void)
{
printf("-- TC-I104SRV-020..023 clock bridge --\n");
reset_all();
CHECK(s_clock_sync(NULL, 0x100000000ULL * 1000ULL) == -1,
"TC-I104SRV-020 秒数超过 32 位一律拒绝(截断写进 RTC 就是 2106 年后的乱值)");
CHECK(g_clock_set_calls == 0, "TC-I104SRV-020b 拒绝时根本不该碰时钟");
reset_all();
(void)s_clock_sync(NULL, 1700000123000ULL);
CHECK((g_clock_set_calls == 1) && (g_clock_last_secs == 1700000123u),
"TC-I104SRV-021 毫秒按整除换秒后写时钟");
reset_all();
g_clock_val = UINT32_MAX; /* 未对时的哨兵值 */
CHECK(s_epoch_ms(NULL) == UINT64_MAX,
"TC-I104SRV-022 未对时回 UINT64_MAX 哨兵(绝不能把 1970 当成设备时间)");
reset_all();
g_clock_val = 1700000123u;
CHECK(s_epoch_ms(NULL) == 1700000123000ULL, "TC-I104SRV-023 正常时对时给出毫秒");
}
/* ===================== 代次重建与 accept 门控 ===================== */
static void tc_generation(void)
{
printf("-- TC-I104SRV-030..033 generation & accept gate --\n");
reset_all();
s_listen = 5;
s_client = 9;
g_n.gen = 101u; /* 网络重建(现场改了 IP) */
s_serv_poll();
CHECK((g_n.close_calls == 2) && (g_n.last_closed_fd == 5) &&
(s_client == -1) && (s_liveness_ctr == 0u) && (s_conn_dead == 0u) &&
(g_lk_close == 1) && (s_gen == 101u) && (g_n.bind_calls == 1),
"TC-I104SRV-030 代次一变就拆掉旧连接与旧监听并重建(新 IP 上端口必须继续服务)");
reset_all();
s_listen = -1;
s_serv_poll();
CHECK((g_n.bind_calls == 1) && (s_listen == 5) && (g_n.accept_calls == 0),
"TC-I104SRV-031 监听丢了就补建,这一轮不去 accept");
reset_all();
s_listen = 5;
g_n.armed = 0; /* 监听还没就绪 */
g_n.sock_exists = 1;
s_serv_poll();
CHECK((g_n.accept_calls == 0) && (s_listen == 5),
"TC-I104SRV-032 监听门未开时不 accept(否则对着未就绪的号空转)");
reset_all();
s_listen = 5;
g_n.armed = 0;
g_n.sock_exists = 0; /* 表项已不存在 */
s_serv_poll();
CHECK((s_listen == -1) && (g_n.accept_calls == 0),
"TC-I104SRV-032b 表项已失效时把监听号丢掉,下一轮重建");
reset_all();
s_listen = 5;
g_n.accept_rc = 9;
s_send_fail_run = 3u;
s_conn_dead = 1u;
s_liveness_ctr = 7u;
s_serv_poll();
CHECK((s_client == 9) && (g_lk_open == 1) && (g_lk_last_now == 5000u) &&
(s_send_fail_run == 0u) && (s_conn_dead == 0u) && (s_liveness_ctr == 0u) &&
(g_i4_info_count == 1),
"TC-I104SRV-033 新连接:打开链路、三个失败计数全部复位");
}
/* ===================== 活性分频、对端 FIN、错误丢弃 ===================== */
static void tc_liveness(void)
{
printf("-- TC-I104SRV-034..041 liveness / EOF / drop --\n");
reset_all();
s_listen = 5;
s_client = 9;
g_n.can_read = 0;
s_liveness_ctr = 0u;
s_serv_poll();
CHECK((g_n.recv_calls == 0) && (g_lk_poll == 1) && (s_liveness_ctr == 1u),
"TC-I104SRV-034 无可读数据时按 32 分频探活,这一轮不收包(省 CPU 预算)");
reset_all();
s_listen = 5;
s_client = 9;
g_n.can_read = 0;
/* 这一轮收到的是"暂无数据"(非阻塞的空读),而不是 0——0 在 recv_nb 的语义里是
* 对端 FIN,会直接走 036 那条断开支路,测不到分频本身。 */
g_n.recv_rc = -1;
g_n.errno_val = NET_ERR_WOULDBLOCK;
s_liveness_ctr = 31u; /* 下一轮正好到分频点 */
s_serv_poll();
CHECK((g_n.recv_calls == 1) && (g_lk_poll == 1) && (s_client == 9),
"TC-I104SRV-035 分频到点才去收一次(TESTFR 等活性帧靠它发出去)");
reset_all();
s_listen = 5;
s_client = 9;
g_n.can_read = 1;
g_n.recv_rc = 0; /* 对端 FIN */
s_serv_poll();
CHECK((g_n.last_closed_fd == 9) && (s_client == -1) && (g_lk_close == 1) &&
(g_n.close_calls == 1) && (s_listen == 5) && (g_n.bind_calls == 0),
"TC-I104SRV-036 对端正常断开:只关数据口、**监听必须留着**(单连接 socket 关错就是端口失联)");
reset_all();
s_listen = 5;
s_client = 5; /* 单连接复用:数据号 == 监听号 */
g_n.can_read = 1;
g_n.recv_rc = 0;
s_serv_poll();
CHECK((g_n.close_calls == 0) && (s_client == -1) && (g_lk_close == 1),
"TC-I104SRV-036b 数据号与监听号同号时**不许关**(否则把监听一起毁掉)");
reset_all();
s_listen = 5;
s_client = 9;
g_n.can_read = 1;
g_n.recv_rc = -1;
g_n.errno_val = NET_ERR_WOULDBLOCK; /* 空转:没数据可读 */
s_serv_poll();
CHECK((s_client == 9) && (g_lk_close == 0) && (g_i4_error_count == 0),
"TC-I104SRV-037 WOULDBLOCK 是正常空转,不能当成错误丢连接");
reset_all();
s_listen = 5;
s_client = 9;
g_n.can_read = 1;
g_n.recv_rc = -1;
g_n.errno_val = NET_ERR_CONNRESET; /* 真错误 */
s_serv_poll();
CHECK((s_client == -1) && (s_listen == 5) && (g_i4_error_count == 1) &&
(g_lk_close == 1) && (s_liveness_ctr == 0u),
"TC-I104SRV-038 真错误:丢连接并**当场重建监听**(不留空窗)");
reset_all();
s_listen = 5;
s_client = 9;
s_conn_dead = 1u;
s_service_link(1000u);
CHECK((s_client == -1) && (g_n.close_calls == 1) && (g_i4_error_count == 1),
"TC-I104SRV-039 判死后由 service_link 收口(发送失败线索单独成一条)");
reset_all();
s_listen = 5;
s_client = 9;
g_lk_state = IEC104_ST_IDLE; /* 链路层自己回到了未建立 */
s_service_link(1000u);
CHECK((s_client == -1) && (g_lk_close >= 1) && (s_listen == 5),
"TC-I104SRV-040 协议状态掉回 IDLE 也要丢并重生(否则服务挂着但永不服务)");
reset_all();
s_listen = 9;
s_client = 9;
g_n.socket_rc = -1; /* 重建失败 */
g_n.sock_exists = 0; /* 且表项已经没了 */
g_n.can_read = 1;
g_n.recv_rc = 0;
s_serv_poll();
CHECK((s_client == -1) && (s_listen == -1) && (g_n.close_calls == 0),
"TC-I104SRV-041 表项没了又重建失败:丢掉假监听号,下一轮继续重试(数据号与监听同号时不许关)");
}
int main(void)
{
printf("=== IEC104 SERV Tests ===\n");
tc_bind_listen();
tc_send();
tc_clock();
tc_generation();
tc_liveness();
printf("\n=== IEC104 SERV Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}