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