Files
STM32F4-Base/App/sync/time_sntp.c
T
super_agent_bot c2dd6f34c0
CI / 静态门禁 (pre-commit) (push) Failing after 5m33s
CI / 主机侧单测 (MSVC) (push) Successful in 45s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 5m20s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 31s
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)
2026-11-04 15:28:05 +08:00

607 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:Time SNTP — 板端 SNTP 客户端(状态机实现)
* 模块功能:见 time_sntp.h。节拍由 timeSyncTask 主循环驱动(每 ≤200ms 一次),
* 报文编解码用 vendor coreSNTP serializer(仅 serializer 层——
* client 层要求自带 transport 接口与定时器,本仓 UDP 已队列化在
* net_socket,节奏循环留在任务里,见 #57 调研 §4.1)
* 适用平台:STM32407ZGTx + CH395F;模拟器/主机单测经 stub 编译
* 作者:王建锋
* 创建日期:2026-11-04
* 修改记录:
* v1.0 2026-11-04 王建锋 创建初始版本(SNTP 客户端接入,#57 调研结论:
* RFC 4330 §10 节奏、§5.3 防欺骗判据、§5.5 步进策略、
* 钟面 = 本地时间(UTC+8 固定,§1.1 裁定))
*/
#include <string.h>
#include "time_sntp.h"
#include "core_sntp_serializer.h"
#include "net_socket.h"
#include "sntp_cfg.h"
#include "sys_clock.h"
#include "sys_events.h"
#include "sys_log.h"
/* 深调用链模块(与 time_sync.c 同任务栈预算口径,见其文件头注释):
* 日志行缓冲收到 128 B(默认 192) */
#define DBG_LINE_BUF_MAX 128U
#define DBG_TAG "[SNTP]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define SNTP_SERVER_PORT 123U
/* 应答超时:基础 5s,失败逐次翻倍封顶 60s(RFC 4330 §10:间隔递增;
* 翻倍节奏 = "超时即退避",下一次请求最早在 timeout_ms 之后发出) */
#define SNTP_RESP_TIMEOUT_BASE_MS 5000U
#define SNTP_RESP_TIMEOUT_MAX_MS 60000U
/* 首次请求随机散布 1~5 分钟(RFC 4330 §10 避免同批设备上电撞服务器) */
#define SNTP_FIRST_DELAY_MIN_MS 60000U
#define SNTP_FIRST_DELAY_SPAN_MS 240000U
/* 等应答期的单次收片粒度(看门狗 100ms 抽样窗约束,同 time_sync.c 第 8b 轮口径) */
#define SNTP_RECV_SLICE_MS 50U
/* 步进策略(调研 §5.5):|钟差| < 2s 只记录不步进(防 RTC 秒抖动来回拨针) */
#define SNTP_STEP_DEADBAND_MS 2000
/* 内部钟面 = 本地时间(#56 裁定 §1.1:sys_clock 存"钟面秒",104 写 UTC 的
* 8 小时差已有文档口径)——SNTP 给的是 UTC,落钟面前加固定东八区偏移;
* 全仓无时区存储,本常量与 UI 手工对时 / 8888 推送同源 */
#define SNTP_TZ_LOCAL_OFFSET_SECS 28800U
/* 合理性下限:2020-01-01 UTC;上限 = sys_clock 钟面域 0x7FFFFFFF 扣除时区 */
#define SNTP_UNIX_MIN_VALID 1577836800U
#define SNTP_UNIX_MAX_UTC (0x7FFFFFFFUL - SNTP_TZ_LOCAL_OFFSET_SECS)
/* 收包缓冲:48B 报文 + 富余(>48 的扩展按 BSD 语义截断,serializer 只读前 48) */
#define SNTP_RX_BUF_SIZE 64U
/* 状态机 */
#define SNTP_ST_IDLE 0 /* 停用 / 从未启用 */
#define SNTP_ST_WAIT_DUE 1 /* 等下一次请求窗口 */
#define SNTP_ST_SEND 2 /* 本轮应发请求 */
#define SNTP_ST_WAIT_RESP 3 /* 等应答(每次 run 收一个 ≤50ms 片) */
/* 静态变量定义区 */
static struct {
uint8_t state; /* SNTP_ST_* */
int fd; /* UDP socket(-1 = 未开) */
uint32_t gen; /* 打开 socket 时的网络代次 */
uint32_t due_ms; /* 下一次请求的 tick 锚点(回绕安全比较) */
uint32_t timeout_ms; /* 当前应答超时(失败翻倍,成功复位) */
uint32_t deadline_ms; /* 本轮应答截止 tick */
uint8_t srv; /* 0 = 主 server1,1 = 备 server2 */
uint8_t no_srv_logged; /* "未配置服务器"只警一次(配置态,不是故障) */
uint8_t ready_posted; /* SYS_EVT_TIMESYNC_READY 已投(一次性) */
uint32_t sync_count; /* 校验通过次数(含死区内未步进) */
int32_t last_off_ms; /* 最近一次钟差(int64 截断,诊断用) */
SntpTimestamp_t t1; /* 本轮请求时间(serializer 要求跨收发保存) */
uint8_t pkt[SNTP_PACKET_BASE_SIZE]; /* 请求报文(48B,静态省栈) */
} s_ctx;
static uint32_t s_rng; /* xorshift32:散布/防欺骗随机数(非密钥用途) */
static uint8_t s_rng_seeded;
/* 私有函数区 */
/*
* 函数功能:PRNG 播种一次(芯片 96 位 UID 折叠 ^ tick ^ 当前钟面)
* 入口参数:无
* 返回值:无
* 限定条件:任务上下文
* 函数说明:F407 软件上无可用硬件 RNG 通路(HAL_RNG 在 hal_conf 关闭),
* 此处只需要"不可预测的 16 位低分数 + 上电散布",UID 折叠足够;
* 非 MCU 构建(宿主单测/模拟器)不碰 0x1FFF7A10,用固定盐——
* 防欺骗强度依赖的是应答 echo 回 T1(含随机低 16 位)的攻击者
* 不可知性,真机上由 UID+tick 提供
*/
static void s_rng_seed_once(void) {
uint32_t seed;
if (s_rng_seeded != 0u) {
return;
}
#ifdef STM32F407XX
{
const volatile uint32_t *uid = (const volatile uint32_t *)0x1FFF7A10UL;
seed = uid[0] ^ (uid[1] * 2654435761UL) ^ uid[2];
}
#else
seed = 0x9E3779B9UL;
#endif
seed ^= (uint32_t)xTaskGetTickCount();
seed ^= sys_clock_get();
if (seed == 0u) {
seed = 0xA5A5A5A5u;
}
s_rng = seed;
s_rng_seeded = 1u;
}
/*
* 函数功能:xorshift32 随机数
* 入口参数:无
* 返回值:伪随机 uint32(播种前兜底返回黄金比例常数,不会为 0) uint32_t
*/
static uint32_t s_rng_next(void) {
uint32_t x = s_rng;
if (x == 0u) {
x = 0x9E3779B9u;
}
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
s_rng = x;
return x;
}
/*
* 函数功能:回绕安全的"到点"判断
* 入口参数:now - 当前 tick;due - 目标 tick uint32_t
* 返回值:1 - 已到/已过;0 - 未到 uint8_t
*/
static uint8_t s_tick_due(uint32_t now, uint32_t due) {
return ((int32_t)(now - due) >= 0) ? 1u : 0u;
}
/*
* 函数功能:IPv4 四字节是否全零(未配置)
* 入口参数:p_ip - 4 字节 const uint8_t *
* 返回值:1 - 全零;0 - 已配置 uint8_t
*/
static uint8_t s_ip_is_zero(const uint8_t *p_ip) {
return ((p_ip[0] | p_ip[1] | p_ip[2] | p_ip[3]) == 0u) ? 1u : 0u;
}
/*
* 函数功能:取当前应使用的服务器 IP(按 s_ctx.srv 选主/备,缺位自动落另一侧)
* 入口参数:无
* 返回值:4 字节 IP 指针;主备都未配置返回 NULL const uint8_t *
*/
static const uint8_t *s_server_ip(const sntp_cfg_t *p_cfg) {
const uint8_t *pri = p_cfg->server_pri;
const uint8_t *bak = p_cfg->server_bak;
if (s_ctx.srv == 0u) {
if (s_ip_is_zero(pri) == 0u) {
return pri;
}
s_ctx.srv = 1u;
}
if (s_ip_is_zero(bak) == 0u) {
return bak;
}
if (s_ip_is_zero(pri) == 0u) {
s_ctx.srv = 0u;
return pri;
}
return NULL;
}
/*
* 函数功能:主备切换(仅两侧都配置时有效;失败退避时调用)
* 入口参数:p_cfg - 配置副本 const sntp_cfg_t *
* 返回值:无
*/
static void s_server_toggle(const sntp_cfg_t *p_cfg) {
if ((s_ip_is_zero(p_cfg->server_pri) == 0u) &&
(s_ip_is_zero(p_cfg->server_bak) == 0u)) {
s_ctx.srv = (s_ctx.srv == 0u) ? 1u : 0u;
}
}
/*
* 函数功能:当前 UTC 秒(钟面有效才返回)
* 入口参数:p_utc - 输出 UTC 秒 int64_t *
* 返回值:1 - 钟面有效;0 - 未初始化/早于合理性下限 uint8_t
*/
static uint8_t s_utc_now(int64_t *p_utc) {
uint32_t face = sys_clock_get();
if ((face == UINT32_MAX) ||
((uint64_t)face < (uint64_t)SNTP_UNIX_MIN_VALID + SNTP_TZ_LOCAL_OFFSET_SECS)) {
return 0u;
}
*p_utc = (int64_t)face - (int64_t)SNTP_TZ_LOCAL_OFFSET_SECS;
return 1u;
}
/*
* 函数功能:取当前 SNTP 时间戳(1900 纪元秒 + 毫秒分数)
* 入口参数:p_ts - 输出 SntpTimestamp_t *
* 返回值:无
* 函数说明:钟面无效(冷启动)时用"纪元差 + 开机秒 + 随机扰动"构造非零且
* 每次上电唯一的时间戳——serializer 只要求 T1 非零防欺骗,冷启动
* 路径的成功处理不使用 clockOffset(直接采用服务器 T3),乱值无害
*/
static void s_ts_now(SntpTimestamp_t *p_ts) {
int64_t utc;
uint32_t ms;
ms = (uint32_t)xTaskGetTickCount();
if (s_utc_now(&utc) != 0u) {
/* uint32 加和天然回绕 = SNTP era 进位语义(2036 年后仍成立) */
p_ts->seconds = (uint32_t)(utc + (int64_t)SNTP_TIME_AT_UNIX_EPOCH_SECS);
}
else {
p_ts->seconds = SNTP_TIME_AT_UNIX_EPOCH_SECS + (ms / 1000u) + (s_rng_next() & 0xFFFFu);
}
p_ts->fractions = (ms % 1000u) * (1000u * SNTP_FRACTION_VALUE_PER_MICROSECOND);
}
/*
* 函数功能:打开并绑定 UDP socket(客户端侧动态端口)
* 入口参数:gen - 当前网络代次 uint32_t
* 返回值:0 - 成功;-1 - 失败(fd 保持 -1) int
* 函数说明:UDP bind 恒开芯片服务器形态 Socket(PR #74 后语义),
* 端口 0 = 动态分配;发送用带目的地址的 net_sendto,不经
* net_udp_set_peer 轻切换(每包显式对端,源过滤未证不依赖)
*/
static int s_socket_open(uint32_t gen) {
struct net_sockaddr_in addr;
int s;
s = net_socket(NET_AF_INET, NET_SOCK_DGRAM);
if (s < 0) {
DBG_ERROR("net_socket FAIL errno=%d", net_get_errno());
return -1;
}
(void)memset(&addr, 0, sizeof(addr));
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons(0u); /* 0 = 动态本地端口 */
addr.sin_addr.s_addr = NET_INADDR_ANY;
if (net_bind(s, (struct net_sockaddr *)&addr, (int)sizeof(addr)) != 0) {
DBG_ERROR("udp bind FAIL errno=%d", net_get_errno());
net_close(s);
return -1;
}
s_ctx.fd = s;
s_ctx.gen = gen;
return 0;
}
/*
* 函数功能:关闭 UDP socket(代次变化 / 配置停用 / 死 fd 时调用)
* 入口参数:无
* 返回值:无
*/
static void s_socket_close(void) {
if (s_ctx.fd >= 0) {
net_close(s_ctx.fd);
s_ctx.fd = -1;
}
}
/*
* 函数功能:一次失败收口——超时翻倍(封顶)并重排下一次请求
* 入口参数:now - 当前 tick uint32_t
* 返回值:无
* 函数说明:状态回 WAIT_DUE;socket 保留(轻问题不重开,代次变化才重开)
*/
static void s_fail_backoff(uint32_t now) {
uint32_t t = s_ctx.timeout_ms * 2u;
if (t > SNTP_RESP_TIMEOUT_MAX_MS) {
t = SNTP_RESP_TIMEOUT_MAX_MS;
}
s_ctx.timeout_ms = t;
s_ctx.due_ms = now + t;
s_ctx.state = SNTP_ST_WAIT_DUE;
}
/*
* 函数功能:int64 毫秒钟差截断到 int32(日志/诊断口)
* 入口参数:off - 毫秒钟差 int64_t
* 返回值:截断后的 int32 毫秒 int32_t
*/
static int32_t s_off_clamp(int64_t off) {
if (off > 2147483647LL) {
return 2147483647;
}
if (off < -2147483647LL) {
return -2147483647;
}
return (int32_t)off;
}
/*
* 函数功能:校验通过的应答落地——步进策略 + 节奏复位 + 就绪事件
* 入口参数:p_resp - 解析出的应答数据;now - 当前 tick uint32_t
* 返回值:无
* 函数说明:见文件头步进策略注释;**任何校验通过都算一次成功同步**
* (含死区内未步进),复位退避并按 poll_min 重排下一轮;
* 首个成功投 SYS_EVT_TIMESYNC_READY(状态位,不消费);
* 每次实际步进落一条 quiet 操作记录("clock set by SNTP",
* 与 8888/104 同口径,失败不落痕)
*/
static void s_handle_success(const SntpResponseData_t *p_resp, uint32_t now) {
int64_t utc;
int64_t new_utc;
int32_t off_ms = s_off_clamp(p_resp->clockOffsetMs);
uint8_t clock_valid;
s_ctx.sync_count++;
s_ctx.last_off_ms = off_ms;
s_ctx.timeout_ms = SNTP_RESP_TIMEOUT_BASE_MS;
s_ctx.due_ms = now + (uint32_t)sntp_cfg_poll_min() * 60000u;
s_ctx.state = SNTP_ST_WAIT_DUE;
if ((s_ctx.ready_posted == 0u) && (sys_evtHandle != NULL)) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_TIMESYNC_READY);
s_ctx.ready_posted = 1u;
}
clock_valid = s_utc_now(&utc);
if (clock_valid != 0u) {
int64_t off = p_resp->clockOffsetMs;
if ((off <= (int64_t)SNTP_STEP_DEADBAND_MS) &&
(off >= -(int64_t)SNTP_STEP_DEADBAND_MS)) {
/* 死区:本地钟足够准,不拨针也不写 RTC(防 SD2506 无谓磨损) */
DBG_INFO("off=%ldms within deadband, no step", (long)off_ms);
return;
}
/* 四元组中点估计:把"服务器秒"折算到本地应有的 UTC 秒 */
{
int64_t off_s = (off >= 0) ? ((off + 500) / 1000) : ((off - 500) / 1000);
new_utc = utc + off_s;
}
}
else {
/* 冷启动(钟面未初始化/早于 2020):钟差不可信,直接采用服务器 T3 */
uint32_t t3_secs = 0u;
uint32_t t3_us = 0u;
if (Sntp_ConvertToUnixTime(&p_resp->serverTime, &t3_secs, &t3_us) != SntpSuccess) {
DBG_ERROR("unix convert FAIL");
return;
}
new_utc = (int64_t)t3_secs;
}
if ((new_utc < (int64_t)SNTP_UNIX_MIN_VALID) || (new_utc > (int64_t)SNTP_UNIX_MAX_UTC)) {
DBG_ERROR("target unix out of domain, skip step");
return;
}
(void)sys_clock_set_unix((uint32_t)(new_utc + (int64_t)SNTP_TZ_LOCAL_OFFSET_SECS));
syslog_post_quiet(SYSLOG_OPER, "clock set by SNTP");
DBG_INFO("step offset=%ldms face=%lu", (long)off_ms,
(unsigned long)(uint32_t)(new_utc + (int64_t)SNTP_TZ_LOCAL_OFFSET_SECS));
}
/*
* 函数功能:收包校验 + 反欺骗判据 + 解析落地(WAIT_RESP 态的单包处理)
* 入口参数:p_buf - 载荷;len - 长度;p_src - 源地址;now - tick uint32_t
* 返回值:1 - 本轮已收口(成功或按失败处理,调用方可停止收片);
* 0 - 该包丢弃,继续等到 deadline uint8_t
* 函数说明:判据顺序 = 长度 → 源地址(IP+端口必须 = 本轮请求目的)→
* stratum≤15 → LI≠3(服务器未同步告警态)→ serializer 校验
* (mode=4、origin 与 T1 含随机低 16 位精确匹配、时间戳非零、
* KoD 分类)。调研 §5.3:LI=3 / stratum>15 serializer 不拦,
* 必须在这里补;KoD 与畸形应答按失败退避(KoD 语义里
* ChangeServer 换服务器,RATE 保留当前服务器)
*/
static uint8_t s_handle_packet(const uint8_t *p_buf, int len,
const struct net_sockaddr_in *p_src,
const uint8_t *p_srv_ip, uint32_t now) {
const sntp_cfg_t *cfg = sntp_cfg_get();
SntpResponseData_t resp;
SntpTimestamp_t t4;
SntpStatus_t st;
if (len < (int)SNTP_PACKET_BASE_SIZE) {
DBG_INFO("WARN: short resp len=%d, drop", len);
return 0u;
}
if ((memcmp(&p_src->sin_addr.s_addr, p_srv_ip, 4u) != 0) ||
(p_src->sin_port != net_htons(SNTP_SERVER_PORT))) {
/* 防欺骗第一道:应答源必须是我们刚发过请求的那个 IP:123 */
DBG_INFO("WARN: resp from unexpected src, drop");
return 0u;
}
if ((p_buf[1] & 0x7FU) > 15u) {
DBG_INFO("WARN: stratum=%u invalid, drop", (unsigned)(p_buf[1] & 0x7FU));
s_fail_backoff(now);
return 1u;
}
if ((p_buf[0] >> 6) == 3u) {
/* LI=3:服务器自己没同步,时间不可用;再等多久也一样 → 直接失败 */
DBG_INFO("WARN: server not synced (LI=3)");
s_server_toggle(cfg);
s_fail_backoff(now);
return 1u;
}
s_ts_now(&t4);
st = Sntp_DeserializeResponse(&s_ctx.t1, &t4, p_buf, (size_t)len, &resp);
switch (st) {
case SntpSuccess:
s_handle_success(&resp, now);
return 1u;
case SntpRejectedResponseChangeServer:
case SntpRejectedResponseOtherCode:
DBG_INFO("WARN: kiss-of-death code=%lu, switch server",
(unsigned long)resp.rejectedResponseCode);
s_server_toggle(cfg);
s_fail_backoff(now);
return 1u;
case SntpRejectedResponseRetryWithBackoff:
DBG_INFO("WARN: kiss-of-death RATE, backoff");
s_fail_backoff(now);
return 1u;
case SntpInvalidResponse:
default:
/* originate 不匹配 / 时间戳为零 / mode 错 = 伪造或损坏:丢弃继续等 */
DBG_INFO("WARN: invalid resp st=%d, drop", (int)st);
return 0u;
}
}
/*
* 函数功能:发起一轮请求(SEND 态执行体:选服务器 → 开 socket → 序列化 → 发送)
* 入口参数:net_gen - 网络代次;now - 当前 tick uint32_t
* 返回值:无
* 函数说明:成功 → 状态进 WAIT_RESP;任一失败 → 主备切换(适用时)+
* s_fail_backoff 退避重排;服务器未配置 → 停在 WAIT_DUE 只警一次,
* 等下一次 run(配置补齐即恢复,不产生退避——没发过请求不算失败)
*/
static void s_send_round(uint32_t net_gen, uint32_t now) {
const sntp_cfg_t *cfg = sntp_cfg_get();
const uint8_t *srv_ip;
struct net_sockaddr_in dst;
srv_ip = s_server_ip(cfg);
if (srv_ip == NULL) {
if (s_ctx.no_srv_logged == 0u) {
DBG_INFO("WARN: enabled but no server configured");
s_ctx.no_srv_logged = 1u;
}
return;
}
s_ctx.no_srv_logged = 0u;
if (s_ctx.fd < 0) {
if (s_socket_open(net_gen) != 0) {
s_fail_backoff(now);
return;
}
}
s_ts_now(&s_ctx.t1);
if (Sntp_SerializeRequest(&s_ctx.t1, s_rng_next(), s_ctx.pkt,
sizeof(s_ctx.pkt)) != SntpSuccess) {
/* T1 为 0 才会失败(ts 构造保证非零)——真到这儿是构造性 bug */
DBG_ERROR("serialize FAIL");
s_fail_backoff(now);
return;
}
(void)memset(&dst, 0, sizeof(dst));
dst.sin_family = NET_AF_INET;
dst.sin_port = net_htons(SNTP_SERVER_PORT);
(void)memcpy(&dst.sin_addr.s_addr, srv_ip, 4u);
if (net_sendto(s_ctx.fd, s_ctx.pkt, (int)sizeof(s_ctx.pkt), 0,
(struct net_sockaddr *)&dst, (int)sizeof(dst))
!= (int)sizeof(s_ctx.pkt)) {
DBG_ERROR("sendto FAIL errno=%d", net_get_errno());
s_server_toggle(cfg);
s_fail_backoff(now);
return;
}
s_ctx.deadline_ms = now + s_ctx.timeout_ms;
s_ctx.state = SNTP_ST_WAIT_RESP;
/* 发送轮不占收片时间,立即让出回主循环 */
}
/* 函数定义区 */
void time_sntp_run(uint32_t net_gen) {
const sntp_cfg_t *cfg;
const uint8_t *srv_ip;
uint32_t now = (uint32_t)xTaskGetTickCount();
s_rng_seed_once();
cfg = sntp_cfg_get();
/* 网络代次变化:旧 socket 随芯片复位失效,弃掉重排首轮散布 */
if (net_gen != s_ctx.gen) {
s_socket_close();
s_ctx.gen = net_gen;
s_ctx.state = SNTP_ST_IDLE;
s_ctx.no_srv_logged = 0u;
}
if (cfg->enable == 0u) {
if (s_ctx.state != SNTP_ST_IDLE) {
s_socket_close();
s_ctx.state = SNTP_ST_IDLE;
}
return;
}
if (s_ctx.state == SNTP_ST_IDLE) {
s_ctx.state = SNTP_ST_WAIT_DUE;
s_ctx.timeout_ms = SNTP_RESP_TIMEOUT_BASE_MS;
s_ctx.due_ms = now + SNTP_FIRST_DELAY_MIN_MS +
(s_rng_next() % SNTP_FIRST_DELAY_SPAN_MS);
}
switch (s_ctx.state) {
case SNTP_ST_WAIT_DUE:
if (s_tick_due(now, s_ctx.due_ms) != 0u) {
s_send_round(net_gen, now);
}
return;
case SNTP_ST_SEND:
s_send_round(net_gen, now);
return;
case SNTP_ST_WAIT_RESP: {
uint8_t rxbuf[SNTP_RX_BUF_SIZE];
struct net_sockaddr_in src;
int src_len = (int)sizeof(src);
int n;
(void)memset(&src, 0, sizeof(src));
n = net_recvfrom_to(s_ctx.fd, rxbuf, (int)sizeof(rxbuf),
SNTP_RECV_SLICE_MS,
(struct net_sockaddr *)&src, &src_len);
if (n > 0) {
srv_ip = s_server_ip(cfg);
if (srv_ip == NULL) {
s_fail_backoff(now);
return;
}
(void)s_handle_packet(rxbuf, n, &src, srv_ip, now);
return;
}
if (n == -2) {
/* 本片等满无数据:看总截止 */
if (s_tick_due(now, s_ctx.deadline_ms) != 0u) {
DBG_INFO("WARN: resp timeout, tmo=%lu ms", (unsigned long)s_ctx.timeout_ms);
s_server_toggle(cfg);
s_fail_backoff(now);
}
return;
}
if (n == -1) {
int e = net_get_errno();
DBG_ERROR("recvfrom FAIL errno=%d", e);
if ((e == NET_ERR_BADF) || (e == NET_ERR_CONNRESET) ||
(e == NET_ERR_INVAL)) {
/* fd 已死(重建取消/非法):关掉等代次/下轮重开 */
s_socket_close();
s_fail_backoff(now);
}
/* 其余瞬时错误:继续等到 deadline,超时自然收口 */
return;
}
/* n == 0:UDP 语义下不应出现(EOF 是 TCP 概念),防御收口 */
s_fail_backoff(now);
return;
}
default:
s_ctx.state = SNTP_ST_IDLE;
return;
}
}
uint32_t time_sntp_sync_count(void) {
return s_ctx.sync_count;
}
int32_t time_sntp_last_offset_ms(void) {
return s_ctx.last_off_ms;
}