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