Skip to main content

dope_runtime/
civil.rs

1//! Driver-bound civil-time authority for protocol transitions.
2
3use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
4
5use dope_core::driver::schedule::clock;
6use o3::base::time;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum Error {
10    BeforeUnixEpoch { delta: Duration },
11    NanosecondsOverflow { duration: Duration },
12    Add { source: time::Add },
13    Sub { source: time::Sub },
14    MonotonicRegression { origin: Instant, observed: Instant },
15}
16
17impl From<time::Add> for Error {
18    fn from(source: time::Add) -> Self {
19        Self::Add { source }
20    }
21}
22
23impl From<time::Sub> for Error {
24    fn from(source: time::Sub) -> Self {
25        Self::Sub { source }
26    }
27}
28
29/// Statically dispatched source of absolute civil time.
30pub trait Source: Sized {
31    fn read(&self) -> Result<time::Unix<time::Nanos>, Error>;
32
33    /// Pairs civil time with the monotonic sample immediately preceding its read.
34    /// The clocks are sampled sequentially; their skew is confined to acquisition.
35    /// Injected sources can supply the exact corresponding pair instead.
36    fn sample(&self) -> Result<(time::Unix<time::Nanos>, Instant), Error> {
37        let origin = Instant::now();
38        Ok((self.read()?, origin))
39    }
40
41    /// Projects one fresh sample back to this admitted driver turn. Retaining the
42    /// turn origin keeps observations in the same turn valid without counting
43    /// time spent before acquisition twice in subsequent turns.
44    fn anchor<'d>(&self, witness: &clock::Moment<'d>) -> Result<Anchor<'d>, Error> {
45        let (epoch, observed) = self.sample()?;
46        let origin = witness.into_raw();
47        let elapsed = observed
48            .checked_duration_since(origin)
49            .ok_or(Error::MonotonicRegression { origin, observed })?;
50        Ok(Anchor {
51            epoch: epoch.try_sub(elapsed)?,
52            origin: *witness,
53        })
54    }
55}
56
57#[derive(Clone, Copy, Default)]
58pub enum System {
59    #[default]
60    Wall,
61}
62
63impl System {
64    pub const NEW: Self = Self::Wall;
65}
66
67impl Source for System {
68    fn read(&self) -> Result<time::Unix<time::Nanos>, Error> {
69        let now = SystemTime::now();
70        let duration = match now.duration_since(UNIX_EPOCH) {
71            Ok(duration) => duration,
72            Err(cause) => {
73                return Err(Error::BeforeUnixEpoch {
74                    delta: cause.duration(),
75                });
76            }
77        };
78        let nanos = u64::try_from(duration.as_nanos())
79            .map_err(|_| Error::NanosecondsOverflow { duration })?;
80        Ok(time::Unix::new(nanos))
81    }
82}
83
84/// Stable civil epoch projected by one monotonic driver.
85#[derive(Clone, Copy)]
86pub struct Anchor<'d> {
87    epoch: time::Unix<time::Nanos>,
88    origin: clock::Moment<'d>,
89}
90
91impl<'d> Anchor<'d> {
92    pub fn observe(&self, witness: &clock::Moment<'d>) -> Result<time::Unix<time::Nanos>, Error> {
93        let elapsed = witness
94            .try_since(self.origin)
95            .ok_or_else(|| Error::MonotonicRegression {
96                origin: self.origin.into_raw(),
97                observed: witness.into_raw(),
98            })?;
99        Ok(self.epoch.try_add(elapsed)?)
100    }
101}
102
103const _: () = {
104    assert!(std::mem::size_of::<System>() == 0);
105    assert!(
106        std::mem::size_of::<Anchor<'static>>()
107            == std::mem::size_of::<time::Unix<time::Nanos>>()
108                + std::mem::size_of::<clock::Moment<'static>>()
109    );
110    assert!(!std::mem::needs_drop::<Anchor<'static>>());
111};