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 }