Skip to main content

statime_wire/common/
timestamp.rs

1use statime_base::TAI;
2
3use crate::Error;
4
5/// A timestamp as they occur in PTP messages.
6///
7/// This is a number of seconds and nanoseconds since the epoch of the timescale
8/// in use. For the PTP timescale the epoch is 00:00:00TAI on January 1, 1970.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)]
10pub struct Timestamp {
11    /// The seconds field of the timestamp.
12    /// 48-bit, must be less than 281474976710656
13    seconds: u64,
14    /// The nanoseconds field of the timestamp.
15    /// Must be less than 10^9
16    nanos: u32,
17}
18
19impl Timestamp {
20    /// Try to create a new timestamp with the given number of seconds and
21    /// nanoseconds since the epoch.
22    ///
23    /// # Errors
24    /// This function fails if the number of seconds is larger than or equal to
25    /// 2^48, or when the number of nanoseconds represents more than a whole
26    /// second.
27    pub fn new(seconds: u64, nanos: u32) -> Result<Self, Error> {
28        if seconds >= (1 << 48) || nanos >= 1_000_000_000 {
29            Err(Error::Invalid)
30        } else {
31            Ok(Self { seconds, nanos })
32        }
33    }
34
35    /// The number of whole seconds passed since the epoch.
36    #[must_use]
37    pub fn seconds(self) -> u64 {
38        self.seconds
39    }
40
41    /// The number of nanoseconds passed in addition to the whole seconds.
42    #[must_use]
43    pub fn nanos(self) -> u32 {
44        self.nanos
45    }
46
47    /// Update the whole number of seconds since the epoch.
48    ///
49    /// # Errors
50    /// This fails when the number of seconds is larger than or eque
51    pub fn try_set_seconds(&mut self, seconds: u64) -> Result<(), Error> {
52        if seconds >= (1 << 48) {
53            Err(Error::Invalid)
54        } else {
55            self.seconds = seconds;
56            Ok(())
57        }
58    }
59
60    /// Update the number of nanoseconds passed.
61    ///
62    /// # Errors
63    /// This fails when the number of nanoseconds is larger than a whole second.
64    pub fn try_set_nanos(&mut self, nanos: u32) -> Result<(), Error> {
65        if nanos >= 1_000_000_000 {
66            Err(Error::Invalid)
67        } else {
68            self.nanos = nanos;
69            Ok(())
70        }
71    }
72
73    /// Serialize the timestmap to the provided buffer (expected to be 10 bytes long).
74    ///
75    /// # Errors
76    /// Fails when the provided buffer is too short.
77    pub fn serialize(self, buffer: &mut [u8]) -> Result<(), Error> {
78        buffer
79            .get_mut(0..6)
80            .ok_or(Error::BufferTooShort)?
81            .copy_from_slice(&self.seconds.to_be_bytes()[2..8]);
82        buffer
83            .get_mut(6..10)
84            .ok_or(Error::BufferTooShort)?
85            .copy_from_slice(&self.nanos.to_be_bytes());
86        Ok(())
87    }
88
89    /// Deserialize the timestamp from the provided buffer (expected to be 10 bytes long).
90    ///
91    /// # Errors
92    /// Fails when the provided buffer is too short, or when the number of nanoseconds
93    /// encoded in the buffer is outside of the interval [0, 1000000000).
94    #[allow(
95        clippy::missing_panics_doc,
96        reason = "Clippy can't tell the try_into will never fail"
97    )]
98    pub fn deserialize(buffer: &[u8]) -> Result<Self, Error> {
99        let mut seconds_buffer = [0; 8];
100        seconds_buffer[2..8].copy_from_slice(buffer.get(0..6).ok_or(Error::BufferTooShort)?);
101
102        let nanos = u32::from_be_bytes(
103            buffer
104                .get(6..10)
105                .ok_or(Error::BufferTooShort)?
106                .try_into()
107                .unwrap(),
108        );
109
110        if nanos > 1_000_000_000 {
111            return Err(Error::Invalid);
112        }
113
114        Ok(Self {
115            seconds: u64::from_be_bytes(seconds_buffer),
116            nanos,
117        })
118    }
119}
120
121impl From<Timestamp> for statime_base::Timestamp<TAI> {
122    fn from(value: Timestamp) -> Self {
123        statime_base::Timestamp::from_seconds_nanos_since_unix_epoch(value.seconds, value.nanos)
124    }
125}
126
127impl TryFrom<statime_base::Timestamp<TAI>> for Timestamp {
128    type Error = Error;
129
130    fn try_from(value: statime_base::Timestamp<TAI>) -> Result<Self, Self::Error> {
131        let nanos = (value - statime_base::Timestamp::UNIX_EPOCH).as_nanos();
132        let seconds = nanos.div_euclid(1_000_000_000);
133        let nanos = nanos.rem_euclid(1_000_000_000);
134        Timestamp::new(
135            seconds.try_into().map_err(|_| Error::Invalid)?,
136            nanos.try_into().map_err(|_| Error::Invalid)?,
137        )
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn timestamp_wireformat() {
147        let representations = [
148            (
149                [0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01u8],
150                Timestamp {
151                    seconds: 0x0000_0000_0002,
152                    nanos: 0x0000_0001,
153                },
154            ),
155            (
156                [0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x01u8],
157                Timestamp {
158                    seconds: 0x1000_0000_0002,
159                    nanos: 0x1000_0001,
160                },
161            ),
162        ];
163
164        for (byte_representation, object_representation) in representations {
165            // Test the serialization output
166            let mut serialization_buffer = [0; 10];
167            object_representation
168                .serialize(&mut serialization_buffer)
169                .unwrap();
170            assert_eq!(serialization_buffer, byte_representation);
171
172            // Test the deserialization output
173            let deserialized_data = Timestamp::deserialize(&byte_representation).unwrap();
174            assert_eq!(deserialized_data, object_representation);
175        }
176    }
177}