Skip to main content

structured_pades/material/
parsed.rs

1//! Parsed retained DSS items and the existing exact-artifact index.
2
3use super::{Decoded, MaterialError, Validation};
4use crate::{
5    error::{Error, ErrorKind},
6    model::admit,
7    signature::Signature,
8    timestamp::{ISSUER, Node, Pades},
9};
10use structured_asn1::memory::{Execution, Memory, OwnedVec, Work};
11use structured_ltv::{
12    material::{ArtifactRef, ArtifactSource, Index, IndexError, Occurrence},
13    source::{Location, Query},
14};
15
16/// Original parsed value, without authentication or a successful status claim.
17pub enum Value<'a> {
18    /// Actual X.509 certificate.
19    Certificate(structured_pkix::Certificate<'a>),
20    /// Actual complete or delta `CertificateList`.
21    Crl(structured_pkix::crl::CertificateList<'a>),
22    /// Original whole OCSP envelope and its known basic payload, if available.
23    Ocsp {
24        /// Whole original response, retaining protocol failures independently.
25        envelope: structured_pkix::ocsp::Response<'a>,
26        /// Parsed original known payload; absent is not a successful status.
27        basic: Result<Option<structured_pkix::ocsp::BasicResponse<'a>>, structured_pkix::Error>,
28    },
29    /// Actual optional VRI time-stamp token.
30    Timestamp(TimestampValue<'a>),
31    /// Original whole value was unreadable; raw bytes/placement remain owned
32    /// by Validation and the located cause is not converted to absence.
33    Unreadable(MaterialError),
34}
35/// Once-read VRI TS envelope and its actual carried revocation choices.
36pub struct TimestampValue<'a> {
37    pub(crate) token: structured_cms::timestamp::Token<'a>,
38    pub(crate) revocations: OwnedVec<crate::timestamp::sources::Revocation<'a>>,
39}
40impl<'a> TimestampValue<'a> {
41    /// Original parsed envelope, without replaying the TS stream.
42    #[must_use]
43    pub fn token(&self) -> &structured_cms::timestamp::Token<'a> {
44        &self.token
45    }
46}
47
48/// One actual DSS occurrence's historical placement.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum Placement {
51    /// Historical revision selecting the original stream definition.
52    Dss {
53        /// Historical revision in which the source was visible.
54        revision: usize,
55        /// Ordinal in the same owning Validation item pool.
56        item: usize,
57    },
58    /// Unchanged inner CMS location, projected only at its PDF enclosure.
59    Cms(structured_cades::material::Placement),
60    /// Actual certificate/revocation choice of a PDF document timestamp.
61    DocumentTimestamp {
62        /// Original PDF timestamp occurrence, including earlier failed slots.
63        timestamp: usize,
64        /// Original certificate or revocation CHOICE ordinal.
65        choice: TokenChoice,
66    },
67    /// Material carried by an actual VRI TS in this historical DSS window.
68    VriTimestamp {
69        /// Original historical DSS revision.
70        revision: usize,
71        /// Original VRI dictionary ordinal in that window.
72        vri: usize,
73        /// Original certificate or revocation choice in the TS envelope.
74        choice: TokenChoice,
75    },
76}
77
78/// CMS CHOICE role inside one original PDF document timestamp.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum TokenChoice {
81    /// Original certificate CHOICE index, not the normalized X.509 catalogue.
82    Certificate(usize),
83    /// Original revocation CHOICE index.
84    Revocation(usize),
85}
86
87/// One parse of every retained material stream. Models borrow the actual
88/// Validation owner; no second DER grammar or copied certificate is introduced.
89pub struct Parsed<'v, 's, D, G> {
90    pub(super) owner: &'v Validation<'s, D, G>,
91    pub(super) values: OwnedVec<Value<'v>, Memory>,
92    readable: bool,
93}
94
95/// Existing exact identity/occurrence mechanism in the original PDF domain.
96pub type MaterialIndex<'v, D, G, W> = Index<'v, Node, D, G, Placement, (), W, Pades>;
97
98impl<'s, D: Copy + Eq, G: Copy + Eq> Validation<'s, D, G> {
99    /// Parses every value once under the actual current/original execution.
100    /// Protocol OCSP failures and unknown response syntaxes remain distinct
101    /// retained values and never become a fabricated basic response.
102    #[expect(
103        clippy::result_large_err,
104        reason = "a terminal inner diagnostic retains its actual encoded material enclosure without allocating on the refusal path"
105    )]
106    pub fn parse<'v, W: Work + 'v>(
107        &'v self,
108        operation: &'v W,
109    ) -> Result<Parsed<'v, 's, D, G>, super::MaterialFailure> {
110        let execution = self.session.query(operation);
111        let mut values = execution.vector();
112        let mut readable = true;
113        for item in &self.items {
114            let located = |cause| super::MaterialFailure {
115                location: item.location,
116                cause,
117            };
118            let work = item.session.query(operation);
119            admit(&work, item.location, 1).map_err(|cause| located(MaterialError::Pdf(cause)))?;
120            let (value, reader) = match item.read(&work) {
121                Ok(value) => value,
122                Err(cause) if cause.is_terminal() => return Err(located(cause)),
123                Err(cause) => {
124                    readable = false;
125                    values.try_push(Value::Unreadable(cause)).map_err(|cause| {
126                        located(MaterialError::Pdf(Error {
127                            kind: ErrorKind::Resource(cause),
128                            location: item.location,
129                        }))
130                    })?;
131                    continue;
132                }
133            };
134            let value = match value {
135                Decoded::Certificate(value) => Value::Certificate(value),
136                Decoded::Crl(value) => Value::Crl(value),
137                Decoded::Ocsp(envelope) => {
138                    let basic =
139                        if let structured_pkix::ocsp::Response::Successful(value) = &envelope {
140                            match value.basic_in(&reader) {
141                                Ok(value) => Ok(value),
142                                Err(cause)
143                                    if cause.is_resource()
144                                        || cause.code()
145                                            == structured_pkix::ErrorCode::ForeignReader =>
146                                {
147                                    return Err(located(MaterialError::Pkix(cause)));
148                                }
149                                Err(cause) => {
150                                    readable = false;
151                                    Err(cause)
152                                }
153                            }
154                        } else {
155                            Ok(None)
156                        };
157                    Value::Ocsp { envelope, basic }
158                }
159                Decoded::Timestamp(token) => {
160                    let revocations = crate::timestamp::sources::read_revocations(
161                        &token,
162                        &reader,
163                        &work,
164                        item.location,
165                    )
166                    .map_err(|cause| match cause {
167                        crate::timestamp::sources::RevocationError::Pdf(cause) => {
168                            MaterialError::Pdf(cause)
169                        }
170                        crate::timestamp::sources::RevocationError::Cms(cause) => {
171                            MaterialError::Cms(cause)
172                        }
173                        crate::timestamp::sources::RevocationError::Pkix(cause) => {
174                            MaterialError::Pkix(cause)
175                        }
176                    })
177                    .map_err(located)?;
178                    Value::Timestamp(TimestampValue { token, revocations })
179                }
180            };
181            values.try_push(value).map_err(|cause| {
182                located(MaterialError::Pdf(Error {
183                    kind: ErrorKind::Resource(cause),
184                    location: item.location,
185                }))
186            })?;
187        }
188        Ok(Parsed {
189            owner: self,
190            values: values.into_owned(),
191            readable,
192        })
193    }
194}
195
196impl<'v, D: Copy + Eq, G: Copy + Eq> Parsed<'v, '_, D, G> {
197    /// Historical VRI inclusion independently of individual value syntax.
198    #[must_use]
199    pub fn all_vri_material_in_dss(&self) -> bool {
200        self.owner.all_vri_material_in_dss()
201    }
202    /// All parsed values in the immutable owning Validation item order.
203    #[must_use]
204    pub fn values(&self) -> &[Value<'v>] {
205        &self.values
206    }
207    /// Every known whole value and basic OCSP body was readable. This is
208    /// independent of successful wire status, authentication or freshness.
209    #[must_use]
210    pub fn all_values_readable(&self) -> bool {
211        self.readable
212    }
213
214    /// Builds the common index from actual original occurrences, preserving
215    /// the whole OCSP envelope and its independently parsed basic payload.
216    pub fn index<'q, 'operation, S: crate::range::RangeSource<Document = D, Generation = G>>(
217        &'q self,
218        signature: Signature<'_, '_, D, G>,
219        query: &crate::query::Query<'operation, '_, S>,
220    ) -> Result<MaterialIndex<'q, D, G, Execution<'operation>>, IndexError<Node>>
221    where
222        'v: 'q,
223    {
224        let source = signature.owner();
225        let root = Node::of(signature.location());
226        if !core::ptr::eq(source.value.session, self.owner.session)
227            || !query.belongs(self.owner.session)
228            || source.value.stamp != self.owner.stamp
229            || query.snapshot() != self.owner.stamp.snapshot
230        {
231            return Err(IndexError::Foreign { signature: root });
232        }
233        let mut index = Index::new(ISSUER, root, &[], query)?;
234        let mut ordinal = 0;
235        self.append(&mut index, &mut ordinal, query)?;
236        Ok(index)
237    }
238
239    pub(super) fn append<'q, Q, O>(
240        &'q self,
241        index: &mut Index<'q, Node, D, G, Placement, O, Q::Work, Pades>,
242        ordinal: &mut usize,
243        query: &Q,
244    ) -> Result<(), IndexError<Node>>
245    where
246        Q: Query<Id = Node, Document = D, Generation = G>,
247        'v: 'q,
248    {
249        if query.snapshot() != self.owner.stamp.snapshot {
250            return Err(IndexError::Foreign {
251                signature: index.signature(),
252            });
253        }
254        for (item_ordinal, value) in self.values.iter().enumerate() {
255            index.admit(query)?;
256            let item = &self.owner.items[item_ordinal];
257            let artifact = match value {
258                Value::Certificate(value) => Some(ArtifactRef::Certificate(value)),
259                Value::Crl(value) => Some(ArtifactRef::Crl(value)),
260                Value::Ocsp {
261                    basic: Ok(Some(value)),
262                    ..
263                } => Some(ArtifactRef::Ocsp(value)),
264                Value::Ocsp {
265                    basic: Ok(None) | Err(_),
266                    ..
267                }
268                | Value::Timestamp(_)
269                | Value::Unreadable(_) => None,
270            };
271            if let Some(artifact) = artifact {
272                index.insert(
273                    ISSUER,
274                    artifact,
275                    Occurrence::new(
276                        ArtifactSource::Material(*ordinal),
277                        Location {
278                            snapshot: self.owner.stamp.snapshot,
279                            node: Node::dss(item.location, self.owner.revision),
280                        },
281                        Placement::Dss {
282                            revision: self.owner.revision,
283                            item: item_ordinal,
284                        },
285                    ),
286                    query,
287                )?;
288                *ordinal = ordinal.checked_add(1).ok_or(IndexError::Resource {
289                    signature: index.signature(),
290                    cause: structured_asn1::memory::MemoryError::CapacityOverflow,
291                })?;
292            }
293        }
294        Ok(())
295    }
296}