10322208fd
- vendored coreSNTP v2.0.0 序列化器(MIT,仅 serializer+LICENSE,零堆), 48 字节报文编解码在库,节奏与准入判据在本方(RFC 4330 §10 调研口径) - App/sync/time_sntp.c 挂 timeSyncTask 的 8888 事件循环,等待切片 ≤50 ms, 不饿任务狗与 IWDG;懒建 UDP socket,代际变化/禁用/死错误码即关闭归还 - 防伪 = OriginTimestamp 逐位等于发出时 T1(含低 16 位随机小数位); mode/stratum/闰秒指示/KoD 按 RFC 拒收;DENY·RSTR 换服务器、RATE 只退避 - 主备轮换 + 失败指数退避(5 s 起、60 s 封顶)+ 首轮 1~5 分钟随机错峰; 成功恢复 poll_min 周期(1~1440 分,出厂默认 60,默认不启用不发包) - 钟面 = 本地时间:UTC+8 固定偏移落 sys_clock_set_unix(§1.1 时基裁定, 第三个写入口,装置仍不存时区);|offset|<2 s 只记日志不步进,冷启动或 更大偏差步进并回写 SD2506,留痕 [SNTP] step 与 syslog OPER - 首个校验通过的同步置 SYS_EVT_TIMESYNC_READY(就绪位,消费方按需 NoClear) - [sntp] 配置段(enable/server1/server2/poll_min)属主 sntp_cfg.c, 注册在 net_cfg_load();UI 页 31「SNTP 设置」(密码叶):启用开关 + 主/备服务器 octet 编辑 + 周期(分) + 保存,复用 net_cfg_save_request 链 - 测试与门禁:test/host/sntp 新套件(报文/防伪/KoD/退避周期/阶跃域/配置 注册表)、net_cfg 与 time_sync 套件补 fake、黄金参考 66→74 张零漂移、 字符集 228 字、Keil 0 错 0 警(RO +5,848 B 含字体)、覆盖率基线回填 - 文档:墙钟维护说明 §16、网络配置说明 [sntp] 节、配置文件设计说明变更记录、 产品需求 FR-TIME-3 与 8.3/9.1.2 台账、UI 补齐规划/UI补齐 31 叶口径、 登记簿销项并登记「SNTP 校时真机判据」等台架(PC 时间源 192.168.31.161/.13)
968 lines
34 KiB
C
968 lines
34 KiB
C
/*
|
||
* 模块名称: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 <stdint.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
/* 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;
|
||
}
|