/ crates / tor-checkable / src / timed.rs
timed.rs
  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  fn 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  
 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      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  
166  impl<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  
182  impl<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)]
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  }