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

            
3
use std::ops::{Bound, RangeBounds};
4
use 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
pub struct TimerangeBound<T> {
30
    /// The underlying object, which we only want to expose if it is
31
    /// currently timely.
32
    obj: T,
33
    /// If present, when the object first became valid.
34
    start: Option<time::SystemTime>,
35
    /// If present, when the object will no longer be valid.
36
    end: Option<time::SystemTime>,
37
}
38

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

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

            
68
    /// Adjust this time-range bound to tolerate an expiration time farther
69
    /// in the future.
70
    #[must_use]
71
1
    pub fn extend_tolerance(self, d: time::Duration) -> Self {
72
1
        let end = self.end.map(|t| t + d);
73
1
        Self { end, ..self }
74
1
    }
75
    /// Adjust this time-range bound to tolerate an initial validity
76
    /// time farther in the past.
77
    #[must_use]
78
1
    pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
79
1
        let start = self.start.map(|t| t - d);
80
1
        Self { start, ..self }
81
1
    }
82
}
83

            
84
impl<T> crate::Timebound<T> for TimerangeBound<T> {
85
    type Error = crate::TimeValidityError;
86

            
87
    fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
88
        use crate::TimeValidityError;
89
25
        if let Some(start) = self.start {
90
16
            if let Ok(d) = start.duration_since(*t) {
91
2
                return Err(TimeValidityError::NotYetValid(d));
92
14
            }
93
9
        }
94

            
95
23
        if let Some(end) = self.end {
96
21
            if let Ok(d) = t.duration_since(end) {
97
4
                return Err(TimeValidityError::Expired(d));
98
17
            }
99
2
        }
100

            
101
19
        Ok(())
102
25
    }
103

            
104
27
    fn dangerously_assume_timely(self) -> T {
105
27
        self.obj
106
27
    }
107
}
108

            
109
#[cfg(test)]
110
mod test {
111
    use super::*;
112
    use crate::{TimeValidityError, Timebound};
113
    use std::time::{Duration, SystemTime};
114

            
115
    #[test]
116
    fn test_bounds() {
117
        let one_day = Duration::new(86400, 0);
118
        let mixminion_v0_0_1 = SystemTime::UNIX_EPOCH + 12059 * one_day; //2003-01-07
119
        let tor_v0_0_2pre13 = SystemTime::UNIX_EPOCH + 12344 * one_day; //2003-10-19
120
        let cussed_nougat = SystemTime::UNIX_EPOCH + 14093 * one_day; //2008-08-02
121
        let tor_v0_4_4_5 = SystemTime::UNIX_EPOCH + 18520 * one_day; //2020-09-15
122
        let today = SystemTime::UNIX_EPOCH + 18527 * one_day; //2020-09-22
123

            
124
        let tr = TimerangeBound::new((), ..tor_v0_4_4_5);
125
        assert_eq!(tr.start, None);
126
        assert_eq!(tr.end, Some(tor_v0_4_4_5));
127
        assert!(tr.is_valid_at(&mixminion_v0_0_1).is_ok());
128
        assert!(tr.is_valid_at(&tor_v0_0_2pre13).is_ok());
129
        assert_eq!(
130
            tr.is_valid_at(&today),
131
            Err(TimeValidityError::Expired(7 * one_day))
132
        );
133

            
134
        let tr = TimerangeBound::new((), tor_v0_0_2pre13..=tor_v0_4_4_5);
135
        assert_eq!(tr.start, Some(tor_v0_0_2pre13));
136
        assert_eq!(tr.end, Some(tor_v0_4_4_5));
137
        assert_eq!(
138
            tr.is_valid_at(&mixminion_v0_0_1),
139
            Err(TimeValidityError::NotYetValid(285 * one_day))
140
        );
141
        assert!(tr.is_valid_at(&cussed_nougat).is_ok());
142
        assert_eq!(
143
            tr.is_valid_at(&today),
144
            Err(TimeValidityError::Expired(7 * one_day))
145
        );
146

            
147
        let tr = tr
148
            .extend_pre_tolerance(5 * one_day)
149
            .extend_tolerance(2 * one_day);
150
        assert_eq!(tr.start, Some(tor_v0_0_2pre13 - 5 * one_day));
151
        assert_eq!(tr.end, Some(tor_v0_4_4_5 + 2 * one_day));
152

            
153
        let tr = TimerangeBound::new((), tor_v0_4_4_5..);
154
        assert_eq!(tr.start, Some(tor_v0_4_4_5));
155
        assert_eq!(tr.end, None);
156
        assert_eq!(
157
            tr.is_valid_at(&cussed_nougat),
158
            Err(TimeValidityError::NotYetValid(4427 * one_day))
159
        );
160
        assert!(tr.is_valid_at(&today).is_ok());
161
    }
162

            
163
    #[test]
164
    fn test_checking() {
165
        let one_day = Duration::new(86400, 0);
166
        let de = SystemTime::UNIX_EPOCH + one_day * 7580;
167
        let cz_sk = SystemTime::UNIX_EPOCH + one_day * 8401;
168
        let eu = SystemTime::UNIX_EPOCH + one_day * 8705;
169
        let za = SystemTime::UNIX_EPOCH + one_day * 8882;
170

            
171
        let tr = TimerangeBound::new("Hello world", cz_sk..eu);
172
        assert!(tr.check_valid_at(&za).is_err());
173

            
174
        let tr = TimerangeBound::new("Hello world", cz_sk..za);
175
        assert_eq!(tr.check_valid_at(&eu), Ok("Hello world"));
176

            
177
        let tr = TimerangeBound::new("hello world", de..);
178
        assert_eq!(tr.check_valid_now(), Ok("hello world"));
179

            
180
        let tr = TimerangeBound::new("hello world", ..za);
181
        assert!(tr.check_valid_now().is_err());
182
    }
183
}