Repo of no-std crates for my personal embedded projects
at main 196 lines 6.1 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 = "jiff")] 71 pub fn try_to_unix_timestamp(self) -> Result<jiff::Timestamp, 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]) -> (usize, Result<(), SntpError>) { 86 if packet.len() != Self::SNTP_PACKET_SIZE { 87 return (0, Err(SntpError::InvalidPacket)); 88 } 89 90 packet[0] = (3 << 6) | (4 << 3) | 3; 91 92 (Self::SNTP_PACKET_SIZE, 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 125#[cfg(feature = "jiff")] 126impl TryFrom<SntpTimestamp> for jiff::Timestamp { 127 type Error = SntpError; 128 129 fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> { 130 jiff::Timestamp::from_microsecond(timestamp.utc_micros()) 131 .map_err(|_| SntpError::InvalidTime) 132 } 133} 134 135#[inline] 136fn read_be_u64(input: &[u8]) -> u64 { 137 let (int_bytes, _) = input.split_at(core::mem::size_of::<u64>()); 138 u64::from_be_bytes(int_bytes.try_into().unwrap()) 139} 140 141#[cfg(feature = "embassy-net")] 142pub trait SntpSocket { 143 fn resolve_time( 144 &mut self, 145 addrs: &[embassy_net::IpAddress], 146 ) -> impl Future<Output = Result<SntpTimestamp, SntpError>>; 147} 148 149#[cfg(feature = "embassy-net")] 150impl SntpSocket for embassy_net::udp::UdpSocket<'_> { 151 async fn resolve_time( 152 &mut self, 153 addrs: &[embassy_net::IpAddress], 154 ) -> Result<SntpTimestamp, SntpError> { 155 use embassy_time::{Duration, WithTimeout}; 156 use sachy_fmt::{debug, error}; 157 158 if addrs.is_empty() { 159 return Err(SntpError::NetFailure); 160 } 161 162 debug!("SNTP address list: {}", addrs); 163 164 let addr = addrs[0]; 165 166 debug!("Binding to port 123"); 167 self.bind(123).map_err(|_| SntpError::NetFailure)?; 168 169 debug!("Sending SNTP request"); 170 self.send_to_with( 171 SntpRequest::SNTP_PACKET_SIZE, 172 embassy_net::IpEndpoint::new(addr, 123), 173 SntpRequest::create_packet_from_buffer, 174 ) 175 .await 176 .map_err(|e| { 177 error!("Failed to send: {}", e); 178 self.close(); 179 SntpError::NetFailure 180 })??; 181 182 debug!("Waiting for a response..."); 183 let res = self 184 .recv_from_with(|buf, _from| SntpRequest::read_timestamp(buf)) 185 .with_timeout(Duration::from_secs(5)) 186 .await 187 .map_err(|_| SntpError::NetTimeout) 188 .flatten(); 189 190 debug!("Received: {}", &res); 191 debug!("Closing SNTP port..."); 192 self.close(); 193 194 res 195 } 196}