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

            
3
use crate::{TimeBound, TimeValidityError};
4
use itertools::chain;
5
use std::ops::{Bound, Deref, RangeBounds};
6
use web_time_compat as time;
7

            
8
/// A `TimeBound` object that is valid for a specified range of time.
9
///
10
/// The range is given as an argument, as in `t1..t2`.
11
///
12
/// The range is always treated as inclusive.
13
///
14
/// **Non-invariant**: it is possible for the start to be after the end.
15
/// In that case, it's simply never valid: either expired, or too soon, or both.
16
///
17
/// `TimeRangeBound<()>` aka `TimeRange` is sometimes used as a representation of a time range,
18
/// for example, the return value from [`TimeBound::bounds`].
19
///
20
/// ```
21
/// use web_time_compat::{SystemTime, SystemTimeExt, Duration};
22
/// use tor_checkable::{TimeBound, TimeValidityError, timed::TimeRangeBound};
23
///
24
/// let now = SystemTime::get();
25
/// let one_hour = Duration::new(3600, 0);
26
///
27
/// // This seven is only valid for another hour!
28
/// let seven = TimeRangeBound::new(7_u32, ..now+one_hour);
29
///
30
/// assert_eq!(seven.if_valid_at(&now).unwrap(), 7);
31
///
32
/// // That consumed the previous seven. Try another one.
33
/// let seven = TimeRangeBound::new(7_u32, ..now+one_hour);
34
/// assert_eq!(seven.if_valid_at(&(now+2*one_hour)),
35
///            Err(TimeValidityError::Expired(one_hour)));
36
///
37
/// ```
38
#[derive(Debug, Clone)]
39
#[cfg_attr(test, derive(Eq, PartialEq))]
40
pub struct TimeRangeBound<T> {
41
    /// The underlying object, which we only want to expose if it is
42
    /// currently timely.
43
    obj: T,
44
    /// If present, when the object first became valid.
45
    start: Option<time::SystemTime>,
46
    /// If present, when the object will no longer be valid.
47
    end: Option<time::SystemTime>,
48
}
49

            
50
/// Validity time range.
51
///
52
/// We use `TimeRangeBound<()>` to represent just a validity range.
53
//
54
// We could have a separate `TimeBounds` struct but it would have to have
55
// many of the same constructors, accessors, etc.
56
pub type TimeRange = TimeRangeBound<()>;
57

            
58
/// Deprecated compatibility alias for [`TimeRangeBound`]
59
#[deprecated = "use the new name, TimeRangeBound, instead"]
60
pub type TimerangeBound<T> = TimeRangeBound<T>;
61

            
62
/// Helper: convert a Bound to its underlying value, if any.
63
///
64
/// This helper discards information about whether the bound was
65
/// inclusive or exclusive.  However, since SystemTime has sub-second
66
/// precision, we really don't care about what happens when the
67
/// nanoseconds are equal to exactly 0.
68
134808
fn unwrap_bound(b: Bound<&'_ time::SystemTime>) -> Option<time::SystemTime> {
69
134808
    match b {
70
54043
        Bound::Included(x) => Some(*x),
71
31966
        Bound::Excluded(x) => Some(*x),
72
48799
        _ => None,
73
    }
74
134808
}
75

            
76
impl<T> TimeRangeBound<T> {
77
    /// Construct a new TimeRangeBound object from a given object and range.
78
    ///
79
    /// Note that we do not distinguish between inclusive and
80
    /// exclusive bounds: `x..y` and `x..=y` are treated the same
81
    /// here - as an inclusive range.
82
    ///
83
    /// Use `TimeRange::new_range` to create a `TimeRange` aka a `TimeRangeBound<()>`.
84
42855
    pub fn new<U>(obj: T, range: U) -> Self
85
42855
    where
86
42855
        U: RangeBounds<time::SystemTime>,
87
    {
88
42855
        let start = unwrap_bound(range.start_bound());
89
42855
        let end = unwrap_bound(range.end_bound());
90
42855
        Self { obj, start, end }
91
42855
    }
92

            
93
    /// Construct a new TimeRangeBound object from a given object, start time, and end time.
94
82944
    pub fn new_from_start_end(
95
82944
        obj: T,
96
82944
        start: Option<time::SystemTime>,
97
82944
        end: Option<time::SystemTime>,
98
82944
    ) -> Self {
99
82944
        Self { obj, start, end }
100
82944
    }
101

            
102
    /// Adjust this time-range bound to tolerate an initial validity
103
    /// time farther in the past.
104
    #[must_use]
105
42
    pub fn extend_start_bound(self, d: time::Duration) -> Self {
106
42
        let start = match self.start {
107
42
            Some(t) => t.checked_sub(d),
108
            _ => None,
109
        };
110
42
        Self { start, ..self }
111
42
    }
112
    /// Adjust this time-range bound to tolerate an expiration time farther
113
    /// in the future.
114
    #[must_use]
115
48
    pub fn extend_end_bound(self, d: time::Duration) -> Self {
116
48
        let end = match self.end {
117
48
            Some(t) => t.checked_add(d),
118
            _ => None,
119
        };
120
48
        Self { end, ..self }
121
48
    }
122

            
123
    /// Deprecated alias for `extend_start_bound`
124
    #[deprecated = "use extend_start_bound instead"]
125
    #[must_use]
126
    pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
127
        self.extend_start_bound(d)
128
    }
