1use chrono::{DateTime, NaiveDateTime, Utc};
2use core::fmt::Display;
3use std::net::UdpSocket;
4use core::time::Duration;
5use serde::{Deserialize, Serialize};
6
7use crate::{Time, TimeDiff, OFFSET_1601, REF_TIME_1970};
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
14pub struct Ntp {
15 inner_secs: u64,
16 inner_milliseconds: u64,
17 server: String,
18 utc_offset: i32,
19}
20
21impl Display for Ntp {
22 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23 write!(f, "{}", self.pretty())
24 }
25}
26
27impl Ntp {
28 pub fn server(&self) -> String {
30 self.server.to_string()
31 }
32
33 pub fn valid_server(&self) -> bool {
35 !["chrono::Utc", "strptime"].contains(&self.server.as_str())
36 }
37}
38
39impl TimeDiff for Ntp {}
40
41impl Time for Ntp {
42 fn now() -> Self {
44 match Ntp::new("pool.ntp.org") {
45 Ok(x) => x,
46 Err(_) => {
47 let now = Utc::now();
48 Ntp {
49 inner_secs: (now.timestamp() + OFFSET_1601 as i64) as u64,
50 inner_milliseconds: now.timestamp_subsec_millis() as u64,
51 server: "chrono::Utc".to_string(),
52 utc_offset: 0,
53 }
54 },
55 }
56 }
57 fn unix(&self) -> i64 {
58 (self.inner_secs as i64) - (OFFSET_1601 as i64)
59 }
60 fn unix_ms(&self) -> i64 {
61 ((self.inner_secs as i64 * 1000i64) + self.inner_milliseconds as i64) - (OFFSET_1601 as i64 * 1000i64)
62 }
63 fn utc_offset(&self) -> i32 {
64 self.utc_offset
65 }
66
67 fn strptime<T: ToString, G: ToString>(s: T, format: G) -> Self {
68 let s = s.to_string();
69 let format = format.to_string();
70 let temp = DateTime::parse_from_str(&s, &format);
71 let x = match temp {
72 Err(_) => {
73 if !format.contains("%z") {
74 return Self::strptime(s + " +0000", format + "%z");
75 }
76 panic!("Bad format string");
77 }
78 Ok(dt) => dt,
79 };
80 Ntp {
81 inner_secs: (x.timestamp() + (OFFSET_1601 as i64)) as u64,
82 inner_milliseconds: x.timestamp_subsec_millis() as u64,
83 server: "strptime".to_string(),
84 utc_offset: x.offset().local_minus_utc() as i32,
85 }
86 }
87
88 fn strftime(&self, format: &str) -> String {
89 NaiveDateTime::from_timestamp_opt(self.inner_secs as i64 - OFFSET_1601 as i64, 0)
90 .unwrap()
91 .format(format)
92 .to_string()
93 }
94
95 fn from_epoch(timestamp: u64) -> Self {
96 Ntp {
97 inner_secs: timestamp / 1000,
98 inner_milliseconds: timestamp % 1000,
99 server: "from_epoch".to_string(),
100 utc_offset: 0,
101 }
102 }
103
104 fn raw(&self) -> u64 {
105 (self.inner_secs * 1000) + self.inner_milliseconds
106 }
107
108 fn from_epoch_offset(timestamp: u64, offset: i32) -> Self {
109 Ntp {
110 inner_secs: timestamp / 1000,
111 inner_milliseconds: timestamp % 1000,
112 server: "from_epoch_offset".to_string(),
113 utc_offset: offset,
114 }
115 }
116}
117
118
119impl Ntp {
120 pub fn new<T: ToString>(server_addr: T) -> Result<Ntp, Box<dyn std::error::Error>> {
129 let server = server_addr.to_string();
130 let client = UdpSocket::bind("0.0.0.0:0")?;
131 client.set_read_timeout(Some(Duration::from_secs(5)))?;
132
133 let mut data = vec![0x1b];
134 data.extend(vec![0; 47]); let start_time = Utc::now().timestamp_millis();
137 client.send_to(&data, format!("{}:123", server))?;
138
139 let mut buffer = [0; 1024];
140 let (size, _) = client.recv_from(&mut buffer)?;
141
142 if size > 0 {
143 let t = u32::from_be_bytes([buffer[40], buffer[41], buffer[42], buffer[43]]) as u64;
144 let t = t - REF_TIME_1970;
145
146 let elapsed_time = start_time - Utc::now().timestamp_millis();
147 let milliseconds = (elapsed_time % 1000).try_into().unwrap_or(0);
148
149 return Ok(Ntp {
150 server: server.to_string(),
151 inner_secs: (t) + OFFSET_1601,
152 inner_milliseconds: milliseconds,
153 utc_offset: 0,
154 });
155 }
156
157 Err("Failed to receive NTP response".into())
158 }
159
160}