ntp-timer 0.1.0

A lightweight NTP time synchronization library for Rust applications. Provides in-process cached global time with background sync and clock-jump detection.
Documentation
use crate::config::Config;
use crate::error::TimerError;
use std::net::UdpSocket;
use std::time::Duration;
use tracing::{debug, error, warn};

/// 从 NTP 服务器获取秒级时间戳
pub fn get_ntp_timestamp(config: &Config) -> Result<i64, TimerError> {
    debug!("开始从 NTP 服务器获取时间戳");

    for (idx, server) in config.ntp_servers.iter().enumerate() {
        debug!(server = %server, index = idx, "尝试连接 NTP 服务器");

        for attempt in 0..config.max_retries {
            match fetch_ntp_time(server, config.socket_timeout_secs) {
                Ok(timestamp) => {
                    debug!(server = %server, timestamp = timestamp, "成功获取 NTP 时间戳");
                    return Ok(timestamp);
                }
                Err(e) => {
                    if attempt < config.max_retries - 1 {
                        warn!(
                            server = %server,
                            attempt = attempt + 1,
                            max_retries = config.max_retries,
                            error = %e,
                            "NTP 请求失败,准备重试"
                        );
                    }
                }
            }
        }
    }

    error!("所有 NTP 服务器均不可用");
    Err(TimerError::NtpError(
        "无法从任何 NTP 服务器获取时间戳".to_string(),
    ))
}

fn fetch_ntp_time(server: &str, timeout_secs: u64) -> Result<i64, TimerError> {
    let server_addr = format!("{}:123", server);
    let socket = UdpSocket::bind("0.0.0.0:0")
        .map_err(|e| TimerError::SocketError(format!("创建套接字失败: {}", e)))?;

    let timeout = Duration::from_secs(timeout_secs);
    socket
        .set_read_timeout(Some(timeout))
        .map_err(|e| TimerError::SocketError(format!("设置读超时失败: {}", e)))?;
    socket
        .set_write_timeout(Some(timeout))
        .map_err(|e| TimerError::SocketError(format!("设置写超时失败: {}", e)))?;

    let mut ntp_request = [0u8; 48];
    ntp_request[0] = 0x1b;

    socket
        .send_to(&ntp_request, &server_addr)
        .map_err(|e| TimerError::SocketError(format!("发送 NTP 请求失败: {}", e)))?;

    let mut ntp_response = [0u8; 48];
    socket
        .recv(&mut ntp_response)
        .map_err(|e| TimerError::SocketError(format!("接收 NTP 响应失败: {}", e)))?;

    let seconds = u32::from_be_bytes([
        ntp_response[40],
        ntp_response[41],
        ntp_response[42],
        ntp_response[43],
    ]);

    const NTP_UNIX_EPOCH_DELTA: u32 = 2208988800;
    if seconds < NTP_UNIX_EPOCH_DELTA {
        return Err(TimerError::NtpError("NTP 时间戳异常".to_string()));
    }

    let unix_timestamp = (seconds - NTP_UNIX_EPOCH_DELTA) as i64;
    debug!(server = %server, ntp_secs = seconds, unix_timestamp = unix_timestamp, "从 NTP 服务器获取时间戳成功");

    Ok(unix_timestamp)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ntp_config() {
        let config = Config::default();
        assert!(!config.ntp_servers.is_empty());
        assert_eq!(config.socket_timeout_secs, 5);
    }
}