129
    /// Deprecated alias for `extend_end_bound`
130
    #[deprecated = "use extend_end_bound instead"]
131
    #[must_use]
132
    pub fn extend_tolerance(self, d: time::Duration) -> Self {
133
        self.extend_end_bound(d)
134
    }
135

            
136
    /// Consume this [`TimeRangeBound`], and return a new one with the same
137
    /// bounds, applying `f` to its protected value.
138
    ///
139
    /// The caller must ensure that `f` does not make any assumptions about the
140
    /// timeliness of the protected value, or leak any of its contents in
141
    /// an inappropriate way.
142
    #[must_use]
143
1408
    pub fn dangerously_map<F, U>(self, f: F) -> TimeRangeBound<U>
144
1408
    where
145
1408
        F: FnOnce(T) -> U,
146
    {
147
1408
        TimeRangeBound {
148
1408
            obj: f(self.obj),
149
1408
            start: self.start,
150
1408
            end: self.end,
151
1408
        }
152
1408
    }
153

            
154
    /// Consume this TimeRangeBound, and return its underlying time bounds and
155
    /// object.
156
    ///
157
    /// The caller takes responsibility for making sure that the bounds are
158
    /// actually checked.
159
64
    pub fn dangerously_into_parts(self) -> (T, TimeRange) {
160
64
        let bounds = self.bounds();
161

            
162
64
        (self.obj, bounds)
163
64
    }
164

            
165
    /// Return a reference to the inner object of this TimeRangeBound, without
166
    /// checking the time interval.
167
    ///
168
    /// The caller takes responsibility for making sure that nothing is actually
169
    /// done with the inner object that would rely on the bounds being correct, until
170
    /// the bounds are (eventually) checked.
171
16
    pub fn dangerously_peek(&self) -> &T {
172
16
        &self.obj
173
16
    }
174

            
175
    /// Return a `TimeRangeBound` containing a reference
176
    ///
177
    /// This can be useful to call methods like `.check_valid_at`
178
    /// without consuming the inner `T`.
179
74
    pub fn as_ref(&self) -> TimeRangeBound<&T> {
180
74
        TimeRangeBound {
181
74
            obj: &self.obj,
182
74
            start: self.start,
183
74
            end: self.end,
184
74
        }
185
74
    }
186

            
187
    /// Return a `TimeRangeBound` containing a reference to `T`'s `Deref`
188
2
    pub fn as_deref(&self) -> TimeRangeBound<&T::Target>
189
2
    where
190
2
        T: Deref,
191
    {
192
2
        self.as_ref().dangerously_map(|t| &**t)
193
2
    }
194

            
195
    /// Return the underlying time bounds of this object.
196
40
    pub fn bounds_start_end(&self) -> (Option<time::SystemTime>, Option<time::SystemTime>) {
197
40
        (self.start, self.end)
198
40
    }
199

            
200
    /// Narrow the bounds of `self` to the overlap with `bounds`
201
    ///
