1use crate::config::Config;
2use crate::error::TimerError;
3use std::net::UdpSocket;
4use std::time::Duration;
5use tracing::{debug, error, warn};
6
7pub fn get_ntp_timestamp(config: &Config) -> Result<i64, TimerError> {
9 debug!("开始从 NTP 服务器获取时间戳");
10
11 for (idx, server) in config.ntp_servers.iter().enumerate() {
12 debug!(server = %server, index = idx, "尝试连接 NTP 服务器");
13
14 for attempt in 0..config.max_retries {
15 match fetch_ntp_time(server, config.socket_timeout_secs) {
16 Ok(timestamp) => {
17 debug!(server = %server, timestamp = timestamp, "成功获取 NTP 时间戳");
18 return Ok(timestamp);
19 }
20 Err(e) => {
21 if attempt < config.max_retries - 1 {
22 warn!(
23 server = %server,
24 attempt = attempt + 1,
25 max_retries = config.max_retries,
26 error = %e,
27 "NTP 请求失败,准备重试"
28 );
29 }
30 }
31 }
32 }
33 }
34
35 error!("所有 NTP 服务器均不可用");
36 Err(TimerError::NtpError(
37 "无法从任何 NTP 服务器获取时间戳".to_string(),
38 ))
39}
40
41fn fetch_ntp_time(server: &str, timeout_secs: u64) -> Result<i64, TimerError> {
42 let server_addr = format!("{}:123", server);
43 let socket = UdpSocket::bind("0.0.0.0:0")
44 .map_err(|e| TimerError::SocketError(format!("创建套接字失败: {}", e)))?;
45
46 let timeout = Duration::from_secs(timeout_secs);
47 socket
48 .set_read_timeout(Some(timeout))
49 .map_err(|e| TimerError::SocketError(format!("设置读超时失败: {}", e)))?;
50 socket
51 .set_write_timeout(Some(timeout))
52 .map_err(|e| TimerError::SocketError(format!("设置写超时失败: {}", e)))?;
53
54 let mut ntp_request = [0u8; 48];
55 ntp_request[0] = 0x1b;
56
57 socket
58 .send_to(&ntp_request, &server_addr)
59 .map_err(|e| TimerError::SocketError(format!("发送 NTP 请求失败: {}", e)))?;
60
61 let mut ntp_response = [0u8; 48];
62 socket
63 .recv(&mut ntp_response)
64 .map_err(|e| TimerError::SocketError(format!("接收 NTP 响应失败: {}", e)))?;
65
66 let seconds = u32::from_be_bytes([
67 ntp_response[40],
68 ntp_response[41],
69 ntp_response[42],
70 ntp_response[43],
71 ]);
72
73 const NTP_UNIX_EPOCH_DELTA: u32 = 2208988800;
74 if seconds < NTP_UNIX_EPOCH_DELTA {
75 return Err(TimerError::NtpError("NTP 时间戳异常".to_string()));
76 }
77
78 let unix_timestamp = (seconds - NTP_UNIX_EPOCH_DELTA) as i64;
79 debug!(server = %server, ntp_secs = seconds, unix_timestamp = unix_timestamp, "从 NTP 服务器获取时间戳成功");
80
81 Ok(unix_timestamp)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn test_ntp_config() {
90 let config = Config::default();
91 assert!(!config.ntp_servers.is_empty());
92 assert_eq!(config.socket_timeout_secs, 5);
93 }
94}