1use crate::document::Document;
4use crate::error::{Error, ErrorKind, ReadFailure, Syntax};
5use crate::model::admit;
6use crate::range::RangeSource;
7use crate::signature::Signature;
8use crate::source::Location;
9use structured_asn1::{
10 Reader,
11 memory::{Memory, OwnedVec, Work},
12};
13use structured_cms::{ContentInfo, SignedData};
14
15#[derive(Debug)]
19pub enum ErrorOf<E> {
20 Pdf(ReadFailure<E>),
22 Pkix {
24 enclosure: Location,
26 cause: structured_pkix::Error,
28 },
29 Encoding {
31 enclosure: Location,
33 cause: structured_asn1::Error,
35 },
36 Cms {
38 enclosure: Location,
40 cause: structured_cms::Error,
42 },
43}
44impl<E> ErrorOf<E> {
45 #[must_use]
47 pub fn is_terminal(&self) -> bool {
48 match self {
49 Self::Pdf(cause) => cause.is_terminal(),
50 Self::Encoding { cause, .. } => cause.is_resource(),
51 Self::Cms { cause, .. } => cause.is_terminal(),
52 Self::Pkix { cause, .. } => {
53 cause.is_resource() || cause.code() == structured_pkix::ErrorCode::ForeignReader
54 }
55 }
56 }
57}
58
59pub struct Container<'a, 's, D, G> {
63 signature: Signature<'a, 's, D, G>,
64 bytes: OwnedVec<u8, Memory>,
65 der_length: usize,
66 session: &'s crate::Session,
67}
68
69impl<'a, 's, D: Copy + Eq, G: Copy + Eq> Container<'a, 's, D, G> {
70 pub(crate) fn require(
71 &self,
72 message: &SignedData<'_>,
73 parent: &Reader<'_>,
74 ) -> crate::Result<()> {
75 let location = self.signature.contents();
76 self.session
77 .query(&parent.execution())
78 .charge_work(1)
79 .map_err(|cause| Error {
80 kind: ErrorKind::Resource(cause),
81 location,
82 })?;
83 let span = message.span();
84 let start = usize::try_from(span.start).map_err(|_| Error {
85 kind: ErrorKind::Foreign,
86 location,
87 })?;
88 let end = start.checked_add(message.as_bytes().len()).ok_or(Error {
89 kind: ErrorKind::Foreign,
90 location,
91 })?;
92 if !self.session.owns(parent)
93 || self
94 .der()
95 .get(start..end)
96 .is_none_or(|bytes| !core::ptr::eq(bytes, message.as_bytes()))
97 {
98 return Err(Error {
99 kind: ErrorKind::Foreign,
100 location,
101 });
102 }
103 Ok(())
104 }
105 #[must_use]
107 pub fn signature(&self) -> Signature<'a, 's, D, G> {
108 self.signature
109 }
110
111 #[must_use]
113 pub fn contents(&self) -> &[u8] {
114 &self.bytes
115 }
116
117 #[must_use]
119 pub fn der(&self) -> &[u8] {
120 &self.bytes[..self.der_length]
121 }
122
123 #[expect(
126 clippy::result_large_err,
127 reason = "an unchanged timestamp grammar error retains its actual PDF enclosure without allocating an error envelope"
128 )]
129 pub fn read_timestamp<'q, W: Work + 'q>(
130 &'q self,
131 operation: &W,
132 ) -> Result<
133 (structured_cms::timestamp::Token<'q>, Reader<'q>),
134 ErrorOf<core::convert::Infallible>,
135 > {
136 let enclosure = self.signature.contents();
137 let mut reader = self
138 .session
139 .carried(self.der(), operation)
140 .map_err(|cause| ErrorOf::Encoding { enclosure, cause })?;
141 let token = structured_cms::timestamp::Token::read_from(&mut reader)
142 .map_err(|cause| ErrorOf::Cms { enclosure, cause })?;
143 self.require(token.message(), &reader)
144 .map_err(|cause| ErrorOf::Pdf(cause.into()))?;
145 Ok((token, reader))
146 }
147
148 #[expect(
151 clippy::result_large_err,
152 reason = "the original CMS diagnostic and encoded PDF enclosure must survive the scoped reading without a fallible error allocation"
153 )]
154 pub fn read<'q, W: Work + 'q>(
155 &'q self,
156 operation: &W,
157 ) -> Result<(SignedData<'q>, Reader<'q>), ErrorOf<core::convert::Infallible>> {
158 let enclosure = self.signature.contents();
159 let mut root = self
160 .session
161 .carried(self.der(), operation)
162 .map_err(|cause| ErrorOf::Encoding { enclosure, cause })?;
163 let (content, parent) =
164 ContentInfo::read(&mut root).map_err(|cause| ErrorOf::Cms { enclosure, cause })?;
165 let execution = parent.execution();
166 execution
167 .charge_work(content.kind().as_bytes().len() as u64)
168 .map_err(|cause| {
169 ErrorOf::Pdf(ReadFailure::Format(Error {
170 kind: ErrorKind::Resource(cause),
171 location: enclosure,
172 }))
173 })?;
174 if content.kind() != structured_cms::oid::SIGNED_DATA {
175 return Err(ErrorOf::Pdf(ReadFailure::Format(Error {
176 kind: ErrorKind::Syntax(Syntax::Container),
177 location: enclosure,
178 })));
179 }
180 let message = SignedData::read_element(&parent, content.content())
181 .map_err(|cause| ErrorOf::Cms { enclosure, cause })?;
182 Ok((message, parent))
183 }
184}
185
186impl<'s, S: RangeSource> Document<'s, S> {
187 #[expect(
192 clippy::result_large_err,
193 reason = "the original source/CMS refusal retains its original coordinate system rather than being boxed or flattened"
194 )]
195 pub async fn container<'a, W: Work>(
196 &self,
197 signature: Signature<'a, 's, S::Document, S::Generation>,
198 operation: &W,
199 ) -> Result<Container<'a, 's, S::Document, S::Generation>, ErrorOf<S::Error>> {
200 let bytes = self
201 .contents(signature, operation)
202 .await
203 .map_err(ErrorOf::Pdf)?;
204 let enclosure = signature.contents();
205 let work = self.session.query(operation);
206 let mut reader = self
207 .session
208 .carried(&bytes, &work)
209 .map_err(|cause| ErrorOf::Encoding { enclosure, cause })?;
210 let element = reader
211 .next_element()
212 .map_err(|cause| ErrorOf::Encoding { enclosure, cause })?
213 .ok_or_else(|| {
214 ErrorOf::Pdf(ReadFailure::Format(Error {
215 kind: ErrorKind::Syntax(Syntax::String),
216 location: enclosure,
217 }))
218 })?;
219 let der_length = element.as_bytes().len();
220 admit(&work, enclosure, (bytes.len() - der_length) as u64)
221 .map_err(|cause| ErrorOf::Pdf(cause.into()))?;
222 if bytes[der_length..].iter().any(|&byte| byte != 0) {
223 return Err(ErrorOf::Pdf(
224 Error {
225 kind: ErrorKind::Syntax(Syntax::String),
226 location: enclosure,
227 }
228 .into(),
229 ));
230 }
231 drop(reader);
232 Ok(Container {
233 signature,
234 bytes,
235 der_length,
236 session: self.session,
237 })
238 }
239}