1use crate::Error;
7use core::cmp::Ordering;
8use structured_asn1::memory::{OwnedVec, SortError, Work};
9use structured_cms::{
10 digest::{DigestBatch, DigestError, DigestMemo, PassResult, RequestSource},
11 stream::{Receiver, Refused, StreamError},
12};
13use structured_pkix::algorithm::digest::{Algorithm, AsnDigest};
14
15const FIELD: &str = "cades.longTermValidation.input";
16
17#[derive(Debug)]
19pub enum InputError<SE, PE> {
20 Processing(Error),
22 Digest(DigestError<PE>),
24 Content(crate::timestamp::InputError<SE>),
26 Unsupported,
28 MissingSignedAttributes,
30 Date(structured_pkix::temporal::CompareError),
32 Augmentation(crate::timestamp::historical::SelectionError),
34 Foreign,
36 Receiver(Refused),
38}
39
40pub(super) fn batch<'p, P: AsnDigest<'p>, W: Work, SE>(
41 provider: &'p P,
42 algorithms: &[Algorithm],
43 work: &W,
44 at: u64,
45) -> Result<DigestBatch<'p, P>, InputError<SE, P::Error>> {
46 let mut identities = work.vector();
47 identities
48 .try_reserve_exact(algorithms.len())
49 .map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
50 for algorithm in algorithms {
51 work.charge_work(1)
52 .map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
53 identities
54 .try_push(
55 provider
56 .identity(*algorithm)
57 .ok_or(InputError::Unsupported)?,
58 )
59 .map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
60 }
61 DigestBatch::prepare(provider, identities.into_owned(), work, FIELD, at)
62 .map_err(InputError::Digest)
63}
64
65pub(super) struct Eight<'p, P: AsnDigest<'p>> {
68 parts: OwnedVec<DigestMemo<'p, P>>,
69 covered: OwnedVec<structured_asn1::Span>,
70 algorithms: OwnedVec<Algorithm>,
71 at: u64,
72}
73impl<'p, P: AsnDigest<'p>> Eight<'p, P> {
74 pub(super) fn build<W: Work, SE>(
75 provider: &'p P,
76 algorithms: &[Algorithm],
77 work: &W,
78 at: u64,
79 covered: OwnedVec<structured_asn1::Span>,
80 mut component: impl FnMut(
81 usize,
82 DigestBatch<'p, P>,
83 &W,
84 ) -> Result<DigestMemo<'p, P>, InputError<SE, P::Error>>,
85 ) -> Result<Self, InputError<SE, P::Error>> {
86 let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, at));
87 let mut parts = work.vector();
88 parts.try_reserve_exact(8).map_err(resource)?;
89 let mut saved = work.vector();
90 saved.try_extend_from_slice(algorithms).map_err(resource)?;
91 for index in 0..8 {
92 work.charge_work(1).map_err(resource)?;
93 let pending = batch(provider, algorithms, work, at)?;
94 let completed = component(index, pending, work)?;
95 parts.try_push(completed).map_err(resource)?;
96 }
97 Ok(Self {
98 parts: parts.into_owned(),
99 covered,
100 algorithms: saved.into_owned(),
101 at,
102 })
103 }
104
105 pub(super) fn stream<W: Work, SE>(
106 &self,
107 ordinal: usize,
108 provider: &'p P,
109 identity: &P::Identity,
110 out: &mut impl Receiver,
111 work: &W,
112 ) -> Result<(), InputError<SE, P::Error>> {
113 work.charge_work(1)
114 .map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, self.at)))?;
115 let algorithm = self.algorithms.get(ordinal).ok_or(InputError::Foreign)?;
116 debug_assert_eq!(self.parts.len(), 8);
119 let mut outputs = [&[][..]; 8];
122 for (slot, part) in outputs.iter_mut().zip(&*self.parts) {
123 *slot = checked(
124 part,
125 ordinal,
126 provider,
127 identity,
128 algorithm.width(),
129 work,
130 self.at,
131 )?;
132 }
133 for bytes in outputs {
134 out.write(bytes).map_err(InputError::Receiver)?;
135 }
136 Ok(())
137 }
138}
139
140fn checked<'m, 'p, P: AsnDigest<'p>, W: Work, SE>(
141 memo: &'m DigestMemo<'p, P>,
142 ordinal: usize,
143 provider: &'p P,
144 identity: &P::Identity,
145 width: usize,
146 work: &W,
147 at: u64,
148) -> Result<&'m [u8], InputError<SE, P::Error>> {
149 work.charge_work(1)
150 .map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
151 let value = memo
152 .for_request(ordinal, work)
153 .map_err(InputError::Digest)?
154 .ok_or(InputError::Foreign)?;
155 if !value.finished().from_provider(provider) {
156 return Err(InputError::Foreign);
157 }
158 if provider
159 .compare(value.identity(), identity, work)
160 .map_err(|cause| InputError::Digest(DigestError::Callback(cause)))?
161 != Ordering::Equal
162 {
163 return Err(InputError::Foreign);
164 }
165 if value.as_bytes().len() != width {
166 return Err(InputError::Digest(DigestError::OutputWidth {
167 expected: width,
168 actual: value.as_bytes().len(),
169 }));
170 }
171 Ok(value.as_bytes())
172}
173
174pub(super) struct Tree<'alg, 'p, P: AsnDigest<'p>, SE> {
177 provider: &'p P,
178 items: OwnedVec<DigestMemo<'p, P>>,
179 algorithms: &'alg [Algorithm],
180 at: u64,
181 error: core::marker::PhantomData<fn() -> SE>,
182}
183impl<'alg, 'p, P: AsnDigest<'p>, SE> Tree<'alg, 'p, P, SE> {
184 pub(super) fn new(
185 provider: &'p P,
186 items: OwnedVec<DigestMemo<'p, P>>,
187 algorithms: &'alg [Algorithm],
188 at: u64,
189 ) -> Self {
190 Self {
191 provider,
192 items,
193 algorithms,
194 at,
195 error: core::marker::PhantomData,
196 }
197 }
198}
199impl<'p, P: AsnDigest<'p>, SE> RequestSource<'p, P> for Tree<'_, 'p, P, SE> {
200 type Error = InputError<SE, P::Error>;
201 fn stream_request<W: Work>(
202 &mut self,
203 ordinal: usize,
204 provider: &'p P,
205 identity: &P::Identity,
206 out: &mut impl Receiver,
207 work: &W,
208 ) -> Result<(), Self::Error> {
209 let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, self.at));
210 work.charge_work(1).map_err(resource)?;
211 if !core::ptr::eq(provider, self.provider) {
212 return Err(InputError::Foreign);
213 }
214 let width = self
215 .algorithms
216 .get(ordinal)
217 .ok_or(InputError::Foreign)?
218 .width();
219 let mut hashes = work.vector();
220 hashes
221 .try_reserve_exact(self.items.len())
222 .map_err(resource)?;
223 for item in &self.items {
224 let value = checked(item, ordinal, provider, identity, width, work, self.at)?;
225 hashes.try_push(value).map_err(resource)?;
226 }
227 hashes
228 .try_sort_unstable_by(|left, right| {
229 work.charge_work(1 + left.len().min(right.len()) as u64)?;
230 Ok::<_, structured_asn1::memory::MemoryError>(left.cmp(right))
231 })
232 .map_err(|cause| match cause {
233 SortError::Resource(cause) | SortError::Comparison(cause) => resource(cause),
234 })?;
235 for value in &hashes {
236 work.charge_work(1).map_err(resource)?;
237 out.write(value).map_err(InputError::Receiver)?;
238 }
239 Ok(())
241 }
242}
243
244pub(super) fn derived_result<T, SE, PE>(
245 result: PassResult<T, InputError<SE, PE>, PE>,
246) -> Result<T, InputError<SE, PE>> {
247 result.map_err(|cause| match cause {
248 StreamError::Source(cause) => cause,
249 StreamError::Callback(cause) => InputError::Digest(cause),
250 StreamError::Processing(cause) => InputError::Processing(Error::Cms(cause)),
251 })
252}
253
254mod check;
255mod recipe;
256pub use check::{CheckError, CheckResult};
257
258struct Lazy<'s, 'a, 'm, 'd, 'p, 'utc, P: AsnDigest<'p>, S> {
261 view: &'s super::View<'a, 'm, 'd>,
262 later: &'s [crate::timestamp::historical::Later<'s, 'm, 'd>],
263 provider: &'p P,
264 algorithms: &'s [Algorithm],
265 operation: &'s structured_asn1::Reader<'d>,
266 utc: &'s structured_pkix::temporal::LeapTable<'utc>,
267 detached: Option<&'s mut S>,
268 ready: Option<Eight<'p, P>>,
269}
270impl<'p, P: AsnDigest<'p>, S: structured_cms::stream::Source> Lazy<'_, '_, '_, '_, 'p, '_, P, S> {
271 fn ready<W: Work>(
272 &mut self,
273 work: &W,
274 ) -> Result<&Eight<'p, P>, InputError<S::Error, P::Error>> {
275 if self.ready.is_none() {
276 self.ready = Some(self.view.components_in(
277 self.provider,
278 self.algorithms,
279 self.operation,
280 self.detached.take(),
281 self.utc,
282 self.later,
283 work,
284 )?);
285 }
286 Ok(self
287 .ready
288 .as_ref()
289 .expect("successful component construction"))
290 }
291}
292impl<'p, P: AsnDigest<'p>, S: structured_cms::stream::Source> RequestSource<'p, P>
293 for Lazy<'_, '_, '_, '_, 'p, '_, P, S>
294{
295 type Error = InputError<S::Error, P::Error>;
296 fn stream_request<W: Work>(
297 &mut self,
298 ordinal: usize,
299 provider: &'p P,
300 identity: &P::Identity,
301 out: &mut impl Receiver,
302 work: &W,
303 ) -> Result<(), Self::Error> {
304 self.ready(work)?
305 .stream(ordinal, provider, identity, out, work)
306 }
307}
308impl<'p, P: AsnDigest<'p>, S: structured_cms::stream::Source> structured_cms::stream::Source
309 for Lazy<'_, '_, '_, '_, 'p, '_, P, S>
310{
311 type Error = InputError<S::Error, P::Error>;
312 fn stream<W: Work>(&mut self, out: &mut impl Receiver, work: &W) -> Result<(), Self::Error> {
313 work.charge_work(1).map_err(|cause| {
314 InputError::Processing(Error::resource(
315 cause,
316 FIELD,
317 self.view.required().property.span().start,
318 ))
319 })?;
320 if self.algorithms.len() != 1 {
321 return Err(InputError::Foreign);
322 }
323 let provider = self.provider;
324 let identity = provider
325 .identity(self.algorithms[0])
326 .ok_or(InputError::Unsupported)?;
327 self.ready(work)?.stream(0, provider, &identity, out, work)
328 }
329}
330
331#[cfg(test)]
332#[path = "poe/tests.rs"]
333mod tests;