1
//! Convenience implementation of a TimeBound object.
2

            
3
use std::ops::{Bound, Deref, RangeBounds};
4
use web_time_compat as 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
/// The range is always treated as inclusive.
11
///
12
/// ```
13
/// use web_time_compat::{SystemTime, SystemTimeExt, Duration};
14
/// use tor_checkable::{Timebound, TimeValidityError, timed::TimerangeBound};
15
///
16
/// let now = SystemTime::get();
17
/// let one_hour = Duration::new(3600, 0);
18
///
19
/// // This seven is only valid for another hour!
20
/// let seven = TimerangeBound::new(7_u32, ..now+one_hour);
21
///
22
/// assert_eq!(seven.check_valid_at(&now).unwrap(), 7);
23
///
24
/// // That consumed the previous seven. Try another one.
25
/// let seven = TimerangeBound::new(7_u32, ..now+one_hour);
26
/// assert_eq!(seven.check_valid_at(&(now+2*one_hour)),
27
///            Err(TimeValidityError::Expired(one_hour)));
28
///
29
/// ```
30
#[derive(Debug, Clone)]
31
#[cfg_attr(test, derive(Eq, PartialEq))]
32
pub struct TimerangeBound<T> {
33
    /// The underlying object, which we only want to expose if it is
34
    /// currently timely.
35
    obj: T,
36
    /// If present, when the object first became valid.
37
    start: Option<time::SystemTime>,
38
    /// If present, when the object will no longer be valid.
39
    end: Option<time::SystemTime>,
40
}
41

            
42
/// Helper: convert a Bound to its underlying value, if any.
43
///
44
/// This helper discards information about whether the bound was
45
/// inclusive or exclusive.  However, since SystemTime has sub-second
46
/// precision, we really don't care about what happens when the
47
/// nanoseconds are equal to exactly 0.
48
35456
fn unwrap_bound(b: Bound<&'_ time::SystemTime>) -> Option<time::SystemTime> {
49
35456
    match b {
50
11366
        Bound::Included(x) => Some(*x),
51
15556
        Bound::Excluded(x) => Some(*x),
52
8534
        _ => None,
53
    }
54
35456
}
55

            
56
impl<T> TimerangeBound<T> {
57
    /// Construct a new TimerangeBound object from a given object and range.
58
    ///
59
    /// Note that we do not distinguish between inclusive and
60
    /// exclusive bounds: `x..y` and `x..=y` are treated the same
61
    /// here - as an inclusive range.
62
11894
    pub fn new<U>(obj: T, range: U) -> Self
63
11894
    where
64
11894
        U: RangeBounds<time::SystemTime>,
65
    {
66
11894
        let start = unwrap_bound(range.start_bound());
67
11894
        let end = unwrap_bound(range.end_bound());
68
11894
        Self { obj, start, end }
69
11894
    }
70

            
71
    /// Construct a new TimerangeBound object from a given object, start time, and end time.
72
    pub fn new_from_start_end(
73
        obj: T,
74
        start: Option<time::SystemTime>,
75
        end: Option<time::SystemTime>,
76
    ) -> Self {
77
        Self { obj, start, end }
78
    }
79

            
80
    /// Adjust this time-range bound to tolerate an expiration time farther
81
    /// in the future.
82
    #[must_use]
83
52
    pub fn extend_tolerance(self, d: time::Duration) -> Self {
84
52
        let end = match self.end {
85
52
            Some(t) => t.checked_add(d),
86
            _ => None,
87
        };
88
52
        Self { end, ..self }
89
52
    }
90
    /// Adjust this time-range bound to tolerate an initial validity
91
    /// time farther in the past.
92
    #[must_use]
93
42
    pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
94
42
        let start = match self.start {
95
42
            Some(t) => t.checked_sub(d),
96
            _ => None,
97
        };
98
42
        Self { start, ..self }
99
42
    }
100
    /// Consume this [`TimerangeBound`], and return a new one with the same
101
    /// bounds, applying `f` to its protected value.
102
    ///
103
    /// The caller must ensure that `f` does not make any assumptions about the
104
    /// timeliness of the protected value, or leak any of its contents in
