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

            
3
use std::ops::{Bound, Deref, 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
#[derive(Debug, Clone)]
30
#[cfg_attr(test, derive(Eq, PartialEq))]
31
pub 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.
47
11652
fn unwrap_bound(b: Bound<&'_ time::SystemTime>) -> Option<time::SystemTime> {
48
11652
    match b {
49
3524
        Bound::Included(x) => Some(*x),
50
5714
        Bound::Excluded(x) => Some(*x),
51
2414
        _ => None,
52
    }
53
11652
}
54

            
55
impl<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
3188
    pub fn new<U>(obj: T, range: U) -> Self
62
3188
    where
63
3188
        U: RangeBounds<time::SystemTime>,
64
3188
    {
65
3188
        let start = unwrap_bound(range.start_bound());
66
3188
        let end = unwrap_bound(range.end_bound());
67
3188
        Self { obj, start, end }
68
3188
    }
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
20
    pub fn extend_tolerance(self, d: time::Duration) -> Self {
83
20
        let end = match self.end {
84
20
            Some(t) => t.checked_add(d),
85
            _ => None,
86
        };
87
20
        Self { end, ..self }
88
20
    }
89
    /// Adjust this time-range bound to tolerate an initial validity
90
    /// time farther in the past.
91
    #[must_use]
92
20
    pub fn extend_pre_tolerance(self, d: time::Duration) -> Self {
93
20
        let start = match self.start {
94
20
            Some(t) => t.checked_sub(d),
95
            _ => None,
96
        };
97
20
        Self { start, ..self }
98
20
    }
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
688
    pub fn dangerously_map<F, U>(self, f: F) -> TimerangeBound<U>
107
688
    where
108
688
        F: FnOnce(T) -> U,
109
688
    {
110
688
        TimerangeBound {
111
688
            obj: f(self.obj),
112
688
            start: self.start,
113
688
            end: self.end,
114
688
        }
115
688
    }
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
88
    pub fn dangerously_into_parts(
123
88
        self,
124
88
    ) -> (T, (Option<time::SystemTime>, Option<time::SystemTime>)) {
125
88
        let bounds = self.bounds();
126
88

            
127
88
        (self.obj, bounds)
128
88
    }
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
96
    pub fn dangerously_peek(&self) -> &T {
137
96
        &self.obj
138
96
    }
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
6
    pub fn as_ref(&self) -> TimerangeBound<&T> {
145
6
        TimerangeBound {
146
6
            obj: &self.obj,
147
6
            start: self.start,
148
6
            end: self.end,
149
6
        }
150
6
    }
151

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

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

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

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

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

            
185
800
    fn is_valid_at(&self, t: &time::SystemTime) -> Result<(), Self::Error> {
186
        use crate::TimeValidityError;
187
800
        if let Some(start) = self.start {
188
98
            if let Ok(d) = start.duration_since(*t) {
189
8
                return Err(TimeValidityError::NotYetValid(d));
190
90
            }
191
702
        }
192

            
193
792
        if let Some(end) = self.end {
194
784
            if let Ok(d) = t.duration_since(end) {
195
16
                return Err(TimeValidityError::Expired(d));
196
768
            }
197
8
        }
198

            
199
776
        Ok(())
200
800
    }
201

            
202
662
    fn dangerously_assume_timely(self) -> T {
203
662
        self.obj
204
662
    }
205
}
206

            
207
#[cfg(test)]
208
mod 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
}