minip2p_platform/
deadline.rs1use core::fmt;
2
3use crate::Now;
4
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
20pub struct Deadline(u64);
21
22impl Deadline {
23 pub const NEVER: Self = Self(u64::MAX);
28
29 pub const IMMEDIATE: Self = Self(0);
35
36 pub const fn from_millis(millis: u64) -> Self {
38 Self(millis)
39 }
40
41 pub const fn as_millis(self) -> u64 {
43 self.0
44 }
45
46 pub const fn is_never(self) -> bool {
48 self.0 == u64::MAX
49 }
50
51 pub const fn is_expired_at(self, now: Now) -> bool {
56 !self.is_never() && now.monotonic_ms >= self.0
57 }
58
59 pub const fn millis_until(self, now: Now) -> u64 {
66 if self.is_never() {
67 return u64::MAX;
68 }
69 self.0.saturating_sub(now.monotonic_ms)
70 }
71
72 pub const fn saturating_add_millis(self, millis: u64) -> Self {
75 Self(self.0.saturating_add(millis))
76 }
77
78 pub const fn earliest(self, other: Self) -> Self {
80 if self.0 <= other.0 { self } else { other }
81 }
82
83 pub const fn earliest_opt(left: Option<Self>, right: Option<Self>) -> Option<Self> {
88 match (left, right) {
89 (Some(left), Some(right)) => Some(left.earliest(right)),
90 (Some(only), None) | (None, Some(only)) => Some(only),
91 (None, None) => None,
92 }
93 }
94}
95
96impl fmt::Display for Deadline {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 if self.is_never() {
99 f.write_str("never")
100 } else {
101 write!(f, "{}ms", self.0)
102 }
103 }
104}
105
106impl From<u64> for Deadline {
107 fn from(value: u64) -> Self {
108 Self::from_millis(value)
109 }
110}
111
112impl From<Deadline> for u64 {
113 fn from(value: Deadline) -> Self {
114 value.0
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use alloc::format;
122
123 #[test]
124 fn expires_once_now_reaches_it() {
125 let deadline = Deadline::from_millis(100);
126 assert!(!deadline.is_expired_at(Now::from_millis(99)));
127 assert!(deadline.is_expired_at(Now::from_millis(100)));
128 assert!(deadline.is_expired_at(Now::from_millis(101)));
129 }
130
131 #[test]
132 fn never_does_not_expire_at_end_of_timeline() {
133 assert!(Deadline::NEVER.is_never());
134 assert!(!Deadline::NEVER.is_expired_at(Now::from_millis(u64::MAX)));
135 }
136
137 #[test]
138 fn never_reports_full_remaining_time_however_far_now_has_advanced() {
139 for now in [0, 1, 1_000_000, u64::MAX / 2, u64::MAX - 1, u64::MAX] {
143 let now = Now::from_millis(now);
144 assert_eq!(
145 Deadline::NEVER.millis_until(now),
146 u64::MAX,
147 "millis_until disagreed with is_expired_at at {now:?}"
148 );
149 assert!(!Deadline::NEVER.is_expired_at(now));
150 }
151 }
152
153 #[test]
154 fn immediate_is_always_due() {
155 for now in [0, 1, 1_000_000, u64::MAX] {
156 let now = Now::from_millis(now);
157 assert!(
158 Deadline::IMMEDIATE.is_expired_at(now),
159 "IMMEDIATE must be due at {now:?}"
160 );
161 assert_eq!(Deadline::IMMEDIATE.millis_until(now), 0);
162 }
163 assert_eq!(
165 Deadline::IMMEDIATE.earliest(Deadline::from_millis(5)),
166 Deadline::IMMEDIATE
167 );
168 }
169
170 #[test]
171 fn remaining_time_saturates_at_zero() {
172 let deadline = Deadline::from(100u64);
174 assert_eq!(deadline.as_millis(), 100);
175 assert_eq!(u64::from(deadline), 100);
176
177 assert_eq!(deadline.millis_until(Now::from_millis(40)), 60);
178 assert_eq!(deadline.millis_until(Now::from_millis(100)), 0);
179 assert_eq!(deadline.millis_until(Now::from_millis(500)), 0);
180 }
181
182 #[test]
183 fn adding_saturates_at_never() {
184 assert_eq!(
185 Deadline::from_millis(10).saturating_add_millis(5),
186 Deadline::from_millis(15)
187 );
188 assert_eq!(
189 Deadline::from_millis(10).saturating_add_millis(u64::MAX),
190 Deadline::NEVER
191 );
192 }
193
194 #[test]
195 fn earliest_picks_the_sooner_deadline() {
196 let soon = Deadline::from_millis(10);
197 let late = Deadline::from_millis(20);
198 assert_eq!(soon.earliest(late), soon);
199 assert_eq!(late.earliest(soon), soon);
200 assert_eq!(soon.earliest(Deadline::NEVER), soon);
201 }
202
203 #[test]
204 fn earliest_opt_treats_none_as_unscheduled() {
205 let soon = Some(Deadline::from_millis(10));
206 let late = Some(Deadline::from_millis(20));
207 assert_eq!(Deadline::earliest_opt(soon, late), soon);
208 assert_eq!(Deadline::earliest_opt(late, soon), soon);
209 assert_eq!(Deadline::earliest_opt(soon, None), soon);
210 assert_eq!(Deadline::earliest_opt(None, late), late);
211 assert_eq!(Deadline::earliest_opt(None, None), None);
212 }
213
214 #[test]
215 fn ordering_is_chronological_with_never_last() {
216 let mut deadlines = [
217 Deadline::NEVER,
218 Deadline::from_millis(30),
219 Deadline::from_millis(10),
220 ];
221 deadlines.sort();
222 assert_eq!(
223 deadlines,
224 [
225 Deadline::from_millis(10),
226 Deadline::from_millis(30),
227 Deadline::NEVER
228 ]
229 );
230 assert_eq!(
231 deadlines.iter().copied().min(),
232 Some(Deadline::from_millis(10))
233 );
234 }
235
236 #[test]
237 fn display_names_the_never_sentinel() {
238 assert_eq!(format!("{}", Deadline::from_millis(25)), "25ms");
239 assert_eq!(format!("{}", Deadline::NEVER), "never");
240 }
241}