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