motorcortex_rust/time_spec.rs
1//! Wire-level timestamp embedded in subscription payloads.
2//!
3//! 16 bytes, two little-endian `i64`s (seconds + nanoseconds). The
4//! on-wire layout is fixed by the server; decoding is explicit
5//! rather than a native-layout pointer read so the library stays
6//! portable to big-endian hosts.
7
8use chrono::{DateTime, Local, Utc};
9
10/// Size of the wire representation: two `i64` fields packed
11/// back-to-back.
12pub(crate) const TIMESPEC_WIRE_SIZE: usize = 16;
13
14#[derive(Debug, Clone, Copy)]
15pub struct TimeSpec {
16 pub sec: i64, // Seconds
17 pub nsec: i64, // Nanoseconds
18}
19
20impl TimeSpec {
21 /// Decode a [`TimeSpec`] from the first 16 bytes of `buffer`.
22 /// Returns `None` if the buffer is shorter than that.
23 pub fn from_buffer(buffer: &[u8]) -> Option<Self> {
24 if buffer.len() < TIMESPEC_WIRE_SIZE {
25 return None;
26 }
27 // Explicit little-endian decode of each field. Fixed-size
28 // array slicing gives us `TryFrom` for the two 8-byte
29 // chunks; `i64::from_le_bytes` is endian-safe and matches
30 // what the server writes.
31 let sec = i64::from_le_bytes(buffer[0..8].try_into().ok()?);
32 let nsec = i64::from_le_bytes(buffer[8..16].try_into().ok()?);
33 Some(Self { sec, nsec })
34 }
35
36 pub fn to_date_time(&self) -> DateTime<Local> {
37 self.to_utc_date_time().with_timezone(&Local)
38 }
39
40 pub fn to_utc_date_time(&self) -> DateTime<Utc> {
41 DateTime::from_timestamp(self.sec, self.nsec as u32).expect("Invalid TimeSpec")
42 }
43}