Repo of no-std crates for my personal embedded projects
at main 6.5 kB view raw
1#![no_std] 2 3#[derive(Debug, PartialEq, Eq)] 4#[cfg_attr(feature = "defmt", derive(defmt::Format))] 5pub enum SntpError { 6 InvalidPacket, 7 InvalidVersion, 8 InvalidMode, 9 KissOfDeath, 10 InvalidTime, 11 NetFailure, 12 NetTimeout, 13} 14 15pub struct SntpRequest; 16 17#[derive(Debug, PartialEq, Eq, Clone, Copy)] 18#[cfg_attr(feature = "defmt", derive(defmt::Format))] 19/// SNTP timestamp. 20pub struct SntpTimestamp(u64); 21 22impl 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 76impl 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")] 131impl 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")] 140impl 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] 150fn 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")] 156pub 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")] 164impl 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}