Skip to main content

structured_cms/
digest.rs

1//! One-pass digest fan-out with one final output per resolved semantic identity.
2
3use crate::Error;
4use crate::provider::{Finalized, Finished, Prehash};
5use crate::stream::{self, Sink, Source, StreamError};
6use core::cmp::Ordering;
7use structured_asn1::digest::{Digest, Resolution};
8use structured_asn1::memory::{OwnedVec, SortError, Work};
9
10/// A complete pass preserves source, common-processing and provider causes.
11pub type PassError<SE, PE> = StreamError<SE, DigestError<PE>>;
12/// A finalized output with the selected provider's full identity and storage type.
13pub type DigestOutput<'a, P> =
14    Finalized<'a, P, <P as Digest<'a>>::Identity, <P as Digest<'a>>::Output>;
15/// Fallible digest processing, without reducing provider refusal to absence.
16pub type DigestResult<T, E> = Result<T, DigestError<E>>;
17/// A complete source pass, preserving acquisition and processing causes.
18pub type PassResult<T, SE, PE> = Result<T, PassError<SE, PE>>;
19
20/// Failure of common digest processing or the selected provider.
21#[derive(Debug)]
22pub enum DigestError<E> {
23    /// Common storage, dispatch, sorting or lookup refusal before work.
24    Processing(Error),
25    /// Original callback error, including any opaque backend resource refusal.
26    Callback(E),
27    /// A callback returned a width inconsistent with its resolved identity.
28    /// This is a provider contract failure, not malformed document data.
29    OutputWidth {
30        /// Fixed width promised by the selected resolved identity.
31        expected: usize,
32        /// Actual returned output width.
33        actual: usize,
34    },
35    /// The provider resolved this identity but cannot stream it, which CMS
36    /// content and attribute digests require. A capability answer of the
37    /// provider, not a document fault.
38    Unstreamable,
39}
40
41fn started<S, E>(start: Result<Resolution<S>, E>) -> Result<S, DigestError<E>> {
42    match start.map_err(DigestError::Callback)? {
43        Resolution::Supported(state) => Ok(state),
44        Resolution::Unsupported | Resolution::Malformed => Err(DigestError::Unstreamable),
45    }
46}
47
48struct Pending<I, S> {
49    ordinal: usize,
50    identity: I,
51    state: S,
52}
53
54struct Request<I> {
55    ordinal: usize,
56    identity: I,
57}
58
59/// Prepared states from one selected provider; no state cloning or source replay.
60pub struct DigestBatch<'a, P: Digest<'a>> {
61    provider: &'a P,
62    pending: OwnedVec<Pending<P::Identity, P::State>>,
63    mapping: OwnedVec<usize>,
64    field: &'static str,
65    at: u64,
66}
67
68impl<'a, P: Digest<'a>> DigestBatch<'a, P> {
69    /// Moves resolved identities into the current operation and initializes each once.
70    ///
71    /// Identities must come from this selected provider's resolver. They are
72    /// sorted and deduplicated through its fallible semantic comparison, not
73    /// OID/width equality. Admission precedes every callback, move and allocation.
74    /// `field`/`at` identify the caller's enclosing original source structure.
75    pub fn prepare<W: Work>(
76        provider: &'a P,
77        identities: OwnedVec<P::Identity>,
78        execution: &W,
79        field: &'static str,
80        at: u64,
81    ) -> Result<Self, DigestError<P::Error>> {
82        crate::provider::require_instance(provider, field, at).map_err(DigestError::Processing)?;
83        let refusal = |reason| DigestError::Processing(Error::resource(reason, field, at));
84        execution.charge_work(1).map_err(refusal)?;
85        let count = identities.len();
86        let mut ordered = execution.vector();
87        ordered
88            .try_reserve_exact(identities.len())
89            .map_err(refusal)?;
90        execution
91            .charge_work(core::mem::size_of_val(&*identities) as u64)
92            .map_err(refusal)?;
93        for (ordinal, identity) in identities.into_iter().enumerate() {
94            ordered
95                .try_push(Request { ordinal, identity })
96                .map_err(refusal)?;
97        }
98        ordered
99            .try_sort_unstable_by(|left, right| {
100                let order = provider.compare(&left.identity, &right.identity, execution)?;
101                // A distinct identity's derived preimage belongs to its first
102                // original request. An unstable equal run must not choose a
103                // different representative and silently change that source.
104                if order == Ordering::Equal {
105                    Ok(left.ordinal.cmp(&right.ordinal))
106                } else {
107                    Ok(order)
108                }
109            })
110            .map_err(|error| match error {
111                SortError::Resource(reason) => refusal(reason),
112                SortError::Comparison(error) => DigestError::Callback(error),
113            })?;
114        let mut pending: OwnedVec<Pending<P::Identity, P::State>, _> = execution.vector();
115        let mut mapping = execution.vector();
116        mapping.try_reserve_exact(count).map_err(refusal)?;
117        // The sorted request records are a permutation of ordinals 0..count.
118        // Every slot is assigned exactly once before this mapping is exposed.
119        for _ in 0..count {
120            mapping.try_push(0).map_err(refusal)?;
121        }
122        execution
123            .charge_work(core::mem::size_of_val(&*ordered) as u64)
124            .map_err(refusal)?;
125        for Request { ordinal, identity } in ordered.into_owned() {
126            execution.charge_work(1).map_err(refusal)?;
127            if let Some(previous) = pending.last()
128                && provider
129                    .compare(&previous.identity, &identity, execution)
130                    .map_err(DigestError::Callback)?
131                    == Ordering::Equal
132            {
133                mapping[ordinal] = pending.len() - 1;
134                continue;
135            }
136            pending.try_reserve(1).map_err(refusal)?;
137            let state = started(provider.start(&identity, execution))?;
138            pending
139                .try_push(Pending {
140                    ordinal,
141                    identity,
142                    state,
143                })
144                .map_err(refusal)?;
145            mapping[ordinal] = pending.len() - 1;
146        }
147        Ok(Self {
148            provider,
149            pending: pending.into_owned(),
150            mapping: mapping.into_owned(),
151            field,
152            at,
153        })
154    }
155
156    /// Number of unique resolved digest states, without rescanning identities.
157    #[must_use]
158    pub fn len(&self) -> usize {
159        self.pending.len()
160    }
161    /// Whether no digest was requested.
162    #[must_use]
163    pub fn is_empty(&self) -> bool {
164        self.pending.is_empty()
165    }
166
167    /// Feeds the source once; outputs are finalized only after complete successful EOF.
168    ///
169    /// A source or callback error drops every unfinished state and partial
170    /// output. No partial digest is returned, and a source ignoring a refused
171    /// write cannot make subsequent callbacks run or turn refusal into success.
172    pub fn run<S: Source, W: Work>(
173        mut self,
174        source: &mut S,
175        execution: &W,
176    ) -> Result<DigestMemo<'a, P>, PassError<S::Error, P::Error>> {
177        let field = self.field;
178        let at = self.at;
179        let mut outputs = execution.vector();
180        outputs
181            .try_reserve_exact(self.pending.len())
182            .map_err(|reason| StreamError::Processing(Error::resource(reason, field, at)))?;
183        stream::run(source, &mut BatchSink(&mut self), execution, field, at)?;
184        self.finish(outputs, execution)
185            .map_err(StreamError::Callback)
186    }
187
188    /// Completes algorithm-dependent derived inputs, once per distinct full
189    /// identity. The callback receives the first ORIGINAL request ordinal,
190    /// preserving the prepare mapping after identity sorting/deduplication.
191    ///
192    /// TS101733 ยง6.5.1's `PoE` preimage is eight hashes under the selected
193    /// algorithm, so different algorithms cannot share one literal byte stream.
194    /// All states, sticky stream errors, output widths and finalization use
195    /// exactly the same kernels as [`Self::run`]. No raw-output constructor or
196    /// unchecked digest/identity pair is exposed.
197    pub fn run_requests<S: RequestSource<'a, P>, W: Work>(
198        mut self,
199        source: &mut S,
200        execution: &W,
201    ) -> PassResult<DigestMemo<'a, P>, S::Error, P::Error> {
202        let mut outputs = execution.vector();
203        outputs
204            .try_reserve_exact(self.pending.len())
205            .map_err(|cause| {
206                StreamError::Processing(Error::resource(cause, self.field, self.at))
207            })?;
208        for pending in &mut self.pending {
209            execution.charge_work(1).map_err(|cause| {
210                StreamError::Processing(Error::resource(cause, self.field, self.at))
211            })?;
212            let mut selected = SelectedSource {
213                source,
214                ordinal: pending.ordinal,
215                provider: self.provider,
216                identity: &pending.identity,
217            };
218            stream::run(
219                &mut selected,
220                &mut RelatedSink {
221                    provider: self.provider,
222                    state: &mut pending.state,
223                },
224                execution,
225                self.field,
226                self.at,
227            )?;
228        }
229        self.finish(outputs, execution)
230            .map_err(StreamError::Callback)
231    }
232
233    pub(crate) fn feed<W: Work>(
234        &mut self,
235        bytes: &[u8],
236        execution: &W,
237    ) -> Result<(), DigestError<P::Error>> {
238        for pending in &mut self.pending {
239            execution.charge_work(1).map_err(|reason| {
240                DigestError::Processing(Error::resource(reason, self.field, self.at))
241            })?;
242            self.provider
243                .update(&mut pending.state, bytes, execution)
244                .map_err(DigestError::Callback)?;
245        }
246        Ok(())
247    }
248
249    pub(crate) fn finish<W: Work>(
250        self,
251        mut outputs: OwnedVec<DigestOutput<'a, P>, W>,
252        execution: &W,
253    ) -> Result<DigestMemo<'a, P>, DigestError<P::Error>> {
254        let refusal =
255            |reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
256        execution
257            .charge_work(core::mem::size_of_val(&*self.pending) as u64)
258            .map_err(refusal)?;
259        for pending in self.pending {
260            execution.charge_work(1).map_err(refusal)?;
261            let output = self
262                .provider
263                .finish(pending.state, execution)
264                .map_err(DigestError::Callback)?;
265            if let Some(expected) = self.provider.width(&pending.identity) {
266                let actual = output.as_ref().len();
267                if actual != expected {
268                    return Err(DigestError::OutputWidth { expected, actual });
269                }
270            }
271            outputs
272                .try_push(Finalized::new(self.provider, pending.identity, output))
273                .map_err(refusal)?;
274        }
275        Ok(DigestMemo {
276            provider: self.provider,
277            outputs: outputs.into_owned(),
278            mapping: self.mapping,
279            field: self.field,
280            at: self.at,
281        })
282    }
283
284    pub(crate) fn provider(&self) -> &'a P {
285        self.provider
286    }
287
288    pub(crate) fn location(&self) -> (&'static str, u64) {
289        (self.field, self.at)
290    }
291}
292
293/// A derived byte stream indexed by the original resolved-digest request.
294/// Source errors remain independent of provider/stream-driver refusals.
295pub trait RequestSource<'provider, P: Digest<'provider>> {
296    /// Original derivation/acquisition refusal.
297    type Error;
298    /// Supplies the complete algorithm-dependent preimage, with current
299    /// transitive work/storage admission before processing each component.
300    fn stream_request<W: Work>(
301        &mut self,
302        ordinal: usize,
303        provider: &'provider P,
304        identity: &P::Identity,
305        receiver: &mut impl crate::stream::Receiver,
306        execution: &W,
307    ) -> Result<(), Self::Error>;
308}
309struct SelectedSource<'s, 'p, P: Digest<'p>, S> {
310    source: &'s mut S,
311    ordinal: usize,
312    provider: &'p P,
313    identity: &'s P::Identity,
314}
315impl<'p, P: Digest<'p>, S: RequestSource<'p, P>> Source for SelectedSource<'_, 'p, P, S> {
316    type Error = S::Error;
317    fn stream<W: Work>(
318        &mut self,
319        receiver: &mut impl crate::stream::Receiver,
320        execution: &W,
321    ) -> Result<(), Self::Error> {
322        self.source.stream_request(
323            self.ordinal,
324            self.provider,
325            self.identity,
326            receiver,
327            execution,
328        )
329    }
330}
331
332struct BatchSink<'s, 'a, P: Digest<'a>>(&'s mut DigestBatch<'a, P>);
333
334impl<'a, P: Digest<'a>> Sink for BatchSink<'_, 'a, P> {
335    type Error = DigestError<P::Error>;
336    fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
337        self.0.feed(bytes, execution)
338    }
339}
340
341/// Immutable completed outputs, sorted by the selected provider's full identity.
342pub struct DigestMemo<'a, P: Digest<'a>> {
343    provider: &'a P,
344    outputs: OwnedVec<DigestOutput<'a, P>>,
345    mapping: OwnedVec<usize>,
346    field: &'static str,
347    at: u64,
348}
349
350/// A separately completed input under an existing memo's resolved identity.
351///
352/// Signed attributes need their own digest output, while sharing the payload's
353/// already resolved identity. This result borrows that identity and owns its
354/// output; it cannot outlive the memo or become a fabricated digest identity.
355pub struct RelatedDigest<'m, 'a, P: Digest<'a>> {
356    provider: &'a P,
357    identity: &'m P::Identity,
358    output: P::Output,
359}
360
361impl<'a, P: Digest<'a>> RelatedDigest<'_, 'a, P> {
362    /// Typed finalized input with short borrows, without identity/output clones.
363    #[must_use]
364    pub fn prehash(&self) -> Prehash<'_, '_, P, P::Identity> {
365        Prehash::new(self.provider, self.identity, self.output.as_ref())
366    }
367
368    /// Borrows the provider's own output for its comparison, with provenance.
369    #[must_use]
370    pub fn finished(&self) -> Finished<'_, P, P::Identity, P::Output> {
371        Finished::new(self.provider, self.identity, &self.output)
372    }
373}
374
375struct RelatedSink<'s, 'a, P: Digest<'a>> {
376    provider: &'a P,
377    state: &'s mut P::State,
378}
379
380impl<'a, P: Digest<'a>> Sink for RelatedSink<'_, 'a, P> {
381    type Error = DigestError<P::Error>;
382
383    fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
384        self.provider
385            .update(self.state, bytes, execution)
386            .map_err(DigestError::Callback)
387    }
388}
389
390impl<'a, P: Digest<'a>> DigestMemo<'a, P> {
391    /// Completes another input with the original request's resolved identity.
392    ///
393    /// For CMS signed attributes, feed their original retagged ranges here;
394    /// their output is distinct from the payload output used by message-digest.
395    /// No resolver, Identity clone, state reconstruction or payload replay is
396    /// performed. An absent ordinal does not call the source or provider.
397    /// `field`/`at` locate the caller's enclosing original input structure.
398    pub fn digest_related<'m, S: Source, W: Work>(
399        &'m self,
400        ordinal: usize,
401        source: &mut S,
402        execution: &W,
403        field: &'static str,
404        at: u64,
405    ) -> PassResult<Option<RelatedDigest<'m, 'a, P>>, S::Error, P::Error> {
406        let Some(existing) = self
407            .for_request(ordinal, execution)
408            .map_err(StreamError::Callback)?
409        else {
410            return Ok(None);
411        };
412        let identity = existing.identity();
413        let mut state =
414            started(self.provider.start(identity, execution)).map_err(StreamError::Callback)?;
415        stream::run(
416            source,
417            &mut RelatedSink {
418                provider: self.provider,
419                state: &mut state,
420            },
421            execution,
422            field,
423            at,
424        )?;
425        let output = self
426            .provider
427            .finish(state, execution)
428            .map_err(|error| StreamError::Callback(DigestError::Callback(error)))?;
429        if let Some(expected) = self.provider.width(identity) {
430            let actual = output.as_ref().len();
431            if actual != expected {
432                return Err(StreamError::Callback(DigestError::OutputWidth {
433                    expected,
434                    actual,
435                }));
436            }
437        }
438        Ok(Some(RelatedDigest {
439            provider: self.provider,
440            identity,
441            output,
442        }))
443    }
444
445    /// Number of unique finalized outputs.
446    #[must_use]
447    pub fn len(&self) -> usize {
448        self.outputs.len()
449    }
450    /// Whether no completed output is held.
451    #[must_use]
452    pub fn is_empty(&self) -> bool {
453        self.outputs.is_empty()
454    }
455
456    /// Number of original requests, including identities that shared a state.
457    #[must_use]
458    pub fn request_count(&self) -> usize {
459        self.mapping.len()
460    }
461
462    /// The already finalized output for an original request ordinal.
463    ///
464    /// This constant-work link avoids resolving an algorithm again or cloning
465    /// its identity/state merely to find a shared output. Out-of-range ordinals
466    /// mean absence only after current-operation admission.
467    pub fn for_request<W: Work>(
468        &self,
469        ordinal: usize,
470        execution: &W,
471    ) -> DigestResult<Option<&DigestOutput<'a, P>>, P::Error> {
472        execution.charge_work(1).map_err(|reason| {
473            DigestError::Processing(Error::resource(reason, self.field, self.at))
474        })?;
475        let Some(index) = self.mapping.get(ordinal) else {
476            return Ok(None);
477        };
478        // Preparation assigned every ordinal to a pending state; successful
479        // finalization produced exactly one output for every such state.
480        debug_assert!(*index < self.outputs.len());
481        Ok(Some(&self.outputs[*index]))
482    }
483
484    /// Looks up a full resolved identity in the current execution account.
485    ///
486    /// Storage remains on its original account. No state/output clone, new hash
487    /// or finalization is performed. A resource refusal cannot become absence.
488    pub fn find<W: Work>(
489        &self,
490        identity: &P::Identity,
491        execution: &W,
492    ) -> DigestResult<Option<&DigestOutput<'a, P>>, P::Error> {
493        let refusal =
494            |reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
495        execution.charge_work(1).map_err(refusal)?;
496        let mut from = 0;
497        let mut to = self.outputs.len();
498        while from < to {
499            execution.charge_work(1).map_err(refusal)?;
500            let middle = from + (to - from) / 2;
501            let held = &self.outputs[middle];
502            match self
503                .provider
504                .compare(held.identity(), identity, execution)
505                .map_err(DigestError::Callback)?
506            {
507                Ordering::Equal => return Ok(Some(held)),
508                Ordering::Less => from = middle + 1,
509                Ordering::Greater => to = middle,
510            }
511        }
512        Ok(None)
513    }
514}