Skip to main content

statime_wire/common/
time_interval.rs

1use statime_base::Duration;
2
3use crate::Error;
4
5/// An interval of passed time as represented in PTP messages.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct TimeInterval(
9    /// The length of the time intervals in 2^16ths of a nanoseconds.
10    pub i64,
11);
12
13impl From<TimeInterval> for Duration {
14    fn from(value: TimeInterval) -> Self {
15        Duration::from_raw_steps((i128::from(value.0) << 48) / 1_000_000_000)
16    }
17}
18
19impl TimeInterval {
20    /// Serialize the timestamp to an 8 byte buffer.
21    ///
22    /// # Errors
23    /// Fails when the provided buffer is too short.
24    pub fn serialize(self, buffer: &mut [u8]) -> Result<(), Error> {
25        buffer
26            .get_mut(0..8)
27            .ok_or(Error::BufferTooShort)?
28            .copy_from_slice(&self.0.to_be_bytes());
29        Ok(())
30    }
31
32    /// Deserialize the timestamp from an 8 byte buffer.
33    ///
34    /// # Errors
35    /// Fails when the provided buffer is too short.
36    #[allow(
37        clippy::missing_panics_doc,
38        reason = "Clippy can't tell the try_into will never fail"
39    )]
40    pub fn deserialize(buffer: &[u8]) -> Result<Self, Error> {
41        Ok(Self(i64::from_be_bytes(
42            buffer
43                .get(0..8)
44                .ok_or(Error::BufferTooShort)?
45                .try_into()
46                .unwrap(),
47        )))
48    }
49
50    /// Length of this interval in nano seconds
51    #[must_use]
52    #[expect(
53        clippy::cast_precision_loss,
54        reason = "Precision loss here is acceptable as it only happens when the interval is relatively large"
55    )]
56    pub fn to_nanos(self) -> f64 {
57        (self.0 as f64) / f64::from(1 << 16)
58    }
59
60    /// Create a new interval with the given length.
61    ///
62    /// This conversion is not exact.
63    ///
64    /// # Errors
65    /// This will error if the input is NaN or of magnitude larger than
66    /// about 2^47 nanoseconds
67    pub fn from_nanos(nanos: f64) -> Result<Self, Error> {
68        // We need to do the checks for the conversion manually unfortunately,
69        // as rust doesn't have usable builtin conversions for floats.
70        let ticks = nanos * f64::from(1 << 16);
71        // The as casts in the check are not exact, but that is acceptable as
72        // the MAX value rounds up by 1, which can be solved with checking for
73        // equality as well, and the MIN value is exactly representable. After
74        // the check, the as cast back to integer from the float will be exact.
75        #[expect(clippy::cast_precision_loss)]
76        #[expect(clippy::cast_possible_truncation)]
77        if ticks.is_nan() || ticks >= (i64::MAX as f64) || ticks < (i64::MIN as f64) {
78            Err(Error::Invalid)
79        } else {
80            Ok(Self((nanos * f64::from(1 << 16)) as i64))
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn time_interval_wireformat() {
91        let representations = [
92            (
93                [0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00u8],
94                TimeInterval::from_nanos(2.5f64).unwrap(),
95            ),
96            (
97                [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01u8],
98                TimeInterval::from_nanos(1.0f64 / f64::from(u16::MAX)).unwrap(),
99            ),
100            (
101                [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00u8],
102                TimeInterval::from_nanos(-1.0f64).unwrap(),
103            ),
104        ];
105
106        for (byte_representation, object_representation) in representations {
107            // Test the serialization output
108            let mut serialization_buffer = [0; 8];
109            object_representation
110                .serialize(&mut serialization_buffer)
111                .unwrap();
112            assert_eq!(serialization_buffer, byte_representation);
113
114            // Test the deserialization output
115            let deserialized_data = TimeInterval::deserialize(&byte_representation).unwrap();
116            assert_eq!(deserialized_data, object_representation);
117        }
118    }
119}