tor_checkable/
timed.rs

1//! Convenience implementation of a TimeBound object.
2
3use std::ops::{Bound, Deref, RangeBounds};
4use std::time;
5
6/// A TimeBound object that is valid for a specified range of time.
7///
8/// The range is given as an argument, as in `t1..t2`.
9///
10///
11/// ```
12/// use std::time::{SystemTime, Duration};
13/// use tor_checkable::{Timebound, TimeValidityError, timed::TimerangeBound};
14///
15/// let now = SystemTime::now();
16/// let one_hour = Duration::new(3600, 0);
17///
18/// // This seven is only valid for another hour!
19/// let seven = TimerangeBound::new(7_u32, ..now+one_hour);
20///
21/// assert_eq!(seven.check_valid_at(&now).unwrap(), 7);
22///
23/// // That consumed the previous seven. Try another one.
24/// let seven = TimerangeBound::new(7_u32, ..now+one_hour);
25/// assert_eq!(seven.check_valid_at(&(now+2*one_hour)),
26///            Err(TimeValidityError::Expired(one_hour)));
27///
28/// ```
29#[derive(Debug, Clone)]
30#[cfg_attr(test, derive(Eq, PartialEq))]
31pub struct TimerangeBound<T> {
32    /// The underlying object, which we only want to expose if it is
33    /// currently timely.
34    obj: T,
35    /// If present, when the object first became valid.
36    start: Option<time::SystemTime>,
37    /// If present, when the object will no longer be valid.
38    end: Option<time::SystemTime>,
39}
40
41/// Helper: convert a Bound to its underlying value, if any.
42///
43/// This helper discards information about whether the bound was
44/// inclusive or exclusive.  However, since SystemTime has sub-second
45/// precision, we really don't care about what happens when the
46/// nanoseconds are equal to exactly 0.
47fn unwrap_bound(b: Bound<&'_ time::SystemTime>) -> Option<time::SystemTime> {
48    match b {
49        Bound::Included(x) => Some(*x),
50        Bound::Excluded(x) => Some(*x),
51        _ => None,
52    }
53}
54
55impl<T> TimerangeBound<T> {
56    /// Construct a new TimerangeBound object from a given object and range.
57    ///
58    /// Note that we do not distinguish between inclusive and
59    /// exclusive bounds: `x..y` and `x..=y` are treated the same
60    /// here.
61    pub fn new<U>(obj: T, range: U) -> Self
62    where
63        U: RangeBounds<time::SystemTime>,
64    {
65        let start = unwrap_bound(range.start_bound());
66        let end = unwrap_bound(range.end_bound());
67        Self { obj, start, end }
68    }
69
70    /// Construct a new TimerangeBound object from a given object, start time, and end time.
71    pub fn new_from_start_end(
72        obj: T,
73        start: Option<time::SystemTime>,
74        end: Option<time::SystemTime>,
75    ) -> Self {
76        Self { obj, start, end }
77    }
78
79    /// Adjust this time-range bound to tolerate an expiration time farther
80    /// in the future.
81    #[must_use]
82    pub fn extend_tolerance(self, d: time::Duration) -> Self {
83        let end = match self.end {
84            Some(t) => t.checked_add(d),
85            _ => None,
86        };
87        Self { end, ..self }
88    }
89    /// Adjust this time-range bound to tolerate an initial validity
90    /// time farther in the past.
91    #[must_use]
92    pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
93        let start = match self.start {
94            Some(t) => t.checked_sub(d),
95            _ => None,
96        };
97        Self { start, ..self }
98    }
99    /// Consume this [`TimerangeBound`], and return a new one with the same
100    /// bounds, applying `f` to its protected value.
101    ///
102    /// The caller must ensure that `f` does not make any assumptions about the
103    /// timeliness of the protected value, or leak any of its contents in
104    /// an inappropriate way.
105    #[must_use]
106    pub fn dangerously_map<F, U>(self, f: F) -> TimerangeBound<U>
107    where
108        F: FnOnce(T) -> U,
109    {
110        TimerangeBound {
111            obj: f(self.obj),
112            start: self.start,
113            end: self.end,
114        }
115    }
116
117    /// Consume this TimeRangeBound, and return its underlying time bounds and
118    /// object.
119    ///
120    /// The caller takes responsibility for making sure that the bounds are
121    /// actually checked.
122    pub fn dangerously_into_parts(
123        self,
124    ) -> (T, (Option<time::SystemTime>, Option<time::SystemTime>)) {
125        let bounds = self.bounds();
126
127        (self.obj, bounds)
128    }
129
130    /// Return a reference to the inner object of this TimeRangeBound, without
131    /// checking the time interval.
132    ///
133    /// The caller takes responsibility for making sure that nothing is actually
134    /// done with the inner object that would rely on the bounds being correct, until
135    /// the bounds are (eventually) checked.
136    pub fn dangerously_peek(&self) -> &T {
137        &self.obj
138    }
139
140    /// Return a `TimerangeBound` containing a reference
141    ///
142    /// This can be useful to call methods like `.check_valid_at`
143    /// without consuming the inner `T`.
144    pub fn as_ref(&self) -> TimerangeBound<&T> {
145        TimerangeBound {
146            obj: &self.obj,
147            start: self.start,
148            end: self.end,
149        }
150    }
151
152    /// Return a `TimerangeBound` containing a reference to `T`'s `Deref`
153    pub fn as_deref(&self) -> TimerangeBound<&T::Target>
154    where
155        T: Deref,
156    {
157        self.as_ref().dangerously_map(|t| &**t)
158    }
159
160    /// Return the underlying time bounds of this object.
161    pub fn bounds(&self) -> (Option<time::SystemTime>, Option<time::SystemTime>) {
162        (self.start, self.end)
163    }
164}
165
166impl<T> RangeBounds<time::SystemTime> for TimerangeBound<T> {
167    fn start_bound(&self) -> Bound<&time::SystemTime> {
168        self.start
169            .as_ref()
170            .map(Bound::Included)
171            .unwrap_or(Bound::Unbounded)
172    }
173
174    fn end_bound(&self) -> Bound<&time::SystemTime> {
175        self.end
176            .as_ref()
177            .map(Bound::Included)
178            .unwrap_or(Bound::Unbounded)
179    }
180}
181
182impl<T> crate::Timebound<T> for TimerangeBound<T> {
183    type Error = crate::TimeValidityError;
184
185    fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
186        use crate::TimeValidityError;
187        if let Some(start) = self.start {
188            if let Ok(d) = start.duration_since(*t) {
189                return Err(TimeValidityError::NotYetValid(d));
190            }
191        }
192
193        if let Some(end) = self.end {
194            if let Ok(d) = t.duration_since(end) {
195                return Err(TimeValidityError::Expired(d));
196            }
197        }
198
199        Ok(())
200    }
201
202    fn dangerously_assume_timely(self) -> T {
203        self.obj
204    }
205}
206
207#[cfg(test)]
208mod test {
209    // @@ begin test lint list maintained by maint/add_warning @@
210    #![allow(clippy::bool_assert_comparison)]
211    #![allow(clippy::clone_on_copy)]
212    #![allow(clippy::dbg_macro)]
213    #![allow(clippy::mixed_attributes_style)]
214    #![allow(clippy::print_stderr)]
215    #![allow(clippy::print_stdout)]
216    #![allow(clippy::single_char_pattern)]
217    #![allow(clippy::unwrap_used)]
218    #![allow(clippy::unchecked_duration_subtraction)]
219    #![allow(clippy::useless_vec)]
220    #![allow(clippy::needless_pass_by_value)]
221    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
222    use super::*;
223    use crate::{TimeValidityError, Timebound};
224    use humantime::parse_rfc3339;
225    use std::time::{Duration, SystemTime};
226
227    #[test]
228    fn test_bounds() {
229        #![allow(clippy::unwrap_used)]
230        let one_day = Duration::new(86400, 0);
231        let mixminion_v0_0_1 = parse_rfc3339("2003-01-07T00:00:00Z").unwrap();
232        let tor_v0_0_2pre13 = parse_rfc3339("2003-10-19T00:00:00Z").unwrap();
233        let cussed_nougat = parse_rfc3339("2008-08-02T00:00:00Z").unwrap();
234        let tor_v0_4_4_5 = parse_rfc3339("2020-09-15T00:00:00Z").unwrap();
235        let today = parse_rfc3339("2020-09-22T00:00:00Z").unwrap();
236
237        let tr = TimerangeBound::new((), ..tor_v0_4_4_5);
238        assert_eq!(tr.start, None);
239        assert_eq!(tr.end, Some(tor_v0_4_4_5));
240        assert!(tr.is_valid_at(&mixminion_v0_0_1).is_ok());
241        assert!(tr.is_valid_at(&tor_v0_0_2pre13).is_ok());
242        assert_eq!(
243            tr.is_valid_at(&today),
244            Err(TimeValidityError::Expired(7 * one_day))
245        );
246
247        let tr = TimerangeBound::new((), tor_v0_0_2pre13..=tor_v0_4_4_5);
248        assert_eq!(tr.start, Some(tor_v0_0_2pre13));
249        assert_eq!(tr.end, Some(tor_v0_4_4_5));
250        assert_eq!(
251            tr.is_valid_at(&mixminion_v0_0_1),
252            Err(TimeValidityError::NotYetValid(285 * one_day))
253        );
254        assert!(tr.is_valid_at(&cussed_nougat).is_ok());
255        assert_eq!(
256            tr.is_valid_at(&today),
257            Err(TimeValidityError::Expired(7 * one_day))
258        );
259
260        let tr = tr
261            .extend_pre_tolerance(5 * one_day)
262            .extend_tolerance(2 * one_day);
263        assert_eq!(tr.start, Some(tor_v0_0_2pre13 - 5 * one_day));
264        assert_eq!(tr.end, Some(tor_v0_4_4_5 + 2 * one_day));
265
266        let tr = tr
267            .extend_pre_tolerance(Duration::MAX)
268            .extend_tolerance(Duration::MAX);
269        assert_eq!(tr.start, None);
270        assert_eq!(tr.end, None);
271
272        let tr = TimerangeBound::new((), tor_v0_4_4_5..);
273        assert_eq!(tr.start, Some(tor_v0_4_4_5));
274        assert_eq!(tr.end, None);
275        assert_eq!(
276            tr.is_valid_at(&cussed_nougat),
277            Err(TimeValidityError::NotYetValid(4427 * one_day))
278        );
279        assert!(tr.is_valid_at(&today).is_ok());
280    }
281
282    #[test]
283    fn test_checking() {
284        // West and East Germany reunified
285        let de = humantime::parse_rfc3339("1990-10-03T00:00:00Z").unwrap();
286        // Czechoslovakia separates into Czech Republic (Bohemia) & Slovakia
287        let cz_sk = humantime::parse_rfc3339("1993-01-01T00:00:00Z").unwrap();
288        // European Union created
289        let eu = humantime::parse_rfc3339("1993-11-01T00:00:00Z").unwrap();
290        // South Africa holds first free and fair elections
291        let za = humantime::parse_rfc3339("1994-04-27T00:00:00Z").unwrap();
292
293        // check_valid_at
294        let tr = TimerangeBound::new("Hello world", cz_sk..eu);
295        assert!(tr.check_valid_at(&za).is_err());
296
297        let tr = TimerangeBound::new("Hello world", cz_sk..za);
298        assert_eq!(tr.check_valid_at(&eu), Ok("Hello world"));
299
300        // check_valid_now
301        let tr = TimerangeBound::new("hello world", de..);
302        assert_eq!(tr.check_valid_now(), Ok("hello world"));
303
304        let tr = TimerangeBound::new("hello world", ..za);
305        assert!(tr.check_valid_now().is_err());
306
307        // Now try check_valid_at_opt() api
308        let tr = TimerangeBound::new("hello world", de..);
309        assert_eq!(tr.check_valid_at_opt(None), Ok("hello world"));
310        let tr = TimerangeBound::new("hello world", de..);
311        assert_eq!(
312            tr.check_valid_at_opt(Some(SystemTime::now())),
313            Ok("hello world")
314        );
315        let tr = TimerangeBound::new("hello world", ..za);
316        assert!(tr.check_valid_at_opt(None).is_err());
317    }
318
319    #[test]
320    fn test_dangerous() {
321        let t1 = SystemTime::now();
322        let t2 = t1 + Duration::from_secs(60 * 525600);
323        let tr = TimerangeBound::new("cups of coffee", t1..=t2);
324
325        assert_eq!(tr.dangerously_peek(), &"cups of coffee");
326
327        let (a, b) = tr.dangerously_into_parts();
328        assert_eq!(a, "cups of coffee");
329        assert_eq!(b.0, Some(t1));
330        assert_eq!(b.1, Some(t2));
331    }
332
333    #[test]
334    fn test_map() {
335        let t1 = SystemTime::now();
336        let min = Duration::from_secs(60);
337
338        let tb = TimerangeBound::new(17_u32, t1..t1 + 5 * min);
339        let tb = tb.dangerously_map(|v| v * v);
340        assert!(tb.is_valid_at(&(t1 + 1 * min)).is_ok());
341        assert!(tb.is_valid_at(&(t1 + 10 * min)).is_err());
342
343        let val = tb.check_valid_at(&(t1 + 1 * min)).unwrap();
344        assert_eq!(val, 289);
345    }
346
347    #[test]
348    fn test_as_ref() {
349        let t1 = SystemTime::now();
350        let min = Duration::from_secs(60);
351
352        let tb1: TimerangeBound<String> = TimerangeBound::new("hi".into(), t1..t1 + 5 * min);
353        let tb2: TimerangeBound<&String> = tb1.as_ref();
354        let tb3: TimerangeBound<&str> = tb1.as_deref();
355        assert_eq!(tb1, tb2.dangerously_map(|s| s.clone()));
356        assert_eq!(tb1, tb3.dangerously_map(|s| s.to_owned()));
357    }
358}