202
    /// If the bounds conflict (ie, if the intersection is empty),
203
    /// simply yields a `TimeRangeBound` that is never valid.
204
    ///
205
    /// (This is unlike `tor_basic_utils::rangebounds::RangeBoundsExt::intersect`
206
    /// which *is* implemented for `TimeRange` via [`RangeBounds`]:
207
    /// `intersect` insists on returning a well-formed range,
208
    /// whereas `TimeRangeBound` can be empty if `start > end`.)
209
    // (we can't make the ref to tor_basic_utils a doc link since that's not in scope!)
210
61281
    pub fn intersect_bounds(&mut self, bounds: TimeRange) {
211
61281
        self.start = chain!(self.start, bounds.start()).max();
212
61281
        self.end = chain!(self.end, bounds.end()).min();
213
61281
    }
214

            
215
    /// Process multiple `TimeBound`s, intersecting their validity ranges
216
    ///
217
    /// Within `logic`, [`TimeBound::unwrap_with`] can be used,
218
    /// for unwrapping [`TimeBound`]s.
219
    ///
220
    /// Those time bounds are accumulated within the [`TimeRangeBoundBuilder`],
221
    /// and when `logic` returns, they are applied to its result.
222
    ///
223
    /// This allows multiple time-bound components of (a Tor protocol element)
224
    /// to be conveniently processed into an overall return value.
225
    ///
226
    /// The API is intended to prevent accidentally forgetting to check
227
    /// or process one of the time bounds; `TimeRangeBoundBuilder` is
228
    /// an alternative to manual use of `dangerously_*` and `intersect`.
229
    ///
230
    /// # CORRECTNESS
231
    ///
232
    /// Everything that needs to be bound to the time range must be returned
233
    /// only as part of the return value from `logic`.
234
    ///
235
    /// It is the caller's responsibility not to smuggle out
236
    /// values whose validity time has not been checked
237
    /// out via mutable captures in `logic`, global variables, etc.
238
    ///
239
    /// Likewise, if `logic` returns `Err`, this must mean that callers don't treat
240
    /// the data as valid or successful.  I.e. `Error` must really be an error,
241
    /// and not be used as a way to smuggle out potentially-out-of-time-range data.
242
    ///
243
    /// # Example
244
    ///
245
    /// ```
246
    /// use humantime::parse_rfc3339;
247
    /// use tor_checkable::{TimeBound as _, TimeRangeBound};
248
    ///
249
    /// // Fake document.  A real document would involve signature verification too.
250
    /// struct Data {}
251
    /// struct FakeDoc { data: Data, sig: TimeRangeBound<()>, }
252
    /// impl FakeDoc {
253
    ///     fn parse(_dummy: &str) -> TimeRangeBound<Self> {
254
    ///         let t = |s| parse_rfc3339(s).unwrap();
255
    ///         let sig = TimeRangeBound::new((), ..=t("2001-01-01T00:00:01Z"));
256
    ///         let doc = FakeDoc { data: Data {}, sig };
257
    ///         TimeRangeBound::new(doc, ..=t("2000-01-01T00:00:01Z"))
258
    ///     }
259
    /// }
260
    ///
261
    /// // Demo usage of TimeBoundRangeBuilder, in verification function
262
    /// fn parse_verify(input: &str) -> Result<TimeRangeBound<Data>, ()> {
263
    ///     let parsed = FakeDoc::parse(input); // real parser would be fallible
264
    ///     TimeRangeBound::build_intersect(move |times| {
265
    ///         let FakeDoc { data, sig } = parsed.unwrap_with(times);
266
    ///         let _: () = sig.unwrap_with(times); // would verify signature too
267
    ///         Ok(data)
268
    ///     })
269
    /// }
270
    ///
271
    /// assert_eq!(
272
    ///     parse_verify("dummy").unwrap().bounds().end(),
273
    ///     Some(parse_rfc3339("2000-01-01T00:00:01Z").unwrap()),
274
    /// );
275
    /// ```
276
2120
    pub fn build_intersect<Error, Logic>(logic: Logic) -> Result<Self, Error>
277
2120
    where
278
2120
        Logic: FnOnce(&mut TimeRangeBoundBuilder) -> Result<T, Error>,
