1use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct Duration {
14 pub seconds: i32,
16 pub fraction: u32,
18}
19
20impl Duration {
21 pub const INFINITE: Self = Self {
23 seconds: i32::MAX,
24 fraction: u32::MAX,
25 };
26
27 pub const ZERO: Self = Self {
29 seconds: 0,
30 fraction: 0,
31 };
32
33 #[must_use]
35 pub const fn from_secs(seconds: i32) -> Self {
36 Self {
37 seconds,
38 fraction: 0,
39 }
40 }
41
42 #[must_use]
55 pub const fn from_millis(ms: i32) -> Self {
56 let seconds = ms.div_euclid(1000);
57 let remainder_ms = ms.rem_euclid(1000) as u32; let fraction = ((remainder_ms as u64 * (1u64 << 32)) / 1000) as u32;
59 Self { seconds, fraction }
60 }
61
62 #[must_use]
64 pub const fn is_infinite(self) -> bool {
65 self.seconds == i32::MAX && self.fraction == u32::MAX
66 }
67
68 #[must_use]
72 pub const fn to_nanos(self) -> u128 {
73 if self.is_infinite() {
74 return u128::MAX;
75 }
76 if self.seconds < 0 {
77 return 0;
78 }
79 let secs = self.seconds as u128;
80 let frac_nanos = (self.fraction as u128 * 1_000_000_000) >> 32;
82 secs * 1_000_000_000 + frac_nanos
83 }
84
85 #[must_use]
87 pub const fn is_zero(self) -> bool {
88 self.seconds == 0 && self.fraction == 0
89 }
90
91 pub fn encode_into(self, w: &mut BufferWriter) -> Result<(), EncodeError> {
96 w.write_u32(self.seconds as u32)?;
97 w.write_u32(self.fraction)
98 }
99
100 pub fn decode_from(r: &mut BufferReader<'_>) -> Result<Self, DecodeError> {
105 let seconds = r.read_u32()? as i32;
106 let fraction = r.read_u32()?;
107 Ok(Self { seconds, fraction })
108 }
109
110 #[must_use]
112 pub fn to_bytes_le(self) -> [u8; 8] {
113 let mut out = [0u8; 8];
114 out[..4].copy_from_slice(&self.seconds.to_le_bytes());
115 out[4..].copy_from_slice(&self.fraction.to_le_bytes());
116 out
117 }
118
119 #[must_use]
121 pub fn from_bytes_le(bytes: [u8; 8]) -> Self {
122 let seconds = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
123 let fraction = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
124 Self { seconds, fraction }
125 }
126}
127
128impl Default for Duration {
129 fn default() -> Self {
130 Self::ZERO
131 }
132}
133
134#[cfg(test)]
135#[allow(clippy::unwrap_used)]
136mod tests {
137 use super::*;
138 use zerodds_cdr::Endianness;
139
140 #[test]
141 fn infinite_constant_matches_spec() {
142 assert_eq!(Duration::INFINITE.seconds, i32::MAX);
143 assert_eq!(Duration::INFINITE.fraction, u32::MAX);
144 assert!(Duration::INFINITE.is_infinite());
145 }
146
147 #[test]
148 fn zero_is_default_and_zero() {
149 assert_eq!(Duration::default(), Duration::ZERO);
150 assert!(Duration::ZERO.is_zero());
151 }
152
153 #[test]
154 fn from_secs_has_zero_fraction() {
155 let d = Duration::from_secs(42);
156 assert_eq!(d.seconds, 42);
157 assert_eq!(d.fraction, 0);
158 }
159
160 #[test]
161 fn from_millis_splits_correctly() {
162 let d = Duration::from_millis(1500);
163 assert_eq!(d.seconds, 1);
164 assert_eq!(d.fraction, 2_147_483_648);
166 }
167
168 #[test]
169 fn encode_decode_roundtrip() {
170 let d = Duration {
171 seconds: 7,
172 fraction: 0xCAFE_BABE,
173 };
174 let mut w = BufferWriter::new(Endianness::Little);
175 d.encode_into(&mut w).unwrap();
176 let bytes = w.into_bytes();
177 assert_eq!(bytes.len(), 8);
178 let mut r = BufferReader::new(&bytes, Endianness::Little);
179 let back = Duration::decode_from(&mut r).unwrap();
180 assert_eq!(back, d);
181 }
182
183 #[test]
184 fn to_from_bytes_le_roundtrip() {
185 let d = Duration {
186 seconds: -3,
187 fraction: 0xDEAD_BEEF,
188 };
189 let bytes = d.to_bytes_le();
190 let back = Duration::from_bytes_le(bytes);
191 assert_eq!(back, d);
192 }
193
194 #[test]
195 fn ord_compares_seconds_then_fraction() {
196 let a = Duration {
197 seconds: 1,
198 fraction: 0,
199 };
200 let b = Duration {
201 seconds: 1,
202 fraction: 1,
203 };
204 let c = Duration {
205 seconds: 2,
206 fraction: 0,
207 };
208 assert!(a < b);
209 assert!(b < c);
210 }
211}