···11+#![no_std]
22+33+#[derive(Debug, PartialEq, Eq)]
44+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55+pub enum SntpError {
66+ InvalidPacket,
77+ InvalidVersion,
88+ InvalidMode,
99+ KissOfDeath,
1010+ InvalidTime,
1111+ NetFailure,
1212+ NetTimeout,
1313+}
1414+1515+pub struct SntpRequest;
1616+1717+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1818+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1919+/// SNTP timestamp.
2020+pub struct SntpTimestamp(u64);
2121+2222+impl SntpTimestamp {
2323+ pub fn microseconds(&self) -> u64 {
2424+ (self.0 >> 32) * 1_000_000 + ((self.0 & 0xFFFFFFFF) * 1_000_000 / 0x100000000)
2525+ }
2626+2727+ /// Returns true if the most significant bit is set.
2828+ ///
2929+ /// Relevant documentation from RFC 2030:
3030+ ///
3131+ /// ```text
3232+ /// Note that, since some time in 1968 (second 2,147,483,648) the most
3333+ /// significant bit (bit 0 of the integer part) has been set and that the
3434+ /// 64-bit field will overflow some time in 2036 (second 4,294,967,296).
3535+ /// Should NTP or SNTP be in use in 2036, some external means will be
3636+ /// necessary to qualify time relative to 1900 and time relative to 2036
3737+ /// (and other multiples of 136 years). There will exist a 200-picosecond
3838+ /// interval, henceforth ignored, every 136 years when the 64-bit field
3939+ /// will be 0, which by convention is interpreted as an invalid or
4040+ /// unavailable timestamp.
4141+ /// As the NTP timestamp format has been in use for the last 17 years,
4242+ /// it remains a possibility that it will be in use 40 years from now
4343+ /// when the seconds field overflows. As it is probably inappropriate
4444+ /// to archive NTP timestamps before bit 0 was set in 1968, a
4545+ /// convenient way to extend the useful life of NTP timestamps is the
4646+ /// following convention: If bit 0 is set, the UTC time is in the
4747+ /// range 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC on 1
4848+ /// January 1900. If bit 0 is not set, the time is in the range 2036-
4949+ /// 2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February
5050+ /// 2036. Note that when calculating the correspondence, 2000 is not a
5151+ /// leap year. Note also that leap seconds are not counted in the
5252+ /// reckoning.
5353+ ///```
5454+ pub fn msb_set(&self) -> bool {
5555+ self.0 & (1 << 63) != 0
5656+ }
5757+5858+ /// Microseconds since the UNIX epoch.
5959+ pub fn utc_micros(&self) -> i64 {
6060+ let ntp_epoch_micros = self.microseconds() as i64;
6161+ let offset: i64 = if self.msb_set() {
6262+ -2208988800000000
6363+ } else {
6464+ 2085978496000000
6565+ };
6666+6767+ ntp_epoch_micros + offset
6868+ }
6969+7070+ #[cfg(feature = "chrono")]
7171+ pub fn try_to_naive_datetime(self) -> Result<chrono::NaiveDateTime, SntpError> {
7272+ self.try_into()
7373+ }
7474+}
7575+7676+impl SntpRequest {
7777+ pub const SNTP_PACKET_SIZE: usize = 48;
7878+7979+ pub const fn create_packet() -> [u8; Self::SNTP_PACKET_SIZE] {
8080+ let mut packet = [0u8; Self::SNTP_PACKET_SIZE];
8181+ packet[0] = (3 << 6) | (4 << 3) | 3;
8282+ packet
8383+ }
8484+8585+ pub fn create_packet_from_buffer(packet: &mut [u8]) -> Result<(), SntpError> {
8686+ if packet.len() != Self::SNTP_PACKET_SIZE {
8787+ return Err(SntpError::InvalidPacket);
8888+ }
8989+9090+ packet[0] = (3 << 6) | (4 << 3) | 3;
9191+9292+ Ok(())
9393+ }
9494+9595+ pub fn read_timestamp(packet: &[u8]) -> Result<SntpTimestamp, SntpError> {
9696+ if packet.len() == Self::SNTP_PACKET_SIZE {
9797+ let header = packet[0];
9898+ let version = (header & 0x38) >> 3;
9999+100100+ if version != 4 {
101101+ return Err(SntpError::InvalidVersion);
102102+ }
103103+104104+ let mode = header & 0x7;
105105+106106+ if !(4..=5).contains(&mode) {
107107+ return Err(SntpError::InvalidMode);
108108+ }
109109+110110+ let kiss_of_death = packet[1] == 0;
111111+112112+ if kiss_of_death {
113113+ return Err(SntpError::KissOfDeath);
114114+ }
115115+116116+ let timestamp = SntpTimestamp(read_be_u64(&packet[40..48]));
117117+118118+ return Ok(timestamp);
119119+ }
120120+121121+ Err(SntpError::InvalidPacket)
122122+ }
123123+124124+ #[cfg(feature = "chrono")]
125125+ pub fn as_naive_datetime(raw_time: SntpTimestamp) -> Result<chrono::NaiveDateTime, SntpError> {
126126+ raw_time.try_into()
127127+ }
128128+}
129129+130130+#[cfg(feature = "chrono")]
131131+impl TryFrom<SntpTimestamp> for chrono::NaiveDateTime {
132132+ type Error = SntpError;
133133+134134+ fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
135135+ Ok(chrono::DateTime::<chrono::Utc>::try_from(timestamp)?.naive_utc())
136136+ }
137137+}
138138+139139+#[cfg(feature = "chrono")]
140140+impl TryFrom<SntpTimestamp> for chrono::DateTime<chrono::Utc> {
141141+ type Error = SntpError;
142142+143143+ fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
144144+ chrono::DateTime::<chrono::Utc>::from_timestamp_micros(timestamp.utc_micros())
145145+ .ok_or(SntpError::InvalidTime)
146146+ }
147147+}
148148+149149+#[inline]
150150+fn read_be_u64(input: &[u8]) -> u64 {
151151+ let (int_bytes, _) = input.split_at(core::mem::size_of::<u64>());
152152+ u64::from_be_bytes(int_bytes.try_into().unwrap())
153153+}
154154+155155+#[cfg(feature = "embassy-net")]
156156+pub trait SntpSocket {
157157+ fn resolve_time(
158158+ &mut self,
159159+ addrs: &[embassy_net::IpAddress],
160160+ ) -> impl Future<Output = Result<SntpTimestamp, SntpError>>;
161161+}
162162+163163+#[cfg(feature = "embassy-net")]
164164+impl SntpSocket for embassy_net::udp::UdpSocket<'_> {
165165+ async fn resolve_time(
166166+ &mut self,
167167+ addrs: &[embassy_net::IpAddress],
168168+ ) -> Result<SntpTimestamp, SntpError> {
169169+ use embassy_time::{Duration, WithTimeout};
170170+ use sachy_fmt::{debug, error};
171171+172172+ if addrs.is_empty() {
173173+ return Err(SntpError::NetFailure);
174174+ }
175175+176176+ debug!("SNTP address list: {}", addrs);
177177+178178+ let addr = addrs[0];
179179+180180+ debug!("Binding to port 123");
181181+ self.bind(123).map_err(|_| SntpError::NetFailure)?;
182182+183183+ debug!("Sending SNTP request");
184184+ self.send_to_with(
185185+ SntpRequest::SNTP_PACKET_SIZE,
186186+ embassy_net::IpEndpoint::new(addr, 123),
187187+ SntpRequest::create_packet_from_buffer,
188188+ )
189189+ .await
190190+ .map_err(|e| {
191191+ error!("Failed to send: {}", e);
192192+ SntpError::NetFailure
193193+ })??;
194194+195195+ debug!("Waiting for a response...");
196196+ let res = self
197197+ .recv_from_with(|buf, _from| SntpRequest::read_timestamp(buf))
198198+ .with_timeout(Duration::from_secs(5))
199199+ .await
200200+ .map_err(|_| SntpError::NetTimeout)
201201+ .flatten();
202202+203203+ debug!("Received: {}", &res);
204204+ debug!("Closing SNTP port...");
205205+ self.close();
206206+207207+ res
208208+ }
209209+}