/* * 模块名称:Time SNTP 主机单测(test/host/sntp) * 模块功能:把 App/sync/time_sntp.c + Drivers/BSP/NETCFG/sntp_cfg.c + * vendor Lib/coreSNTP/core_sntp_serializer.c 一起编进本 TU, * 用脚本化 net/sys_clock/dbg fake 驱动状态机,验证: * RFC 4330 §10 节奏(初始散布/失败翻倍封顶/成功重排)、§5.3 防欺骗 * (源 IP:port / originate 精确匹配 / mode / LI=3 / stratum / KoD)、 * §5.5 步进策略(±2s 死区、冷启动直采 T3、时区面 +28800)、 * poll 钳位与 set 入口、代次/停用收口。 * ([sntp] 段描述符注册断言在 test/host/net_cfg——分层门禁后 * 描述符由 net_cfg.c 组装,本套件只并 sntp_cfg.c 数据本体。) * 约束:生产 .c 以 #include 方式并入——static 白盒可达;fake 计数前缀唯一, * 避免同名 tentative static 静默合并(2026-10-10 教训)。 */ #include #include #include /* stub 头先行:fake 定义要用到这些类型/宏 */ #include "FreeRTOS.h" #include "task.h" #include "cmsis_os2.h" #include "net_types.h" #include "net_socket.h" #include "sys_clock.h" #include "sys_events.h" #include "sys_log.h" #include "dbg_log.h" /* ---- fake 存储(stub/dbg_log.h 已声明 g_dbg_*_count extern,此处落定义) ---- */ int g_dbg_err_count; int g_dbg_warn_count; int g_dbg_info_count; /* 系统 tick(ms) */ static uint32_t g_tick; /* 时钟面:UINT32_MAX = 未初始化;其余 = 本地秒 */ static uint32_t g_face; static uint32_t g_face_set_last; static int g_face_set_calls; /* 事件标志 / 操作日志 */ osEventFlagsId_t sys_evtHandle; /* 测试里指到假对象(非 NULL) */ static int g_evt_calls; static uint32_t g_evt_last; static int g_syslog_calls; static syslog_id_t g_syslog_last_id; /* ---- net fake ---- */ #define SNTP_T_NET_SEND_MAX 8 #define SNTP_T_NET_RECV_MAX 8 #define SNTP_T_NET_IP(o1,o2,o3,o4) { (o1), (o2), (o3), (o4) } static uint32_t g_gen; static int g_sock_next_fd = 100; static int g_sock_opens; static int g_sock_close_calls; static int g_last_close_fd = -1; static int g_bind_calls; static int g_bind_ret; /* 脚本:0 成功 / -1 失败 */ static int g_errno_val; static int g_send_calls; static uint8_t g_sent_ip[4]; static uint16_t g_sent_port_be; static uint8_t g_sent_pkt[128]; static int g_sent_len; static int g_send_ret; /* 脚本:48 / -1 */ static int g_send_history_n; /* 每轮 send_calls 快照(历史计数) */ struct sntp_t_recv { int kind; /* NET_RECV_* */ int len; /* kind==DATA 有效 */ uint8_t data[128]; uint8_t ip[4]; uint16_t port_be; }; static struct sntp_t_recv g_recv_q[SNTP_T_NET_RECV_MAX]; static int g_recv_head, g_recv_tail; static uint32_t g_recv_last_timeout_ms; /* 最近一次 recv 的片超时(50ms 断言) */ /* ---- cfg fake:[sntp] 段描述符组装已移 net_cfg.c(分层门禁),注册内容 * 断言归 test/host/net_cfg;本套件只需 sntp_cfg.c 数据本体可用,无引擎调用 ---- */ /* ---- dbg 宏(stub/dbg_log.h 已声明 g_dbg_*,此处无需再定义宏) ---- */ /* ---- sys_clock / sys_events / sys_log / cmsis fake 实现 ---- */ uint32_t sys_clock_get(void) { return g_face; } int sys_clock_set_unix(uint32_t unix_secs) { g_face_set_calls++; g_face_set_last = unix_secs; g_face = unix_secs; return 0; } uint32_t xTaskGetTickCount(void) { return g_tick; } uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t events) { (void)ef_id; g_evt_calls++; g_evt_last = events; return events; } void syslog_post_quiet(syslog_id_t id, const char *desc) { (void)desc; g_syslog_calls++; g_syslog_last_id = id; } /* ---- net fake 实现 ---- */ static void sntp_t_net_reset(void) { g_sock_next_fd = 100; g_sock_opens = 0; g_sock_close_calls = 0; g_last_close_fd = -1; g_bind_calls = 0; g_bind_ret = 0; g_errno_val = 0; g_send_calls = 0; g_send_ret = 48; g_send_history_n = 0; g_recv_head = g_recv_tail = 0; g_recv_last_timeout_ms = 0; memset(g_sent_ip, 0, sizeof(g_sent_ip)); memset(g_sent_pkt, 0, sizeof(g_sent_pkt)); g_sent_len = 0; g_sent_port_be = 0; } static void sntp_t_push_recv(int kind, const uint8_t *data, int len, const uint8_t *ip, uint16_t port_be) { struct sntp_t_recv *r; if (g_recv_tail - g_recv_head >= SNTP_T_NET_RECV_MAX) { printf("recv queue overflow\n"); return; } r = &g_recv_q[g_recv_tail++ % SNTP_T_NET_RECV_MAX]; r->kind = kind; r->len = len; r->port_be = port_be; if (data != NULL && len > 0) { memcpy(r->data, data, (size_t)len); } if (ip != NULL) { memcpy(r->ip, ip, 4); } else { memset(r->ip, 0, 4); } } int net_socket(int domain, int type) { (void)domain; (void)type; g_sock_opens++; return g_sock_next_fd++; } int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) { (void)sockfd; (void)addr; (void)addrlen; g_bind_calls++; return g_bind_ret; } int net_sendto(int sockfd, const void *buf, int len, int flags, const struct net_sockaddr *dest, int addrlen) { const struct net_sockaddr_in *d = (const struct net_sockaddr_in *)dest; (void)sockfd; (void)flags; (void)addrlen; g_send_calls++; g_send_history_n = g_send_calls; if (len > 0 && len <= (int)sizeof(g_sent_pkt)) { memcpy(g_sent_pkt, buf, (size_t)len); } g_sent_len = len; if (dest != NULL) { memcpy(g_sent_ip, &d->sin_addr.s_addr, 4); g_sent_port_be = d->sin_port; } return g_send_ret; } int net_recvfrom_to(int sockfd, void *buf, int len, uint32_t timeout_ms, struct net_sockaddr *src, int *addrlen) { struct sntp_t_recv *r; struct net_sockaddr_in *sin = (struct net_sockaddr_in *)src; (void)sockfd; g_recv_last_timeout_ms = timeout_ms; if (g_recv_head == g_recv_tail) { return -2; /* 队列空 = 无应答(合同 -2) */ } r = &g_recv_q[g_recv_head++ % SNTP_T_NET_RECV_MAX]; if (r->kind != NET_RECV_DATA) { return r->kind; /* -2 超时 / -1 错误 / 0 防御 */ } if (r->len > len) { r->len = len; } memcpy(buf, r->data, (size_t)r->len); if (src != NULL && addrlen != NULL && *addrlen >= (int)sizeof(*sin)) { memset(sin, 0, sizeof(*sin)); sin->sin_family = NET_AF_INET; sin->sin_port = r->port_be; memcpy(&sin->sin_addr.s_addr, r->ip, 4); } return r->len; } int net_close(int sockfd) { g_sock_close_calls++; g_last_close_fd = sockfd; return 0; } int net_get_errno(void) { return g_errno_val; } uint32_t net_get_generation(void) { return g_gen; } /* ---- 生产代码并入(顺序:vendor → 配置 → 客户端) ---- */ #include "core_sntp_serializer.c" #include "sntp_cfg.c" #include "time_sntp.c" /* ---- 测试框架 ---- */ static int g_total, g_pass, g_fail; #define CHECK(cond, name) \ do { \ g_total++; \ if (cond) { g_pass++; } \ else { g_fail++; printf(" FAIL %s (line %d)\n", (name), __LINE__); } \ } while (0) static void s_case_reset(void) { sntp_cfg_t *c; /* 配置先归零,再借代次跳变走"生产复位通道"(关 socket + 状态回 IDLE) */ c = (sntp_cfg_t *)(void *)sntp_cfg_get(); c->enable = 0u; memset(c->server_pri, 0, 4); memset(c->server_bak, 0, 4); c->poll_min = 60u; g_gen++; (void)time_sntp_run(g_gen); g_tick = 10000u; /* 避开 0 */ g_face = UINT32_MAX; g_face_set_calls = 0; g_face_set_last = 0u; g_evt_calls = 0; g_evt_last = 0; g_syslog_calls = 0; g_dbg_err_count = g_dbg_warn_count = g_dbg_info_count = 0; sntp_t_net_reset(); sys_evtHandle = (osEventFlagsId_t)&g_evt_calls; /* 假句柄:非 NULL */ s_ctx.srv = 0u; } static void s_cfg_enable(const uint8_t *pri, const uint8_t *bak, uint16_t poll) { sntp_cfg_t *c = (sntp_cfg_t *)(void *)sntp_cfg_get(); c->enable = 1u; if (pri != NULL) { memcpy(c->server_pri, pri, 4); } if (bak != NULL) { memcpy(c->server_bak, bak, 4); } c->poll_min = poll; } /* 推进到"下一次"发出请求(基线=当前计数,有界轮询),返回 1=新发一包 */ static int s_run_until_send(uint32_t max_ms) { uint32_t t; int base = g_send_calls; for (t = 0; t < max_ms; t += 200u) { g_tick += 200u; time_sntp_run(g_gen); if (g_send_calls > base) { return 1; } } return 0; } /* 从 g_sent_pkt 的 transmit 时间戳(40..47)拼一条合法应答(不含 KoD) */ static void s_build_resp(uint8_t *out, uint8_t li, uint8_t stratum, uint32_t t2_sec, uint32_t t2_frac, uint32_t t3_sec, uint32_t t3_frac) { memset(out, 0, 48); out[0] = (uint8_t)(((li & 3u) << 6) | (4u << 3) | 4u); /* VN4 mode=4 */ out[1] = stratum; memcpy(&out[24], &g_sent_pkt[40], 8); /* originate = T1 原样 */ /* recvTs 非零(serializer 要求三时间戳非零) */ out[32] = (uint8_t)(t2_sec >> 24); out[33] = (uint8_t)(t2_sec >> 16); out[34] = (uint8_t)(t2_sec >> 8); out[35] = (uint8_t)t2_sec; out[36] = (uint8_t)(t2_frac >> 24); out[37] = (uint8_t)(t2_frac >> 16); out[38] = (uint8_t)(t2_frac >> 8); out[39] = (uint8_t)t2_frac; out[40] = (uint8_t)(t3_sec >> 24); out[41] = (uint8_t)(t3_sec >> 16); out[42] = (uint8_t)(t3_sec >> 8); out[43] = (uint8_t)t3_sec; out[44] = (uint8_t)(t3_frac >> 24); out[45] = (uint8_t)(t3_frac >> 16); out[46] = (uint8_t)(t3_frac >> 8); out[47] = (uint8_t)t3_frac; } static uint32_t s_be32(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } /* 等应答态喂一包(服务器地址 = 本轮实际发送目的),返回后状态自取 */ static void s_feed(const uint8_t *resp, int len, const uint8_t *src_ip, uint16_t src_port) { sntp_t_push_recv(NET_RECV_DATA, resp, len, src_ip, src_port); g_tick += 200u; time_sntp_run(g_gen); } /* ============================ 测试用例 ============================ */ static void tc_poll_clamp(void) { sntp_cfg_t *c; /* [sntp] 段描述符/键表/默认值的注册断言在 test/host/net_cfg(描述符 已改由 net_cfg.c 组装);这里只钉客户端实际消费的 poll 钳位口 */ s_case_reset(); c = (sntp_cfg_t *)(void *)sntp_cfg_get(); c->poll_min = 0u; CHECK(sntp_cfg_poll_min() == 60u, "cfg: poll 0 -> 60"); c->poll_min = 1441u; CHECK(sntp_cfg_poll_min() == 60u, "cfg: poll 1441 -> 60"); c->poll_min = 2u; CHECK(sntp_cfg_poll_min() == 2u, "cfg: poll 2 kept"); CHECK(c->poll_min == 0u || c->poll_min == 2u || c->poll_min == 1441u, "cfg: clamp no writeback"); c->poll_min = 60u; /* set(NULL) 不改写 + set 整包拷贝(UI 编辑入口的行为钉) */ sntp_cfg_set(NULL); CHECK(sntp_cfg_poll_min() == 60u, "cfg: set(NULL) no-op"); } static void tc_disabled_silent(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); s_case_reset(); s_cfg_enable(pri, NULL, 60u); ((sntp_cfg_t *)sntp_cfg_get())->enable = 0u; (void)time_sntp_run(g_gen); CHECK(g_send_calls == 0 && g_sock_opens == 0, "disable: no send/no socket"); } static void tc_first_send_window(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint32_t t0; s_case_reset(); t0 = g_tick; s_cfg_enable(pri, NULL, 60u); CHECK(s_run_until_send(320000u) == 1, "first: send happened"); CHECK(g_tick - t0 >= SNTP_FIRST_DELAY_MIN_MS && g_tick - t0 <= SNTP_FIRST_DELAY_MIN_MS + SNTP_FIRST_DELAY_SPAN_MS, "first: 1..5min window"); CHECK(g_send_calls == 1, "first: single send"); CHECK(g_sent_len == 48, "first: 48B request"); CHECK(g_sent_pkt[0] == 0x23u, "first: LI0 VN4 mode3"); CHECK(g_sent_port_be == net_htons(123u), "first: dst port 123 BE"); CHECK(memcmp(g_sent_ip, pri, 4) == 0, "first: dst = server1"); CHECK(s_be32(&g_sent_pkt[40]) != 0u, "first: T1 nonzero"); CHECK(g_sock_opens == 1 && g_bind_calls == 1, "first: socket opened+bound"); } static void tc_cold_start_step_t3(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t resp[48]; s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); g_tick = (g_tick / 1000u) * 1000u; /* 分数归零,期望精确 */ /* 冷启动:偏移乱也无妨——直接采用服务器 T3(调研 §5.5)。 * T3 用真实当前时刻(服务器不受本地冷钟 epoch 起点影响) */ { const uint32_t real_unix = 1750000000u; /* 2025-06-15,域内 */ s_build_resp(resp, 0u, 5u, SNTP_TIME_AT_UNIX_EPOCH_SECS + real_unix + 1u, 0u, SNTP_TIME_AT_UNIX_EPOCH_SECS + real_unix, 0u); s_feed(resp, 48, pri, net_htons(123u)); CHECK(time_sntp_sync_count() >= 1u, "cold: sync counted"); CHECK(g_face_set_calls == 1, "cold: step once"); CHECK(g_face_set_last == real_unix + 28800u, "cold: face = T3 unix + 8h"); } CHECK(g_syslog_calls == 1 && g_syslog_last_id == SYSLOG_OPER, "cold: oper log"); CHECK(g_evt_calls == 1 && g_evt_last == SYS_EVT_TIMESYNC_READY, "cold: ready evt"); /* 成功节奏:下一轮 = poll_min(60 分钟),退避复位 */ CHECK(s_ctx.state == SNTP_ST_WAIT_DUE && s_ctx.timeout_ms == 5000u, "cold: backoff reset"); } static void tc_step_valid_clock(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t resp[48]; uint32_t t1s; uint32_t face_before = 1750000123u + 28800u; /* 有效本地钟(2025+) */ s_case_reset(); g_face = face_before; s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); t1s = s_be32(&g_sent_pkt[40]); g_tick = (g_tick / 1000u) * 1000u; /* 服务器快 30s:T2=T3=T1+30s,T4=T1 → offset=+30000ms(>2s 死区 → 步进) */ s_build_resp(resp, 0u, 5u, t1s + 30u, 0u, t1s + 30u, 0u); s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 1, "step: stepped once"); CHECK(g_face_set_last == face_before + 30u, "step: face += 30s"); CHECK(time_sntp_last_offset_ms() >= 29499 && time_sntp_last_offset_ms() <= 30001, "step: offset ~30000ms"); } static void tc_deadband_no_step(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t resp[48]; uint32_t t1s; uint32_t cnt0 = time_sntp_sync_count(); s_case_reset(); g_face = 1750000000u + 28800u; s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); t1s = s_be32(&g_sent_pkt[40]); g_tick = (g_tick / 1000u) * 1000u; /* ±1s < 2s 死区:只记录不拨针(防 SD2506 磨损,调研 §5.5) */ s_build_resp(resp, 0u, 5u, t1s, 0u, t1s + 1u, 0u); s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 0, "deadband: no step"); CHECK(g_syslog_calls == 0, "deadband: no oper log"); CHECK(time_sntp_sync_count() == cnt0 + 1u, "deadband: counted sync"); CHECK(s_ctx.state == SNTP_ST_WAIT_DUE, "deadband: next poll scheduled"); } static void tc_anti_spoof_src(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t other[] = SNTP_T_NET_IP(192, 168, 31, 66); uint8_t resp[48]; uint32_t t1s; s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 0u, 5u, t1s, 0u, t1s, 0u); /* 源 IP 不对:丢弃继续等(§5.3 第一道) */ s_feed(resp, 48, other, net_htons(123u)); CHECK(g_face_set_calls == 0, "spoof: wrong ip not stepped"); CHECK(s_ctx.state == SNTP_ST_WAIT_RESP, "spoof: still waiting"); /* 源端口不对(123→999):同样丢弃 */ s_feed(resp, 48, pri, net_htons(999u)); CHECK(g_face_set_calls == 0 && s_ctx.state == SNTP_ST_WAIT_RESP, "spoof: wrong port"); /* originate 篡改:丢弃 */ resp[24] ^= 0xA5u; s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 0 && s_ctx.state == SNTP_ST_WAIT_RESP, "spoof: bad originate"); /* mode=3(服务器回显错 mode):serializer 判无效 → 丢弃 */ s_build_resp(resp, 0u, 5u, t1s, 0u, t1s, 0u); resp[0] = (uint8_t)((resp[0] & 0xF8u) | 3u); s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 0 && s_ctx.state == SNTP_ST_WAIT_RESP, "spoof: bad mode"); /* 短包(<48):丢弃 */ s_feed(resp, 40, pri, net_htons(123u)); CHECK(g_face_set_calls == 0 && s_ctx.state == SNTP_ST_WAIT_RESP, "spoof: short pkt"); /* 最后合法包仍可达:链路未被误伤 */ s_build_resp(resp, 0u, 5u, t1s, 0u, t1s, 0u); g_face = 1750000000u + 28800u; s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 0, "spoof: valid after noise (deadband)"); CHECK(s_ctx.state == SNTP_ST_WAIT_DUE, "spoof: recovered to schedule"); } static void tc_li3_stratum(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t bak[] = SNTP_T_NET_IP(192, 168, 31, 13); uint8_t resp[48]; uint32_t t1s; int sends_at_li; s_case_reset(); s_cfg_enable(pri, bak, 60u); (void)s_run_until_send(320000u); /* LI=3:服务器没同步 → 立即失败 + 切备 + 翻倍退避(serializer 不拦,我们拦) */ t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 3u, 5u, t1s, 0u, t1s, 0u); sends_at_li = g_send_calls; s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 0, "li3: no step"); CHECK(s_ctx.state == SNTP_ST_WAIT_DUE && s_ctx.timeout_ms == 10000u, "li3: doubled backoff"); CHECK(s_ctx.srv == 1u, "li3: switched to backup"); /* 备机应答 stratum=16(非法):失败退避但不再切换(只拦包不怪服务器方向? * 调研:stratum 非法 = 无效应答 → 退避;主备保持) */ (void)s_run_until_send(20000u); CHECK(g_send_calls == sends_at_li + 1, "li3: retry to backup"); CHECK(memcmp(g_sent_ip, bak, 4) == 0, "li3: dst=backup now"); t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 0u, 16u, t1s, 0u, t1s, 0u); s_feed(resp, 48, bak, net_htons(123u)); CHECK(s_ctx.state == SNTP_ST_WAIT_DUE && s_ctx.timeout_ms == 20000u, "stratum16: backoff"); /* 本仓无 DBG_WARN 宏(dbg_log.h 只有 ERROR/INFO/DEBUG,net_udp.c:756 钉), * 生产侧 WARN 走 DBG_INFO("WARN: …")——计数看 info */ CHECK(g_dbg_info_count >= 2, "stratum/li3: warned"); } static void tc_kod(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t bak[] = SNTP_T_NET_IP(192, 168, 31, 13); uint8_t resp[48]; uint32_t t1s; s_case_reset(); s_cfg_enable(pri, bak, 60u); /* KoD stratum=0 + refId 码:DENY → 切备(§5.3/RFC) */ (void)s_run_until_send(320000u); t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 0u, 0u, t1s, 0u, t1s, 0u); resp[12] = 'D'; resp[13] = 'E'; resp[14] = 'N'; resp[15] = 'Y'; s_feed(resp, 48, pri, net_htons(123u)); CHECK(g_face_set_calls == 0 && s_ctx.srv == 1u, "kod DENY: switch"); /* RATE:退避但保留当前服务器 */ (void)s_run_until_send(20000u); t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 0u, 0u, t1s, 0u, t1s, 0u); resp[12] = 'R'; resp[13] = 'A'; resp[14] = 'T'; resp[15] = 'E'; s_feed(resp, 48, bak, net_htons(123u)); CHECK(s_ctx.srv == 1u && s_ctx.timeout_ms == 20000u, "kod RATE: keep+backoff"); /* 未知码:按 OtherCode 切走 */ (void)s_run_until_send(40000u); t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 0u, 0u, t1s, 0u, t1s, 0u); resp[12] = '?'; resp[13] = '?'; resp[14] = '?'; resp[15] = '?'; s_feed(resp, 48, bak, net_htons(123u)); CHECK(s_ctx.srv == 0u, "kod other: switch"); } static void tc_timeout_backoff_cap(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint32_t t; int i; s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); /* 无应答:5s 片到点 → 退避翻倍;socket 保留 */ for (t = 0; t < 6000u; t += 200u) { g_tick += 200u; time_sntp_run(g_gen); } CHECK(s_ctx.state == SNTP_ST_WAIT_DUE && s_ctx.timeout_ms == 10000u, "timeout: doubled 5s->10s"); CHECK(g_sock_close_calls == 0, "timeout: socket kept"); /* 连打封顶 60s(RFC 间隔递增),单参数化推进:每轮 = 应窗 + 应答窗 */ for (i = 0; i < 10 && s_ctx.timeout_ms < SNTP_RESP_TIMEOUT_MAX_MS; i++) { uint32_t win = s_ctx.timeout_ms * 2u + 2000u; (void)s_run_until_send(win); for (t = 0; t < win && s_ctx.state == SNTP_ST_WAIT_RESP; t += 200u) { g_tick += 200u; time_sntp_run(g_gen); } } CHECK(s_ctx.timeout_ms == SNTP_RESP_TIMEOUT_MAX_MS, "timeout: cap 60s"); } static void tc_poll_pacing_after_success(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t resp[48]; uint32_t t1s, t; int send_at_success; s_case_reset(); s_cfg_enable(pri, NULL, 2u); /* 2 分钟一轮(钳位带内) */ (void)s_run_until_send(320000u); t1s = s_be32(&g_sent_pkt[40]); s_build_resp(resp, 0u, 5u, t1s, 0u, t1s, 0u); s_feed(resp, 48, pri, net_htons(123u)); send_at_success = g_send_calls; /* 下一轮 = poll_min*60s:1 分 50s 内不应有第 2 包 */ for (t = 0; t < 110000u; t += 200u) { g_tick += 200u; time_sntp_run(g_gen); } CHECK(g_send_calls == send_at_success, "poll: not early (<2min)"); for (t = 0; t < 20000u; t += 200u) { g_tick += 200u; time_sntp_run(g_gen); if (g_send_calls > send_at_success) { break; } } CHECK(g_send_calls == send_at_success + 1, "poll: second round on time"); } static void tc_no_server_warn_once(void) { uint32_t t; s_case_reset(); s_cfg_enable(NULL, NULL, 60u); /* enable 但两侧全零 */ for (t = 0; t < 700000u; t += 1000u) { g_tick += 1000u; time_sntp_run(g_gen); } CHECK(g_send_calls == 0, "nosrv: never sends"); CHECK(g_dbg_info_count >= 1, "nosrv: warned"); { int w0 = g_dbg_info_count; g_tick += 1000u; time_sntp_run(g_gen); CHECK(g_dbg_info_count == w0, "nosrv: warn once only"); } /* 配置补齐即恢复(不发请求不算失败,无退避拖尾) */ { sntp_cfg_t *c = (sntp_cfg_t *)(void *)sntp_cfg_get(); uint8_t pri[] = SNTP_T_NET_IP(10, 0, 0, 9); memcpy(c->server_pri, pri, 4); CHECK(s_run_until_send(320000u) == 1, "nosrv: recovers on config"); CHECK(memcmp(g_sent_ip, pri, 4) == 0, "nosrv: new server used"); } } static void tc_disable_closes_socket(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); sntp_cfg_t *c; s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); CHECK(g_sock_opens == 1, "disable: opened once"); c = (sntp_cfg_t *)(void *)sntp_cfg_get(); c->enable = 0u; g_tick += 200u; time_sntp_run(g_gen); CHECK(g_sock_close_calls == 1 && s_ctx.state == SNTP_ST_IDLE, "disable: closed+idle"); g_tick += 200u; time_sntp_run(g_gen); CHECK(g_sock_close_calls == 1, "disable: no double close"); } static void tc_gen_change_resets(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); int before; s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); before = g_send_calls; /* 网络代次变化(UI 保存重建 / 链路复位):弃 socket 重新散布; * 同一次 run 内 enable==1 时 IDLE 立即重排为 WAIT_DUE(重新 1..5min 散布) */ g_gen++; g_tick += 200u; time_sntp_run(g_gen); CHECK(g_sock_close_calls == 1 && s_ctx.state == SNTP_ST_WAIT_DUE && s_ctx.gen == g_gen, "gen: close+re-spread"); { uint32_t t; for (t = 0; t < 59000u; t += 200u) { /* 首 59s 不该再发 */ g_tick += 200u; time_sntp_run(g_gen); } CHECK(g_send_calls == before, "gen: fresh 1..5min re-spread"); } } static void tc_send_fail_switch(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t bak[] = SNTP_T_NET_IP(192, 168, 31, 13); s_case_reset(); s_cfg_enable(pri, bak, 60u); g_send_ret = -1; g_errno_val = NET_ERR_NOSPACE; (void)s_run_until_send(320000u); CHECK(s_ctx.state == SNTP_ST_WAIT_DUE && s_ctx.timeout_ms == 10000u, "sendfail: backoff"); CHECK(s_ctx.srv == 1u, "sendfail: switched"); g_send_ret = 48; (void)s_run_until_send(20000u); CHECK(memcmp(g_sent_ip, bak, 4) == 0, "sendfail: retry to backup"); } static void tc_recv_err_deadfd(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint32_t t; s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); sntp_t_push_recv(NET_RECV_ERR, NULL, 0, NULL, 0); g_errno_val = NET_ERR_CONNRESET; g_tick += 200u; time_sntp_run(g_gen); CHECK(g_last_close_fd >= 100 && s_ctx.fd == -1, "recverr: dead fd closed"); CHECK(s_ctx.state == SNTP_ST_WAIT_DUE, "recverr: backoff"); /* 下一轮到点自动重开 socket */ for (t = 0; t < 12000u; t += 200u) { g_tick += 200u; time_sntp_run(g_gen); if (g_send_calls >= 2) { break; } } CHECK(g_send_calls == 2 && g_sock_opens == 2, "recverr: reopened"); } static void tc_oversized_resp_ok(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); uint8_t resp[64]; uint32_t t1s; s_case_reset(); g_face = 1750000000u + 28800u; s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); t1s = s_be32(&g_sent_pkt[40]); memset(resp, 0xAA, sizeof(resp)); s_build_resp(resp, 0u, 5u, t1s, 0u, t1s, 0u); /* 前 48 合法,尾部垃圾 */ s_feed(resp, 56, pri, net_htons(123u)); /* 截断交付 = BSD 语义 */ CHECK(time_sntp_sync_count() >= 1u && s_ctx.state == SNTP_ST_WAIT_DUE, "oversize: parsed first 48"); } static void tc_recv_slice_50ms(void) { uint8_t pri[] = SNTP_T_NET_IP(192, 168, 31, 161); s_case_reset(); s_cfg_enable(pri, NULL, 60u); (void)s_run_until_send(320000u); g_recv_last_timeout_ms = 0u; g_tick += 200u; time_sntp_run(g_gen); CHECK(g_recv_last_timeout_ms == SNTP_RECV_SLICE_MS, "slice: 50ms watchdog-safe"); } static void tc_helpers_direct(void) { uint32_t x, i, prev; s_case_reset(); /* 回绕安全到期比较 */ CHECK(s_tick_due(1000u, 900u) == 1u, "due: past"); CHECK(s_tick_due(800u, 900u) == 0u, "due: future"); CHECK(s_tick_due(5u, 0xFFFFFFF0u) == 1u, "due: wrap past"); CHECK(s_tick_due(0xFFFFFFF0u - 5u, 0xFFFFFFF0u) == 0u, "due: wrap future"); /* int64 截断 */ CHECK(s_off_clamp(3000LL) == 3000, "clamp: passthrough"); CHECK(s_off_clamp(9999999999999LL) == 2147483647, "clamp: hi"); CHECK(s_off_clamp(-9999999999999LL) == -2147483647, "clamp: lo"); /* xorshift 永不为 0(0 会卡死) */ s_rng_seed_once(); prev = s_rng_next(); CHECK(prev != 0u, "rng: nonzero"); for (i = 0; i < 100000u; i++) { x = s_rng_next(); if (x == 0u) { break; } prev = x; } CHECK(prev != 0u && i == 100000u, "rng: stream nonzero 100k"); /* 冷启动兜底 T1 域:2036 前进位用 uint32 环绕语义(seconds 直接 ++ 可回绕) */ { SntpTimestamp_t ts; g_face = 0xFFFFFFFFu - 1000u; /* 钟面 > 0x7FFFFFFF → 视为无效域外? */ /* s_utc_now: face>=MIN+TZ 且 !=UINT32_MAX → 有效;epoch 加法环绕 */ s_ts_now(&ts); { /* uint32 模运算逐项环绕,与 s_ts_now 内部语义一致 */ uint32_t expu = (uint32_t)((0xFFFFFFFFu - 1000u - 28800u) + SNTP_TIME_AT_UNIX_EPOCH_SECS); CHECK(ts.seconds == expu, "ts: era wrap"); } g_face = UINT32_MAX; } } /* 把 host 序 32bit 以大端铺进 p[0..3](origin/receive/transmit 字段都要大端) */ static void s_fill_be(uint8_t *p, uint32_t v) { p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v; } static void tc_serializer_vendored(void) { SntpTimestamp_t t1, t4; SntpResponseData_t r; SntpTimestamp_t zero = { 0u, 0u }; uint8_t req[48]; uint8_t resp[48]; uint32_t us = 0u, us2 = 0u; s_case_reset(); t1.seconds = SNTP_TIME_AT_UNIX_EPOCH_SECS + 1000000000UL; /* 2001+,era0 */ t1.fractions = 0x12345678UL; CHECK(Sntp_SerializeRequest(&t1, 0xAABBCCDDu, req, 48u) == SntpSuccess, "ser: ok"); CHECK(req[0] == 0x23u, "ser: LI0 VN4 mode3"); /* 防欺骗核心:randomNumber>>16(16bit)OR 进 T1 分数低位 + 回写 pRequestTime * ——应答必须原样 echo 这个合成值,否则判无效 */ CHECK(t1.fractions == (0x12345678u | 0xAABBu), "ser: random merged into fractions"); CHECK(s_be32(&req[40]) == t1.seconds && s_be32(&req[44]) == t1.fractions, "ser: BE transmit stamped"); CHECK(Sntp_SerializeRequest(&zero, 0u, req, 48u) == SntpErrorBadParameter, "ser: zero T1 rejected"); CHECK(Sntp_SerializeRequest(&t1, 0u, req, 47u) == SntpErrorBufferTooSmall, "ser: small buf"); /* 合法应答 round-trip:origin = 序列化后的 t1 精确回显(大端) */ memset(resp, 0, 48); resp[0] = (uint8_t)((0u << 6) | (4u << 3) | 4u); resp[1] = 5u; s_fill_be(&resp[24], t1.seconds); s_fill_be(&resp[28], t1.fractions); s_fill_be(&resp[32], t1.seconds + 1u); s_fill_be(&resp[36], 0u); s_fill_be(&resp[40], t1.seconds + 2u); s_fill_be(&resp[44], 0x00000102u); t4 = t1; CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpSuccess, "ser: deserialize ok"); CHECK(r.leapSecondType == NoLeapSecond, "ser: leap none"); CHECK(r.serverTime.seconds == t1.seconds + 2u, "ser: T3 parsed"); /* originate 不匹配 → InvalidResponse(客户机据此丢弃继续等待) */ resp[24] ^= 0xFFu; CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpInvalidResponse, "ser: origin mismatch rejected"); resp[24] ^= 0xFFu; /* mode≠4:回显包/伪造包直接判无效 */ resp[0] = (uint8_t)((resp[0] & 0xF8u) | 3u); CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpInvalidResponse, "ser: mode!=4 rejected"); /* KoD:stratum=0 + refId 码分类(DENY/RSTR 换服、RATE 退避、其余 OtherCode) */ resp[0] = (uint8_t)((4u << 3) | 4u); resp[1] = 0u; resp[12] = 'D'; resp[13] = 'E'; resp[14] = 'N'; resp[15] = 'Y'; CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpRejectedResponseChangeServer, "ser: KoD DENY"); resp[12] = 'R'; resp[13] = 'A'; resp[14] = 'T'; resp[15] = 'E'; CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpRejectedResponseRetryWithBackoff, "ser: KoD RATE"); resp[12] = 'R'; resp[13] = 'S'; resp[14] = 'T'; resp[15] = 'R'; CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpRejectedResponseChangeServer, "ser: KoD RSTR"); resp[12] = '?'; CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpRejectedResponseOtherCode, "ser: KoD other"); resp[1] = 5u; /* 2036 纪元语义:era0 尾回减、era1 头回加 */ { SntpTimestamp_t e; e.seconds = SNTP_TIME_AT_UNIX_EPOCH_SECS; /* 1970-01-01 恰在界上 */ e.fractions = 0u; CHECK(Sntp_ConvertToUnixTime(&e, &us, &us2) == SntpSuccess && us == 0u, "ser: epoch boundary -> unix 0"); e.seconds = SNTP_TIME_AT_UNIX_EPOCH_SECS - 1u; /* era-1 起点附近 */ CHECK(Sntp_ConvertToUnixTime(&e, &us, &us2) == SntpSuccess && us == UNIX_TIME_SECS_AT_SNTP_ERA_1_SMALLEST_TIME + (SNTP_TIME_AT_UNIX_EPOCH_SECS - 1u), "ser: era1 wrap"); } } static void tc_offset_math(void) { /* 四元组中点:T1 含随机低分数(fractionsToMs 向下取整抹掉)、T4 无 → 精确 5000ms */ SntpTimestamp_t t1, t4; SntpResponseData_t r; uint8_t resp[48]; int64_t exp; s_case_reset(); t1.seconds = SNTP_TIME_AT_UNIX_EPOCH_SECS + 1500000000UL; t1.fractions = (4295000u * 123u) | 0x1234u; /* 123ms + 亚 ms 随机噪声 */ memset(resp, 0, 48); resp[0] = (uint8_t)((4u << 3) | 4u); resp[1] = 3u; s_fill_be(&resp[24], t1.seconds); s_fill_be(&resp[28], t1.fractions); { uint32_t f = 4295000u * 123u; /* 服务器对齐到整 ms */ uint32_t s = t1.seconds + 5u; /* T2 = T3 = T1 + 5s */ s_fill_be(&resp[32], s); s_fill_be(&resp[36], f); s_fill_be(&resp[40], s); s_fill_be(&resp[44], f); } t4.seconds = t1.seconds; t4.fractions = 4295000u * 123u; /* 本地 ms 分量(无随机) */ CHECK(Sntp_DeserializeResponse(&t1, &t4, resp, 48u, &r) == SntpSuccess, "off: ok"); exp = r.clockOffsetMs; CHECK(exp == 5000LL, "off: (T2-T1+T3-T4)/2 == 5000ms exact"); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("=== SNTP Client Tests ===\n"); #define TC(fn) do { printf("[" #fn "]\n"); fn(); } while (0) TC(tc_poll_clamp); TC(tc_disabled_silent); TC(tc_first_send_window); TC(tc_cold_start_step_t3); TC(tc_step_valid_clock); TC(tc_deadband_no_step); TC(tc_anti_spoof_src); TC(tc_li3_stratum); TC(tc_kod); TC(tc_timeout_backoff_cap); TC(tc_poll_pacing_after_success); TC(tc_no_server_warn_once); TC(tc_disable_closes_socket); TC(tc_gen_change_resets); TC(tc_send_fail_switch); TC(tc_recv_err_deadfd); TC(tc_oversized_resp_ok); TC(tc_recv_slice_50ms); TC(tc_helpers_direct); TC(tc_serializer_vendored); TC(tc_offset_math); #undef TC printf("\n=== SNTP Client Tests: %d/%d PASSED (failed=%d) ===\n", g_pass, g_total, g_fail); return (g_fail == 0) ? 0 : 1; }