Files
STM32F4-Base/test/host/time_sync/time_sync_test.c
T
wandering1 6394853fef feat(N34): 阶段3 net层收敛——单一reconciler+忙等统一+镜像降级+E-02收口
实施方案 docs/网络/N34-自愈审计/实施方案.md §5 项1/2/3/5(项4 U15受E6暂缓):

- 3a 合并reconciler:net_listener_pump删除,与net_reconcile_tick合并为
  单一100ms tick;判据读驱动状态缓存(唯一刷新点=net_poll每轮全量刷新,
  E2背书18.1us);去抖分级②=1/③④⑤=2/①=4轮(M3让位窗400ms保留)+冷却1s;
  链路态订阅留痕(F-DRV-13);探针去抖删除(F-MS-9)
- 3b 忙等统一(P2b):listen/connect open重试与close排空循环改
  net_sock_reset_sync(驱动sock_reset:读空RX→CLOSE→排空事件→等
  byte0=00H,内置LISTEN态二段CLOSE补发);close不再读清全局中断寄存器
  (F-NET-7);net_poll每轮步进活跃复位状态机(驱动契约,防搁浅)
- 3c 镜像降级:net_sock_state_t声明为本层会话投影,芯片事实源=驱动缓存
- E-02收口:新API net_drop_conn(拆会话保监听,QUARANTINE语义,幂等;
  模拟器双端实现;timeSync 30s预算耗尽接入;主机单测TC-TS-027d)
  定案:CH395F close无RST,对内核存活不close的对端停FIN_WAIT_2,
  监听恢复受限于对端close(Trap 07补充),兜底=E13 keepalive/TIMEOUT

台架全绿(Code=211348 RW=1104 ZI=154232):@test PASS;[STACK] net
1116/3840;Trap22 :7003 50/50+:21三档各50/50;Trap23 RST10/10+FIN5/5;
Trap25 n30 11/11;E-02双PASS;iec104 18/18;对时跨会话连续;
ftp_regression 11/11;integrity 15/15 SHA;ci_local L0+L0.5全过。
2026-10-13 15:09:58 +08:00

