structured_xades/validation/
ref_time.rs1use super::{ReferenceIdentifier, ReferenceProperty};
4use crate::{
5 properties::{CalendarFact, CalendarValue, CalendarXml},
6 reference::VisitError,
7 xml::{Access, AccessError},
8};
9use structured_asn1::{
10 ScopedReader,
11 memory::{MemoryError, Work},
12};
13use structured_pkix::Time;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ReferenceTime {
18 Matches,
20 Differs,
22 TimezoneUnstated,
24 Malformed,
26 Unsupported,
28}
29
30#[derive(Debug)]
32pub enum ReferenceTimeError<E, N> {
33 Access(AccessError<E, N>),
35 Xml(E),
37 Index {
39 index: usize,
41 },
42 Resource {
44 node: N,
46 cause: MemoryError,
48 },
49}
50
51pub fn matches_reference_time<X: CalendarXml>(
59 xml: &X,
60 references: &ReferenceProperty<X::Id, X::Document, X::Generation>,
61 index: usize,
62 actual: Time,
63 operation: &ScopedReader<'_, '_>,
64) -> Result<Option<ReferenceTime>, ReferenceTimeError<X::Error, X::Id>> {
65 let work = operation.execution();
66 let access = Access::new(xml, references.snapshot(), &work);
67 access
68 .query(references.node(), |_, _, _| Ok(()))
69 .map_err(ReferenceTimeError::Access)?;
70 let Some(entry) = references.entries().get(index) else {
71 return Err(ReferenceTimeError::Index { index });
72 };
73 let (ReferenceIdentifier::Crl { time: node, .. }
74 | ReferenceIdentifier::Ocsp { time: node, .. }) = entry.identifier()
75 else {
76 return Ok(None);
77 };
78 let compared = access
79 .query(node, |xml, snapshot, work| {
80 Ok(xml.with_calendar(snapshot, node, work, |value| {
81 let answer = match value {
82 CalendarFact::Malformed => ReferenceTime::Malformed,
83 CalendarFact::Unsupported => ReferenceTime::Unsupported,
84 CalendarFact::Value(value) => compare(value, actual, work)
85 .map_err(|cause| ReferenceTimeError::Resource { node, cause })?,
86 };
87 Ok(Some(answer))
88 }))
89 })
90 .map_err(ReferenceTimeError::Access)?;
91 compared.map_err(|cause| match cause {
92 VisitError::Processing(cause) => ReferenceTimeError::Xml(cause),
93 VisitError::Visitor(cause) => cause,
94 })
95}
96
97fn compare<W: Work>(
98 value: CalendarValue<'_>,
99 actual: Time,
100 work: &W,
101) -> Result<ReferenceTime, MemoryError> {
102 let length = value
103 .year_digits
104 .len()
105 .checked_add(value.fraction_digits.map_or(0, str::len))
106 .and_then(|length| length.checked_add(32))
107 .ok_or(MemoryError::WorkExceeded)?;
108 work.charge_work(u64::try_from(length).map_err(|_| MemoryError::WorkExceeded)?)?;
109 let Some(offset) = value.timezone_minutes else {
110 return Ok(ReferenceTime::TimezoneUnstated);
111 };
112 if !(-840..=840).contains(&offset) {
113 return Ok(ReferenceTime::Malformed);
114 }
115 if value.year_digits.len() > 5 {
118 return Ok(ReferenceTime::Differs);
119 }
120 let mut year = 0i64;
121 for digit in value.year_digits.bytes() {
122 if !digit.is_ascii_digit() {
123 return Ok(ReferenceTime::Malformed);
124 }
125 year = year * 10 + i64::from(digit - b'0');
126 }
127 let mut nanos = 0u32;
128 let mut digits = 0;
129 if let Some(fraction) = value.fraction_digits {
130 for digit in fraction.bytes() {
131 if !digit.is_ascii_digit() {
132 return Ok(ReferenceTime::Malformed);
133 }
134 if digits < 9 {
135 nanos = nanos * 10 + u32::from(digit - b'0');
136 } else if digit != b'0' {
137 return Ok(ReferenceTime::Differs);
138 }
139 digits += 1;
140 }
141 }
142 while digits < 9 {
143 nanos *= 10;
144 digits += 1;
145 }
146 let written = structured_pkix::time::Xsd10Label {
147 year: if value.negative_year { -year } else { year },
148 month: value.month,
149 day: value.day,
150 hour: value.hour,
151 minute: value.minute,
152 second: value.second,
153 nanos,
154 };
155 let Some(written) = written.at_utc(offset, work)? else {
159 return Ok(ReferenceTime::Malformed);
160 };
161 let Some(expected) = actual.xsd10_label(work)?.at_utc(0, work)? else {
162 return Ok(ReferenceTime::Differs);
165 };
166 Ok(if written == expected {
167 ReferenceTime::Matches
168 } else {
169 ReferenceTime::Differs
170 })
171}