simple_ntp/
sntp.rs

1use std::net::{UdpSocket};
2use std::time;
3use std::time::Duration;
4
5#[derive(Debug)]
6pub enum NtpError {
7    ServiceUnavailable(String),
8    BadNtpServerAddr(String),
9    UnexpectedErr(String),
10    TruncatedNtpMessage,
11    UntrustedMessage,
12}
13
14// const NTP_VERSION_3: u8 = 3;
15const NTP_VERSION_4: u8 = 4;
16
17const NTP_MODE_CLIENT: u8 = 3;
18// const NTP_MODE_SERVER: u8 = 4;
19
20const NTP_DEFAULT_PORT: &str = "123";
21
22/// Retrieve current unix timestamp.
23///
24/// Example
25/// ```rust
26/// # use simple_ntp::sntp::clock_offset_nanos;
27///
28/// fn main() {
29///     match unix_timestamp("ntp.aliyun.com:123") {
30///         Ok(msg) => {
31///             println!("{:?}", msg);
32///         }
33///         Err(err) => println!("{:?}", err)
34///     }
35/// }
36/// ```
37pub fn unix_timestamp(ntp_server: &str) -> Result<Duration, NtpError> {
38    let (t1, t2, t3, t4) = ntp(ntp_server)?;
39
40    Ok((t1 * 2 + t2 + t3 - t1 - t4) / 2)
41}
42
43/// Get system clock offset in nano seconds. local timestamp sub remote timestamp.
44///
45/// Example
46/// ```rust
47/// # use simple_ntp::sntp::clock_offset_nanos;
48///
49/// fn main() {
50///     match clock_offset_nanos("ntp.aliyun.com") {
51///         Ok(msg) => { println!("{:?}", msg as f64 / 1e9); }
52///         Err(err) => println!("{:?}", err)
53///     }
54/// }
55///
56/// ```
57pub fn clock_offset_nanos(ntp_server: &str) -> Result<i64, NtpError> {
58    let (t1, t2, t3, t4) = ntp(ntp_server)?;
59
60    let mut diff = (t2.as_secs() as i64 - t1.as_secs() as i64 + t3.as_secs() as i64 - t4.as_secs() as i64) * 1_000_000_000 / 2;
61    diff += (t2.subsec_nanos() as i64 - t1.subsec_nanos() as i64 + t3.subsec_nanos() as i64 - t4.subsec_nanos() as i64) / 2;
62    Ok(diff)
63}
64
65/// Convert time.Duration to ntp timestamp format
66pub fn duration_to_ntp_timestamp(d: &Duration) -> u64 {
67    let seconds = d.as_secs();
68    let nanos = d.subsec_nanos();
69
70    seconds << 32 | (u32::MAX / 1000000000 * nanos) as u64
71}
72
73/// Convert ntp timestamp to time.Duration
74pub fn ntp_timestamp_to_duration(t: u64) -> Duration {
75    let seconds = (t >> 32) - 2208988800; // 2208988800 为 1900.1.1 到 1970.1.1 的秒数
76    let nanos = (t & u32::MAX as u64) * 1000000000 / u32::MAX as u64;
77
78    Duration::new(seconds, nanos as u32)
79}
80
81/// Retrieve four time from ntp server: t1, t2, t3 and t4.
82///
83/// t1: client transmit time
84///
85/// t2: server received time
86///
87/// t3: server transmit time
88///
89/// t4: client received time
90///
91/// So, system clock offset = ((t2 - t1) + (t3 - t4)) / 2,
92/// and round-trip time = ((t4 - t1) - (t3 - t2)) / 2.
93pub fn ntp(ntp_server: &str) -> Result<(Duration, Duration, Duration, Duration), NtpError> {
94    let socket = make_socket(ntp_server)?;
95
96    let validate_time = sys_time();
97    let timestamp = duration_to_ntp_timestamp(&validate_time);
98    let client_msg = NtpMsg::new_for_client(NTP_VERSION_4, timestamp);
99
100    let mut buf = client_msg.marshal();
101    let transmit_time = sys_time();
102    send_full(&socket, buf.as_slice())?;
103    let n = recv_full(&socket, buf.as_mut_slice())?;
104    let receive_time = sys_time();
105    buf.truncate(n);
106
107    let mut server_msg = NtpMsg::new();
108    server_msg.unmarshal(buf.as_slice())?;
109
110    if server_msg.originate_timestamp != timestamp {
111        return Err(NtpError::UntrustedMessage);
112    }
113
114    Ok((transmit_time,
115        ntp_timestamp_to_duration(server_msg.receiver_timestamp),
116        ntp_timestamp_to_duration(server_msg.transmit_timestamp),
117        receive_time
118    ))
119}
120
121fn sys_time() -> Duration {
122    time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap()
123}
124
125fn getaddr(svr: &str) -> String {
126    if svr.contains(':') {
127        svr.to_string()
128    } else {
129        svr.to_string() + ":" + NTP_DEFAULT_PORT
130    }
131}
132
133fn make_socket(target_addr: &str) -> Result<UdpSocket, NtpError> {
134    let socket = UdpSocket::bind("0.0.0.0:0").map_err(|err| {
135        NtpError::ServiceUnavailable(err.to_string())
136    })?;
137    socket.connect(getaddr(target_addr)).map_err(|err| {
138        NtpError::UnexpectedErr(err.to_string())
139    })?;
140    socket.set_write_timeout(Some(Duration::from_secs(5))).map_err(|err| {
141        NtpError::UnexpectedErr(err.to_string())
142    })?;
143    socket.set_read_timeout(Some(Duration::from_secs(5))).map_err(|err| {
144        NtpError::UnexpectedErr(err.to_string())
145    })?;
146
147    Ok(socket)
148}
149
150fn send_full(socket: &UdpSocket, buf: &[u8]) -> Result<(), NtpError> {
151    socket.send(buf).map_err(|err| {
152        NtpError::ServiceUnavailable(err.to_string())
153    })?;
154
155    Ok(())
156}
157
158fn recv_full(socket: &UdpSocket, buf: &mut [u8]) -> Result<usize, NtpError> {
159    let n = socket.recv(buf).map_err(|err| {
160        NtpError::ServiceUnavailable(err.to_string())
161    })?;
162
163    Ok(n)
164}
165
166#[derive(Debug)]
167pub struct NtpMsg {
168    leap_indicator: u8,
169    version_number: u8,
170    mode: u8,
171    stratum: u8,
172    poll: u8,
173    precision: u8,
174    root_delay: u32,
175    root_dispersion: u32,
176    reference_identifier: u32,
177    reference_timestamp: u64,
178    originate_timestamp: u64,
179    receiver_timestamp: u64,
180    transmit_timestamp: u64,
181}
182
183impl NtpMsg {
184    fn new() -> Self {
185        NtpMsg {
186            leap_indicator: 0,
187            version_number: 0,
188            mode: 0,
189            stratum: 0,
190            poll: 0,
191            precision: 0,
192            root_delay: 0,
193            root_dispersion: 0,
194            reference_identifier: 0,
195            reference_timestamp: 0,
196            originate_timestamp: 0,
197            receiver_timestamp: 0,
198            transmit_timestamp: 0,
199        }
200    }
201
202    fn new_for_client(version: u8, transmit_timestamp: u64) -> Self {
203        NtpMsg {
204            leap_indicator: 0,
205            version_number: version,
206            mode: NTP_MODE_CLIENT,
207            stratum: 0,
208            poll: 0,
209            precision: 0,
210            root_delay: 0,
211            root_dispersion: 0,
212            reference_identifier: 0,
213            reference_timestamp: 0,
214            originate_timestamp: 0,
215            receiver_timestamp: 0,
216            transmit_timestamp,
217        }
218    }
219
220    fn marshal(&self) -> Vec<u8> {
221        let mut data = Vec::with_capacity(48);
222        data.push(self.leap_indicator << 6 | self.version_number << 3 | self.mode);
223        data.push(self.stratum);
224        data.push(self.poll);
225        data.push(self.precision);
226        data.extend_from_slice(self.root_delay.to_be_bytes().as_slice());
227        data.extend_from_slice(self.root_dispersion.to_be_bytes().as_slice());
228        data.extend_from_slice(self.reference_identifier.to_be_bytes().as_slice());
229        data.extend_from_slice(self.reference_timestamp.to_be_bytes().as_slice());
230        data.extend_from_slice(self.originate_timestamp.to_be_bytes().as_slice());
231        data.extend_from_slice(self.receiver_timestamp.to_be_bytes().as_slice());
232        data.extend_from_slice(self.transmit_timestamp.to_be_bytes().as_slice());
233
234        data
235    }
236
237    fn unmarshal(&mut self, data: &[u8]) -> Result<(), NtpError> {
238        if data.len() != 48 {
239            return Err(NtpError::TruncatedNtpMessage);
240        }
241
242        self.leap_indicator = data[0] >> 6;
243        self.version_number = (data[0] >> 3) & 0b111;
244        self.mode = data[0] & 0b111;
245        self.stratum = data[1];
246        self.poll = data[2];
247        self.precision = data[3];
248        self.root_delay = u32::from_be_bytes(data[4..8].try_into().unwrap());
249        self.root_dispersion = u32::from_be_bytes(data[8..12].try_into().unwrap());
250        self.reference_identifier = u32::from_be_bytes(data[12..16].try_into().unwrap());
251        self.reference_timestamp = u64::from_be_bytes(data[16..24].try_into().unwrap());
252        self.originate_timestamp = u64::from_be_bytes(data[24..32].try_into().unwrap());
253        self.receiver_timestamp = u64::from_be_bytes(data[32..40].try_into().unwrap());
254        self.transmit_timestamp = u64::from_be_bytes(data[40..48].try_into().unwrap());
255
256        Ok(())
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use crate::sntp::*;
263
264    #[test]
265    fn test_ntp() {
266        match ntp("ntp.aliyun.com") {
267            Ok(msg) => {
268                println!("{:?}", msg);
269            }
270            Err(err) => println!("{:?}", err)
271        }
272    }
273
274    #[test]
275    fn test_delta() {
276        match clock_offset_nanos("ntp.aliyun.com") {
277            Ok(msg) => {
278                println!("{:?}", msg as f64 / 1e9);
279            }
280            Err(err) => println!("{:?}", err)
281        }
282    }
283
284    #[test]
285    fn test_timestamp() {
286        match unix_timestamp("ntp.aliyun.com") {
287            Ok(msg) => {
288                println!("{:?}", msg);
289            }
290            Err(err) => println!("{:?}", err)
291        }
292
293        match unix_timestamp("ntp.aliyun.com:123") {
294            Ok(msg) => {
295                println!("{:?}", msg);
296            }
297            Err(err) => println!("{:?}", err)
298        }
299    }
300}