tor_hsservice/time_store.rs
1//! Saving/loading timestamps to disk
2//!
3//! Storing timestamps on disk is not so straightforward.
4//! We need to use wall clock time in order to survive restarts.
5//! But wall clocks can be wrong,
6//! so we need at least to apply some sanity checks.
7//!
8//! This module encapsulates those checks, and some error handling choices.
9//! It allows `Instant`s to be used while the system is running,
10//! with bespoke types for loading/saving.
11//! See [`Loading::load_future`] for the load/save guarantees provided.
12//!
13//! The initial entrypoints are [`Storing::start`] and [`Loading::start`].
14//!
15//! Granularity is 1 second and the precise rounding behaviour is not specified.
16//!
17//! ### Data model
18//!
19//! To mitigate clock skew, we store the wall clock time at which
20//! each timestamp was saved to disk ([`Reference`])
21//! and the offset from now to that timestamp ([`FutureTimestamp`]).
22//!
23//! The same storage time can be used for multiple timestamps that are stored together.
24//!
25//! ### Example
26//!
27//! ```
28//! use serde::{Serialize, Deserialize};
29//! use std::time::{Duration, Instant};
30//! use tor_rtcompat::{PreferredRuntime, SleepProvider as _};
31//!
32//! # use tor_hsservice::time_store_for_doctests_unstable_no_semver_guarantees as time_store;
33//! # #[cfg(all)] // works like #[cfg(FALSE)]. Instead, we have this workaround ^.
34//! use crate::time_store;
35//!
36//! let runtime = PreferredRuntime::create().unwrap();
37//!
38//! #[derive(Serialize, Deserialize, Debug)]
39//! struct Stored {
40//! time_ref: time_store::Reference,
41//! t0: time_store::FutureTimestamp,
42//! }
43//!
44//! let t0: Instant = runtime.now() + Duration::from_secs(60);
45//!
46//! let storing = time_store::Storing::start(&runtime);
47//! let data = Stored {
48//! time_ref: storing.store_ref(),
49//! t0: storing.store_future(t0),
50//! };
51//!
52//! let json = serde_json::to_string(&data).unwrap();
53//!
54//! // later:
55//!
56//! let data: Stored = serde_json::from_str(&json).unwrap();
57//! let loading = time_store::Loading::start(&runtime, data.time_ref);
58//! let t0: Instant = loading.load_future(data.t0);
59//!
60//! assert!(t0 - runtime.now() <= Duration::from_secs(60));
61//! ```
62//!
63//! ### Time arithmetic overflows and stupid system time settings
64//!
65//! Arithmetic is done with signed 64-bit numbers of seconds.
66//! So overflow cannot occur unless the clock is completely ludicrous.
67//! If the clock is ludicrous, time calculations are going to be a mess.
68//! We treat this as clock skew, using saturating arithmetic, rather than returning errors.
69//! Reasonable operation will resume when the clock becomes sane.
70//
71// We generally use u64 for values that can't, for our algorithms, be negative,
72// but i64 for time_t's (even though negative time_t's can't happen on Unix).
73
74// TODO - eventually we hope this will become pub, in another crate
75
76// Rustdoc can complains if we link to these private docs from these docs which are
77// themselves only formatted with --document-private-items.
78// TODO - Remove when this is actually public
79#![allow(rustdoc::private_intra_doc_links)]
80
81use std::fmt::{self, Display};
82use std::str::FromStr;
83use std::time::{Duration, Instant, SystemTime};
84
85use derive_deftly::{define_derive_deftly, Deftly};
86use serde::{Deserialize, Serialize};
87use serde::{Deserializer, Serializer};
88use thiserror::Error;
89use tracing::warn;
90
91use tor_rtcompat::SleepProvider;
92
93//---------- derive-deftly macro for raw accessors, must come first ----------
94
95define_derive_deftly! {
96 /// Define `as_raw` and `from_raw` methods (for a struct with a single field)
97 //
98 // We provide these for the types which are serde, since we are already exposing
99 // and documenting their innards (and we don't want to force people to use serde
100 // trickery if they want to do something unusual).
101 RawConversions expect items:
102
103 impl $ttype {
104 ${for fields { // we have only one field; but d-a wants a context for "a specific field"
105 /// Returns the raw value, as would be serialised
106 pub fn as_raw(self) -> $ftype {
107 self.$fname
108 }
109 #[doc = concat!("/// Constructs a ",stringify!($tname)," from a raw value")]
110 pub fn from_raw(seconds: $ftype) -> $ttype {
111 Self { $fname: seconds }
112 }
113 }}
114 }
115}
116
117define_derive_deftly! {
118 /// Define [`Serialize`] and [`Deserialize`] via string rep or transparently, depending
119 ///
120 /// In human-readable formats, uses the [`Display`] and [`FromStr`].
121 /// In non-human-readable formats, serialises as the single field.
122 ///
123 /// Uses serde's `is_human_readable` to decide.
124 /// structs which don't have exactly one field will cause a compile error.
125 //
126 // This has to be a macro rather than simply a helper newtype
127 // to implement the "transparent" binary version,
128 // since that involves looking into the struct's field.
129 SerdeStringOrTransparent for struct, expect items:
130
131 impl Serialize for $ttype {
132 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
133 if s.is_human_readable() {
134 s.collect_str(self)
135 } else {
136 let Self { $( $fname: raw, ) } = self;
137 raw.serialize(s)
138 }
139 }
140 }
141
142 ${define STRING_VISITOR { $<Deserialize $ttype StringVisitor> }}
143
144 impl<'de> Deserialize<'de> for $ttype {
145 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
146 if d.is_human_readable() {
147 d.deserialize_str($STRING_VISITOR)
148 } else {
149 let raw = Deserialize::deserialize(d)?;
150 Ok(Self { $( $fname: raw, ) })
151 }
152 }
153 }
154
155 /// Visitor for deserializing from a string
156 struct $STRING_VISITOR;
157
158 impl<'de> serde::de::Visitor<'de> for $STRING_VISITOR {
159 type Value = $ttype;
160 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<$ttype, E> {
161 s.parse().map_err(|e| E::custom(e))
162 }
163 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
164 #[allow(clippy::useless_concat)] // False positive
165 {
166 write!(f, concat!("string representing ", stringify!($tname)))
167 }
168 }
169 }
170}
171
172//---------- data types ----------
173
174/// Representation of an absolute time, in the future, suitable for storing to disk
175///
176/// Only meaningful in combination with a [`Reference`].
177///
178/// Obtain one of these from an `Instant` using [`Storing::store_future()`],
179/// and convert it back to an `Instant` with [`Loading::load_future()`],
180///
181/// (Serialises as a representation of how many seconds this was into the future,
182/// when it was stored - ie, with respect to the corresponding [`Reference`];
183/// in binary as a `u64`, in human readable formats as
184/// `T+` plus [`humantime`]'s formatting of the `Duration` in seconds.)
185#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
186#[derive_deftly(RawConversions, SerdeStringOrTransparent)]
187pub struct FutureTimestamp {
188 /// How far this timestamp was in the future, when we stored it
189 offset: u64,
190}
191
192/// On-disk representation of a reference time, used as context for stored timestamps
193///
194/// During store, obtained by [`Storing::store_ref`], and should then be serialised
195/// along with the [`FutureTimestamp`]s.
196///
197/// During load, should be passed to [`Loading::start`], to build a [`Loading`]
198/// which is then used to convert the [`FutureTimestamp`]s back to `Instant`s.
199///
200/// (Serialises as an absolute time:
201/// in binary, as an `i64` representing the `time_t` (Unix Time);
202/// in human-readable formats, an RFC3339 string with seconds precision and timezone `Z`.)
203#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::Display)]
204#[display(
205 "{}",
206 humantime::format_rfc3339_seconds(time_t_to_system_time(*time_t))
207)]
208#[derive(Deftly)]
209#[derive_deftly(RawConversions, SerdeStringOrTransparent)]
210pub struct Reference {
211 /// Unix time (at which the other timestamps were stored)
212 time_t: i64,
213}
214
215/// Context for storing `Instant`s to disk
216///
217/// Obtained from [`Storing::start`].
218///
219/// Contains a reference time, which must also be serialised,
220/// as `Reference` obtained from [`Storing::store_ref`],
221/// and stored alongside the converted timestamps.
222pub struct Storing(Now);
223
224/// The two notions of the current time, for internal use
225//
226// This is a separate type from Storing so that Loading::start can call Now::new,
227// without having to temporarily create a semantically inappropriate Storing.
228struct Now {
229 /// Represents the current time as an opaque `Instant`
230 inst: Instant,
231 /// Represents the current time as a `time_t`
232 time_t: i64,
233}
234
235/// Context for loading `Instant`s from disk
236///
237/// Obtained by [`Loading::start`],
238/// from a [`Reference`]
239/// (loaded from disk alongside the converted timestamps).
240pub struct Loading {
241 /// The current time when this `Loading` was created
242 inst: Instant,
243 /// How long has elapsed, at `inst`, since the timestamps were stored
244 elapsed: u64,
245}
246
247//---------- implementation ----------
248
249/// Convert a `SystemTime` to an `i64` `time_t`
250fn system_time_to_time_t(st: SystemTime) -> i64 {
251 if let Ok(d) = st.duration_since(SystemTime::UNIX_EPOCH) {
252 d.as_secs().try_into().unwrap_or(i64::MAX)
253 } else if let Ok(d) = SystemTime::UNIX_EPOCH.duration_since(st) {
254 d.as_secs().try_into().map(|v: i64| -v).unwrap_or(i64::MIN)
255 } else {
256 panic!("two SystemTimes are neither <= nor >=")
257 }
258}
259
260/// Minimum value of SystemTime
261///
262/// Not a tight bound but tests guarantee no runtime panics.
263fn system_time_min() -> SystemTime {
264 for attempt in [
265 //
266 0x7fff_ffff_ffff_ffff,
267 0x7fff_ffff,
268 0,
269 ] {
270 if let Some(r) = SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(attempt)) {
271 return r;
272 }
273 }
274 panic!("cannot calculate a minimum value for the SystemTime!");
275}
276
277/// Maximum value of SystemTime
278///
279/// Not a tight bound but tests guarantee no runtime panics.
280fn system_time_max() -> SystemTime {
281 for attempt in [
282 //
283 0x7fff_ffff_ffff_ffff,
284 0x7fff_ffff,
285 ] {
286 if let Some(r) = SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(attempt)) {
287 return r;
288 }
289 }
290 panic!("cannot calculate a maximum value for the SystemTime!");
291}
292
293/// Convert a `SystemTime` to an `i64` `time_t`
294//
295// We need this to make the RFC3339 string using humantime.
296// Otherwise we'd have to implement the whole calendar and leap days stuff ourselves.
297// Probably there doesn't end up being any actual code here on Unix at least...
298fn time_t_to_system_time(time_t: i64) -> SystemTime {
299 if let Ok(time_t) = u64::try_from(time_t) {
300 let d = Duration::from_secs(time_t);
301 SystemTime::UNIX_EPOCH
302 .checked_add(d)
303 .unwrap_or_else(system_time_max)
304 } else {
305 let rev: u64 = time_t
306 .saturating_neg()
307 .try_into()
308 .expect("-(-ve i64) not u64");
309 let d = Duration::from_secs(rev);
310 SystemTime::UNIX_EPOCH
311 .checked_sub(d)
312 .unwrap_or_else(system_time_min)
313 }
314}
315
316impl Now {
317 /// Obtain `Now` from a runtime
318 fn new(runtime: &impl SleepProvider) -> Self {
319 let inst = runtime.now();
320 let st = runtime.wallclock();
321 let time_t = system_time_to_time_t(st);
322 Self { inst, time_t }
323 }
324}
325
326impl Storing {
327 /// Prepare to store timestamps, returning a context for serialising `Instant`s
328 ///
329 /// Incorporates reference times obtained from the runtime's clock.
330 //
331 // We don't provide `Storing::{to,from}_raw_parts` nor `Loading::from_raw_parts`
332 // because you can (sort of) do all of constructors with a suitable `SleepProvider`,
333 // and we don't want to give too much detail about the implementation and innards.
334 pub fn start(runtime: &impl SleepProvider) -> Self {
335 Storing(Now::new(runtime))
336 }
337
338 /// Prepare a reference time for storage
339 pub fn store_ref(&self) -> Reference {
340 Reference {
341 time_t: self.0.time_t,
342 }
343 }
344
345 /// Convert an `Instant` in the future into a form suitable for saving on disk
346 ///
347 /// If `val` *isn't* in the future, the current time will be stored instead.
348 pub fn store_future(&self, val: Instant) -> FutureTimestamp {
349 let offset = val
350 .checked_duration_since(self.0.inst)
351 .unwrap_or_default()
352 .as_secs();
353 FutureTimestamp { offset }
354 }
355}
356
357impl Loading {
358 /// Obtain a [`Loading`] from a stored [`Reference`], for deserialising `Instant`s
359 ///
360 /// Uses the runtime's clock as a basis for understanding the supplied `Reference`
361 /// and relating it to the current monotonic time (`Instant`) on this host.
362 pub fn start(runtime: &impl SleepProvider, stored: Reference) -> Loading {
363 let now = Now::new(runtime);
364 let elapsed = now.time_t.saturating_sub(stored.time_t);
365 // If time went backwards, pretend it stood still
366 let elapsed = elapsed.try_into().unwrap_or(0);
367 Loading {
368 inst: now.inst,
369 elapsed,
370 }
371 }
372
373 /// Convert a future `Instant` from a value saved on disk
374 ///
375 /// If the `Instant` that was saved has since passed,
376 /// the returned value is the current time.
377 ///
378 /// If the system clock is inaccurate (or was inaccurate when the timestamp was saved),
379 /// the value may be wrong:
380 /// but, regardless, the returned value will be no further in the future,
381 /// than how far it was in the future when it was saved.
382 ///
383 /// In other words, even in the presence of clock skew, the effect is, at worst,
384 /// as if the local system's clock has stood still, or has run very fast.
385 pub fn load_future(&self, stored: FutureTimestamp) -> Instant {
386 let offset = stored.offset.saturating_sub(self.elapsed);
387 self.inst
388 .checked_add(Duration::from_secs(offset))
389 .unwrap_or_else(|| {
390 warn!("time overflow loading time_t now+{offset}!");
391 // `Instant` is overflowing, which can only happen if something is
392 // very wrong with the system, or `stored.offset` was stupidly large.
393 // Using "now" is clearly wrong but there is no Instant::MAX,
394 // and this seems better than making this method fallible and bailing.
395 self.inst
396 })
397 }
398
399 //----- accessors for Loading -----
400
401 /// Returns how long has elapsed since the timestamps were stored
402 ///
403 /// This depends on the system wall clock being right both when we stored, and now.
404 /// In the presence of clock skew, may return a value which is far too large,
405 /// or too small.
406 ///
407 /// But, if the system wall clock seems to have gone backwards, returns zero.
408 ///
409 /// The time is measured from when [`start`](Loading::start) was called.
410 /// If you need to know that time as an `Instant`,
411 /// use [`as_raw_parts`](Loading::as_raw_parts).
412 //
413 // We provide this (and `as_raw_parts`) because some callers may
414 // actually have a good use for it.
415 pub fn elapsed(&self) -> Duration {
416 Duration::from_secs(self.elapsed)
417 }
418
419 /// Returns how long has elapsed, in seconds, and the `Instant` at which that was true
420 ///
421 /// Returns number of seconds elapsed,
422 /// between when the timestamps were stored,
423 /// and the returned `Instant`.
424 ///
425 /// See [`elapsed`](Loading::elapsed) for details of clock skew handling.
426 pub fn as_raw_parts(&self) -> (u64, Instant) {
427 (self.elapsed, self.inst)
428 }
429}
430
431//---------- formatting ----------
432
433/// Displays as `T+Duration`, where [`Duration`] is in seconds, formatted using [`humantime`]
434//
435// This format is a balance between human-readability and the desire to avoid
436// allowing the possibility of invalid (corrupted) files whose `FutureTimestamp`
437// is actually before the stored `Reference`.
438impl Display for FutureTimestamp {
439 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
440 let d = humantime::format_duration(Duration::from_secs(self.offset));
441 write!(f, "T+{}", d)
442 }
443}
444
445/// Error parsing a timestamp or reference
446#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
447#[non_exhaustive]
448#[derive(Error)]
449#[error("invalid timestamp or reference time format")]
450pub struct ParseError {
451 // We probably don't need to say precisely what's wrong
452}
453
454impl FromStr for FutureTimestamp {
455 type Err = ParseError;
456 // Bespoke parser so we have control over our error/overflow cases
457 // (and also since ideally we don't want to deal with a complex HMS time API).
458 fn from_str(s: &str) -> Result<Self, Self::Err> {
459 let s = s.strip_prefix("T+").ok_or(ParseError {})?;
460 let offset = humantime::parse_duration(s)
461 .map_err(|_: humantime::DurationError| ParseError {})?
462 .as_secs();
463 Ok(FutureTimestamp { offset })
464 }
465}
466
467impl FromStr for Reference {
468 type Err = ParseError;
469 fn from_str(s: &str) -> Result<Self, Self::Err> {
470 let st = humantime::parse_rfc3339(s).map_err(|_| ParseError {})?;
471 let time_t = system_time_to_time_t(st);
472 Ok(Reference { time_t })
473 }
474}
475
476#[cfg(test)]
477mod test {
478 // @@ begin test lint list maintained by maint/add_warning @@
479 #![allow(clippy::bool_assert_comparison)]
480 #![allow(clippy::clone_on_copy)]
481 #![allow(clippy::dbg_macro)]
482 #![allow(clippy::mixed_attributes_style)]
483 #![allow(clippy::print_stderr)]
484 #![allow(clippy::print_stdout)]
485 #![allow(clippy::single_char_pattern)]
486 #![allow(clippy::unwrap_used)]
487 #![allow(clippy::unchecked_duration_subtraction)]
488 #![allow(clippy::useless_vec)]
489 #![allow(clippy::needless_pass_by_value)]
490 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
491 use super::*;
492 use humantime::parse_rfc3339;
493 use itertools::{chain, Itertools};
494 use tor_rtmock::{simple_time::SimpleMockTimeProvider, MockRuntime};
495
496 fn secs(s: u64) -> Duration {
497 Duration::from_secs(s)
498 }
499
500 #[test]
501 fn hms_fmt() {
502 let ft = FutureTimestamp {
503 offset: 2 * 86400 + 22 * 3600 + 4 * 60 + 5,
504 };
505
506 assert_eq!(ft.to_string(), "T+2days 22h 4m 5s");
507
508 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+2days 22h 4m 5s"));
509 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+70h 4m 5s"));
510 // we inherit rounding behaviour from humantime; we don't mind tolerating that
511 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+2days 22h 4m 5s 100ms"));
512 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+2days 22h 4m 5s 900ms"));
513 let e = Err(ParseError {});
514 assert_eq!(e, FutureTimestamp::from_str("2days"));
515 assert_eq!(e, FutureTimestamp::from_str("T+"));
516 assert_eq!(e, FutureTimestamp::from_str("T+ "));
517 assert_eq!(e, FutureTimestamp::from_str("T+X"));
518 assert_eq!(e, FutureTimestamp::from_str("T+23kg"));
519 }
520
521 #[test]
522 #[allow(clippy::unusual_byte_groupings)] // we want them to line up, dammit!
523 fn system_time_conversions() {
524 assert!(system_time_min() <= SystemTime::UNIX_EPOCH);
525 assert!(system_time_max() > SystemTime::UNIX_EPOCH);
526
527 let p = |s| parse_rfc3339(s).expect(s);
528 let time_t_2st = time_t_to_system_time;
529 assert_eq!(p("1970-01-01T00:00:00Z"), time_t_2st(0));
530 assert_eq!(p("2038-01-19T03:14:07Z"), time_t_2st(0x___7fff_ffff));
531 assert_eq!(p("2038-01-19T03:14:08Z"), time_t_2st(0x___8000_0000));
532 assert_eq!(p("2106-02-07T06:28:16Z"), time_t_2st(0x_1_0000_0000));
533 assert_eq!(p("4147-08-20T07:32:16Z"), time_t_2st(0x10_0000_0000));
534 }
535
536 #[test]
537 fn ref_fmt() {
538 let time_t = 1217635200;
539 let rf = Reference { time_t };
540 let s = "2008-08-02T00:00:00Z";
541 assert_eq!(rf.to_string(), s);
542
543 let p = Reference::from_str;
544 assert_eq!(Ok(rf), p(s));
545 // we inherit rounding behaviour from humantime; we don't mind tolerating that
546 assert_eq!(Ok(rf), p("2008-08-02T00:00:00.00Z"));
547 assert_eq!(Ok(rf), p("2008-08-02T00:00:00.999Z"));
548 let e = Err(ParseError {});
549 assert_eq!(e, p("2008-08-02T00:00Z"));
550 assert_eq!(e, p("2008-08-02T00:00:00"));
551 assert_eq!(e, p("2008-08-02T00:00:00B"));
552 assert_eq!(e, p("2008-08-02T00:00:00+00:00"));
553 }
554
555 #[test]
556 fn basic() {
557 #[derive(Serialize, Deserialize, Debug)]
558 struct Stored {
559 stored: Reference,
560 s0: FutureTimestamp,
561 s1: FutureTimestamp,
562 s2: FutureTimestamp,
563 }
564
565 let real_instant = Instant::now();
566 let test_systime = parse_rfc3339("2008-08-02T00:00:00Z").unwrap();
567
568 let mk_runtime = |instant, systime| {
569 let times = SimpleMockTimeProvider::new(instant, systime);
570 MockRuntime::builder().sleep_provider(times).build()
571 };
572
573 let stored = {
574 let runtime = mk_runtime(real_instant + secs(100_000), test_systime);
575 let now = Storing::start(&runtime);
576
577 let t0 = runtime.now() - secs(1000);
578 let t1 = runtime.now() + secs(10);
579 let t2 = runtime.now() + secs(3000);
580
581 Stored {
582 stored: now.store_ref(),
583 s0: now.store_future(t0),
584 s1: now.store_future(t1),
585 s2: now.store_future(t2),
586 }
587 };
588
589 let json = serde_json::to_string(&stored).unwrap();
590 println!("{json}");
591 assert_eq!(
592 json,
593 format!(concat!(
594 r#"{{"stored":"2008-08-02T00:00:00Z","#,
595 r#""s0":"T+0s","s1":"T+10s","s2":"T+50m"}}"#
596 ))
597 );
598
599 let mpack = rmp_serde::to_vec_named(&stored).unwrap();
600 println!("{}", hex::encode(&mpack));
601 assert_eq!(
602 mpack,
603 chain!(
604 &[132, 166],
605 b"stored",
606 &[206],
607 &[0x48, 0x93, 0xa3, 0x80], // 0x4893a380 1217635200 = 2008-08-02T00:00:00Z
608 &[162],
609 b"s0",
610 &[0],
611 &[162],
612 b"s1",
613 &[10],
614 &[162],
615 b"s2",
616 &[205],
617 &[0x0b, 0xb8], // 0xbb8 == 3000
618 )
619 .cloned()
620 .collect_vec(),
621 );
622
623 // Simulate a restart with an Instant which is *smaller* (maybe the host rebooted),
624 // but with a wall clock time 200s later.
625 {
626 let runtime = mk_runtime(real_instant, test_systime + secs(200));
627 let now = Loading::start(&runtime, stored.stored);
628
629 let t0 = now.load_future(stored.s0);
630 let t1 = now.load_future(stored.s1);
631 let t2 = now.load_future(stored.s2);
632
633 assert_eq!(t0, runtime.now()); // was already in the past when stored
634 assert_eq!(t1, runtime.now()); // is now in the past
635 assert_eq!(t2, runtime.now() + secs(2800));
636 }
637
638 // Simulate a restart with a later Instant
639 // and with a wall clock time 1200s *earlier* due to clock skew.
640 {
641 let runtime = mk_runtime(real_instant + secs(200_000), test_systime - secs(1200));
642 let now = Loading::start(&runtime, stored.stored);
643
644 let t0 = now.load_future(stored.s0);
645 let t1 = now.load_future(stored.s1);
646 let t2 = now.load_future(stored.s2);
647
648 assert_eq!(t0, runtime.now()); // was already in the past when stored
649 assert_eq!(t1, runtime.now() + secs(10)); // well, it was only 10 even then
650 assert_eq!(t2, runtime.now() + secs(3000)); // can't be increased
651 }
652 }
653}