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
14const NTP_VERSION_4: u8 = 4;
16
17const NTP_MODE_CLIENT: u8 = 3;
18const NTP_DEFAULT_PORT: &str = "123";
21
22pub 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
43pub 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
65pub 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
73pub fn ntp_timestamp_to_duration(t: u64) -> Duration {
75 let seconds = (t >> 32) - 2208988800; let nanos = (t & u32::MAX as u64) * 1000000000 / u32::MAX as u64;
77
78 Duration::new(seconds, nanos as u32)
79}
80
81pub 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}