Skip to main content

structured_xades/properties/
claimed_time.rs

1//! Claimed signing-time schema/UTC qualification without timestamp evidence.
2
3use super::Signed;
4use crate::xml::{Access, AccessError, Snapshot, Xml};
5use structured_asn1::memory::Work;
6
7/// Narrow projection AFTER the XML owner checks the complete original XSD1.0
8/// dateTime. Calendar/lexical parsing and arbitrary year/fraction support remain
9/// in that same owner; this is not a second parser or a POSIX time conversion.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum DateTimeFact {
12    /// Absent/complex/invalid original simple content is not a dateTime value.
13    Malformed,
14    /// This source's schema interpretation has not been completed/supported.
15    Unsupported,
16    /// Complete original XSD1.0 value, with its actual optional timezone.
17    Valid {
18        /// Signed offset in minutes; None means genuinely unstated, never UTC.
19        timezone_minutes: Option<i16>,
20    },
21}
22
23/// XML-owned full XSD1.0 interpretation of original dateTime simple content.
24pub trait DateTimeXml: Xml {
25    /// Check THIS original node under the expected immutable source. Implementors
26    /// reuse their actual scalar kernel and admit ALL scans/joined/collapsed
27    /// capacity before work. A resource/processing refusal is Err, never an
28    /// unsupported/malformed value. Do not extract only a timezone suffix, cap
29    /// precision/year silently, default a zone, or substitute RFC3339/XSD1.1.
30    /// Original lexical data remain accessible through the source/node owner;
31    /// second60 validity is not proof of a real UTC leap event.
32    fn date_time<W: Work>(
33        &self,
34        snapshot: Snapshot<Self::Document, Self::Generation>,
35        node: Self::Id,
36        execution: &W,
37    ) -> Result<DateTimeFact, Self::Error>;
38}
39
40/// Exact components supplied by the XML owner's complete XSD1.0 dateTime kernel.
41/// Digits are original and arbitrary precision. This is a parsed lexical view,
42/// not a finite UTC conversion or claim that a leap second actually occurred.
43#[derive(Debug, Clone, Copy)]
44pub struct CalendarValue<'view> {
45    /// XSD1.0 sign: -0001 denotes 1 BCE; year zero is forbidden.
46    pub negative_year: bool,
47    /// Four or more original validated digits, without precision truncation.
48    pub year_digits: &'view str,
49    /// Gregorian month.
50    pub month: u8,
51    /// Day in the month.
52    pub day: u8,
53    /// Original hour, including end-of-day 24.
54    pub hour: u8,
55    /// Minute within the hour.
56    pub minute: u8,
57    /// Original whole seconds, possibly a leap-second label.
58    pub second: u8,
59    /// Complete fractional digits, preserving arbitrary precision and zeros.
60    pub fraction_digits: Option<&'view str>,
61    /// Original optional signed timezone offset, in minutes.
62    pub timezone_minutes: Option<i16>,
63}
64
65/// Complete calendar interpretation without erasing unsupported/invalid data.
66#[derive(Debug, Clone, Copy)]
67pub enum CalendarFact<'view> {
68    /// Absent, complex or malformed original content.
69    Malformed,
70    /// The original requested schema has not been interpreted.
71    Unsupported,
72    /// Complete original XSD1.0 interpretation.
73    Value(CalendarValue<'view>),
74}
75
76/// Exact parsed components for format-owned equality with PKIX time labels.
77/// Reuse the SAME scalar kernel as [`DateTimeXml`]; no second lexical parser,
78/// normalized owned copy, finite year/fraction bound or schema discovery.
79pub trait CalendarXml: DateTimeXml {
80    /// Visit current-operation components once. Original identity, joining,
81    /// lexical scans and temporary capacity are admitted before work. Returned
82    /// values cannot borrow callback-local digits or joined source storage.
83    fn with_calendar<R, VE, W: Work>(
84        &self,
85        snapshot: Snapshot<Self::Document, Self::Generation>,
86        node: Self::Id,
87        execution: &W,
88        visitor: impl for<'view> FnOnce(CalendarFact<'view>) -> Result<R, VE>,
89    ) -> Result<R, crate::reference::VisitError<Self::Error, VE>>;
90}
91
92/// Complete schema-qualified claimed time in original protected `SignedProperties`.
93/// No timestamp, generation instant, historical truth, freshness or ordering is
94/// established by a signer's claim. All original precision stays in the XML.
95pub struct ClaimedTime<N, D, G> {
96    snapshot: Snapshot<D, G>,
97    signature: N,
98    node: N,
99    timezone_minutes: i16,
100}
101
102impl<N: Copy, D: Copy, G: Copy> ClaimedTime<N, D, G> {
103    /// Original immutable document generation.
104    #[must_use]
105    pub fn snapshot(&self) -> Snapshot<D, G> {
106        self.snapshot
107    }
108    /// Selected signature protecting this exact claimed property.
109    #[must_use]
110    pub fn signature(&self) -> N {
111        self.signature
112    }
113    /// Original property; no calendar conversion or truncated value replaces it.
114    #[must_use]
115    pub fn node(&self) -> N {
116        self.node
117    }
118    /// Actual explicit offset, not an inferred local timezone or canonical form.
119    #[must_use]
120    pub fn timezone_minutes(&self) -> i16 {
121        self.timezone_minutes
122    }
123}
124
125/// An incomplete/invalid signing-time claim does not supply a UTC default.
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum ClaimedTimeFailure {
128    /// Original content fails the complete owner-selected XSD1.0 contract.
129    Malformed,
130    /// Valid XSD dateTime but no zone establishes a claimed UTC value.
131    TimezoneUnstated,
132    /// Original scalar processing is unsupported/incomplete, not invalid syntax.
133    Unsupported,
134}
135
136/// Qualified claim or explicit failed/incomplete original property.
137pub enum ClaimedTimeBinding<N, D, G> {
138    /// Original schema and explicit timezone were established.
139    Bound(ClaimedTime<N, D, G>),
140    /// No qualified claim; this cannot become successful baseline evidence.
141    Unproven {
142        /// Actual original property.
143        node: N,
144        /// Malformed/unstated/unsupported remain distinguishable.
145        cause: ClaimedTimeFailure,
146    },
147}
148
149/// Source/current-operation errors remain terminal and unchanged.
150#[derive(Debug)]
151pub enum ClaimedTimeError<E, N> {
152    /// The original signed-property source differs from this operation.
153    Foreign {
154        /// Original property node.
155        node: N,
156    },
157    /// Exact XML owner, resource or generation refusal.
158    Access(AccessError<E, N>),
159}
160
161/// Result in this XML owner's source/error domain.
162pub type ClaimedTimeResult<X> = Result<
163    ClaimedTimeBinding<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
164    ClaimedTimeError<<X as Xml>::Error, <X as Xml>::Id>,
165>;
166
167/// Check the mandatory ORIGINAL claimed `SigningTime` through the XML owner's
168/// scalar kernel. EN319132-1 6.3(h) requires a claimed UTC time; XSD1.0 3.2.7
169/// defines zoned values and permits explicit offsets. Canonical lexical Z is
170/// not an additional format requirement. An absent timezone is not assumed UTC.
171/// This deliberately never creates an Instant or validates a leap event.
172/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
173/// <https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#dateTime>
174pub fn check_time<X: DateTimeXml, W: Work>(
175    signed: &Signed<X::Id, X::Document, X::Generation>,
176    access: &Access<'_, X, W>,
177) -> ClaimedTimeResult<X> {
178    let node = signed.signing_time();
179    if signed.snapshot() != access.snapshot() {
180        return Err(ClaimedTimeError::Foreign { node });
181    }
182    let fact = access
183        .query(node, |xml, snapshot, work| {
184            xml.date_time(snapshot, node, work)
185        })
186        .map_err(ClaimedTimeError::Access)?;
187    let cause = match fact {
188        DateTimeFact::Malformed => ClaimedTimeFailure::Malformed,
189        DateTimeFact::Unsupported => ClaimedTimeFailure::Unsupported,
190        DateTimeFact::Valid {
191            timezone_minutes: None,
192        } => ClaimedTimeFailure::TimezoneUnstated,
193        DateTimeFact::Valid {
194            timezone_minutes: Some(timezone_minutes),
195        } => {
196            return Ok(ClaimedTimeBinding::Bound(ClaimedTime {
197                snapshot: signed.snapshot(),
198                signature: signed.signature(),
199                node,
200                timezone_minutes,
201            }));
202        }
203    };
204    Ok(ClaimedTimeBinding::Unproven { node, cause })
205}