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