105
    /// an inappropriate way.
106
    #[must_use]
107
1434
    pub fn dangerously_map<F, U>(self, f: F) -> TimerangeBound<U>
108
1434
    where
109
1434
        F: FnOnce(T) -> U,
110
    {
111
1434
        TimerangeBound {
112
1434
            obj: f(self.obj),
113
1434
            start: self.start,
114
1434
            end: self.end,
115
1434
        }
116
1434
    }
117

            
118
    /// Consume this TimeRangeBound, and return its underlying time bounds and
119
    /// object.
120
    ///
121
    /// The caller takes responsibility for making sure that the bounds are
122
    /// actually checked.
123
88
    pub fn dangerously_into_parts(
124
88
        self,
125
88
    ) -> (T, (Option<time::SystemTime>, Option<time::SystemTime>)) {
126
88
        let bounds = self.bounds();
127

            
128
88
        (self.obj, bounds)
129
88
    }
130

            
131
    /// Return a reference to the inner object of this TimeRangeBound, without
132
    /// checking the time interval.
133
    ///
134
    /// The caller takes responsibility for making sure that nothing is actually
135
    /// done with the inner object that would rely on the bounds being correct, until
136
    /// the bounds are (eventually) checked.
137
434
    pub fn dangerously_peek(&self) -> &T {
138
434
        &self.obj
139
434
    }
140

            
141
    /// Return a `TimerangeBound` containing a reference
142
    ///
143
    /// This can be useful to call methods like `.check_valid_at`
144
    /// without consuming the inner `T`.
145
74
    pub fn as_ref(&self) -> TimerangeBound<&T> {
146
74
        TimerangeBound {
147
74
            obj: &self.obj,
148
74
            start: self.start,
149
74
            end: self.end,
150
74
        }
151
74
    }
152

            
153
    /// Return a `TimerangeBound` containing a reference to `T`'s `Deref`
154
2
    pub fn as_deref(&self) -> TimerangeBound<&T::Target>
155
2
    where
156
2
        T: Deref,
157
    {
158
2
        self.as_ref().dangerously_map(|t| &**t)
159
2
    }
160

            
161
    /// Return the underlying time bounds of this object.
162
104
    pub fn bounds(&self) -> (Option<time::SystemTime>, Option<time::SystemTime>) {
163
104
        (self.start, self.end)
164
104
    }
165
}
166

            
167
impl<T> RangeBounds<time::SystemTime> for TimerangeBound<T> {
168
864
    fn start_bound(&self) -> Bound<&time::SystemTime> {
169
864
        self.start
170
864
            .as_ref()
171
864
            .map(Bound::Included)
172
864
            .unwrap_or(Bound::Unbounded)
173
864
    }
174

            
175
864
    fn end_bound(&self) -> Bound<&time::SystemTime> {
176
864
        self.end
177
864
            .as_ref()
178
864
            .map(Bound::Included)
179
864
            .unwrap_or(Bound::Unbounded)
180
864
    }
181
}
182

            
183
impl<T> crate::Timebound<T> for TimerangeBound<T> {
184
    type Error = crate::TimeValidityError;
185

            
186
2316
    fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
187
        use crate::TimeValidityError;
188
2316
        if let Some(start) = self.start {
189
804
            if let Ok(d) = start.duration_since(*t)
190
20
                && d > time::Duration::ZERO
191
            {
192
14
                return Err(TimeValidityError::NotYetValid(d));
193
790
            }
194
1512
        }
195

            
196
2302
        if let Some(end) = self.end {
197
2294
            if let Ok(d) = t.duration_since(end)
198
30
                && d > time::Duration::ZERO
199
            {
200
24
                return Err(TimeValidityError::Expired(d));
201
2270
            }
202
8
        }
203

            
204
2278
        Ok(())
205
2316
    }
206

            
207
1832
    fn dangerously_assume_timely(self) -> T {
208
1832
        self.obj
209
1832
    }
