structured_cms/timestamp/timeliness.rs
1//! Request freshness facts, without a replay database or implicit clock.
2
3use super::{Accuracy, BoundUncertainty, earliest, latest};
4use structured_pkix::temporal::{Instant, LeapTable, Reading};
5
6/// Caller-supplied moving window for a uniquely outstanding complete imprint.
7///
8/// RFC3161 §6 requires the requester to remember hashes and establish their
9/// uniqueness. This value asserts that caller-owned precondition; it neither
10/// keeps that history nor proves the request imprint or signature matches.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct RequestWindow {
13 start: Instant,
14 end: Instant,
15}
16
17impl RequestWindow {
18 /// Admits ordered endpoints. Caller tolerance must already be in them.
19 #[must_use]
20 pub fn for_unique_imprint(start: Instant, end: Instant) -> Option<Self> {
21 if start > end {
22 return None;
23 }
24 Some(Self { start, end })
25 }
26}
27
28/// How the supplied facts establish, fail or leave freshness unresolved.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Timeliness {
31 /// An archived token with a supplied local reference, not a live response.
32 NotRequested,
33 /// Complete accuracy/precision interval fits the supplied request window.
34 LocalWindow,
35 /// No usable window comparison; a separately checked nonce matches.
36 RequestNonce,
37 /// A performed local-reference comparison found the token in the future.
38 Future,
39 /// A performed interval comparison found it outside the request window.
40 OutsideWindow,
41 /// Live request context supplied no usable freshness evidence.
42 Unverified,
43 /// Missing accuracy or UTC facts prevent the supplied temporal comparison.
44 Uncertain(BoundUncertainty),
45}
46
47/// Evaluates explicit time facts; it does not establish signature or trust.
48///
49/// `nonce` is the result of comparing the parsed request and token nonce, not
50/// merely nonce presence. `requested` distinguishes a live request from archived
51/// evidence. No clock is read, no tolerance invented and no replay state kept.
52/// Full-interval containment is the explicit supplied-window contract, not an
53/// ASN.1 conformance rule. A completed negative comparison takes precedence over
54/// nonce fallback; missing facts remain uncertainty when no nonce can help.
55#[must_use]
56pub fn timeliness(
57 reading: Reading,
58 accuracy: Option<Accuracy>,
59 utc: &LeapTable<'_>,
60 reference: Option<Instant>,
61 requested: bool,
62 nonce: Option<bool>,
63 window: Option<RequestWindow>,
64) -> Timeliness {
65 // RFC3161 §§2.2, 6 permit nonce freshness without a usable local clock.
66 // They do not make a nonce erase an already performed negative time check.
67 // https://www.rfc-editor.org/rfc/rfc3161.html#section-6
68 let lower = earliest(reading, accuracy, utc);
69 if let (Some(reference), Ok(lower)) = (reference, lower)
70 && lower > reference
71 {
72 return Timeliness::Future;
73 }
74 if let Some(window) = window {
75 match lower.and_then(|start| latest(reading, accuracy, utc).map(|end| (start, end))) {
76 Ok((start, end)) => {
77 if start < window.start || end > window.end {
78 return Timeliness::OutsideWindow;
79 }
80 return Timeliness::LocalWindow;
81 }
82 Err(reason) => {
83 if nonce == Some(true) {
84 return Timeliness::RequestNonce;
85 }
86 return Timeliness::Uncertain(reason);
87 }
88 }
89 }
90 if nonce == Some(true) {
91 return Timeliness::RequestNonce;
92 }
93 if requested {
94 return Timeliness::Unverified;
95 }
96 if reference.is_some() {
97 match lower {
98 Ok(_) => Timeliness::NotRequested,
99 Err(reason) => Timeliness::Uncertain(reason),
100 }
101 } else {
102 Timeliness::Unverified
103 }
104}