···
3
+
#[derive(Debug, PartialEq, Eq)]
4
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
15
+
pub struct SntpRequest;
17
+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
18
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
20
+
pub struct SntpTimestamp(u64);
22
+
impl SntpTimestamp {
23
+
pub fn microseconds(&self) -> u64 {
24
+
(self.0 >> 32) * 1_000_000 + ((self.0 & 0xFFFFFFFF) * 1_000_000 / 0x100000000)
27
+
/// Returns true if the most significant bit is set.
29
+
/// Relevant documentation from RFC 2030:
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
54
+
pub fn msb_set(&self) -> bool {
55
+
self.0 & (1 << 63) != 0
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() {
67
+
ntp_epoch_micros + offset
70
+
#[cfg(feature = "chrono")]
71
+
pub fn try_to_naive_datetime(self) -> Result<chrono::NaiveDateTime, SntpError> {
77
+
pub const SNTP_PACKET_SIZE: usize = 48;
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;
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);
90
+
packet[0] = (3 << 6) | (4 << 3) | 3;
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;
101
+
return Err(SntpError::InvalidVersion);
104
+
let mode = header & 0x7;
106
+
if !(4..=5).contains(&mode) {
107
+
return Err(SntpError::InvalidMode);
110
+
let kiss_of_death = packet[1] == 0;
113
+
return Err(SntpError::KissOfDeath);
116
+
let timestamp = SntpTimestamp(read_be_u64(&packet[40..48]));
118
+
return Ok(timestamp);
121
+
Err(SntpError::InvalidPacket)
124
+
#[cfg(feature = "chrono")]
125
+
pub fn as_naive_datetime(raw_time: SntpTimestamp) -> Result<chrono::NaiveDateTime, SntpError> {
126
+
raw_time.try_into()
130
+
#[cfg(feature = "chrono")]
131
+
impl TryFrom<SntpTimestamp> for chrono::NaiveDateTime {
132
+
type Error = SntpError;
134
+
fn try_from(timestamp: SntpTimestamp) -> Result<Self, Self::Error> {
135
+
Ok(chrono::DateTime::<chrono::Utc>::try_from(timestamp)?.naive_utc())
139
+
#[cfg(feature = "chrono")]
140
+
impl TryFrom<SntpTimestamp> for chrono::DateTime<chrono::Utc> {
141
+
type Error = SntpError;
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)
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())
155
+
#[cfg(feature = "embassy-net")]
156
+
pub trait SntpSocket {
159
+
addrs: &[embassy_net::IpAddress],
160
+
) -> impl Future<Output = Result<SntpTimestamp, SntpError>>;
163
+
#[cfg(feature = "embassy-net")]
164
+
impl SntpSocket for embassy_net::udp::UdpSocket<'_> {
165
+
async fn resolve_time(
167
+
addrs: &[embassy_net::IpAddress],
168
+
) -> Result<SntpTimestamp, SntpError> {
169
+
use embassy_time::{Duration, WithTimeout};
170
+
use sachy_fmt::{debug, error};
172
+
if addrs.is_empty() {
173
+
return Err(SntpError::NetFailure);
176
+
debug!("SNTP address list: {}", addrs);
178
+
let addr = addrs[0];
180
+
debug!("Binding to port 123");
181
+
self.bind(123).map_err(|_| SntpError::NetFailure)?;
183
+
debug!("Sending SNTP request");
185
+
SntpRequest::SNTP_PACKET_SIZE,
186
+
embassy_net::IpEndpoint::new(addr, 123),
187
+
SntpRequest::create_packet_from_buffer,
191
+
error!("Failed to send: {}", e);
192
+
SntpError::NetFailure
195
+
debug!("Waiting for a response...");
197
+
.recv_from_with(|buf, _from| SntpRequest::read_timestamp(buf))
198
+
.with_timeout(Duration::from_secs(5))
200
+
.map_err(|_| SntpError::NetTimeout)
203
+
debug!("Received: {}", &res);
204
+
debug!("Closing SNTP port...");