1use super::{Node, Requirements};
4use crate::{
5 container::{Container, ErrorOf},
6 document::Document,
7 error::{Error, ErrorKind},
8 model::admit,
9 range::RangeSource,
10 source::Location,
11};
12use structured_asn1::memory::{Memory, OwnedVec, Work};
13use structured_cms::{signed::revocation, timestamp::Token};
14
15#[derive(Debug)]
18pub enum Fault {
19 Pdf(Error),
21 Encoding {
23 enclosure: Location,
25 cause: structured_asn1::Error,
27 },
28 Cms {
30 enclosure: Location,
32 cause: structured_cms::Error,
34 },
35}
36
37pub enum Slot<'a, 's, D, G> {
39 Value(Container<'a, 's, D, G>),
41 Unreadable(Fault),
43}
44pub struct Sources<'r, 'a, 's, D, G> {
46 pub(super) required: &'r Requirements<'a, 's, D, G>,
47 pub(super) slots: OwnedVec<Slot<'a, 's, D, G>, Memory>,
48}
49
50pub struct Revocation<'d> {
52 pub(crate) choice: usize,
53 element: structured_asn1::Element<'d>,
54 pub(crate) value: Result<revocation::Reading<'d>, revocation::Error>,
55}
56pub(crate) enum RevocationError {
57 Pdf(Error),
58 Cms(structured_cms::Error),
59 Pkix(structured_pkix::Error),
60}
61pub(crate) fn read_revocations<'d>(
62 token: &Token<'d>,
63 parent: &structured_asn1::Reader<'d>,
64 work: &impl Work,
65 location: Location,
66) -> Result<OwnedVec<Revocation<'d>>, RevocationError> {
67 let mut values = work.vector();
68 for (choice, element) in token.message().revocations().iter().enumerate() {
69 admit(work, location, 1).map_err(RevocationError::Pdf)?;
70 let reader = token
71 .message()
72 .revocation_reader_in(parent, choice)
73 .map_err(RevocationError::Cms)?
74 .expect("bounded original choice");
75 let value = revocation::read(&reader, element);
76 if let Err(cause) = &value
77 && cause.is_terminal()
78 {
79 return Err(match value {
80 Err(revocation::Error::Cms(cause)) => RevocationError::Cms(cause),
81 Err(revocation::Error::Pkix(cause)) => RevocationError::Pkix(cause),
82 Ok(_) => unreachable!("failed original reading"),
83 });
84 }
85 values
86 .try_push(Revocation {
87 choice,
88 element: *element,
89 value,
90 })
91 .map_err(|cause| {
92 RevocationError::Pdf(Error {
93 kind: ErrorKind::Resource(cause),
94 location,
95 })
96 })?;
97 }
98 Ok(values.into_owned())
99}
100impl<'d> Revocation<'d> {
101 #[must_use]
103 pub fn choice(&self) -> usize {
104 self.choice
105 }
106 #[must_use]
108 pub fn element(&self) -> structured_asn1::Element<'d> {
109 self.element
110 }
111 pub fn reading(&self) -> Result<&revocation::Reading<'d>, &revocation::Error> {
113 self.value.as_ref()
114 }
115}
116#[expect(
117 clippy::large_enum_variant,
118 reason = "the funded original-order catalogue stores parsed tokens in place without a separate allocation for every successful occurrence"
119)]
120pub(crate) enum Model<'d> {
121 Known {
122 token: Token<'d>,
123 revocations: OwnedVec<Revocation<'d>, Memory>,
124 },
125 Unreadable(Fault),
126 Source,
127}
128pub struct Models<'v, 'r, 'a, 's, D, G> {
131 pub(crate) sources: &'v Sources<'r, 'a, 's, D, G>,
132 pub(crate) values: OwnedVec<Model<'v>, Memory>,
133}
134
135impl<'s, S: RangeSource> Document<'s, S> {
136 #[expect(
143 clippy::result_large_err,
144 reason = "original unreadable/source failures retain their encoded PDF enclosure while resource refusal cannot allocate an error box"
145 )]
146 pub async fn timestamp_sources<'r, 'a, W: Work>(
147 &self,
148 required: &'r Requirements<'a, 's, S::Document, S::Generation>,
149 operation: &W,
150 ) -> Result<Sources<'r, 'a, 's, S::Document, S::Generation>, ErrorOf<S::Error>> {
151 let root = required.root;
152 let location = root.location();
153 let work = self.session.query(operation);
154 self.check(&work, location)
155 .map_err(|cause| ErrorOf::Pdf(cause.into()))?;
156 if !core::ptr::eq(root.owner().value.session, self.session)
157 || root.owner().value.stamp != self.stamp
158 {
159 return Err(ErrorOf::Pdf(
160 Error {
161 kind: ErrorKind::Foreign,
162 location,
163 }
164 .into(),
165 ));
166 }
167 let mut slots = work.vector();
168 for original in required.document_timestamps() {
169 admit(&work, original.location(), 1).map_err(|cause| ErrorOf::Pdf(cause.into()))?;
170 let value = if let Some(signature) = original.signature() {
171 match self.container(signature, &work).await {
172 Ok(value) => Slot::Value(value),
173 Err(ErrorOf::Pdf(crate::error::ReadFailure::Format(cause)))
174 if matches!(
175 cause.kind,
176 ErrorKind::Syntax(_)
177 | ErrorKind::Ambiguous(_)
178 | ErrorKind::Unsupported(_)
179 ) =>
180 {
181 Slot::Unreadable(Fault::Pdf(cause))
182 }
183 Err(ErrorOf::Encoding { enclosure, cause }) if !cause.is_resource() => {
184 Slot::Unreadable(Fault::Encoding { enclosure, cause })
185 }
186 Err(ErrorOf::Cms { enclosure, cause }) if !cause.is_terminal() => {
187 Slot::Unreadable(Fault::Cms { enclosure, cause })
188 }
189 Err(cause) => return Err(cause),
190 }
191 } else {
192 Slot::Unreadable(Fault::Pdf(
193 original
194 .failure()
195 .expect("unreadable original source")
196 .cause,
197 ))
198 };
199 slots.try_push(value).map_err(|cause| {
200 ErrorOf::Pdf(
201 Error {
202 kind: ErrorKind::Resource(cause),
203 location: original.location(),
204 }
205 .into(),
206 )
207 })?;
208 }
209 self.check(&work, location)
210 .map_err(|cause| ErrorOf::Pdf(cause.into()))?;
211 Ok(Sources {
212 required,
213 slots: slots.into_owned(),
214 })
215 }
216}
217
218impl<'r, 'a, 's, D: Copy + Eq, G: Copy + Eq> Sources<'r, 'a, 's, D, G> {
219 #[must_use]
221 pub fn len(&self) -> usize {
222 self.slots.len()
223 }
224 #[must_use]
226 pub fn is_empty(&self) -> bool {
227 self.slots.is_empty()
228 }
229 pub fn get(
231 &self,
232 ordinal: usize,
233 operation: &impl Work,
234 ) -> crate::Result<Option<&Slot<'a, 's, D, G>>> {
235 let work = self.required.root.owner().value.session.query(operation);
236 admit(&work, self.required.root.location(), 1)?;
237 Ok(self.slots.get(ordinal))
238 }
239 #[expect(
242 clippy::result_large_err,
243 reason = "envelope and revocation parsing preserves original diagnostic storage and PDF coordinates without allocation after failure"
244 )]
245 pub fn parse<'v>(
246 &'v self,
247 operation: &'v impl Work,
248 ) -> Result<Models<'v, 'r, 'a, 's, D, G>, ErrorOf<core::convert::Infallible>> {
249 let work = self.required.root.owner().value.session.query(operation);
250 let location = self.required.root.location();
251 let mut values = work.vector();
252 for slot in &self.slots {
253 admit(&work, location, 1).map_err(|cause| ErrorOf::Pdf(cause.into()))?;
254 let value = match slot {
255 Slot::Unreadable(_) => Model::Source,
256 Slot::Value(container) => match container.read_timestamp(operation) {
257 Ok((token, parent)) => {
258 let enclosure = container.signature().contents();
259 let revocations = read_revocations(&token, &parent, &work, enclosure)
260 .map_err(|cause| match cause {
261 RevocationError::Pdf(cause) => ErrorOf::Pdf(cause.into()),
262 RevocationError::Cms(cause) => ErrorOf::Cms { enclosure, cause },
263 RevocationError::Pkix(cause) => ErrorOf::Pkix { enclosure, cause },
264 })?;
265 Model::Known { token, revocations }
266 }
267 Err(ErrorOf::Encoding { enclosure, cause }) if !cause.is_resource() => {
268 Model::Unreadable(Fault::Encoding { enclosure, cause })
269 }
270 Err(ErrorOf::Cms { enclosure, cause }) if !cause.is_terminal() => {
271 Model::Unreadable(Fault::Cms { enclosure, cause })
272 }
273 Err(cause) => return Err(cause),
274 },
275 };
276 values.try_push(value).map_err(|cause| {
277 ErrorOf::Pdf(
278 Error {
279 kind: ErrorKind::Resource(cause),
280 location,
281 }
282 .into(),
283 )
284 })?;
285 }
286 Ok(Models {
287 sources: self,
288 values: values.into_owned(),
289 })
290 }
291}
292
293impl<'d, D: Copy + Eq, G: Copy + Eq> Models<'d, '_, '_, '_, D, G> {
294 #[expect(
297 clippy::result_large_err,
298 reason = "reopened scoped readers propagate the same typed PDF, ASN.1 and CMS causes as the retained original source"
299 )]
300 pub fn reader<'q, W: Work + 'q>(
301 &'q self,
302 ordinal: usize,
303 operation: &W,
304 ) -> Result<Option<structured_asn1::Reader<'q>>, ErrorOf<core::convert::Infallible>> {
305 let root = self.sources.required.root;
306 let work = root.owner().value.session.query(operation);
307 admit(&work, root.location(), 1).map_err(|cause| ErrorOf::Pdf(cause.into()))?;
308 if !matches!(self.values.get(ordinal), Some(Model::Known { .. })) {
309 return Ok(None);
310 }
311 let Slot::Value(container) = &self.sources.slots[ordinal] else {
312 unreachable!("a parsed token has an original Contents owner")
313 };
314 root.owner()
315 .value
316 .session
317 .carried(container.der(), operation)
318 .map(Some)
319 .map_err(|cause| ErrorOf::Encoding {
320 enclosure: container.signature().contents(),
321 cause,
322 })
323 }
324 pub fn revocations<'q>(
327 &'q self,
328 ordinal: usize,
329 operation: &'q impl Work,
330 ) -> crate::Result<Option<impl Iterator<Item = crate::Result<&'q Revocation<'d>>> + 'q>> {
331 let work = self
332 .sources
333 .required
334 .root
335 .owner()
336 .value
337 .session
338 .query(operation);
339 let location = self.sources.required.root.location();
340 admit(&work, location, 1)?;
341 let Some(Model::Known { revocations, .. }) = self.values.get(ordinal) else {
342 return Ok(None);
343 };
344 let mut next = if revocations.is_empty() {
345 None
346 } else {
347 Some(0)
348 };
349 Ok(Some(core::iter::from_fn(move || {
350 let ordinal = next.take()?;
351 if let Err(cause) = admit(&work, location, 1) {
352 return Some(Err(cause));
353 }
354 let value = &revocations[ordinal];
355 if ordinal + 1 < revocations.len() {
356 next = Some(ordinal + 1);
357 }
358 Some(Ok(value))
359 })))
360 }
361 pub fn token(
363 &self,
364 ordinal: usize,
365 operation: &impl Work,
366 ) -> crate::Result<Option<&Token<'_>>> {
367 let work = self
368 .sources
369 .required
370 .root
371 .owner()
372 .value
373 .session
374 .query(operation);
375 admit(&work, self.sources.required.root.location(), 1)?;
376 Ok(match self.values.get(ordinal) {
377 Some(Model::Known { token, .. }) => Some(token),
378 _ => None,
379 })
380 }
381 pub fn failure(&self, ordinal: usize, operation: &impl Work) -> crate::Result<Option<&Fault>> {
383 let work = self
384 .sources
385 .required
386 .root
387 .owner()
388 .value
389 .session
390 .query(operation);
391 admit(&work, self.sources.required.root.location(), 1)?;
392 Ok(match self.values.get(ordinal) {
393 Some(Model::Unreadable(cause)) => Some(cause),
394 Some(Model::Source) => match &self.sources.slots[ordinal] {
395 Slot::Unreadable(cause) => Some(cause),
396 Slot::Value(_) => unreachable!("unreadable source slot"),
397 },
398 Some(Model::Known { .. }) | None => None,
399 })
400 }
401 pub(crate) fn root(&self) -> Node {
402 Node::of(self.sources.required.root.location())
403 }
404 #[must_use]
406 pub fn revision(&self) -> Option<usize> {
407 self.sources.required.revision()
408 }
409 pub(crate) fn vri_timestamps(&self) -> &[super::VriTimestamp] {
410 self.sources.required.vri_timestamps()
411 }
412 pub(crate) fn total_pdf(&self) -> usize {
413 self.sources.required.total_pdf()
414 }
415 pub(crate) fn snapshot(&self) -> structured_ltv::source::Snapshot<D, G> {
416 self.sources.required.root.owner().value.stamp.snapshot
417 }
418 #[must_use]
420 pub fn location(&self, ordinal: usize) -> Option<Location> {
421 self.sources
422 .required
423 .document_timestamps()
424 .get(ordinal)
425 .map(super::DocumentTimestamp::location)
426 }
427 pub(crate) fn contents(&self, ordinal: usize) -> Option<Location> {
428 match &self.sources.slots[ordinal] {
429 Slot::Value(value) => Some(value.signature().contents()),
430 Slot::Unreadable(_) => None,
431 }
432 }
433 #[must_use]
435 pub fn requirements(
436 &self,
437 ) -> &structured_ltv::timestamp::TimestampRequirements<Node, D, G, super::Pades> {
438 self.sources.required.all()
439 }
440}