279
    {
280
2120
        let mut builder = TimeRangeBoundBuilder(TimeRange::new_range(..));
281
2120
        let output = logic(&mut builder)?;
282
2108
        Ok(builder.0.apply_to(output))
283
2120
    }
284
}
285

            
286
impl TimeRange {
287
    /// Create a new `TimeRange` from a `std::ops::RangeBounds`
288
17463
    pub fn new_range<U>(range: U) -> Self
289
17463
    where
290
17463
        U: RangeBounds<time::SystemTime>,
291
    {
292
17463
        Self::new((), range)
293
17463
    }
294

            
295
    /// Applies this `TimeRange` to a value, protecting it
296
2108
    pub fn apply_to<T>(self, t: T) -> TimeRangeBound<T> {
297
2108
        TimeRangeBound::new(t, self.bounds())
298
2108
    }
299

            
300
    /// Get the start of the validity period
301
    ///
302
    /// `None` means there is no start: the object has been valid forever.
303
    ///
304
    /// Provided only for `TimeRange`; to call on a general [`TimeRangeBound<T>`],
305
    /// write `.bounds().start()`.
306
91753
    pub fn start(&self) -> Option<time::SystemTime> {
307
91753
        self.start
308
91753
    }
309

            
310
    /// Get the end of the validity period
311
    ///
312
    /// `None` means there is no end: the object will been valid forever.
313
    /// This is normally a mistake.
314
    ///
315
    /// Provided only for `TimeRange`; to call on a general [`TimeRangeBound<T>`],
316
    /// write `.bounds().end()`.
317
    //
318
    // We could forbid the lack of an expiry time,
319
    // but it would make everything much less consistent.
320
93528
    pub fn end(&self) -> Option<time::SystemTime> {
321
93528
        self.end
322
93528
    }
323
}
324

            
325
/// Accumulator used by `TimeRangeBounds::build_intersect`
326
///
327
/// Provided to the user's `logic` callback by [`TimeRangeBound::build_intersect`]
328
///
329
/// There is no other way to obtain a `TimeRangeBoundBuilder`.
330
// ^ this property allows the API to prevent accidental drops of time bounds.
331
pub struct TimeRangeBoundBuilder(TimeRange);
332

            
333
impl TimeRangeBoundBuilder {
334
    /// Handle a `TimeBound`, ensuring its validity range will be honoured
335
    ///
336
    /// This is equivalent to [`TimeBound::unwrap_with`],
337
    /// which is normally more convenient.
338
    ///
339
    /// # CORRECTNESS
340
    ///
341
    /// See [`TimeBound::unwrap_with`] and [`TimeRangeBound::build_intersect`].
342
5188
    pub fn incorporate_unwrap<Component: TimeBound>(
343
5188
        &mut self,
344
5188
        component: Component,
345
5188
    ) -> Component::Inner {
346
5188
        self.intersect_bounds(component.bounds());
347
        // Correctness: we include the component's bounds in `self`,
348
        // so that when the whole `build` function returns, those bounds will be re-applied.
349
5188
        component.dangerously_assume_timely()
350
5188
    }
351

            
352
    /// Narrow the bounds of `self` to the overlap with `bounds`
353
    ///
354
    /// Equivalent to `.as_mut_range().intersect_bounds()`.
355
19809
    pub fn intersect_bounds(&mut self, bounds: TimeRange) {
356
19809
        self.as_mut_range().intersect_bounds(bounds);
357
19809
    }
358

            
359
    /// Mutably access the being-built time range.
360
    ///
361
    /// This range is the intersection of all the ranges
362
    /// from calls to `incorporate_unwrap` and
363
    /// `intersect_bounds`.
364
    ///
365
    /// # CORRECTNESS
366
    ///
367
    /// Normally it is only correct to narrow the range, not widen it.
368
    /// Getting the time range right is the responsibility of the caller.
369
    ///
370
    /// Consider [`intersect_bounds`](TimeRangeBoundBuilder::intersect_bounds) instead.
371
19809
    pub fn as_mut_range(&mut self) -> &mut TimeRange {
372
19809
        &mut self.0
373
19809
    }
374
}
375

            
376
impl<T> RangeBounds<time::SystemTime> for TimeRangeBound<T> {
377
85052
    fn start_bound(&self) -> Bound<&time::SystemTime> {
378
85052
        self.start
379
85052
            .as_ref()
380
85052
            .map(Bound::Included)
381
85052
            .unwrap_or(Bound::Unbounded)
382
85052
    }
383

            
384
85052
    fn end_bound(&self) -> Bound<&time::SystemTime> {
385
85052
        self.end
386
85052
            .as_ref()
387
85052
            .map(Bound::Included)
388
85052
            .unwrap_or(Bound::Unbounded)
389
85052
    }
390
}
391

            
392
/// Implement `From<$R> for TimeRange` via `new_range`
393
macro_rules! impl_from_range { { $R:ty } => {
394
    impl From<$R> for TimeRange {
395
        fn from(r: $R) -> TimeRange {
396
            TimeRange::new_range(r)
397
        }
398
    }
399
} }
400

            
401
// We don't implement trivial-seeming `From`/`Into` conversions from non-inclusive ranges,
402
// since strictly speaking we don't preserve the semantics.
403
// They can still be converted manually with `new_range`.
404
impl_from_range! { std::ops::RangeFrom<time::SystemTime> }
405
impl_from_range! { std::ops::RangeFull }
406
impl_from_range! { std::ops::RangeInclusive<time::SystemTime> }
407
impl_from_range! { std::ops::RangeToInclusive<time::SystemTime> }
408

            
409
impl<T> crate::TimeBound for TimeRangeBound<T> {
410
    type Inner = T;
411

            
412
7360
    fn bounds(&self) -> TimeRange {
413
7360
        TimeRangeBound {
414
7360
            obj: (),
415
7360
            start: self.start,
416
7360
            end: self.end,
417
7360
        }
418
7360
    }
419

            
420
2608
    fn check_valid_at(&self, t: &time::SystemTime) -> Result<(), TimeValidityError> {
421
        use crate::TimeValidityError;
422
2608
        if let Some(start) = self.start {
423
876
            if let Ok(d) = start.duration_since(*t)
424
30
                && d > time::Duration::ZERO
425
            {
426
22
                return Err(TimeValidityError::NotYetValid(d));
427
854
            }
428
1732
        }
429

            
430
2586
        if let Some(end) = self.end {
431
2572
            if let Ok(d) = t.duration_since(end)
432
42
                && d > time::Duration::ZERO
433
            {
434
34
                return Err(TimeValidityError::Expired(d));
435
2538
            }
436
14
        }
437

            
438
2552
        Ok(())
439
2608
    }
440

            
441
6728
    fn dangerously_assume_timely(self) -> T {
442
6728
        self.obj
443
6728
    }
444
}
445

            
446
#[cfg(test)]
447
mod test {
448
    // @@ begin test lint list maintained by maint/add_warning @@
449
    #![allow(clippy::bool_assert_comparison)]
450
    #![allow(clippy::clone_on_copy)]
451
    #![allow(clippy::dbg_macro)]
452
    #![allow(clippy::mixed_attributes_style)]
453
    #![allow(clippy::print_stderr)]
454
    #![allow(clippy::print_stdout)]
455
    #![allow(clippy::single_char_pattern)]
456
    #![allow(clippy::unwrap_used)]
457
    #![allow(clippy::unchecked_time_subtraction)]
458
    #![allow(clippy::useless_vec)]
459
    #![allow(clippy::needless_pass_by_value)]
460
    #![allow(clippy::string_slice)] // See arti#2571
461
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
462
    use super::*;
463
    use crate::{TimeBound, TimeValidityError};
464
    use humantime::parse_rfc3339;
465
    use tor_basic_utils::rangebounds::RangeBoundsExt as _;
466
    use web_time_compat::{Duration, SystemTime, SystemTimeExt};
467

            
468
    #[test]
469
    fn test_bounds() {
470
        #![allow(clippy::unwrap_used)]
471
        let one_day = Duration::new(86400, 0);
472
        let mixminion_v0_0_1 = parse_rfc3339("2003-01-07T00:00:00Z").unwrap();
473
        let tor_v0_0_2pre13 = parse_rfc3339("2003-10-19T00:00:00Z").unwrap();
474
        let cussed_nougat = parse_rfc3339("2008-08-02T00:00:00Z").unwrap();
475
        let tor_v0_4_4_5 = parse_rfc3339("2020-09-15T00:00:00Z").unwrap();
476
        let today = parse_rfc3339("2020-09-22T00:00:00Z").unwrap();
477

            
478
        let tr = TimeRangeBound::new((), ..tor_v0_4_4_5);
479
        assert_eq!(tr.start, None);
480
        assert_eq!(tr.end, Some(tor_v0_4_4_5));
481
        assert!(tr.check_valid_at(&mixminion_v0_0_1).is_ok());
482
        assert!(tr.check_valid_at(&tor_v0_0_2pre13).is_ok());
483
        assert_eq!(
484
            tr.check_valid_at(&today),
485
            Err(TimeValidityError::Expired(7 * one_day))
486
        );
487

            
488
        let tr = TimeRangeBound::new((), tor_v0_0_2pre13..=tor_v0_4_4_5);
489
        assert_eq!(tr.start, Some(tor_v0_0_2pre13));
490
        assert_eq!(tr.end, Some(tor_v0_4_4_5));
491
        assert_eq!(
492
            tr.check_valid_at(&mixminion_v0_0_1),
493
            Err(TimeValidityError::NotYetValid(285 * one_day))
494
        );
495
        assert!(tr.check_valid_at(&cussed_nougat).is_ok());
496
        assert_eq!(
497
            tr.check_valid_at(&today),
498
            Err(TimeValidityError::Expired(7 * one_day))
499
        );
500

            
501
        let tr = tr
502
            .extend_start_bound(5 * one_day)
503
            .extend_end_bound(2 * one_day);
504
        assert_eq!(tr.start, Some(tor_v0_0_2pre13 - 5 * one_day));
505
        assert_eq!(tr.end, Some(tor_v0_4_4_5 + 2 * one_day));
506

            
507
        let tr = tr
508
            .extend_start_bound(Duration::MAX)
509
            .extend_end_bound(Duration::MAX);
510
        assert_eq!(tr.start, None);
511
        assert_eq!(tr.end, None);
512

            
513
        let tr = TimeRangeBound::new((), tor_v0_4_4_5..);
514
        assert_eq!(tr.start, Some(tor_v0_4_4_5));
515
        assert_eq!(tr.end, None);
516
        assert_eq!(
517
            tr.check_valid_at(&cussed_nougat),
518
            Err(TimeValidityError::NotYetValid(4427 * one_day))
519
        );
520
        assert!(tr.check_valid_at(&today).is_ok());
521
    }
522

            
523
    #[test]
524
    fn test_checking() {
525
        // West and East Germany reunified
526
        let de = humantime::parse_rfc3339("1990-10-03T00:00:00Z").unwrap();
527
        // Czechoslovakia separates into Czech Republic (Bohemia) & Slovakia
528
        let cz_sk = humantime::parse_rfc3339("1993-01-01T00:00:00Z").unwrap();
529
        // European Union created
530
        let eu = humantime::parse_rfc3339("1993-11-01T00:00:00Z").unwrap();
531
        // South Africa holds first free and fair elections
532
        let za = humantime::parse_rfc3339("1994-04-27T00:00:00Z").unwrap();
533

            
534
        // check_valid_at
535
        let tr = TimeRangeBound::new("Hello world", cz_sk..eu);
536
        assert!(tr.if_valid_at(&za).is_err());
537

            
538
        let tr = TimeRangeBound::new("Hello world", cz_sk..za);
539
        assert_eq!(tr.if_valid_at(&eu), Ok("Hello world"));
540

            
541
        // check_valid_now
542
        #[allow(clippy::disallowed_methods)]
543
        {
544
            let tr = TimeRangeBound::new("hello world", de..);
545
            assert_eq!(tr.if_valid_now(), Ok("hello world"));
546

            
547
            let tr = TimeRangeBound::new("hello world", ..za);
548
            assert!(tr.if_valid_now().is_err());
549
        }
550

            
551
        // Now try check_valid_at_opt() api
552
        let tr = TimeRangeBound::new("hello world", de..);
553
        #[allow(deprecated)]
554
        {
555
            assert_eq!(tr.check_valid_at_opt(None), Ok("hello world"));
556
            let tr = TimeRangeBound::new("hello world", de..);
557
            assert_eq!(
558
                tr.check_valid_at_opt(Some(SystemTime::get())),
559
                Ok("hello world")
560
            );
561
            let tr = TimeRangeBound::new("hello world", ..za);
562
            assert!(tr.check_valid_at_opt(None).is_err());
563
        }
564

            
565
        // edge cases
566
        let tr = TimeRangeBound::new("Hello world", de..eu);
567
        let nano = Duration::from_nanos(1);
568
        assert!(tr.check_valid_at(&(de - nano)).is_err());
569
        assert!(tr.check_valid_at(&de).is_ok());
570
        assert!(tr.check_valid_at(&(de + nano)).is_ok());
571
        assert!(tr.check_valid_at(&(eu - nano)).is_ok());
572
        assert!(tr.check_valid_at(&eu).is_ok());
573
        assert!(tr.check_valid_at(&(eu + nano)).is_err());
574
    }
575

            
576
    #[test]
577
    fn test_dangerous() {
578
        let t1 = SystemTime::get();
579
        let t2 = t1 + Duration::from_secs(60 * 525600);
580
        let tr = TimeRangeBound::new("cups of coffee", t1..=t2);
581

            
582
        assert_eq!(tr.dangerously_peek(), &"cups of coffee");
583

            
584
        let (a, b) = tr.dangerously_into_parts();
585
        assert_eq!(a, "cups of coffee");
586
        assert_eq!(b.start(), Some(t1));
587
        assert_eq!(b.end(), Some(t2));
588
    }
589

            
590
    #[test]
591
    fn test_map() {
592
        let t1 = SystemTime::get();
593
        let min = Duration::from_secs(60);
594

            
595
        let tb = TimeRangeBound::new(17_u32, t1..t1 + 5 * min);
596
        let tb = tb.dangerously_map(|v| v * v);
597
        assert!(tb.check_valid_at(&(t1 + 1 * min)).is_ok());
598
        assert!(tb.check_valid_at(&(t1 + 10 * min)).is_err());
599

            
600
        let val = tb.if_valid_at(&(t1 + 1 * min)).unwrap();
601
        assert_eq!(val, 289);
602
    }
603

            
604
    #[test]
605
    fn test_as_ref() {
606
        let t1 = SystemTime::get();
607
        let min = Duration::from_secs(60);
608

            
609
        let tb1: TimeRangeBound<String> = TimeRangeBound::new("hi".into(), t1..t1 + 5 * min);
610
        let tb2: TimeRangeBound<&String> = tb1.as_ref();
611
        let tb3: TimeRangeBound<&str> = tb1.as_deref();
612
        assert_eq!(tb1, tb2.dangerously_map(|s| s.clone()));
613
        assert_eq!(tb1, tb3.dangerously_map(|s| s.to_owned()));
614
    }
615

            
616
    #[test]
617
    fn test_intersect_bounds() {
618
        // we use tor-basic-utils's intersect as a reference implementation
619
        let bounds = || {
620
            chain!(
621
                [None],
622
                (0..=10)
623
                    .map(|days| {
624
                        parse_rfc3339("2000-01-01T00:00:01Z").unwrap()
625
                            + Duration::from_secs(days * 86400)
626
                    })
627
                    .map(Some),
628
            )
629
        };
630

            
631
        for a_start in bounds() {
632
            for a_end in bounds() {
633
                for b_start in bounds() {
634
                    for b_end in bounds() {
635
                        let mut a = TimeRange::new_from_start_end((), a_start, a_end);
636
                        let b = TimeRange::new_from_start_end((), b_start, b_end);
637
                        let exp = a.intersect(&b).map(TimeRange::new_range);
638
                        a.intersect_bounds(b);
639
                        if let Some(exp) = exp {
640
                            assert_eq!(a, exp);
641
                        } else {
642
                            assert!(a.start() > a.end());
643
                        }
644
                    }
645
                }
646
            }
647
        }
648
    }
649
}