feat(sntp): 板端 SNTP 校时接入——coreSNTP 序列化器 + [sntp] 段 + 设置页(#73)
- 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)
This commit is contained in:
@@ -16,6 +16,11 @@
|
||||
* 口令判定归 test/host/ftp_cfg/ 模块,两边不重复也不互相顶包。
|
||||
* (不能把 ftp_cfg.c 也 include 进来:两个 .c 都有 static s_cfg,
|
||||
* 同一个 TU 里直接重定义。)
|
||||
* - [sntp] 段:描述符由 net_cfg.c 的 s_sntp_register() 组装(分层
|
||||
* 门禁后 sntp_cfg.c 不再自注册),所以本测试直接**编译真实的
|
||||
* sntp_cfg.c 数据本体**(纯 stdint/string,无引擎依赖,改名后的
|
||||
* s_sntp_cfg 与 net_cfg.c 的 s_cfg 不再同名冲突),并用假引擎
|
||||
* 截获注册内容——段键表/偏移/默认值的机器钉在这里打。
|
||||
* ⚠ 引擎的解析/写盘/沉降契约已由 test/host/cfg_file/(103 条)覆盖,
|
||||
* 本模块刻意不重测引擎,只测 net_cfg.c 这一层的编排与交接。
|
||||
*
|
||||
@@ -37,8 +42,10 @@
|
||||
* TC-NETCFG-003 未 load 时 gw = 192.168.31.1
|
||||
* TC-NETCFG-004 未 load 时 mac = 全零(哨兵 = 不覆盖芯片内置 MAC)
|
||||
* —— 注册与 load(TC-NETCFG-005..011)——
|
||||
* TC-NETCFG-005 首次 load 注册 [network] 段恰好一次(且 load 返回 0)
|
||||
* TC-NETCFG-005 首次 load 注册 [network]+[sntp] 各恰好一次(且 load 返回 0)
|
||||
* TC-NETCFG-006 首次 load 一并注册 [ftp] 段(FTP 鉴权:所有受管段先注册、后 load)
|
||||
* TC-NETCFG-006b [sntp] 描述符内容:段名/键表(enable/server1/server2/poll_min
|
||||
* 偏移 0/1/5/10 长 1/4/4/2)/默认值(关 + 60 分钟)/无变更回调
|
||||
* TC-NETCFG-007 重复 load 不重复注册(幂等;否则引擎按"段名重复"拒登 → 静默失配)
|
||||
* TC-NETCFG-008 注册描述符内容:段名/尺寸/键数/默认值指针/变更回调/数据指针 = RAM 副本
|
||||
* TC-NETCFG-009 load 成功:返回 0 且留一条 INFO、不误报 ERROR
|
||||
@@ -100,8 +107,10 @@
|
||||
/* 真实引擎头(接口原汁)+ 被测生产源(原样纳入);dbg_log.h 命中本目录 stub */
|
||||
#include "cfg_file.h"
|
||||
#include "net_cfg.h"
|
||||
#include "sntp_cfg.h"
|
||||
|
||||
#include "net_cfg.c"
|
||||
#include "sntp_cfg.c"
|
||||
|
||||
/* ===================== 断言框架 ===================== */
|
||||
|
||||
@@ -137,6 +146,7 @@ typedef struct {
|
||||
const void *p_defaults;
|
||||
size_t data_size;
|
||||
uint8_t key_count;
|
||||
const cfg_key_def_t *p_keys; /* 键表是生产文件里的 static const,指针长期有效,不必深拷贝 */
|
||||
void (*p_cb)(void);
|
||||
} fake_reg_t;
|
||||
|
||||
@@ -179,6 +189,7 @@ int cfg_file_register(const cfg_section_reg_t *p_reg)
|
||||
rec->p_defaults = p_reg->p_defaults;
|
||||
rec->data_size = p_reg->data_size;
|
||||
rec->key_count = p_reg->key_count;
|
||||
rec->p_keys = p_reg->p_keys;
|
||||
rec->p_cb = p_reg->p_cb;
|
||||
g_reg_n++;
|
||||
return 0;
|
||||
@@ -278,14 +289,41 @@ static void tc_register_load(void)
|
||||
printf("-- TC-NETCFG-005..011 register + load --\n");
|
||||
fake_reset();
|
||||
rc = net_cfg_load();
|
||||
CHECK((rc == 0) && (g_reg_n == 1) && (strcmp(g_regs[0].name, "network") == 0),
|
||||
"TC-NETCFG-005 first load registers [network] exactly once");
|
||||
CHECK((rc == 0) && (g_reg_n == 2) && (strcmp(g_regs[0].name, "network") == 0),
|
||||
"TC-NETCFG-005 first load registers [network] + [sntp] exactly once each");
|
||||
CHECK((g_ftp_reg_calls == 1) && (g_load_calls == 1),
|
||||
"TC-NETCFG-006 also registers [ftp], registered before load");
|
||||
{
|
||||
const fake_reg_t *rs = (g_reg_n == 2) ? &g_regs[1] : NULL;
|
||||
const sntp_cfg_t *d = (const sntp_cfg_t *)(rs != NULL ? rs->p_defaults : NULL);
|
||||
int keys_ok = (rs != NULL) && (rs->key_count == 4u) && (rs->p_keys != NULL);
|
||||
CHECK((rs != NULL) && (strcmp(rs->name, "sntp") == 0) && keys_ok &&
|
||||
(rs->data_size == sizeof(sntp_cfg_t)) &&
|
||||
(rs->p_data == (const void *)sntp_cfg_data()) &&
|
||||
(rs->p_cb == NULL),
|
||||
"TC-NETCFG-006b [sntp] descriptor: name/4 keys/size/RAM copy/no cb");
|
||||
CHECK(keys_ok &&
|
||||
(strcmp(rs->p_keys[0].name, "enable") == 0) &&
|
||||
(rs->p_keys[0].type == CFG_VAL_UINT) &&
|
||||
(rs->p_keys[0].offset == 0u) && (rs->p_keys[0].len == 1u) &&
|
||||
(strcmp(rs->p_keys[1].name, "server1") == 0) &&
|
||||
(rs->p_keys[1].type == CFG_VAL_IP4) &&
|
||||
(rs->p_keys[1].offset == 1u) && (rs->p_keys[1].len == 4u) &&
|
||||
(strcmp(rs->p_keys[2].name, "server2") == 0) &&
|
||||
(rs->p_keys[2].type == CFG_VAL_IP4) &&
|
||||
(rs->p_keys[2].offset == 5u) && (rs->p_keys[2].len == 4u) &&
|
||||
(strcmp(rs->p_keys[3].name, "poll_min") == 0) &&
|
||||
(rs->p_keys[3].type == CFG_VAL_UINT) &&
|
||||
(rs->p_keys[3].offset == 10u) && (rs->p_keys[3].len == 2u),
|
||||
"TC-NETCFG-006c [sntp] key table: enable/server1/server2/poll_min at 0/1/5/10");
|
||||
CHECK((d != NULL) && (d->enable == 0u) && (d->poll_min == 60u) &&
|
||||
(d->server_pri[0] == 0u) && (d->server_bak[0] == 0u),
|
||||
"TC-NETCFG-006d [sntp] factory defaults: off + empty servers + 60 min");
|
||||
}
|
||||
|
||||
rc = net_cfg_load();
|
||||
CHECK((rc == 0) && (g_reg_n == 1) && (g_ftp_reg_calls == 2),
|
||||
"TC-NETCFG-007 repeated load does not re-register [network]");
|
||||
CHECK((rc == 0) && (g_reg_n == 2) && (g_ftp_reg_calls == 2),
|
||||
"TC-NETCFG-007 repeated load does not re-register [network]/[sntp]");
|
||||
|
||||
CHECK((strcmp(g_regs[0].name, "network") == 0) &&
|
||||
(g_regs[0].data_size == sizeof(net_cfg_t)) &&
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
桩的边界(细节见测试单元文件头):
|
||||
配置文件引擎用**真实头**(接口原汁)+ 测试单元内的可控实现;[ftp] 段的
|
||||
ftp_cfg_register 只计数——它归 test/host/ftp_cfg/ 模块,两边不互相顶包。
|
||||
sntp_cfg.c 是分层门禁后的纯数据本体,随 net_cfg.c 一并编译入 TU,
|
||||
[sntp] 段描述符(键表/偏移/工厂默认)由假引擎截获断言——段注册契约
|
||||
的机器钉在本模块(TC-NETCFG-005/006b..d/007)。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/net_cfg/run_unit_test.py
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""SNTP 客户端(time_sntp)的主机侧纯逻辑单元测试运行器(纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/sntp/sntp_test.c(它以 #include 纳入**生产源
|
||||
文件** App/sync/time_sntp.c + Drivers/BSP/NETCFG/sntp_cfg.c + vendor
|
||||
Lib/coreSNTP/core_sntp_serializer.c),在 PC 上离线跑:
|
||||
RFC 4330 §10 节奏(初始 1~5min 随机散布 / 失败超时翻倍封顶 60s / 成功按
|
||||
poll_min 重排)、§5.3 防欺骗(应答源 IP:port / originate 含随机低位精确
|
||||
匹配 / mode / LI=3 / stratum>15 / KoD DENY·RSTR·RATE·他码)、§5.5 步进
|
||||
策略(±2s 死区只记录、冷启动直采 T3、钟面 = 本地 UTC+8 恒加 28800、
|
||||
值域守卫)、[sntp] 配置注册与 poll 钳位、网络代次/停用收口与 socket 生命周期。
|
||||
失败即非 0 退出。零硬件、零烧录、毫秒级。
|
||||
|
||||
工具链探测 / 编译运行 / 退出码判定全部委托通用 harness
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明)。
|
||||
|
||||
为什么要主机侧单测(而不是只用真机):
|
||||
SNTP 的失败路径(伪造应答、KoD、超时风暴、tick 回绕)在真机上不可复现或
|
||||
要等小时级;且本模块直接拨 RTC(sys_clock→SD2506 写回),拨错一次就是
|
||||
事故——判据必须逐包断言。真机只做正向链路回归(PC W32Time 对端)。
|
||||
|
||||
用法(仓库根目录):
|
||||
& '.venv\\Scripts\\python.exe' test/host/sntp/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:#57 调研(docs 见 sntp-调研报告.md)+ App/sync/time_sntp.c 文件头;
|
||||
创建日期:2026-11-04
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
# harness 位于 test/host/common/
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
# 顺序敏感:stub 目录必须**先于**生产目录,才能覆盖 FreeRTOS.h / task.h /
|
||||
# cmsis_os2.h / net_types.h / net_socket.h / sys_clock.h / sys_events.h /
|
||||
# sys_log.h / dbg_log.h(真实头会拖入整套 FreeRTOS / HAL / CH395F)。
|
||||
# cfg_file.h 用**真实头**(App/cfg,纯 stdint/stddef——注册描述符契约原汁);
|
||||
# sntp_cfg.h / time_sntp.h 用真实头(接口契约原汁)。
|
||||
BUILD = TestBuild(
|
||||
name="SNTP Client Tests",
|
||||
src=os.path.join(HERE, "sntp_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "sync"),
|
||||
os.path.join(ROOT, "App", "cfg"),
|
||||
os.path.join(ROOT, "Drivers", "BSP", "NETCFG"),
|
||||
os.path.join(ROOT, "Lib", "coreSNTP"),
|
||||
os.path.join(ROOT, "Lib", "coreSNTP", "include"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== SNTP Client Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#ifndef SNTP_TEST_FREE_RTOS_H
|
||||
#define SNTP_TEST_FREE_RTOS_H
|
||||
#include <stdint.h>
|
||||
typedef uint32_t TickType_t;
|
||||
#endif /* SNTP_TEST_FREE_RTOS_H */
|
||||
@@ -0,0 +1,14 @@
|
||||
/* 主机测试固件(test/host/sntp):cmsis_os2.h 替身——事件标志最小子集。 */
|
||||
#ifndef SNTP_TEST_CMSIS_OS2_H
|
||||
#define SNTP_TEST_CMSIS_OS2_H
|
||||
#include <stdint.h>
|
||||
typedef void *osEventFlagsId_t;
|
||||
#define osFlagsWaitAll 0u
|
||||
#define osFlagsNoClear 0u
|
||||
#define osFlagsErrorUnknown ((uint32_t)0xFFFFFFFFu)
|
||||
#define osFlagsErrorTimeout ((uint32_t)0xFFFFFFFEu)
|
||||
#define osFlagsErrorParameter ((uint32_t)0xFFFFFFFAu)
|
||||
#define osFlagsErrTimeout osFlagsErrorTimeout
|
||||
uint32_t osEventFlagsWait(osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout);
|
||||
uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t events);
|
||||
#endif /* SNTP_TEST_CMSIS_OS2_H */
|
||||
@@ -0,0 +1,13 @@
|
||||
/* 主机测试固件(test/host/sntp):dbg_log.h 替身。
|
||||
* 计数宏替代真实日志;不定义 DBG_TAG/DBG_LINE_BUF_MAX(生产 .c 自带,重复定义触 C4005 /W4)。 */
|
||||
#ifndef SNTP_TEST_DBG_LOG_H
|
||||
#define SNTP_TEST_DBG_LOG_H
|
||||
#include <stdint.h>
|
||||
extern int g_dbg_err_count;
|
||||
extern int g_dbg_warn_count;
|
||||
extern int g_dbg_info_count;
|
||||
#define DBG_ERROR(fmt, ...) (g_dbg_err_count++)
|
||||
#define DBG_WARN(fmt, ...) (g_dbg_warn_count++)
|
||||
#define DBG_INFO(fmt, ...) (g_dbg_info_count++)
|
||||
#define DBG_DEBUG(fmt, ...) ((void)0)
|
||||
#endif /* SNTP_TEST_DBG_LOG_H */
|
||||
@@ -0,0 +1,27 @@
|
||||
/* 主机测试固件(test/host/sntp):net_socket.h 替身——UDP 客户端最小子集。
|
||||
* 合同与真机 Drivers/BSP/NET/net_socket.h(PR #74 后)一致:
|
||||
* net_sendto 返回 >0 = 实发字节数(须恰等 48),-1 = 错误(net_get_errno 可查)
|
||||
* net_recvfrom_to >0 交付 / -2 片内无数据(不置 errno)/ -1 错误 / 0 防御
|
||||
* 载荷/源地址/返回值由测试脚本注入(sntp_test.c 的 fake 实现)。 */
|
||||
#ifndef SNTP_TEST_NET_SOCKET_H
|
||||
#define SNTP_TEST_NET_SOCKET_H
|
||||
#include "task.h"
|
||||
#include "net_types.h"
|
||||
|
||||
/* 脚本注入 token(fake 队列 kind 用):数值即 fake net_recvfrom_to 的返回值 */
|
||||
#define NET_RECV_DATA 1
|
||||
#define NET_RECV_TIMEOUT (-2)
|
||||
#define NET_RECV_CONNCLOSE 0
|
||||
#define NET_RECV_ERR (-1)
|
||||
|
||||
int net_socket(int domain, int type);
|
||||
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen);
|
||||
int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
const struct net_sockaddr *dest, int addrlen);
|
||||
int net_recvfrom_to(int sockfd, void *buf, int len, uint32_t timeout_ms,
|
||||
struct net_sockaddr *src, int *addrlen);
|
||||
int net_close(int sockfd);
|
||||
int net_get_errno(void);
|
||||
uint32_t net_get_generation(void);
|
||||
|
||||
#endif /* SNTP_TEST_NET_SOCKET_H */
|
||||
@@ -0,0 +1,50 @@
|
||||
/* 主机测试固件(test/host/sntp):net_types.h 替身——IPv4 地址与 sockaddr 结构、错误码、htons。
|
||||
* 字段布局与真机 Drivers/BSP/NET/net_types.h 一致(sin_port 大端;s_addr 内存首字节=首个八位组)。 */
|
||||
#ifndef SNTP_TEST_NET_TYPES_H
|
||||
#define SNTP_TEST_NET_TYPES_H
|
||||
#include <stdint.h>
|
||||
|
||||
#define NET_AF_INET 2
|
||||
#define NET_SOCK_STREAM 1
|
||||
#define NET_SOCK_DGRAM 2
|
||||
#define NET_INADDR_ANY 0u
|
||||
|
||||
#define NET_OK 0
|
||||
#define NET_ERR_BADF -2
|
||||
#define NET_ERR_INVAL -3
|
||||
#define NET_ERR_NOMEM -4
|
||||
#define NET_ERR_NOTCONN -5
|
||||
#define NET_ERR_ISCONN -6
|
||||
#define NET_ERR_WOULDBLOCK -7
|
||||
#define NET_ERR_CONNRESET -8
|
||||
#define NET_ERR_TIMEDOUT -9
|
||||
#define NET_ERR_NOSPACE -10
|
||||
#define NET_ERR_BUSY -11
|
||||
|
||||
struct net_in_addr {
|
||||
uint32_t s_addr;
|
||||
};
|
||||
|
||||
struct net_sockaddr {
|
||||
uint16_t sa_family;
|
||||
char sa_data[14];
|
||||
};
|
||||
|
||||
struct net_sockaddr_in {
|
||||
uint16_t sin_family;
|
||||
uint16_t sin_port; /* 大端 */
|
||||
struct net_in_addr sin_addr;
|
||||
char sin_zero[8];
|
||||
};
|
||||
|
||||
static inline uint16_t net_htons(uint16_t hostorder)
|
||||
{
|
||||
return (uint16_t)(((hostorder & 0x00FFu) << 8) | ((hostorder & 0xFF00u) >> 8));
|
||||
}
|
||||
|
||||
static inline uint16_t net_ntohs(uint16_t netorder)
|
||||
{
|
||||
return net_htons(netorder);
|
||||
}
|
||||
|
||||
#endif /* SNTP_TEST_NET_TYPES_H */
|
||||
@@ -0,0 +1,7 @@
|
||||
/* 主机测试固件(test/host/sntp):sys_clock.h 替身——time_sntp.c 只用这两个入口。 */
|
||||
#ifndef SNTP_TEST_SYS_CLOCK_H
|
||||
#define SNTP_TEST_SYS_CLOCK_H
|
||||
#include <stdint.h>
|
||||
uint32_t sys_clock_get(void);
|
||||
int sys_clock_set_unix(uint32_t unix_secs);
|
||||
#endif /* SNTP_TEST_SYS_CLOCK_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
/* 主机测试固件(test/host/sntp):sys_events.h 替身——只暴露 time_sntp.c 用到的成员。 */
|
||||
#ifndef SNTP_TEST_SYS_EVENTS_H
|
||||
#define SNTP_TEST_SYS_EVENTS_H
|
||||
#include "cmsis_os2.h"
|
||||
|
||||
extern osEventFlagsId_t sys_evtHandle;
|
||||
|
||||
#define SYS_EVT_NET_READY (1u << 0)
|
||||
#define SYS_EVT_STORAGE_READY (1u << 1)
|
||||
#define SYS_EVT_FTP_READY (1u << 2)
|
||||
#define SYS_EVT_TIMESYNC_READY (1u << 3)
|
||||
#define SYS_EVT_NET_RECONFIG (1u << 4)
|
||||
#define SYS_EVT_CFG_SAVE (1u << 5)
|
||||
|
||||
#endif /* SNTP_TEST_SYS_EVENTS_H */
|
||||
@@ -0,0 +1,19 @@
|
||||
/* 主机测试固件(test/host/sntp):sys_log.h 替身——syslog_post_quiet 记数即可。 */
|
||||
#ifndef SNTP_TEST_SYS_LOG_H
|
||||
#define SNTP_TEST_SYS_LOG_H
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
SYSLOG_RUN = 0u,
|
||||
SYSLOG_SELFTEST,
|
||||
SYSLOG_ACCIDENT,
|
||||
SYSLOG_ALARM,
|
||||
SYSLOG_START,
|
||||
SYSLOG_OPER,
|
||||
SYSLOG_YK,
|
||||
SYSLOG_COUNT
|
||||
} syslog_id_t;
|
||||
|
||||
void syslog_post_quiet(syslog_id_t id, const char *desc);
|
||||
|
||||
#endif /* SNTP_TEST_SYS_LOG_H */
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef SNTP_TEST_TASK_H
|
||||
#define SNTP_TEST_TASK_H
|
||||
#include <stdint.h>
|
||||
#include "FreeRTOS.h"
|
||||
uint32_t xTaskGetTickCount(void);
|
||||
#endif /* SNTP_TEST_TASK_H */
|
||||
@@ -341,6 +341,24 @@ int g_tstest_info_count = 0;
|
||||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- SNTP 客户机替身(time_sync.c v1.4 起循环调用 time_sntp_run;
|
||||
* 真实头 App/sync/time_sntp.h 与 time_sync.c 同目录,引号包含直达声明,
|
||||
* 这里只落三个空实现——行为断言在 test/host/sntp 独立套件里做) ---- */
|
||||
static int s_sntp_run_calls;
|
||||
|
||||
void time_sntp_run(uint32_t net_gen) {
|
||||
(void)net_gen;
|
||||
s_sntp_run_calls++;
|
||||
}
|
||||
|
||||
uint32_t time_sntp_sync_count(void) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
int32_t time_sntp_last_offset_ms(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===================== 被测代码(原样纳入生产源) ===================== */
|
||||
#include "time_sync.c"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user