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 requested;
12pub use requested::{ImprintOperand, RequestAgreement, RequestImprintError, RequestedContentError};
13mod extension;
14pub use extension::{RecognitionError, TokenExtension, TokenExtensions};
15mod info;
16pub use info::{IntegerField, TokenInfo};
17mod tsa;
18pub use tsa::TsaReading;
19mod token;
20pub use token::{InfoError, Token};
21mod timeliness;
22pub use timeliness::{RequestWindow, Timeliness, timeliness};
23mod request;
24pub use request::Request;
25mod request_extensions;
26mod request_fields;
27pub use request_fields::{RequestFieldError, RequestFields};
28mod authority;
29mod carried;
30mod signature;
31pub use authority::{AuthorityError, AuthorityFacts, AuthoritySource, TsaIdentity, TsaUsage};
32pub use signature::{SignatureError, TokenSignature};
33mod check;
34mod profile;
35pub use check::{
36    CheckContext, CheckError, CheckedTimestamp, Evidence, EvidenceCopyError, InfoCheckError,
37    MappedCheckError, Requested,
38};
39pub use profile::{
40    MappedProfileCheckError, ProfileCheckError, ProfileFailure, ProfileInput, ProfileSource,
41};
42mod retained;
43pub use retained::{RetainedFacts, RetainedImprint};
44
45pub(crate) fn record<W: structured_asn1::memory::Work>(
46    faults: &mut structured_asn1::memory::OwnedVec<crate::Error, W>,
47    error: crate::Error,
48) -> crate::Result<()> {
49    if error.is_terminal() {
50        return Err(error);
51    }
52    let at = error.offset();
53    let field = error.field();
54    faults
55        .try_push(error)
56        .map_err(|reason| crate::Error::resource(reason, field, at))
57}
58
59/// Accuracy components as read, with zero for an omitted component.
60///
61/// The scalar model preserves readable malformed components for inspection;
62/// constructing it does not establish ASN.1 conformance or a successful proof.
63/// A missing entire Accuracy is represented separately by None. RFC3161
64/// §2.4.2 allows external policy to supply accuracy, but absence in a token
65/// alone never establishes a zero deviation.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct Accuracy {
68    seconds: u64,
69    millis: u64,
70    micros: u64,
71}
72
73impl Accuracy {
74    /// Original scalar components, without narrowing or syntax repair.
75    #[must_use]
76    pub const fn new(seconds: u64, millis: u64, micros: u64) -> Self {
77        Self {
78            seconds,
79            millis,
80            micros,
81        }
82    }
83    /// Whole seconds as read.
84    #[must_use]
85    pub const fn seconds(self) -> u64 {
86        self.seconds
87    }
88    /// Milliseconds as read, zero when omitted.
89    #[must_use]
90    pub const fn millis(self) -> u64 {
91        self.millis
92    }
93    /// Microseconds as read, zero when omitted.
94    #[must_use]
95    pub const fn micros(self) -> u64 {
96        self.micros
97    }
98    /// Exact total deviation, including retained malformed scalar components.
99    #[must_use]
100    pub fn nanos(self) -> u128 {
101        // All three parts are below 2^64; this sum is below 2^95.
102        u128::from(self.seconds) * 1_000_000_000
103            + u128::from(self.millis) * 1_000_000
104            + u128::from(self.micros) * 1_000
105    }
106}
107
108/// Why the timestamp facts cannot establish a temporal endpoint.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum BoundUncertainty {
111    /// No accuracy was stated by the token or supplied as authenticated policy.
112    AccuracyUnstated,
113    /// Existing UTC arithmetic cannot place the source or destination.
114    Utc(Uncertainty),
115}
116
117/// Lower endpoint of the authority's generation-time accuracy interval.
118/// This says nothing about when the imprinted document was signed.
119pub fn earliest(
120    reading: Reading,
121    accuracy: Option<Accuracy>,
122    utc: &LeapTable<'_>,
123) -> Result<Instant, BoundUncertainty> {
124    let accuracy = accuracy.ok_or(BoundUncertainty::AccuracyUnstated)?;
125    shift(reading.start(), accuracy, true, utc)
126}
127
128/// Upper endpoint, retaining subnanosecond rounding and supplied leap facts.
129/// This is a factual interval calculation, not signature or trust validation.
130pub fn latest(
131    reading: Reading,
132    accuracy: Option<Accuracy>,
133    utc: &LeapTable<'_>,
134) -> Result<Instant, BoundUncertainty> {
135    // RFC3161 §2.4.2 allows a policy to state omitted accuracy, but the
136    // absent token field alone supplies none. Prioritize this API limitation
137    // before attempting rounding with independently incomplete UTC facts.
138    // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
139    let accuracy = accuracy.ok_or(BoundUncertainty::AccuracyUnstated)?;
140    let upper = reading.upper_bound(utc).map_err(BoundUncertainty::Utc)?;
141    shift(upper, accuracy, false, utc)
142}
143
144fn shift(
145    at: Instant,
146    accuracy: Accuracy,
147    earlier: bool,
148    utc: &LeapTable<'_>,
149) -> Result<Instant, BoundUncertainty> {
150    let nanos = i128::try_from(accuracy.nanos())
151        .map_err(|_| BoundUncertainty::Utc(Uncertainty::OutOfRange))?;
152    utc.shift(at, if earlier { -nanos } else { nanos })
153        .map_err(BoundUncertainty::Utc)
154}