···1+#![no_std]
2+3+#[derive(Debug, PartialEq, Eq)]
4+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
5+pub enum SntpError {
6+ InvalidPacket,
7+ InvalidVersion,
8+ InvalidMode,
9+ KissOfDeath,
10+ InvalidTime,
11+ NetFailure,
12+ NetTimeout,
13+}
14+15+pub struct SntpRequest;
16+17+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19+/// SNTP timestamp.
20+pub struct SntpTimestamp(u64);
21+22+impl SntpTimestamp {
23+ pub fn microseconds(&self) -> u64 {
24+ (self.0 >> 32) * 1_000_000 + ((self.0 & 0xFFFFFFFF) * 1_000_000 / 0x100000000)
25+ }
26+27+ /// Returns true if the most significant bit is set.
28+ ///
29+ /// Relevant documentation from RFC 2030:
30+ ///
31+ /// ```text
32+ /// Note that, since some time in 1968 (second 2,147,483,648) the most
33+ /// significant bit (bit 0 of the integer part) has been set and that the
34+ /// 64-bit field will overflow some time in 2036 (second 4,294,967,296).
35+ /// Should NTP or SNTP be in use in 2036, some external means will be
36+ /// necessary to qualify time relative to 1900 and time relative to 2036
37+ /// (and other multiples of 136 years). There will exist a 200-picosecond
38+ /// interval, henceforth ignored, every 136 years when the 64-bit field
39+ /// will be 0, which by convention is interpreted as an invalid or
40+ /// unavailable timestamp.
41+ /// As the NTP timestamp format has been in use for the last 17 years,
42+ /// it remains a possibility that it will be in use 40 years from now
43+ /// when the seconds field overflows. As it is probably inappropriate
44+ /// to archive NTP timestamps before bit 0 was set in 1968, a
45+ /// convenient way to extend the useful life of NTP timestamps is the
46+ /// following convention: If bit 0 is set, the UTC time is in the
47+ /// range 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC on 1
48+ /// January 1900. If bit 0 is not set, the time is in the range 2036-
49+ /// 2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February
50+ /// 2036. Note that when calculating the correspondence, 2000 is not a
51+ /// leap year. Note also that leap seconds are not counted in the
52+ /// reckoning.
53+ ///```
54+ pub fn msb_set(&self) -> bool {
55+ self.0 & (1 << 63) != 0
56+ }
57+58+ /// Microseconds since the UNIX epoch.
59+ pub fn utc_micros(&self) -> i64 {
60+ let ntp_epoch_micros = self.microseconds() as i64;
61+ let offset: i64 = if self.msb_set() {
62+ -2208988800000000
63+ } else {
64+ 2085978496000000
65+ };
66+67+ ntp_epoch_micros + offset
68+ }
69+70+ #[cfg(feature = "chrono")]
71+ pub fn try_to_naive_datetime(self) -> Result<chrono::NaiveDateTime, SntpError> {
72+ self.try_into()
73+ }
74+}
75+76+impl SntpRequest {
77+ pub const SNTP_PACKET_SIZE: usize = 48;
78+79+ pub const fn create_packet() -> [u8; Self::SNTP_PACKET_SIZE] {
80+ let mut packet = [0u8; Self::SNTP_PACKET_SIZE];
81+ packet[0] = (3 << 6) | (4 << 3) | 3;
82+ packet
83+ }
84+85+ pub fn create_packet_from_buffer(packet: &mut [u8]) -> Result<(), SntpError> {
86+ if packet.len() != Self::SNTP_PACKET_SIZE {
87+ return Err(SntpError::InvalidPacket);
88+ }
89+90+ packet[0] = (3 << 6) | (4 << 3) | 3;
91+92+ Ok(())
93+ }
94+95+ pub fn read_timestamp(packet: &[u8]) -> Result<SntpTimestamp, SntpError> {
96+ if packet.len() == Self::SNTP_PACKET_SIZE {
97+ let header = packet[0];
98+ let version = (header & 0x38) >> 3;
99+100+ if version != 4 {
101+ return Err(SntpError::InvalidVersion);
102+ }
103+104+ let mode = header & 0x7;
105+106+ if !(4..=5).contains(&mode) {
107+ return Err(SntpError::InvalidMode);
108+ }
109+110+ let kiss_of_death = packet[1] == 0;
111+112+ if kiss_of_death {
113+ return Err(SntpError::KissOfDeath);
114+ }
115+116+ let timestamp = SntpTimestamp(read_be_u64(&packet[40..48]));
117+118+ return Ok(timestamp);
119+ }
120+121+ Err(SntpError::InvalidPacket)
122+ }
123+124+ #[cfg(feature = "chrono")]
125+ pub fn as_naive_datetime(raw_time: SntpTimestamp) -> Result<chrono::NaiveDateTime, SntpError> {
126+ raw_time.try_into()
127+ }
128+}
129+130+#[cfg(feature = "chrono")]
131+impl TryFrom<SntpTimestamp> for chrono::NaiveDateTime {
132+ type Error = SntpError;
133+134+ fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
135+ Ok(chrono::DateTime::<chrono::Utc>::try_from(timestamp)?.naive_utc())
136+ }
137+}
138+139+#[cfg(feature = "chrono")]
140+impl TryFrom<SntpTimestamp> for chrono::DateTime<chrono::Utc> {
141+ type Error = SntpError;
142+143+ fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
144+ chrono::DateTime::<chrono::Utc>::from_timestamp_micros(timestamp.utc_micros())
145+ .ok_or(SntpError::InvalidTime)
146+ }
147+}
148+149+#[inline]
150+fn read_be_u64(input: &[u8]) -> u64 {
151+ let (int_bytes, _) = input.split_at(core::mem::size_of::<u64>());
152+ u64::from_be_bytes(int_bytes.try_into().unwrap())
153+}
154+155+#[cfg(feature = "embassy-net")]
156+pub trait SntpSocket {
157+ fn resolve_time(
158+ &mut self,
159+ addrs: &[embassy_net::IpAddress],
160+ ) -> impl Future<Output = Result<SntpTimestamp, SntpError>>;
161+}
162+163+#[cfg(feature = "embassy-net")]
164+impl SntpSocket for embassy_net::udp::UdpSocket<'_> {
165+ async fn resolve_time(
166+ &mut self,
167+ addrs: &[embassy_net::IpAddress],
168+ ) -> Result<SntpTimestamp, SntpError> {
169+ use embassy_time::{Duration, WithTimeout};
170+ use sachy_fmt::{debug, error};
171+172+ if addrs.is_empty() {
173+ return Err(SntpError::NetFailure);
174+ }
175+176+ debug!("SNTP address list: {}", addrs);
177+178+ let addr = addrs[0];
179+180+ debug!("Binding to port 123");
181+ self.bind(123).map_err(|_| SntpError::NetFailure)?;
182+183+ debug!("Sending SNTP request");
184+ self.send_to_with(
185+ SntpRequest::SNTP_PACKET_SIZE,
186+ embassy_net::IpEndpoint::new(addr, 123),
187+ SntpRequest::create_packet_from_buffer,
188+ )
189+ .await
190+ .map_err(|e| {
191+ error!("Failed to send: {}", e);
192+ SntpError::NetFailure
193+ })??;
194+195+ debug!("Waiting for a response...");
196+ let res = self
197+ .recv_from_with(|buf, _from| SntpRequest::read_timestamp(buf))
198+ .with_timeout(Duration::from_secs(5))
199+ .await
200+ .map_err(|_| SntpError::NetTimeout)
201+ .flatten();
202+203+ debug!("Received: {}", &res);
204+ debug!("Closing SNTP port...");
205+ self.close();
206+207+ res
208+ }
209+}