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}