1use crate::{Error, Result};
10use structured_asn1::{Element, Encoding, Parser, Reader, Span, Tag, universal};
11use structured_cms::{
12 ErrorCode,
13 evidence::EvidenceRecord,
14 signed::{CertificateSet, RevocationSet},
15 timestamp::Token,
16};
17use structured_pkix::temporal::{self, Reading};
18
19const FIELD: &str = "cades.longTermValidation";
20mod closure;
21mod cms_object;
22mod coverage;
23mod poe;
24mod record;
25pub use closure::{Closure, ClosureError, History as PreservationHistory, close_preservation};
26pub use cms_object::{CmsObject, CmsSnapshot, ObjectError, SnapshotError};
27pub use coverage::{Archive as RecordArchive, CoverageError as RecordCoverageError};
28pub use poe::{
29 CheckError as PoeCheckError, CheckResult as PoeCheckResult, InputError as PoeInputError,
30};
31pub use record::{
32 Authorities, Authority, CheckError as RecordCheckError, CheckResult as RecordCheckResult,
33 Checked as RecordChecked, SelectionError as AuthorityError, SourceError as RecordSourceError,
34 Verified as VerifiedRecord,
35};
36
37#[derive(Debug)]
40#[expect(
41 clippy::large_enum_variant,
42 reason = "Both first-read alternatives retain quota-backed flat catalogues inline; boxing adds an allocation to every proof."
43)]
44pub enum PoeValue<'d> {
45 Timestamp(Token<'d>),
47 EvidenceRecord(EvidenceRecord<'d>),
49}
50
51#[derive(Debug)]
54pub struct LongTermValidation<'d> {
55 parser: &'d Parser,
56 element: Element<'d>,
57 encoding: Encoding,
58 depth: u32,
59 date: temporal::ExactReading,
60 date_element: Element<'d>,
61 proof: Option<PoeValue<'d>>,
62 token_source: Option<crate::source::Value<'d>>,
63 certificates: Option<CertificateSet<'d>>,
64 revocations: Option<RevocationSet<'d>>,
65}
66impl<'d> LongTermValidation<'d> {
67 pub fn read(parent: &Reader<'d>, element: &Element<'d>) -> Result<Self> {
71 if element.tag() != Tag::constructed(universal::SEQUENCE) {
72 return Err(syntax(ErrorCode::UnexpectedTag, element.span()));
73 }
74 let mut inside = parent.enter(element).map_err(encoding)?;
75 let date_element = required(&mut inside)?;
76 let date =
77 temporal::read_generalized_exact(&inside, &date_element, FIELD).map_err(Error::Pkix)?;
78 if parent.encoding().is_der() {
79 structured_pkix::der::conforms(&inside, &date_element, FIELD).map_err(Error::Pkix)?;
80 }
81 let mut next = inside.next_element().map_err(encoding)?;
82 let mut token_source = None;
83 let proof = if let Some(candidate) = next {
84 if is_proof(&inside, &candidate)? {
85 let mut wrapper = inside.enter(&candidate).map_err(encoding)?;
86 let value = required(&mut wrapper)?;
87 let proof = if candidate.tag() == Tag::context(true, 0) {
88 token_source = Some(crate::source::Value::of(&wrapper, value, FIELD));
89 let mut source = wrapper
90 .reread(&value, wrapper.encoding())
91 .map_err(encoding)?;
92 PoeValue::Timestamp(Token::read_from(&mut source)?)
93 } else {
94 PoeValue::EvidenceRecord(EvidenceRecord::read(&wrapper, &value)?)
95 };
96 finish(&wrapper)?;
97 next = inside.next_element().map_err(encoding)?;
98 Some(proof)
99 } else {
100 None
101 }
102 } else {
103 None
104 };
105 let certificates =
106 if let Some(candidate) = next.filter(|value| value.tag() == Tag::context(true, 0)) {
107 let value = CertificateSet::read(&inside, &candidate, FIELD)?;
108 next = inside.next_element().map_err(encoding)?;
109 Some(value)
110 } else {
111 None
112 };
113 let revocations =
114 if let Some(candidate) = next.filter(|value| value.tag() == Tag::context(true, 1)) {
115 let value = RevocationSet::read(&inside, &candidate, FIELD)?;
116 next = inside.next_element().map_err(encoding)?;
117 Some(value)
118 } else {
119 None
120 };
121 if let Some(extra) = next {
122 return Err(syntax(ErrorCode::TrailingData, extra.span()));
123 }
124 finish(&inside)?;
125 Ok(Self {
126 parser: parent.parser(),
127 element: *element,
128 encoding: parent.encoding(),
129 depth: parent.depth(),
130 date,
131 date_element,
132 proof,
133 token_source,
134 certificates,
135 revocations,
136 })
137 }
138 #[must_use]
140 pub fn date(&self) -> Reading {
141 self.date.reading()
142 }
143 pub fn compare_date<W: structured_asn1::memory::Work>(
146 &self,
147 other: &Self,
148 utc: &temporal::LeapTable<'_>,
149 work: &W,
150 ) -> core::result::Result<core::cmp::Ordering, temporal::CompareError> {
151 self.date.compare(&other.date, utc, work)
152 }
153 #[must_use]
155 pub fn date_element(&self) -> Element<'d> {
156 self.date_element
157 }
158 #[must_use]
160 pub fn proof(&self) -> Option<&PoeValue<'d>> {
161 self.proof.as_ref()
162 }
163 pub(crate) fn timestamp_source(&self) -> Option<crate::source::Value<'d>> {
164 self.token_source
165 }
166 #[must_use]
168 pub fn certificates(&self) -> Option<&CertificateSet<'d>> {
169 self.certificates.as_ref()
170 }
171 #[must_use]
173 pub fn revocations(&self) -> Option<&RevocationSet<'d>> {
174 self.revocations.as_ref()
175 }
176 #[must_use]
178 pub fn element(&self) -> Element<'d> {
179 self.element
180 }
181 pub fn reader_in<'q>(&self, query: &Reader<'q>) -> Result<Reader<'q>>
183 where
184 'd: 'q,
185 {
186 self.parser
187 .read_query_in(
188 query,
189 self.element.as_bytes(),
190 self.element.span().start,
191 self.encoding,
192 self.depth,
193 )
194 .map_err(encoding)
195 }
196}
197
198fn is_proof(parent: &Reader<'_>, candidate: &Element<'_>) -> Result<bool> {
199 if candidate.tag() != Tag::context(true, 0) && candidate.tag() != Tag::context(true, 1) {
200 return Ok(false);
201 }
202 let mut wrapper = parent.enter(candidate).map_err(encoding)?;
203 let Some(value) = wrapper.next_element().map_err(encoding)? else {
204 return Ok(false);
205 };
206 if value.tag() != Tag::constructed(universal::SEQUENCE) {
207 return Ok(false);
208 }
209 let mut fields = wrapper.enter(&value).map_err(encoding)?;
210 let first = required(&mut fields)?;
211 Ok(if candidate.tag() == Tag::context(true, 0) {
212 first.tag() == Tag::primitive(universal::OID)
213 } else {
214 first.tag() == Tag::primitive(universal::INTEGER)
215 })
216}
217fn required<'d>(reader: &mut Reader<'d>) -> Result<Element<'d>> {
218 reader
219 .next_element()
220 .map_err(encoding)?
221 .ok_or(Error::Syntax {
222 code: ErrorCode::MissingField,
223 field: FIELD,
224 at: reader.offset(),
225 })
226}
227fn finish(reader: &Reader<'_>) -> Result<()> {
228 if !reader.is_empty() {
229 return Err(Error::Syntax {
230 code: ErrorCode::TrailingData,
231 field: FIELD,
232 at: reader.offset(),
233 });
234 }
235 Ok(())
236}
237fn syntax(code: ErrorCode, span: Span) -> Error {
238 Error::Syntax {
239 code,
240 field: FIELD,
241 at: span.start,
242 }
243}
244fn encoding(cause: structured_asn1::Error) -> Error {
245 Error::encoding(cause, FIELD)
246}
247
248pub struct View<'a, 'm, 'd> {
251 pub(crate) prepared: &'a crate::prepare::Preparation<'m, 'd>,
252 pub(crate) entry: &'a crate::prepare::proofs::Entry<'d>,
253 pub(crate) ordinal: usize,
254}
255impl<'a, 'm, 'd> View<'a, 'm, 'd> {
256 #[must_use]
258 pub fn required(&self) -> crate::preservation::Required<'d> {
259 self.entry.required
260 }
261 #[must_use]
263 pub fn ordinal(&self) -> usize {
264 self.ordinal
265 }
266 #[must_use]
268 pub fn timestamps(&self) -> core::ops::Range<usize> {
269 self.entry.timestamps.clone()
270 }
271 #[must_use]
273 pub fn record(&self) -> Option<&'a EvidenceRecord<'d>> {
274 self.entry.record()
275 }
276 #[must_use]
278 pub fn poe(&self) -> Option<&'a LongTermValidation<'d>> {
279 match &self.entry.body {
280 crate::prepare::proofs::Body::Poe(poe) => Some(poe),
281 crate::prepare::proofs::Body::Record(_) => None,
282 }
283 }
284 #[must_use]
286 pub fn prepared(&self) -> &'a crate::prepare::Preparation<'m, 'd> {
287 self.prepared
288 }
289}
290
291impl<'m, 'd> crate::prepare::Preparation<'m, 'd> {
292 #[must_use]
295 pub fn evidence_records(&self) -> usize {
296 self.parsed.proofs.entries.len()
297 }
298 #[must_use]
300 pub fn evidence_faults(&self) -> &[(crate::source::Node, Error)] {
301 &self.parsed.proofs.faults
302 }
303 pub fn evidence(
305 &self,
306 ordinal: usize,
307 operation: &Reader<'d>,
308 ) -> Result<Option<View<'_, 'm, 'd>>> {
309 self.document()
310 .message()
311 .require_session(operation, FIELD)?;
312 operation.charge(1).map_err(encoding)?;
313 Ok(self.parsed.proofs.entries.get(ordinal).map(|entry| View {
314 prepared: self,
315 entry,
316 ordinal,
317 }))
318 }
319}