210
}
211

            
212
#[cfg(test)]
213
mod test {
214
    // @@ begin test lint list maintained by maint/add_warning @@
215
    #![allow(clippy::bool_assert_comparison)]
216
    #![allow(clippy::clone_on_copy)]
217
    #![allow(clippy::dbg_macro)]
218
    #![allow(clippy::mixed_attributes_style)]
219
    #![allow(clippy::print_stderr)]
220
    #![allow(clippy::print_stdout)]
221
    #![allow(clippy::single_char_pattern)]
222
    #![allow(clippy::unwrap_used)]
223
    #![allow(clippy::unchecked_time_subtraction)]
224
    #![allow(clippy::useless_vec)]
225
    #![allow(clippy::needless_pass_by_value)]
226
    #![allow(clippy::string_slice)] // See arti#2571
227
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
228
    use super::*;
229
    use crate::{TimeValidityError, Timebound};
230
    use humantime::parse_rfc3339;
231
    use web_time_compat::{Duration, SystemTime, SystemTimeExt};
232

            
233
    #[test]
234
    fn test_bounds() {
235
        #![allow(clippy::unwrap_used)]
236
        let one_day = Duration::new(86400, 0);
237
        let mixminion_v0_0_1 = parse_rfc3339("2003-01-07T00:00:00Z").unwrap();
238
        let tor_v0_0_2pre13 = parse_rfc3339("2003-10-19T00:00:00Z").unwrap();
239
        let cussed_nougat = parse_rfc3339("2008-08-02T00:00:00Z").unwrap();
240
        let tor_v0_4_4_5 = parse_rfc3339("2020-09-15T00:00:00Z").unwrap();
241
        let today = parse_rfc3339("2020-09-22T00:00:00Z").unwrap();
242

            
243
        let tr = TimerangeBound::new((), ..tor_v0_4_4_5);
244
        assert_eq!(tr.start, None);
245
        assert_eq!(tr.end, Some(tor_v0_4_4_5));
246
        assert!(tr.is_valid_at(&mixminion_v0_0_1).is_ok());
247
        assert!(tr.is_valid_at(&tor_v0_0_2pre13).is_ok());
248
        assert_eq!(
249
            tr.is_valid_at(&today),
250
            Err(TimeValidityError::Expired(7 * one_day))
251
        );
252

            
253
        let tr = TimerangeBound::new((), tor_v0_0_2pre13..=tor_v0_4_4_5);
254
        assert_eq!(tr.start, Some(tor_v0_0_2pre13));
255
        assert_eq!(tr.end, Some(tor_v0_4_4_5));
256
        assert_eq!(
257
            tr.is_valid_at(&mixminion_v0_0_1),
258
            Err(TimeValidityError::NotYetValid(285 * one_day))
259
        );
260
        assert!(tr.is_valid_at(&cussed_nougat).is_ok());
261
        assert_eq!(
262
            tr.is_valid_at(&today),
263
            Err(TimeValidityError::Expired(7 * one_day))
264
        );
265

            
266
        let tr = tr
267
            .extend_pre_tolerance(5 * one_day)
268
            .extend_tolerance(2 * one_day);
269
        assert_eq!(tr.start, Some(tor_v0_0_2pre13 - 5 * one_day));
270
        assert_eq!(tr.end, Some(tor_v0_4_4_5 + 2 * one_day));
271

            
272
        let tr = tr
273
            .extend_pre_tolerance(Duration::MAX)
274
            .extend_tolerance(Duration::MAX);
275
        assert_eq!(tr.start, None);
276
        assert_eq!(tr.end, None);
277

            
278
        let tr = TimerangeBound::new((), tor_v0_4_4_5..);
279
        assert_eq!(tr.start, Some(tor_v0_4_4_5));
280
        assert_eq!(tr.end, None);
281
        assert_eq!(
282
            tr.is_valid_at(&cussed_nougat),
283
            Err(TimeValidityError::NotYetValid(4427 * one_day))
284
        );
285
        assert!(tr.is_valid_at(&today).is_ok());
286
    }
287

            
288
    #[test]
289
    fn test_checking() {
290
        // West and East Germany reunified
291
        let de = humantime::parse_rfc3339("1990-10-03T00:00:00Z").unwrap();
292
        // Czechoslovakia separates into Czech Republic (Bohemia) & Slovakia
293
        let cz_sk = humantime::parse_rfc3339("1993-01-01T00:00:00Z").unwrap();
294
        // European Union created
295
        let eu = humantime::parse_rfc3339("1993-11-01T00:00:00Z").unwrap();
296
        // South Africa holds first free and fair elections
297
        let za = humantime::parse_rfc3339("1994-04-27T00:00:00Z").unwrap();
298

            
299
        // check_valid_at
300
        let tr = TimerangeBound::new("Hello world", cz_sk..eu);
301
        assert!(tr.check_valid_at(&za).is_err());
302

            
303
        let tr = TimerangeBound::new("Hello world", cz_sk..za);
304
        assert_eq!(tr.check_valid_at(&eu), Ok("Hello world"));
305

            
306
        // check_valid_now
307
        let tr = TimerangeBound::new("hello world", de..);
308
        assert_eq!(tr.check_valid_now(), Ok("hello world"));
309

            
310
        let tr = TimerangeBound::new("hello world", ..za);
311
        assert!(tr.check_valid_now().is_err());
312

            
313
        // Now try check_valid_at_opt() api
314
        let tr = TimerangeBound::new("hello world", de..);
315
        assert_eq!(tr.check_valid_at_opt(None), Ok("hello world"));
316
        let tr = TimerangeBound::new("hello world", de..);
317
        assert_eq!(
318
            tr.check_valid_at_opt(Some(SystemTime::get())),
319
            Ok("hello world")
320
        );
321
        let tr = TimerangeBound::new("hello world", ..za);
322
        assert!(tr.check_valid_at_opt(None).is_err());
323

            
324
        // edge cases
325
        let tr = TimerangeBound::new("Hello world", de..eu);
326
        let nano = Duration::from_nanos(1);
327
        assert!(tr.is_valid_at(&(de - nano)).is_err());
328
        assert!(tr.is_valid_at(&de).is_ok());
329
        assert!(tr.is_valid_at(&(de + nano)).is_ok());
330
        assert!(tr.is_valid_at(&(eu - nano)).is_ok());
331
        assert!(tr.is_valid_at(&eu).is_ok());
332
        assert!(tr.is_valid_at(&(eu + nano)).is_err());
333
    }
334

            
335
    #[test]
336
    fn test_dangerous() {
337
        let t1 = SystemTime::get();
338
        let t2 = t1 + Duration::from_secs(60 * 525600);
339
        let tr = TimerangeBound::new("cups of coffee", t1..=t2);
340

            
341
        assert_eq!(tr.dangerously_peek(), &"cups of coffee");
342

            
343
        let (a, b) = tr.dangerously_into_parts();
344
        assert_eq!(a, "cups of coffee");
345
        assert_eq!(b.0, Some(t1));
346
        assert_eq!(b.1, Some(t2));
347
    }
348

            
349
    #[test]
350
    fn test_map() {
351
        let t1 = SystemTime::get();
352
        let min = Duration::from_secs(60);
353

            
354
        let tb = TimerangeBound::new(17_u32, t1..t1 + 5 * min);
355
        let tb = tb.dangerously_map(|v| v * v);
356
        assert!(tb.is_valid_at(&(t1 + 1 * min)).is_ok());
357
        assert!(tb.is_valid_at(&(t1 + 10 * min)).is_err());
358

            
359
        let val = tb.check_valid_at(&(t1 + 1 * min)).unwrap();
360
        assert_eq!(val, 289);
361
    }
362

            
363
    #[test]
364
    fn test_as_ref() {
365
        let t1 = SystemTime::get();
366
        let min = Duration::from_secs(60);
367

            
368
        let tb1: TimerangeBound<String> = TimerangeBound::new("hi".into(), t1..t1 + 5 * min);
369
        let tb2: TimerangeBound<&String> = tb1.as_ref();
370
        let tb3: TimerangeBound<&str> = tb1.as_deref();
371
        assert_eq!(tb1, tb2.dangerously_map(|s| s.clone()));
372
        assert_eq!(tb1, tb3.dangerously_map(|s| s.to_owned()));
373
    }
374
}