Skip to main content

structured_cms/evidence/
input.rs

1//! Accounted RFC4998 reduced-tree computation; no cryptographic engine here.
2
3use super::objects::{AsyncObjects, Sources};
4use super::{ArchiveTimeStamp, FIELD};
5use crate::{
6    Error,
7    digest::{DigestBatch, DigestError, DigestMemo},
8    stream::{self, Sink, Source, StreamError},
9    timestamp::{AsyncComputedInput, ComputedInput, ImprintContext, ImprintProof, InputFailure},
10};
11use core::cell::{OnceCell, RefCell};
12use core::cmp::Ordering;
13use structured_asn1::{
14    digest::Resolution,
15    memory::{MemoryError, OwnedVec, SortError, Work},
16};
17use structured_pkix::algorithm::digest::{self, AsnDigest};
18
19/// Whether the proof names one object or exactly an entire document group.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Membership {
22    /// Exactly one supplied object must occur in the first partial tree.
23    Object,
24    /// All supplied objects, and no others, form the first partial tree.
25    Group,
26}
27
28/// Original acquisition/provider failure or a completed unusable ERS recipe.
29#[derive(Debug)]
30pub enum InputError<SE, PE> {
31    /// Original source/depth/storage/work refusal.
32    Processing(Error),
33    /// The selected provider failed; its cause is preserved unchanged.
34    Provider(PE),
35    /// Actual covered source did not reach successful EOF.
36    Source(SE),
37    /// The selected provider does not implement this stream or written identity.
38    Unsupported,
39    /// A written digest violates its algorithm-specific syntax.
40    MalformedAlgorithm,
41    /// A supplied group is empty, or Object was given several sources.
42    ObjectCount,
43    /// The selected object/group is absent from the first partial tree.
44    Membership,
45    /// A declared tree hash has the wrong full output width.
46    HashWidth,
47    /// Provider output contradicts its own promised width.
48    OutputWidth,
49    /// A previous-sequence receipt names another record or another new chain.
50    ForeignRenewal,
51    /// Cached input came from another provider, identity or request ordinal.
52    ForeignDigest,
53}
54
55/// Closed tree recipe over complete caller-supplied object streams.
56///
57/// Sources are visited once per required record algorithm, never buffered into
58/// a whole-document allocation. Group equality is a multiset equality: repeated
59/// identical objects/hashes are not collapsed.
60pub struct ObjectInput<'input, 'document, S> {
61    stamp: &'input ArchiveTimeStamp<'document>,
62    sources: &'input mut [S],
63    membership: Membership,
64    previous: Option<&'input super::PriorSequence<'input, 'document>>,
65}
66impl<'i, 'd, S> ObjectInput<'i, 'd, S> {
67    /// Selects one original archive timestamp and the actual complete input.
68    #[must_use]
69    pub fn new(
70        stamp: &'i ArchiveTimeStamp<'d>,
71        sources: &'i mut [S],
72        membership: Membership,
73    ) -> Self {
74        Self {
75            stamp,
76            sources,
77            membership,
78            previous: None,
79        }
80    }
81    /// Selects hash-tree renewal for this prefix's actual first new timestamp.
82    /// RFC4998 §5.2(4) hashes dataHash then previousSequenceHash, unsorted;
83    /// Figure4 and reported erratum7411 do not enable a silent alternative.
84    #[must_use]
85    pub fn renewed(
86        stamp: &'i ArchiveTimeStamp<'d>,
87        sources: &'i mut [S],
88        membership: Membership,
89        previous: &'i super::PriorSequence<'i, 'd>,
90    ) -> Self {
91        Self {
92            stamp,
93            sources,
94            membership,
95            previous: Some(previous),
96        }
97    }
98}
99
100pub(super) struct Hasher<'p, 'i, P: AsnDigest<'p>, W: Work> {
101    pub(super) provider: &'p P,
102    pub(super) identity: &'i P::Identity,
103    pub(super) work: W,
104    pub(super) at: u64,
105}
106impl<'p, P: AsnDigest<'p>, W: Work> Hasher<'p, '_, P, W> {
107    pub(super) fn resource<SE>(&self, cause: MemoryError) -> InputError<SE, P::Error> {
108        InputError::Processing(Error::resource(cause, FIELD, self.at))
109    }
110    pub(super) fn start<SE>(&self) -> Result<P::State, InputError<SE, P::Error>> {
111        match self
112            .provider
113            .start(self.identity, &self.work)
114            .map_err(InputError::Provider)?
115        {
116            Resolution::Supported(state) => Ok(state),
117            Resolution::Unsupported => Err(InputError::Unsupported),
118            Resolution::Malformed => Err(InputError::MalformedAlgorithm),
119        }
120    }
121    pub(super) fn finish<SE>(
122        &self,
123        state: P::State,
124    ) -> Result<P::Output, InputError<SE, P::Error>> {
125        let output = self
126            .provider
127            .finish(state, &self.work)
128            .map_err(InputError::Provider)?;
129        if let Some(width) = self.provider.width(self.identity)
130            && output.as_ref().len() != width
131        {
132            return Err(InputError::OutputWidth);
133        }
134        Ok(output)
135    }
136    pub(super) fn update<SE>(
137        &self,
138        state: &mut P::State,
139        bytes: &[u8],
140    ) -> Result<(), InputError<SE, P::Error>> {
141        self.work
142            .charge_work(1)
143            .map_err(|cause| self.resource(cause))?;
144        self.provider
145            .update(state, bytes, &self.work)
146            .map_err(InputError::Provider)
147    }
148    pub(super) fn hash_source<S: Source>(
149        &self,
150        source: &mut S,
151    ) -> Result<P::Output, InputError<S::Error, P::Error>> {
152        let mut state = self.start()?;
153        let mut sink = Feed {
154            provider: self.provider,
155            state: &mut state,
156        };
157        stream::run(source, &mut sink, &self.work, FIELD, self.at).map_err(
158            |cause| match cause {
159                StreamError::Source(cause) => InputError::Source(cause),
160                StreamError::Callback(cause) => InputError::Provider(cause),
161                StreamError::Processing(cause) => InputError::Processing(cause),
162            },
163        )?;
164        self.finish(state)
165    }
166    fn hash_list<SE>(
167        &self,
168        values: &[Hash<'_, P::Output>],
169    ) -> Result<P::Output, InputError<SE, P::Error>> {
170        let mut state = self.start()?;
171        for value in values {
172            self.update(&mut state, value.bytes())?;
173        }
174        self.finish(state)
175    }
176    fn compare<SE>(&self, a: &[u8], b: &[u8]) -> Result<Ordering, InputError<SE, P::Error>> {
177        self.work
178            .charge_work(1 + a.len().min(b.len()) as u64)
179            .map_err(|cause| self.resource(cause))?;
180        Ok(a.cmp(b))
181    }
182}
183
184struct Feed<'p, 's, P: AsnDigest<'p>> {
185    provider: &'p P,
186    state: &'s mut P::State,
187}
188impl<'p, P: AsnDigest<'p>> Sink for Feed<'p, '_, P> {
189    type Error = P::Error;
190    fn write<W: Work>(&mut self, bytes: &[u8], work: &W) -> Result<(), Self::Error> {
191        self.provider.update(self.state, bytes, work)
192    }
193}
194
195enum Hash<'d, O> {
196    Borrowed(&'d [u8]),
197    Joined(OwnedVec<u8>),
198    Root(Root<'d, O>),
199}
200impl<O: AsRef<[u8]>> Hash<'_, O> {
201    fn bytes(&self) -> &[u8] {
202        match self {
203            Self::Borrowed(bytes) => bytes,
204            Self::Joined(bytes) => bytes,
205            Self::Root(output) => output.output().as_ref(),
206        }
207    }
208}
209
210impl<P, PE, S: Source> ComputedInput<P, PE> for ObjectInput<'_, '_, S> {
211    type Error = InputError<S::Error, PE>;
212    fn compute<'context, 'reader, 'document>(
213        &mut self,
214        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
215    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
216    where
217        P: AsnDigest<'document, Error = PE>,
218    {
219        let stamp = self.stamp;
220        let (root, written) = self.compute_root(context).map_err(InputFailure::Source)?;
221        let identity = written.as_ref().unwrap_or(context.identity());
222        let origin = stamp
223            .original
224            .reader_in(context.reader())
225            .map_err(|cause| InputFailure::Source(InputError::Processing(cause)))?;
226        let execution = origin.execution_query(context.reader());
227        context
228            .compare_derived(|expected, _| {
229                context
230                    .provider()
231                    .matches(identity, root.output(), expected, &execution)
232                    .map_err(InputError::Provider)
233            })
234            .map_err(InputFailure::Refused)
235    }
236}
237impl<S: Source> ObjectInput<'_, '_, S> {
238    fn compute_root<'p, P: AsnDigest<'p>>(
239        &mut self,
240        context: &ImprintContext<'_, 'p, P, InputError<S::Error, P::Error>>,
241    ) -> ResolvedRoot<'_, P::Output, P::Identity, S::Error, P::Error> {
242        if self.sources.is_empty()
243            || (self.membership == Membership::Object && self.sources.len() != 1)
244        {
245            return Err(InputError::ObjectCount);
246        }
247        if !membership_shape(self.stamp, self.sources.len(), self.membership) {
248            return Err(InputError::Membership);
249        }
250        if let Some(previous) = self.previous
251            && !previous.contains(self.stamp)
252        {
253            return Err(InputError::ForeignRenewal);
254        }
255        let origin = self
256            .stamp
257            .original
258            .reader_in(context.reader())
259            .map_err(InputError::Processing)?;
260        let work = origin.execution_query(context.reader());
261        // Explicit identity can differ from the RFC3161 imprint algorithm:
262        // RFC4998 §4.2(5). Absence uses the context identity without cloning it.
263        let provider = context.provider();
264        let written = if let Some(identifier) = self.stamp.algorithm {
265            match digest::resolve(provider, &identifier, &work)
266                .map_err(|cause| InputError::Processing(Error::pkix(cause)))?
267            {
268                Resolution::Supported(identity) => Some(identity),
269                Resolution::Unsupported => return Err(InputError::Unsupported),
270                Resolution::Malformed => return Err(InputError::MalformedAlgorithm),
271            }
272        } else {
273            None
274        };
275        let identity = written.as_ref().unwrap_or(context.identity());
276        let hasher = Hasher {
277            provider,
278            identity,
279            work,
280            at: self.stamp.original.element.span().start,
281        };
282        let output = root(
283            &hasher,
284            self.stamp,
285            self.sources,
286            self.membership,
287            self.previous,
288        )?;
289        Ok((output, written))
290    }
291}
292
293fn root<'s, 'p, P: AsnDigest<'p>, W: Work, S: Source>(
294    hasher: &Hasher<'p, '_, P, W>,
295    stamp: &'s ArchiveTimeStamp<'_>,
296    sources: &mut [S],
297    membership: Membership,
298    previous: Option<&super::PriorSequence<'_, '_>>,
299) -> RootResult<'s, P::Output, S::Error, P::Error> {
300    validate_tree(hasher, stamp)?;
301    let mut objects = hasher.work.vector();
302    objects
303        .try_reserve_exact(sources.len())
304        .map_err(|cause| hasher.resource(cause))?;
305    let previous_hash = previous.map(|prior| prior.hash(hasher)).transpose()?;
306    for source in sources {
307        let mut output = hasher.hash_source(source)?;
308        if let Some(previous) = &previous_hash {
309            let mut state = hasher.start()?;
310            hasher.update(&mut state, output.as_ref())?;
311            hasher.update(&mut state, previous.as_ref())?;
312            output = hasher.finish(state)?;
313        }
314        objects
315            .try_push(Hash::Root(Root::Owned(output)))
316            .map_err(|cause| hasher.resource(cause))?;
317    }
318    fold(hasher, stamp, objects, membership)
319}
320
321fn validate_tree<'p, P: AsnDigest<'p>, W: Work, SE>(
322    hasher: &Hasher<'p, '_, P, W>,
323    stamp: &ArchiveTimeStamp<'_>,
324) -> Result<(), InputError<SE, P::Error>> {
325    // Validate every declared width before provider state or source I/O.
326    let width = hasher.provider.width(hasher.identity);
327    if let Some(tree) = &stamp.tree {
328        for level in tree {
329            hasher
330                .work
331                .charge_work(1)
332                .map_err(|cause| hasher.resource(cause))?;
333            for hash in &level.hashes {
334                hasher
335                    .work
336                    .charge_work(1)
337                    .map_err(|cause| hasher.resource(cause))?;
338                if let Some(width) = width
339                    && hash.len() != width
340                {
341                    return Err(InputError::HashWidth);
342                }
343            }
344        }
345    }
346    Ok(())
347}
348
349pub(super) fn membership_shape(
350    stamp: &ArchiveTimeStamp<'_>,
351    objects: usize,
352    membership: Membership,
353) -> bool {
354    let Some(tree) = &stamp.tree else {
355        return objects == 1;
356    };
357    let Some(first) = tree.first() else {
358        return false;
359    };
360    if first.hashes.is_empty() {
361        return false;
362    }
363    membership != Membership::Group || first.hashes.len() == objects
364}
365
366pub(super) enum Root<'s, O> {
367    Owned(O),
368    Borrowed(&'s O),
369}
370type RootResult<'s, O, SE, PE> = Result<Root<'s, O>, InputError<SE, PE>>;
371type ResolvedRoot<'s, O, I, SE, PE> = Result<(Root<'s, O>, Option<I>), InputError<SE, PE>>;
372impl<O> Root<'_, O> {
373    pub(super) fn output(&self) -> &O {
374        match self {
375            Self::Owned(o) => o,
376            Self::Borrowed(o) => o,
377        }
378    }
379}
380
381fn fold<'s, 'p, P: AsnDigest<'p>, W: Work, SE>(
382    hasher: &Hasher<'p, '_, P, W>,
383    stamp: &'s ArchiveTimeStamp<'_>,
384    mut objects: OwnedVec<Hash<'s, P::Output>, W>,
385    membership: Membership,
386) -> Result<Root<'s, P::Output>, InputError<SE, P::Error>> {
387    sort(hasher, &mut objects)?;
388    let Some(tree) = &stamp.tree else {
389        if objects.len() != 1 {
390            return Err(InputError::Membership);
391        }
392        let Some(Hash::Root(output)) = objects.pop() else {
393            unreachable!("one complete output")
394        };
395        return Ok(output);
396    };
397    let Some(first) = tree.first() else {
398        return Err(InputError::Membership);
399    };
400    let mut siblings = hasher.work.vector();
401    for hash in &first.hashes {
402        siblings
403            .try_push(read_hash(hash, hasher)?)
404            .map_err(|cause| hasher.resource(cause))?;
405    }
406    sort(hasher, &mut siblings)?;
407    if membership == Membership::Group {
408        if objects.len() != siblings.len() {
409            return Err(InputError::Membership);
410        }
411        for (object, hash) in objects.iter().zip(siblings.iter()) {
412            if hasher.compare(object.bytes(), hash.bytes())? != Ordering::Equal {
413                return Err(InputError::Membership);
414            }
415        }
416    } else {
417        let mut found = false;
418        for hash in &siblings {
419            if hasher.compare(objects[0].bytes(), hash.bytes())? == Ordering::Equal {
420                found = true;
421                break;
422            }
423        }
424        if !found {
425            return Err(InputError::Membership);
426        }
427    }
428    let mut current = if siblings.len() == 1 {
429        // A singleton leaf is already the root (RFC4998 §4.2 steps3–4).
430        let Some(Hash::Root(output)) = objects.pop() else {
431            unreachable!("matched singleton has one actual output")
432        };
433        output
434    } else {
435        Root::Owned(hasher.hash_list(&siblings)?)
436    };
437    drop(objects);
438    for level in &tree[1..] {
439        siblings.clear();
440        siblings
441            .try_push(Hash::Root(current))
442            .map_err(|cause| hasher.resource(cause))?;
443        for hash in &level.hashes {
444            siblings
445                .try_push(read_hash(hash, hasher)?)
446                .map_err(|cause| hasher.resource(cause))?;
447        }
448        sort(hasher, &mut siblings)?;
449        current = if siblings.len() == 1 {
450            let Some(Hash::Root(output)) = siblings.pop() else {
451                unreachable!("computed child")
452            };
453            output
454        } else {
455            Root::Owned(hasher.hash_list(&siblings)?)
456        };
457    }
458    Ok(current)
459}
460
461fn read_hash<'d, 'p, P: AsnDigest<'p>, W: Work, SE>(
462    hash: &crate::Octets<'d>,
463    hasher: &Hasher<'p, '_, P, W>,
464) -> Result<Hash<'d, P::Output>, InputError<SE, P::Error>> {
465    if let Some(bytes) = hash.as_one() {
466        return Ok(Hash::Borrowed(bytes));
467    }
468    let mut joined = hasher.work.vector();
469    joined
470        .try_reserve_exact(hash.len())
471        .map_err(|cause| hasher.resource(cause))?;
472    for piece in hash.pieces() {
473        hasher
474            .work
475            .charge_work(1)
476            .map_err(|cause| hasher.resource(cause))?;
477        joined
478            .try_extend_from_slice(piece.as_bytes())
479            .map_err(|cause| hasher.resource(cause))?;
480    }
481    Ok(Hash::Joined(joined.into_owned()))
482}
483
484fn sort<'p, P: AsnDigest<'p>, W: Work, SE>(
485    hasher: &Hasher<'p, '_, P, W>,
486    siblings: &mut OwnedVec<Hash<'_, P::Output>, W>,
487) -> Result<(), InputError<SE, P::Error>> {
488    siblings
489        .try_sort_unstable_by(|a, b| hasher.compare(a.bytes(), b.bytes()))
490        .map_err(|cause| match cause {
491            SortError::Resource(cause) => hasher.resource(cause),
492            SortError::Comparison(cause) => cause,
493        })
494}
495
496// Record verification hashes every actual object once through DigestBatch's
497// full-identity fan-out. Nested joined TSTInfo checks borrow those outputs;
498// identities/outputs cannot borrow that temporary inner information source.
499pub(super) struct MemoSet<'p, P: AsnDigest<'p>> {
500    pub(super) provider: &'p P,
501    pub(super) memos: OwnedVec<DigestMemo<'p, P>>,
502}
503trait RootComputation<P> {
504    type Output: AsRef<[u8]>;
505    type Error;
506    type SourceError;
507    fn belongs_to_provider(&self, provider: &P) -> bool;
508    fn root<'s>(
509        &'s self,
510        stamp: &'s ArchiveTimeStamp<'_>,
511        ordinal: usize,
512        membership: Membership,
513        previous: Option<&super::PriorSequence<'_, '_>>,
514        query: &structured_asn1::Reader<'_>,
515    ) -> RootResult<'s, Self::Output, Self::SourceError, Self::Error>;
516    fn matches<W: Work>(
517        &self,
518        ordinal: usize,
519        output: &Self::Output,
520        expected: &[u8],
521        work: &W,
522    ) -> Result<bool, InputError<Self::SourceError, Self::Error>>;
523}
524impl<'p, P: AsnDigest<'p>> RootComputation<P> for MemoSet<'p, P> {
525    type Output = P::Output;
526    type Error = P::Error;
527    type SourceError = core::convert::Infallible;
528    fn belongs_to_provider(&self, provider: &P) -> bool {
529        core::ptr::eq(self.provider, provider)
530    }
531    fn root<'s>(
532        &'s self,
533        stamp: &'s ArchiveTimeStamp<'_>,
534        ordinal: usize,
535        membership: Membership,
536        previous: Option<&super::PriorSequence<'_, '_>>,
537        query: &structured_asn1::Reader<'_>,
538    ) -> Result<Root<'s, P::Output>, InputError<core::convert::Infallible, P::Error>> {
539        let origin = stamp
540            .original
541            .reader_in(query)
542            .map_err(InputError::Processing)?;
543        let work = origin.execution_query(query);
544        if self.memos.is_empty() || (membership == Membership::Object && self.memos.len() != 1) {
545            return Err(InputError::ObjectCount);
546        }
547        let mut objects = work.vector();
548        objects
549            .try_reserve_exact(self.memos.len())
550            .map_err(|cause| {
551                InputError::Processing(Error::resource(
552                    cause,
553                    FIELD,
554                    stamp.original.element.span().start,
555                ))
556            })?;
557        let first = self.memos[0]
558            .for_request(ordinal, &work)
559            .map_err(map_digest)?
560            .ok_or(InputError::ForeignDigest)?;
561        if !first.finished().from_provider(self.provider) {
562            return Err(InputError::ForeignDigest);
563        }
564        if let Some(prior) = previous
565            && !prior.contains(stamp)
566        {
567            return Err(InputError::ForeignRenewal);
568        }
569        let hasher = Hasher {
570            provider: self.provider,
571            identity: first.identity(),
572            work,
573            at: stamp.original.element.span().start,
574        };
575        validate_tree(&hasher, stamp)?;
576        let previous = previous.map(|prior| prior.hash(&hasher)).transpose()?;
577        for memo in &self.memos {
578            let value = memo
579                .for_request(ordinal, &hasher.work)
580                .map_err(map_digest)?
581                .ok_or(InputError::ForeignDigest)?;
582            if !value.finished().from_provider(self.provider) {
583                return Err(InputError::ForeignDigest);
584            }
585            if self
586                .provider
587                .compare(value.identity(), hasher.identity, &hasher.work)
588                .map_err(InputError::Provider)?
589                != Ordering::Equal
590            {
591                return Err(InputError::ForeignDigest);
592            }
593            let output = if let Some(previous) = &previous {
594                let mut state = hasher.start()?;
595                hasher.update(&mut state, value.as_bytes())?;
596                hasher.update(&mut state, previous.as_ref())?;
597                Root::Owned(hasher.finish(state)?)
598            } else {
599                Root::Borrowed(value.finished().output())
600            };
601            objects
602                .try_push(Hash::Root(output))
603                .map_err(|cause| hasher.resource(cause))?;
604        }
605        fold(&hasher, stamp, objects, membership)
606    }
607    fn matches<W: Work>(
608        &self,
609        ordinal: usize,
610        output: &P::Output,
611        expected: &[u8],
612        work: &W,
613    ) -> Result<bool, InputError<core::convert::Infallible, P::Error>> {
614        let first = self.memos[0]
615            .for_request(ordinal, work)
616            .map_err(map_digest)?
617            .ok_or(InputError::ForeignDigest)?;
618        self.provider
619            .matches(first.identity(), output, expected, work)
620            .map_err(InputError::Provider)
621    }
622}
623
624// Initialize on the first actual object-input request, AFTER the same token's
625// common schema/profile/key preparation. Successful output is immutable and
626// owned by OnceCell; later trees borrow it without refcount, Clone or replay.
627pub(super) struct LazyMemos<'s, 'p, P: AsnDigest<'p>, S: Sources<'p, P>> {
628    provider: &'p P,
629    algorithms: &'s [structured_pkix::algorithm::digest::Algorithm],
630    sources: RefCell<S>,
631    initialized: OnceCell<MemoSet<'p, P>>,
632}
633impl<'s, 'p, P: AsnDigest<'p>, S: Sources<'p, P>> LazyMemos<'s, 'p, P, S> {
634    pub(super) fn new(
635        provider: &'p P,
636        algorithms: &'s [structured_pkix::algorithm::digest::Algorithm],
637        sources: S,
638    ) -> Self {
639        Self {
640            provider,
641            algorithms,
642            sources: RefCell::new(sources),
643            initialized: OnceCell::new(),
644        }
645    }
646    fn ready(
647        &self,
648        stamp: &ArchiveTimeStamp<'_>,
649        query: &structured_asn1::Reader<'_>,
650    ) -> Result<&MemoSet<'p, P>, InputError<S::Error, P::Error>> {
651        if let Some(memos) = self.initialized.get() {
652            return Ok(memos);
653        }
654        let origin = stamp
655            .original
656            .reader_in(query)
657            .map_err(InputError::Processing)?;
658        let work = origin.execution_query(query);
659        let at = stamp.original.element.span().start;
660        let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, at));
661        let mut sources = self
662            .sources
663            .try_borrow_mut()
664            .map_err(|_| InputError::ForeignDigest)?;
665        let mut memos = work.vector();
666        memos.try_reserve_exact(sources.len()).map_err(resource)?;
667        for index in 0..sources.len() {
668            let batch = prepare_batch(self.provider, self.algorithms, &work, at)?;
669            let memo = sources.run(index, batch, &work).map_err(pass_error)?;
670            memos.try_push(memo).map_err(resource)?;
671        }
672        self.initialized
673            .set(MemoSet {
674                provider: self.provider,
675                memos: memos.into_owned(),
676            })
677            .map_err(|_| InputError::ForeignDigest)?;
678        self.initialized.get().ok_or(InputError::ForeignDigest)
679    }
680}
681impl<'p, P: AsnDigest<'p>, S: Sources<'p, P>> RootComputation<P> for LazyMemos<'_, 'p, P, S> {
682    type Output = P::Output;
683    type Error = P::Error;
684    type SourceError = S::Error;
685    fn belongs_to_provider(&self, provider: &P) -> bool {
686        core::ptr::eq(self.provider, provider)
687    }
688    fn root<'s>(
689        &'s self,
690        stamp: &'s ArchiveTimeStamp<'_>,
691        ordinal: usize,
692        membership: Membership,
693        previous: Option<&super::PriorSequence<'_, '_>>,
694        query: &structured_asn1::Reader<'_>,
695    ) -> Result<Root<'s, P::Output>, InputError<S::Error, P::Error>> {
696        self.ready(stamp, query)?
697            .root(stamp, ordinal, membership, previous, query)
698            .map_err(widen)
699    }
700    fn matches<W: Work>(
701        &self,
702        ordinal: usize,
703        output: &P::Output,
704        expected: &[u8],
705        work: &W,
706    ) -> Result<bool, InputError<S::Error, P::Error>> {
707        self.initialized
708            .get()
709            .ok_or(InputError::ForeignDigest)?
710            .matches(ordinal, output, expected, work)
711            .map_err(widen)
712    }
713}
714fn widen<SE, PE>(cause: InputError<core::convert::Infallible, PE>) -> InputError<SE, PE> {
715    match cause {
716        InputError::Processing(cause) => InputError::Processing(cause),
717        InputError::Provider(cause) => InputError::Provider(cause),
718        InputError::Source(never) => match never {},
719        InputError::Unsupported => InputError::Unsupported,
720        InputError::MalformedAlgorithm => InputError::MalformedAlgorithm,
721        InputError::ObjectCount => InputError::ObjectCount,
722        InputError::Membership => InputError::Membership,
723        InputError::HashWidth => InputError::HashWidth,
724        InputError::OutputWidth => InputError::OutputWidth,
725        InputError::ForeignRenewal => InputError::ForeignRenewal,
726        InputError::ForeignDigest => InputError::ForeignDigest,
727    }
728}
729
730// Mutable ownership spans acquisition; completed outputs become immutable
731// before tree computation. No RefCell guard or lock is held over suspension.
732pub(super) struct AsyncMemos<'s, 'p, P: AsnDigest<'p>, S: AsyncObjects<'p, P>> {
733    provider: &'p P,
734    algorithms: &'s [structured_pkix::algorithm::digest::Algorithm],
735    sources: S,
736    initialized: Option<MemoSet<'p, P>>,
737}
738impl<'s, 'p, P: AsnDigest<'p>, S: AsyncObjects<'p, P>> AsyncMemos<'s, 'p, P, S> {
739    pub(super) fn new(
740        provider: &'p P,
741        algorithms: &'s [structured_pkix::algorithm::digest::Algorithm],
742        sources: S,
743    ) -> Self {
744        Self {
745            provider,
746            algorithms,
747            sources,
748            initialized: None,
749        }
750    }
751    async fn ready(
752        &mut self,
753        stamp: &ArchiveTimeStamp<'_>,
754        query: &structured_asn1::Reader<'_>,
755    ) -> Result<(), InputError<S::Error, P::Error>> {
756        if self.initialized.is_some() {
757            return Ok(());
758        }
759        let origin = stamp
760            .original
761            .reader_in(query)
762            .map_err(InputError::Processing)?;
763        let work = origin.execution_query(query);
764        let at = stamp.original.element.span().start;
765        let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, at));
766        let count = self.sources.len();
767        let mut memos = work.vector();
768        memos.try_reserve_exact(count).map_err(resource)?;
769        for index in 0..count {
770            let batch = prepare_batch(self.provider, self.algorithms, &work, at)?;
771            let memo = self
772                .sources
773                .run(index, batch, &work)
774                .await
775                .map_err(pass_error)?;
776            memos.try_push(memo).map_err(resource)?;
777        }
778        self.initialized = Some(MemoSet {
779            provider: self.provider,
780            memos: memos.into_owned(),
781        });
782        Ok(())
783    }
784}
785impl<'p, P: AsnDigest<'p>, S: AsyncObjects<'p, P>> RootComputation<P> for AsyncMemos<'_, 'p, P, S> {
786    type Output = P::Output;
787    type Error = P::Error;
788    type SourceError = S::Error;
789    fn belongs_to_provider(&self, provider: &P) -> bool {
790        core::ptr::eq(self.provider, provider)
791    }
792    fn root<'s>(
793        &'s self,
794        stamp: &'s ArchiveTimeStamp<'_>,
795        ordinal: usize,
796        membership: Membership,
797        previous: Option<&super::PriorSequence<'_, '_>>,
798        query: &structured_asn1::Reader<'_>,
799    ) -> RootResult<'s, Self::Output, Self::SourceError, Self::Error> {
800        self.initialized
801            .as_ref()
802            .ok_or(InputError::ForeignDigest)?
803            .root(stamp, ordinal, membership, previous, query)
804            .map_err(widen)
805    }
806    fn matches<W: Work>(
807        &self,
808        ordinal: usize,
809        output: &Self::Output,
810        expected: &[u8],
811        work: &W,
812    ) -> Result<bool, InputError<Self::SourceError, Self::Error>> {
813        self.initialized
814            .as_ref()
815            .ok_or(InputError::ForeignDigest)?
816            .matches(ordinal, output, expected, work)
817            .map_err(widen)
818    }
819}
820
821trait AsyncRootComputation<P>: RootComputation<P> {
822    fn prepare(
823        &mut self,
824        stamp: &ArchiveTimeStamp<'_>,
825        query: &structured_asn1::Reader<'_>,
826    ) -> impl core::future::Future<Output = Result<(), InputError<Self::SourceError, Self::Error>>>;
827}
828impl<'p, P: AsnDigest<'p>, S: AsyncObjects<'p, P>> AsyncRootComputation<P>
829    for AsyncMemos<'_, 'p, P, S>
830{
831    async fn prepare(
832        &mut self,
833        stamp: &ArchiveTimeStamp<'_>,
834        query: &structured_asn1::Reader<'_>,
835    ) -> Result<(), InputError<Self::SourceError, Self::Error>> {
836        self.ready(stamp, query).await
837    }
838}
839
840pub(super) struct AsyncCachedInput<'s, 'd, F> {
841    pub(super) stamp: &'s ArchiveTimeStamp<'d>,
842    pub(super) memos: &'s mut F,
843    pub(super) ordinal: usize,
844    pub(super) membership: Membership,
845    pub(super) previous: Option<&'s super::PriorSequence<'s, 'd>>,
846}
847impl<P, PE, SE, F: AsyncRootComputation<P, Error = PE, SourceError = SE>> AsyncComputedInput<P, PE>
848    for AsyncCachedInput<'_, '_, F>
849{
850    type Error = InputError<SE, PE>;
851    async fn compute<'context: 'scope, 'reader, 'document, 'scope>(
852        &'scope mut self,
853        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
854    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
855    where
856        P: AsnDigest<'document, Error = PE>,
857    {
858        if !self.memos.belongs_to_provider(context.provider()) {
859            return Err(InputFailure::Source(InputError::ForeignDigest));
860        }
861        self.memos
862            .prepare(self.stamp, context.reader())
863            .await
864            .map_err(InputFailure::Source)?;
865        CachedInput {
866            stamp: self.stamp,
867            memos: &*self.memos,
868            ordinal: self.ordinal,
869            membership: self.membership,
870            previous: self.previous,
871        }
872        .compute(context)
873    }
874}
875
876// A renewal covers resident original timestamp bytes. The asynchronous driver
877// uses the identical synchronous reduced-tree recipe without copying its input.
878pub(super) struct ResidentInput<I>(pub(super) I);
879impl<P, PE, I: ComputedInput<P, PE>> AsyncComputedInput<P, PE> for ResidentInput<I> {
880    type Error = I::Error;
881    fn compute<'context: 'scope, 'reader, 'document, 'scope>(
882        &'scope mut self,
883        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
884    ) -> impl core::future::Future<Output = Result<ImprintProof<'context>, InputFailure<Self::Error>>>
885    where
886        P: AsnDigest<'document, Error = PE>,
887    {
888        core::future::ready(self.0.compute(context))
889    }
890}
891
892fn prepare_batch<'p, P: AsnDigest<'p>, W: Work, SE>(
893    provider: &'p P,
894    algorithms: &[structured_pkix::algorithm::digest::Algorithm],
895    work: &W,
896    at: u64,
897) -> Result<DigestBatch<'p, P>, InputError<SE, P::Error>> {
898    let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, at));
899    let mut identities = work.vector();
900    for algorithm in algorithms {
901        work.charge_work(1).map_err(resource)?;
902        let identity = provider
903            .identity(*algorithm)
904            .ok_or(InputError::Unsupported)?;
905        identities.try_push(identity).map_err(resource)?;
906    }
907    DigestBatch::prepare(provider, identities.into_owned(), work, FIELD, at).map_err(map_digest)
908}
909
910fn pass_error<SE, PE>(cause: StreamError<SE, DigestError<PE>>) -> InputError<SE, PE> {
911    match cause {
912        StreamError::Source(cause) => InputError::Source(cause),
913        StreamError::Callback(cause) => map_digest(cause),
914        StreamError::Processing(cause) => InputError::Processing(cause),
915    }
916}
917pub(super) struct CachedInput<'s, 'd, F> {
918    pub(super) stamp: &'s ArchiveTimeStamp<'d>,
919    pub(super) memos: &'s F,
920    pub(super) ordinal: usize,
921    pub(super) membership: Membership,
922    pub(super) previous: Option<&'s super::PriorSequence<'s, 'd>>,
923}
924impl<P, PE, SE, F: RootComputation<P, Error = PE, SourceError = SE>> ComputedInput<P, PE>
925    for CachedInput<'_, '_, F>
926{
927    type Error = InputError<SE, PE>;
928    fn compute<'context, 'reader, 'document>(
929        &mut self,
930        context: &'context ImprintContext<'reader, 'document, P, Self::Error>,
931    ) -> Result<ImprintProof<'context>, InputFailure<Self::Error>>
932    where
933        P: AsnDigest<'document, Error = PE>,
934    {
935        if !self.memos.belongs_to_provider(context.provider()) {
936            return Err(InputFailure::Source(InputError::ForeignDigest));
937        }
938        let root = self
939            .memos
940            .root(
941                self.stamp,
942                self.ordinal,
943                self.membership,
944                self.previous,
945                context.reader(),
946            )
947            .map_err(InputFailure::Source)?;
948        context
949            .compare_derived(|expected, work| {
950                self.memos
951                    .matches(self.ordinal, root.output(), expected, work)
952            })
953            .map_err(InputFailure::Refused)
954    }
955}
956fn map_digest<SE, PE>(cause: DigestError<PE>) -> InputError<SE, PE> {
957    match cause {
958        DigestError::Processing(cause) => InputError::Processing(cause),
959        DigestError::Callback(cause) => InputError::Provider(cause),
960        DigestError::OutputWidth { .. } => InputError::OutputWidth,
961        DigestError::Unstreamable => InputError::Unsupported,
962    }
963}