Skip to main content

structured_cms/
timestamp.rs

1//! RFC3161 timestamp facts, independent of application timelines and trust.
2
3use structured_pkix::temporal::{Instant, LeapTable, Reading, Uncertainty};
4
5mod accuracy;
6pub use accuracy::{AccuracyReading, Component, ComponentReading, DeviationError, DeviationReason};
7mod policy_accuracy;
8pub use policy_accuracy::{EffectiveAccuracy, InvalidPolicyAccuracy, PolicyAccuracy};
9mod imprint;
10pub use imprint::{HashAlgorithmReading, MessageImprint};
11mod imprint_check;
12pub use imprint_check::{ImprintAgreement, ImprintError};
13mod computed;
14pub use computed::{
15    ComputedInput, ImprintContext, ImprintDigest, ImprintProof, ImprintState, InputFailure,
16    RawInput,
17};
18mod requested;
19pub use requested::{ImprintOperand, RequestAgreement, RequestImprintError, RequestedContentError};
20mod extension;
21pub use extension::{RecognitionError, TokenExtension, TokenExtensions};
22mod info;
23pub use info::{IntegerField, TokenInfo};
24mod tsa;
25pub use tsa::TsaReading;
26mod token;
27pub use computed::{AsyncComputedInput, AsyncRawInput};
28pub use token::{AsyncInfoVisitor, InfoError, Token};
29mod timeliness;
30pub use timeliness::{RequestWindow, Timeliness, timeliness};
31mod request;
32pub use request::Request;
33pub mod response;
34pub use response::Response;
35mod request_extensions;
36mod request_fields;
37pub use request_fields::{RequestFieldError, RequestFields};
38mod authority;
39mod carried;
40mod signature;
41pub use authority::{AuthorityError, AuthorityFacts, AuthoritySource, TsaIdentity, TsaUsage};
42pub use signature::{SignatureError, TokenSignature};
43mod check;
44mod profile;
45pub use check::{
46    CheckContext, CheckError, CheckedTimestamp, Evidence, EvidenceCopyError, InfoCheckError,
47    MappedCheckError, Requested,
48};
49pub use profile::{
50    MappedProfileCheckError, ProfileCheckError, ProfileFailure, ProfileInput, ProfileSource,
51};
52mod retained;
53pub use retained::{RetainedFacts, RetainedImprint};
54
55pub(crate) fn record<W: structured_asn1::memory::Work>(
56    faults: &mut structured_asn1::memory::OwnedVec<crate::Error, W>,
57    error: crate::Error,
58) -> crate::Result<()> {
59    if error.is_terminal() {
60        return Err(error);
61    }
62    let at = error.offset();
63    let field = error.field();
64    faults
65        .try_push(error)
66        .map_err(|reason| crate::Error::resource(reason, field, at))
67}
68
69/// Accuracy components as read, with zero for an omitted component.
70///
71/// The scalar model preserves readable malformed components for inspection;
72/// constructing it does not establish ASN.1 conformance or a successful proof.
73/// A missing entire Accuracy is represented separately by None. RFC3161
74/// §2.4.2 allows external policy to supply accuracy, but absence in a token
75/// alone never establishes a zero deviation.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct Accuracy {
78    seconds: u64,
79    millis: u64,
80    micros: u64,
81}
82
83impl Accuracy {
84    /// Original scalar components, without narrowing or syntax repair.
85    #[must_use]
86    pub const fn new(seconds: u64, millis: u64, micros: u64) -> Self {
87        Self {
88            seconds,
89            millis,
90            micros,
91        }
92    }
93    /// Whole seconds as read.
94    #[must_use]
95    pub const fn seconds(self) -> u64 {
96        self.seconds
97    }
98    /// Milliseconds as read, zero when omitted.
99    #[must_use]
100    pub const fn millis(self) -> u64 {
101        self.millis
102    }
103    /// Microseconds as read, zero when omitted.
104    #[must_use]
105    pub const fn micros(self) -> u64 {
106        self.micros
107    }
108    /// Exact total deviation, including retained malformed scalar components.
109    #[must_use]
110    pub fn nanos(self) -> u128 {
111        // All three parts are below 2^64; this sum is below 2^95.
112        u128::from(self.seconds) * 1_000_000_000
113            + u128::from(self.millis) * 1_000_000
114            + u128::from(self.micros) * 1_000
115    }
116}
117
118/// Why the timestamp facts cannot establish a temporal endpoint.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum BoundUncertainty {
121    /// No accuracy was stated by the token or supplied as authenticated policy.
122    AccuracyUnstated,
123    /// Existing UTC arithmetic cannot place the source or destination.
124    Utc(Uncertainty),
125}
126
127/// Exact time-comparison refusal, independent of proof, trust or policy.
128pub use structured_pkix::temporal::CompareError as GenerationOrderError;
129
130/// Complete generation interval from stated accuracy and explicit UTC facts.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct GenerationInterval {
133    /// Lower endpoint including source decimal precision.
134    pub earliest: Instant,
135    /// Upper endpoint including source decimal precision.
136    pub latest: Instant,
137}
138/// Failure to compute a generation interval; resource refusal is terminal.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum IntervalError {
141    /// Actual current/source work refusal.
142    Resource(structured_asn1::memory::MemoryError),
143    /// No stated accuracy or insufficient authenticated UTC coverage.
144    Uncertain(BoundUncertainty),
145}
146/// Computes both endpoints with the common UTC arithmetic, admitting every
147/// binary table lookup before work. No accuracy is inferred from ordering.
148pub fn interval_in<W: structured_asn1::memory::Work>(
149    reading: Reading,
150    accuracy: Option<Accuracy>,
151    utc: &LeapTable<'_>,
152    work: &W,
153) -> core::result::Result<GenerationInterval, IntervalError> {
154    let bits = usize::BITS - utc.transitions().len().leading_zeros();
155    work.charge_work(48 + 9 * (u64::from(bits) + 1))
156        .map_err(IntervalError::Resource)?;
157    Ok(GenerationInterval {
158        earliest: earliest(reading, accuracy, utc).map_err(IntervalError::Uncertain)?,
159        latest: latest(reading, accuracy, utc).map_err(IntervalError::Uncertain)?,
160    })
161}
162
163/// Lower endpoint of the authority's generation-time accuracy interval.
164/// This says nothing about when the imprinted document was signed.
165pub fn earliest(
166    reading: Reading,
167    accuracy: Option<Accuracy>,
168    utc: &LeapTable<'_>,
169) -> Result<Instant, BoundUncertainty> {
170    let accuracy = accuracy.ok_or(BoundUncertainty::AccuracyUnstated)?;
171    shift(reading.start(), accuracy, true, utc)
172}
173
174/// Upper endpoint, retaining subnanosecond rounding and supplied leap facts.
175/// This is a factual interval calculation, not signature or trust validation.
176pub fn latest(
177    reading: Reading,
178    accuracy: Option<Accuracy>,
179    utc: &LeapTable<'_>,
180) -> Result<Instant, BoundUncertainty> {
181    // RFC3161 §2.4.2 allows a policy to state omitted accuracy, but the
182    // absent token field alone supplies none. Prioritize this API limitation
183    // before attempting rounding with independently incomplete UTC facts.
184    // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
185    let accuracy = accuracy.ok_or(BoundUncertainty::AccuracyUnstated)?;
186    let upper = reading.upper_bound(utc).map_err(BoundUncertainty::Utc)?;
187    shift(upper, accuracy, false, utc)
188}
189
190fn shift(
191    at: Instant,
192    accuracy: Accuracy,
193    earlier: bool,
194    utc: &LeapTable<'_>,
195) -> Result<Instant, BoundUncertainty> {
196    let nanos = i128::try_from(accuracy.nanos())
197        .map_err(|_| BoundUncertainty::Utc(Uncertainty::OutOfRange))?;
198    utc.shift(at, if earlier { -nanos } else { nanos })
199        .map_err(BoundUncertainty::Utc)
200}