use crate::config::Config;
use crate::error::TimerError;
use std::net::UdpSocket;
use std::time::Duration;
use tracing::{debug, error, warn};
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);
}
}