737 lines
27 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.
/*
* 模块名称:Time Sync(PC 本地时间推送接收)主机侧单元测试
* 模块功能:在 PC 上对 App/sync/time_sync.c 做离线验证(4 个函数全覆盖:
* time_sync_delay_fed / time_sync_open_listener / time_sync_handle_conn /
* time_sync_task)。
*
* 被测对象:真实源文件 ../../../App/sync/time_sync.c(以 #include 原样纳入,
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
* - FreeRTOS/task.h -> vTaskDelay / xTaskGetTickCount(可控时钟)
* - net_socket.h -> socket/bind/listen/accept/close/recv_to/
* get_generation/htons(全部脚本化)
* - sys_events.h -> osEventFlagsWait(可注入"未就绪")
* - sys_clock.h -> sys_clock_set_unix(记录 epoch)
* - task_watchdog.h -> task_wd_beat / task_wd_bind(记录心跳节奏)
* - dbg_log.h -> DBG_ERROR/DBG_INFO 计数
*
* 为什么要主机侧单测(而不是只用板端套件):
* 1. **本模块是 N-17 的当事方**:登记簿 N-17 记录"time_sync 监听重建期间心跳缺口
* 800ms~1s,距 IWDG 最坏超时 1701ms 余量仅 ~0.7s"。缺口的成因全部是本模块的
* **纯逻辑**——分片延时(delay_fed)的心跳节奏、监听重试退避(500/2000ms)、
* accept 轮询退避(200ms)、收尾延时(50ms)、以及"屏障等待态也要喂心跳"。
* 这些在真机上只能靠"跑起来看复位不复位"间接观察,主机侧可**逐片断言**。
* 2. **时间包重组状态机**(handle_conn)是纯逻辑:累积到 8 字节才解析、30s 总预算
* 随 tick 推进耗尽、TCP 分段送达、EOF/真错误/超时三种返回码分支——板端套件
* 无法精确构造"分段 + 预算耗尽 + tick 回绕"的组合。
* 3. 不占 MCU 的 RAM,毫秒级完成。
*
* 覆盖点(TC-TS-xxx):
* —— time_sync_delay_fed(可喂心跳的分片延时)——
* TC-TS-001 0ms 不睡眠不喂狗
* TC-TS-002 恰好一片(50ms)
* TC-TS-003 非整片(120ms)切成 50/50/20,每片喂心跳
* TC-TS-004 长延时(2000ms)= 40 片 × 50ms,每片喂心跳
* TC-TS-005 不足一片(49ms)单片处理
* TC-TS-006 总预算 30s = 600 片(片长上限恒 50ms)
* —— time_sync_open_listener(监听建立与重试)——
* TC-TS-010 首次成功:socket/bind/listen 各一次,端口经 net_htons,无退避
* TC-TS-011 bind 失败 -> 关 socket + 退避 2000ms + 留痕,随后重试成功
* TC-TS-012 listen 失败 -> 关 socket + 退避 2000ms,随后重试成功
* TC-TS-013 socket 连续失败 -> 5 次后返回 -1(5 条 ERROR,5×500ms 退避)
* TC-TS-014 第 3 次重试成功并返回新描述符
* TC-TS-015 每次重试前 + 每片延时各喂一次心跳,且心跳位恒为 TIMESYNC
* —— time_sync_handle_conn(时间包重组状态机)——
* TC-TS-020 8 字节一次收齐 -> 大端解析 epoch + 更新墙钟 + 关连接
* TC-TS-021 分段送达(4+4)-> 累积后解析;第二次只请求剩余 12 字节
* TC-TS-022 一次到达 16 字节 -> 只取前 8 字节
* TC-TS-023 前缀非 "TIME" -> 不更新墙钟,但仍关连接
* TC-TS-024 对端关闭(n==0)-> 立即退出、不解析
* TC-TS-025 真错误(n==-1)-> 立即退出、不解析
* TC-TS-026 只收到 4 字节后 EOF -> 不足 8 字节不解析
* TC-TS-027 30s 预算耗尽 -> 留痕 "no TIME packet in 30s, drop conn"
* TC-TS-028 剩余预算不足一片 -> 末片按剩余(20ms)切分
* TC-TS-029 32 位 tick 回绕 -> 预算计算仍正确
* —— time_sync_task(任务主循环)——
* TC-TS-040 sys_evtHandle==NULL:跳过就绪屏障;accept 轮询->成功->处理->收尾
* TC-TS-041 就绪屏障:100ms 分片等待 NET_READY(osFlagsNoClear 不消费就绪位)
* TC-TS-042 屏障未就绪期间持续喂心跳(2026-10-02 台架根因的回归防线)
* TC-TS-043 网络代次变化 -> 关闭旧监听并重监听
* TC-TS-044 监听建立失败 -> 5 次重试后放弃并退避 2000ms
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-10
*/
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
#include "FreeRTOS.h"
#include "task.h"
#include "net_socket.h"
#include "sys_clock.h"
#include "sys_events.h"
#include "task_watchdog.h" /* 真实头(App/util):task_wd_id_t / TASK_WD_TIMESYNC */
/* ===================== FreeRTOS 桩 ===================== */
static TickType_t s_tick;
#define DELAY_LOG_MAX 800
static uint32_t s_delays[DELAY_LOG_MAX];
static int s_delay_calls;
void vTaskDelay(TickType_t xTicksToDelay) {
if (s_delay_calls < DELAY_LOG_MAX) {
s_delays[s_delay_calls] = (uint32_t)xTicksToDelay;
}
s_delay_calls++;
}
TickType_t xTaskGetTickCount(void) {
return s_tick;
}
/* ===================== CMSIS-RTOS 事件标志桩 ===================== */
osEventFlagsId_t sys_evtHandle = NULL;
static uint32_t s_evt_seq[8];
static int s_evt_seq_n;
static uint32_t s_evt_default = 0xFFFFFFFFu; /* 未脚本化时的返回值(默认:就绪) */
static int s_evt_calls;
static int s_evt_longjmp_at = -1; /* >0 时第 N 次调用触发 longjmp */
static uint32_t s_evt_last_flags;
static uint32_t s_evt_last_options;
static uint32_t s_evt_last_timeout;
static jmp_buf s_jmp;
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout) {
int i = s_evt_calls;
(void)ef_id;
s_evt_last_flags = flags;
s_evt_last_options = options;
s_evt_last_timeout = timeout;
s_evt_calls++;
if (s_evt_longjmp_at > 0 && (i + 1) >= s_evt_longjmp_at) {
longjmp(s_jmp, 2); /* 跳出 time_sync_task 的屏障循环(用例收尾) */
}
if (i < s_evt_seq_n) {
return s_evt_seq[i];
}
return s_evt_default;
}
/* ===================== 看门狗桩 ===================== */
static int s_beat_calls;
static int s_beat_id_bad;
/* ⚠ 命名必须与 net_socket 桩的 net_bind 计数区分开:C 的"暂定定义"规则下,
* 同一编译单元里两个 `static int x;` 会被**静默合并为同一个对象**(不报错),
* 于是 task_wd_bind 与 net_bind 会共用计数器,断言全部失真(2026-10-10 踩过)。 */
static int s_wd_bind_calls;
static int s_wd_bind_id_bad;
void task_wd_beat(task_wd_id_t id) {
s_beat_calls++;
if (id != TASK_WD_TIMESYNC) {
s_beat_id_bad++;
}
}
void task_wd_bind(task_wd_id_t id) {
s_wd_bind_calls++;
if (id != TASK_WD_TIMESYNC) {
s_wd_bind_id_bad++;
}
}
/* ===================== sys_clock 桩 ===================== */
static int s_set_unix_calls;
static uint32_t s_set_unix_last;
int sys_clock_set_unix(uint32_t unix_secs) {
s_set_unix_calls++;
s_set_unix_last = unix_secs;
return 0;
}
/* ===================== net_socket 桩 ===================== */
static uint16_t s_htons_last;
static int s_socket_calls;
static int s_socket_seq[8];
static int s_socket_seq_n;
static int s_socket_default = 5;
static int s_bind_calls;
static int s_bind_seq[8];
static int s_bind_seq_n;
static uint16_t s_bind_port;
static uint16_t s_bind_family;
static int s_listen_calls;
static int s_listen_seq[8];
static int s_listen_seq_n;
static int s_accept_calls;
static int s_accept_seq[16];
static int s_accept_seq_n;
static int s_close_calls;
static int s_close_socks[32];
static uint32_t s_gen_seq[8];
static int s_gen_seq_n;
static uint32_t s_gen_default = 1u;
static int s_gen_calls;
static int s_gen_longjmp_at = -1; /* >0 时第 N 次调用触发 longjmp */
uint16_t net_htons(uint16_t hostshort) {
s_htons_last = hostshort;
return (uint16_t)(((hostshort & 0xFFu) << 8) | (hostshort >> 8));
}
int net_socket(int domain, int type) {
int i = s_socket_calls++;
(void)domain;
(void)type;
return (i < s_socket_seq_n) ? s_socket_seq[i] : s_socket_default;
}
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
const struct net_sockaddr_in *a = (const struct net_sockaddr_in *)addr;
int i = s_bind_calls++;
(void)sockfd;
(void)addrlen;
s_bind_family = a->sin_family;
s_bind_port = a->sin_port;
return (i < s_bind_seq_n) ? s_bind_seq[i] : 0;
}
int net_listen(int sockfd) {
int i = s_listen_calls++;
(void)sockfd;
return (i < s_listen_seq_n) ? s_listen_seq[i] : 0;
}
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
int i = s_accept_calls++;
(void)sockfd;
(void)addr;
(void)addrlen;
return (i < s_accept_seq_n) ? s_accept_seq[i] : -1;
}
int net_close(int sockfd) {
if (s_close_calls < 32) {
s_close_socks[s_close_calls] = sockfd;
}
s_close_calls++;
return 0;
}
/* ---- net_drop_conn(N34 阶段 3 E-02 收口):计数 + 记录 fd ---- */
static int s_drop_calls;
static int s_drop_socks[32];
int net_drop_conn(int sockfd) {
if (s_drop_calls < 32) {
s_drop_socks[s_drop_calls] = sockfd;
}
s_drop_calls++;
return 0;
}
uint32_t net_get_generation(void) {
int i = s_gen_calls++;
if (s_gen_longjmp_at > 0 && (i + 1) >= s_gen_longjmp_at) {
longjmp(s_jmp, 1); /* 跳出 time_sync_task 的主循环(用例收尾) */
}
return (i < s_gen_seq_n) ? s_gen_seq[i] : s_gen_default;
}
/* ---- net_recv_to:脚本化返回码 + 可推进 tick ---- */
typedef struct {
int n; /* 返回码:>0 字节数 / 0 EOF / -1 错误 / -2 超时无数据 */
uint8_t data[16]; /* 要"送达"的字节 */
int len; /* data 有效长度 */
uint32_t tick_adv; /* 返回前把假时钟推进多少 ms */
} recv_step_t;
static recv_step_t s_recv_seq[16];
static int s_recv_seq_n;
static int s_recv_idx;
static int s_recv_calls;
static int s_recv_default = 0; /* 脚本用尽后的返回码(0=EOF,保证循环终止) */
static uint32_t s_recv_to_log[64];
static int s_recv_len_log[64];
int net_recv_to(int sockfd, void *buf, int len, uint32_t timeout_ms) {
int i = s_recv_calls;
(void)sockfd;
if (i < 64) {
s_recv_to_log[i] = timeout_ms;
s_recv_len_log[i] = len;
}
s_recv_calls++;
if (s_recv_idx >= s_recv_seq_n) {
return s_recv_default;
}
{
const recv_step_t *st = &s_recv_seq[s_recv_idx];
int copy;
s_recv_idx++;
s_tick += st->tick_adv;
if (st->n <= 0) {
return st->n;
}
copy = (st->n <= len) ? st->n : len;
if (copy > st->len) {
copy = st->len;
}
if ((copy > 0) && (buf != NULL)) {
memcpy(buf, st->data, (size_t)copy);
}
return st->n;
}
}
/* ===================== 断言 ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
int g_tstest_err_count = 0;
int g_tstest_info_count = 0;
#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)
/* ===================== 被测代码(原样纳入生产源) ===================== */
#include "time_sync.c"
/* ===================== 工具 ===================== */
/*
* 函数功能:把全部桩复位到"健康"初值
* 入口参数:无
* 返 回 值:无
* 限定条件:无
* 函数说明:默认 = 网络全成功、gen 恒为 1、recv 脚本为空(脚本用尽返回 EOF)、
* 事件标志恒就绪。各用例按需覆盖。
*/
static void fakes_reset(void) {
s_tick = 0;
s_delay_calls = 0;
sys_evtHandle = NULL;
s_evt_seq_n = 0;
s_evt_default = SYS_EVT_NET_READY; /* 健康默认:就绪 */
s_evt_calls = 0;
s_evt_longjmp_at = -1;
s_evt_last_flags = 0;
s_evt_last_options = 0;
s_evt_last_timeout = 0;
s_beat_calls = 0;
s_beat_id_bad = 0;
s_wd_bind_calls = 0;
s_wd_bind_id_bad = 0;
s_set_unix_calls = 0;
s_set_unix_last = 0;
s_htons_last = 0;
s_socket_calls = 0;
s_socket_seq_n = 0;
s_socket_default = 5;
s_bind_calls = 0;
s_bind_seq_n = 0;
s_bind_port = 0;
s_bind_family = 0;
s_listen_calls = 0;
s_listen_seq_n = 0;
s_accept_calls = 0;
s_accept_seq_n = 0;
s_close_calls = 0;
s_drop_calls = 0;
s_gen_seq_n = 0;
s_gen_default = 1u;
s_gen_calls = 0;
s_gen_longjmp_at = -1;
s_recv_seq_n = 0;
s_recv_idx = 0;
s_recv_calls = 0;
s_recv_default = 0;
g_tstest_err_count = 0;
g_tstest_info_count = 0;
}
/* 装配一个 recv 脚本步 */
static void recv_set(int idx, int n, const uint8_t *data, int len, uint32_t tick_adv) {
memset(&s_recv_seq[idx], 0, sizeof(s_recv_seq[idx]));
s_recv_seq[idx].n = n;
s_recv_seq[idx].len = len;
s_recv_seq[idx].tick_adv = tick_adv;
if ((data != NULL) && (len > 0)) {
int copy = (len > 16) ? 16 : len;
memcpy(s_recv_seq[idx].data, data, (size_t)copy);
}
}
/* 装配一个 "TIME" + uint32 大端 时间包 */
static void make_time_packet(uint8_t *out, uint32_t epoch) {
out[0] = (uint8_t)'T';
out[1] = (uint8_t)'I';
out[2] = (uint8_t)'M';
out[3] = (uint8_t)'E';
out[4] = (uint8_t)(epoch >> 24);
out[5] = (uint8_t)(epoch >> 16);
out[6] = (uint8_t)(epoch >> 8);
out[7] = (uint8_t)(epoch);
}
/* 检查 s_delays[0..n) 是否全部等于 v */
static int delays_all(uint32_t v, int n) {
int k;
for (k = 0; k < n; k++) {
if (s_delays[k] != v) {
return 0;
}
}
return 1;
}
/* ===================== TC-TS-001..006:delay_fed ===================== */
static void tc_delay_fed(void) {
fakes_reset();
time_sync_delay_fed(0U);
CHECK((s_beat_calls == 0) && (s_delay_calls == 0), "TC-TS-001 delay_fed(0) 不睡眠不喂狗");
fakes_reset();
time_sync_delay_fed(50U);
CHECK((s_beat_calls == 1) && (s_delay_calls == 1) && (s_delays[0] == 50U),
"TC-TS-002 delay_fed(50) = 1 片 50ms + 1 次心跳");
fakes_reset();
time_sync_delay_fed(120U);
CHECK((s_beat_calls == 3) && (s_delay_calls == 3), "TC-TS-003 delay_fed(120) 切成 3 片");
CHECK((s_delays[0] == 50U) && (s_delays[1] == 50U) && (s_delays[2] == 20U),
"TC-TS-003b 片长 50/50/20(末片为余数)");
fakes_reset();
time_sync_delay_fed(2000U);
CHECK((s_delay_calls == 40) && (s_beat_calls == 40) && delays_all(50U, 40),
"TC-TS-004 delay_fed(2000) = 40 片 × 50ms,每片喂一次心跳");
fakes_reset();
time_sync_delay_fed(49U);
CHECK((s_beat_calls == 1) && (s_delays[0] == 49U), "TC-TS-005 不足一片(49ms)单片处理");
fakes_reset();
time_sync_delay_fed(30000U);
CHECK(s_delay_calls == 600, "TC-TS-006 总预算 30s = 600 片(片长上限恒 50ms)");
}
/* ===================== TC-TS-010..015:open_listener ===================== */
static void tc_open_listener(void) {
fakes_reset();
{
int s = time_sync_open_listener();
CHECK(s == 5, "TC-TS-010 open_listener 成功返回 socket 描述符");
CHECK((s_socket_calls == 1) && (s_bind_calls == 1) && (s_listen_calls == 1),
"TC-TS-010b socket / bind / listen 各一次");
CHECK(s_htons_last == (uint16_t)TIME_SYNC_PORT, "TC-TS-010c 端口经 net_htons(8888)");
CHECK(s_bind_port == net_htons((uint16_t)TIME_SYNC_PORT), "TC-TS-010d sin_port 为网络序");
CHECK(s_bind_family == (uint16_t)NET_AF_INET, "TC-TS-010e sin_family = NET_AF_INET");
CHECK(s_delay_calls == 0, "TC-TS-010f 成功路径无退避延时");
}
fakes_reset();
s_bind_seq[0] = -1;
s_bind_seq_n = 1;
{
int s = time_sync_open_listener();
CHECK(s == 5, "TC-TS-011 bind 失败一次后重试成功");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TS-011b bind 失败关闭该 socket");
CHECK(s_delay_calls == 40, "TC-TS-011c bind 失败退避 2000ms(40 片)");
CHECK(g_tstest_err_count == 1, "TC-TS-011d bind 失败留痕 DBG_ERROR");
}
fakes_reset();
s_listen_seq[0] = -1;
s_listen_seq_n = 1;
{
int s = time_sync_open_listener();
CHECK(s == 5, "TC-TS-012 listen 失败一次后重试成功");
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TS-012b listen 失败关闭该 socket");
CHECK(s_delay_calls == 40, "TC-TS-012c listen 失败退避 2000ms");
}
fakes_reset();
s_socket_default = -1;
{
int s = time_sync_open_listener();
CHECK(s == -1, "TC-TS-013 socket 连续失败 -> 返回 -1");
CHECK(s_socket_calls == 5, "TC-TS-013b 重试上限 5 次");
CHECK(g_tstest_err_count == 5, "TC-TS-013c 每次失败各留痕一条(5 条 DBG_ERROR)");
CHECK(s_delay_calls == 50, "TC-TS-013d 每次失败退避 500ms(5 × 10 片)");
}
fakes_reset();
s_socket_seq[0] = -1;
s_socket_seq[1] = -1;
s_socket_seq[2] = 9;
s_socket_seq_n = 3;
{
int s = time_sync_open_listener();
CHECK(s == 9, "TC-TS-014 第 3 次重试成功并返回新描述符");
CHECK(s_socket_calls == 3, "TC-TS-014b 恰好尝试 3 次");
CHECK(s_delay_calls == 20, "TC-TS-014c 两次失败退避 500ms × 2");
}
fakes_reset();
s_socket_default = -1;
(void)time_sync_open_listener();
CHECK(s_beat_calls == 55, "TC-TS-015 每轮重试(5) + 每片延时(50) 各喂一次心跳");
CHECK(s_beat_id_bad == 0, "TC-TS-015b 心跳位恒为 TASK_WD_TIMESYNC");
}
/* ===================== TC-TS-020..029:handle_conn =====================
* ⚠ N34 阶段1(2026-10-13)契约变更:handle_conn **不再** net_close
* (单连接模式下 conn==listen,close 会连监听一起毁);会话收尾只返回外层,
* 由 time_sync_task 在 `conn != ls`(模拟器多连接后端)时才关数据 fd。
* 故本组断言一律为 s_close_calls == 0;任务级关闭见 TC-TS-040e。 */
static void tc_handle_conn(void) {
uint8_t pkt[16];
/* 1) 8 字节一次收齐 */
fakes_reset();
make_time_packet(pkt, 0x12345678u);
recv_set(0, 8, pkt, 8, 0U);
s_recv_seq_n = 1;
s_tick = 100U;
time_sync_handle_conn(7);
CHECK(s_set_unix_calls == 1, "TC-TS-020 TIME 包一次收齐 -> 更新墙钟一次");
CHECK(s_set_unix_last == 0x12345678u, "TC-TS-020b epoch 按大端解析");
CHECK((s_recv_calls == 1) && (s_recv_len_log[0] == 16), "TC-TS-020c 首次请求 16 字节");
CHECK(s_recv_to_log[0] == 50U, "TC-TS-020d 单次等待片长 = 50ms");
CHECK(s_close_calls == 0, "TC-TS-020e 处理完**不**关闭连接(N34 阶段1:close 交外层 conn!=ls 分支)");
CHECK(s_drop_calls == 0, "TC-TS-020g 成功会话不拆(drop 只用于放弃路径)");
CHECK(g_tstest_info_count == 1, "TC-TS-020f 留痕 wall clock updated");
/* 2) 分段送达:4 + 4 */
fakes_reset();
make_time_packet(pkt, 10u);
recv_set(0, 4, pkt, 4, 10U);
recv_set(1, 4, pkt + 4, 4, 10U);
s_recv_seq_n = 2;
time_sync_handle_conn(3);
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 10u), "TC-TS-021 分段送达累积后解析正确");
CHECK(s_recv_calls == 2, "TC-TS-021b 读了两次");
CHECK((s_recv_len_log[0] == 16) && (s_recv_len_log[1] == 12),
"TC-TS-021c 第二次只请求剩余 12 字节");
CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 50U), "TC-TS-021d 每片 50ms(预算充裕)");
/* 3) 16 字节一次到达(只取前 8) */
fakes_reset();
make_time_packet(pkt, 5u);
recv_set(0, 16, pkt, 16, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(4);
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 5u), "TC-TS-022 超长包只取前 8 字节");
CHECK(s_recv_calls == 1, "TC-TS-022b 一次读够即停");
/* 4) 前缀不是 TIME */
fakes_reset();
pkt[0] = (uint8_t)'X';
pkt[1] = (uint8_t)'X';
pkt[2] = (uint8_t)'X';
pkt[3] = (uint8_t)'X';
pkt[4] = 0U;
pkt[5] = 0U;
pkt[6] = 0U;
pkt[7] = 1U;
recv_set(0, 8, pkt, 8, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(4);
CHECK(s_set_unix_calls == 0, "TC-TS-023 非 TIME 前缀不更新墙钟");
CHECK(s_close_calls == 0, "TC-TS-023b 非 TIME 前缀也不关闭连接");
/* 5) 对端关闭(EOF) */
fakes_reset();
recv_set(0, 0, NULL, 0, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK((s_set_unix_calls == 0) && (s_recv_calls == 1), "TC-TS-024 对端关闭(EOF)立即退出不解析");
CHECK(s_close_calls == 0, "TC-TS-024b EOF 也不关闭连接(net 层 auto_relisten 收口监听)");
/* 6) 真错误 */
fakes_reset();
recv_set(0, -1, NULL, 0, 0U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK((s_recv_calls == 1) && (s_set_unix_calls == 0), "TC-TS-025 真错误立即退出不解析");
/* 7) 4 字节后 EOF */
fakes_reset();
make_time_packet(pkt, 7u);
recv_set(0, 4, pkt, 4, 0U);
recv_set(1, 0, NULL, 0, 0U);
s_recv_seq_n = 2;
time_sync_handle_conn(6);
CHECK(s_set_unix_calls == 0, "TC-TS-026 不足 8 字节不解析");
CHECK(s_recv_calls == 2, "TC-TS-026b 继续等待后续字节");
/* 8) 30s 预算耗尽 */
fakes_reset();
recv_set(0, -2, NULL, 0, 30000U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK(s_recv_calls == 1, "TC-TS-027 预算耗尽后不再读(只读 1 次)");
CHECK(g_tstest_info_count == 1, "TC-TS-027b 留痕 no TIME packet in 30s, drop conn");
CHECK(s_close_calls == 0, "TC-TS-027c 预算耗尽也不关闭连接");
CHECK(s_drop_calls == 1, "TC-TS-027d 预算耗尽主动拆会话(N34 阶段 3 E-02 收口:net_drop_conn 拆连接保监听)");
/* 9) 剩余预算不足一片 */
fakes_reset();
recv_set(0, -2, NULL, 0, 29980U);
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK(s_recv_calls == 2, "TC-TS-028 两次等待后结束");
CHECK((s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 20U),
"TC-TS-028b 第二次片长 = 剩余 20ms");
/* 10) 32 位 tick 回绕 */
fakes_reset();
s_tick = 0xFFFFFF00U;
recv_set(0, -2, NULL, 0, 512U); /* 推进后回绕到 0x00000100 */
s_recv_seq_n = 1;
time_sync_handle_conn(6);
CHECK((s_recv_calls == 2) && (s_recv_to_log[0] == 50U) && (s_recv_to_log[1] == 50U),
"TC-TS-029 32 位 tick 回绕后预算计算仍正确");
}
/* ===================== TC-TS-040..044:time_sync_task ===================== */
static void tc_task(void) {
uint8_t pkt[16];
/* A) 无就绪屏障(sys_evtHandle == NULL):accept 轮询 2 次失败后成功 */
fakes_reset();
make_time_packet(pkt, 9u);
recv_set(0, 8, pkt, 8, 0U);
s_recv_seq_n = 1;
s_accept_seq[0] = -1;
s_accept_seq[1] = -1;
s_accept_seq[2] = 7;
s_accept_seq_n = 3;
/* gen 调用序:iter1 顶部(1) + accept 两次失败各一次(2,3) + iter2 顶部(4) → 第 4 次跳出 */
s_gen_longjmp_at = 4;
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK((s_wd_bind_calls == 1) && (s_wd_bind_id_bad == 0),
"TC-TS-040 任务入口登记心跳位(task_wd_bind 一次)");
CHECK(s_evt_calls == 0, "TC-TS-040b sys_evtHandle==NULL 时跳过就绪屏障");
CHECK(s_accept_calls == 3, "TC-TS-040c accept 轮询 3 次(2 次无连接 + 1 次成功)");
CHECK((s_set_unix_calls == 1) && (s_set_unix_last == 9u), "TC-TS-040d 收到时间包并更新墙钟");
CHECK(s_close_calls == 1, "TC-TS-040e 连接被关闭一次");
CHECK(s_delay_calls == 9, "TC-TS-040f 延时片数 = 4+4+1(200+200+50ms)");
CHECK(delays_all(50U, 9), "TC-TS-040g 所有延时均按 50ms 分片");
CHECK(s_beat_calls == 13, "TC-TS-040h 心跳次数 = 主循环1 + 退避8 + 收连接1 + 收尾1 + 次轮1");
CHECK(s_beat_id_bad == 0, "TC-TS-040i 所有心跳均打 TIMESYNC 位");
/* B) 就绪屏障:2 次未就绪 + 1 次就绪 */
fakes_reset();
sys_evtHandle = (osEventFlagsId_t)0x55;
s_evt_seq[0] = osFlagsErrorTimeout;
s_evt_seq[1] = osFlagsErrorTimeout;
s_evt_seq[2] = SYS_EVT_NET_READY;
s_evt_seq_n = 3;
s_gen_longjmp_at = 1;
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK(s_evt_calls == 3, "TC-TS-041 就绪屏障轮询 3 次(2 次未就绪 + 1 次就绪)");
CHECK(s_evt_last_timeout == 100U, "TC-TS-041b 屏障等待按 100ms 分片");
CHECK(s_evt_last_flags == SYS_EVT_NET_READY, "TC-TS-041c 等待 NET_READY 位");
CHECK(s_evt_last_options == (osFlagsWaitAny | osFlagsNoClear), "TC-TS-041d 就绪位不被消费");
CHECK(s_beat_calls == 3, "TC-TS-041e 未就绪的两次各喂一次心跳(+主循环 1 次)");
CHECK(s_wd_bind_calls == 1, "TC-TS-041f 仍先登记心跳路由");
/* C) 屏障期间持续喂心跳(就绪位长期不置位) */
fakes_reset();
sys_evtHandle = (osEventFlagsId_t)0x55;
s_evt_default = osFlagsErrorTimeout;
s_evt_longjmp_at = 4;
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK(s_evt_calls == 4, "TC-TS-042 屏障等待 4 次均未就绪(循环未挂死)");
CHECK(s_beat_calls == 3, "TC-TS-042b 每次未就绪后各喂一次心跳(第 4 次跳出前)");
/* D) 网络代次变化 -> 关闭旧监听并重监听 */
fakes_reset();
s_gen_seq[0] = 1u;
s_gen_seq[1] = 2u;
s_gen_seq_n = 2;
s_gen_default = 2u;
s_gen_longjmp_at = 4;
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK((s_close_calls == 1) && (s_close_socks[0] == 5), "TC-TS-043 代次变化时关闭旧监听");
/* INFO 三条:两次 open_listener 成功各一条 "TCP listen on" + 一条 "network re-init, relisten" */
CHECK(g_tstest_info_count == 3, "TC-TS-043b 留痕 network re-init, relisten(另含 2 条 listen 成功)");
CHECK(s_accept_calls == 2, "TC-TS-043c 每次循环只轮询一次 accept 即放弃");
CHECK((s_socket_calls == 2) && (s_bind_calls == 2) && (s_listen_calls == 2),
"TC-TS-043d 第二次循环重建了监听");
/* E) 监听建立失败 -> 5 次重试后放弃并退避 2000ms */
fakes_reset();
s_socket_default = -1;
s_gen_longjmp_at = 2;
if (setjmp(s_jmp) == 0) {
time_sync_task(NULL);
}
CHECK(s_socket_calls == 5, "TC-TS-044 open_listener 内部重试 5 次后放弃");
CHECK(g_tstest_err_count == 6, "TC-TS-044b 5 条 net_socket FAIL + 1 条 retry later");
CHECK(s_delay_calls == 90, "TC-TS-044c 退避 5×500ms + 2000ms = 90 片");
CHECK(s_beat_calls == 97, "TC-TS-044d 心跳次数 = 2 + 5 + 50 + 40");
CHECK(s_beat_id_bad == 0, "TC-TS-044e 所有心跳均打 TIMESYNC 位");
}
/* ===================== main ===================== */
int main(void) {
printf("=== Time Sync Tests ===\n");
printf("-- delay_fed (heartbeat-fed slicing) --\n");
tc_delay_fed();
printf("-- open_listener (retry / backoff) --\n");
tc_open_listener();
printf("-- handle_conn (packet reassembly / budget) --\n");
tc_handle_conn();
printf("-- time_sync_task (barrier / accept / relisten) --\n");
tc_task();
printf("\n=== Time Sync Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}