Files
STM32F4-Base/App/sync/time_sntp.c
T
super_agent_bot 3a2f9b21fb
CI / 静态门禁 (pre-commit) (push) Failing after 43s
CI / 主机侧单测 (MSVC) (push) Failing after 45s
CI / 静态门禁 (pre-commit) (pull_request) Failing after 45s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 45s
refactor(time_sync)!: 退役 TCP :8888 对时任务,timeSyncTask 转纯 SNTP 校时节拍宿主(#78)
SNTP 客户端已随 #73/PR #77 上线合入(merge 2b848c4a),用户裁定
8888 对时任务退出历史舞台(gitea:77 线程原话,跟踪 Issue #78)。

生产侧(App/sync/time_sync.c v2.0,任务入口名不变,freertos.c 零改动):
- 删除 :8888 监听、TIME 包重组、sys_clock 写入、OPER 落痕、
  net_drop_conn 队头自救整条链(TIME_SYNC_PORT 宏随退役删除)
- 任务体收敛为:就绪屏障(100ms 分片、osFlagsNoClear)→
  200ms 校时节拍 task_wd_beat → time_sntp_run(net_get_generation())
  → 分片延时;喂狗节奏(TASK_WD_TIMESYNC=5,required_mask)不变
- 时钟写入口自此 = SNTP / UI 手工 / 104 C_CS,双写竞态(后写者赢)
  随之消失(墙钟说明 §15 退役公告、§16 现势口径)

工具与测试:
- 删除 test/host/time_sync_server.py(对时源)与 e02_probe.py
  (队头阻塞探针,载体随监听退役;判据脚本 v5_log_gap_report.py
  保留供旧日志解读)
- time_sync 主机单测重写 v2.0:33 断言覆盖分片延时 / 屏障 /
  SNTP 节拍与网络代次当轮交接 / 未出网守门;stub 的
  net_socket.h 裁剪至 net_get_generation 作编译期绊线
- 覆盖率基线 --update-baseline 收口(time_sync.c 101→20 行为刻意
  缩减,全仓 48/48 套绿)

文档与图:墙钟说明 §15 改退役公告(教训保留:CH395F FIN 丢未读
缓冲)、§16 单一网络来源;产品需求 FR-TIME-2 退役注记、§6.3 重写;
架构总览 / 软件架构规格 / 网络配置 / 网络自愈 §9.3 / BSD 指南 /
看门狗说明心跳与白名单行 / 实机测试指南 / 部署环境 / 继保规划 /
UI补齐时基口径 / 页面补齐规划 SNTP 页行 / 任务规划 README 叶数与
暂缓项行(SNTP 叶已绑、UI_PAGE_MAP_COUNT=31 口径)等约 30 处现势
口径同步;arch / net_stack 两图生成器与 SVG 同笔重跑。

台账:ram_baseline --update + check_mem_numbers --write + 体积凭证
(Code 254,648→253,428,−1,220 B)。静态链复测发现 timeSyncTask
1,904 B / iec104Task 1,600 B 低于 ISR 硬线:master 2b848c4a 同判据
timeSyncTask 即 2,056 B(本 PR 只减不增,−152 B 旧任务自身帧),
双 FAIL 为 #74/#77 后既有债;抬栈属 Ask-first 未擅自执行,登记簿
新增「静态链硬线双 FAIL」待台架复采,复测快照入 docs/内存管理/data。

验证:keil-build 0 错 0 警;time_sync/sntp 主机单测绿;
ci_local 全绿;doc-links --all 0。
2026-11-04 18:07:03 +08:00

625 lines
22 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",
* 与 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;
/* 发送轮不占收片时间,立即让出回主循环 */
}
/* 函数定义区 */
/**
* 函数功能:SNTP 客户端单步驱动——由 timeSyncTask 主循环每轮调用一次
* 入口参数:net_gen - 当前网络代次(time_sync 循环持有) uint32_t
* 返回值:无
* 限定条件:仅 timeSyncTask 上下文;单次调用最多停留一片 SNTP_RECV_SLICE_MS,
* 看门狗喂点由调用方节奏保证;配置热读 [sntp] RAM 副本,无需重启
*/
void time_sntp_run(uint32_t net_gen) {
const sntp_cfg_t *cfg;
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) {
const uint8_t *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
* 限定条件:任意上下文只读
*/
uint32_t time_sntp_sync_count(void) {
return s_ctx.sync_count;
}
/**
* 函数功能:最近一次校验通过应答的毫秒钟差(诊断口,int64 截断值)
* 入口参数:无
* 返回值:(T2-T1+T3-T4)/2 毫秒,截断到 int32;从未成功为 0 int32_t
* 限定条件:任意上下文只读;仅日志/排障用,不作判据
*/
int32_t time_sntp_last_offset_ms(void) {
return s_ctx.last_off_ms;
}