@@ -0,0 +1,967 @@
/*
* 模块名称: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 ;
}