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 an asynchronous bounded source through the same digest
189    /// states, request mapping, width checks and consuming finalization.
190    pub async fn run_async<S: stream::AsyncSource, W: Work>(
191        mut self,
192        source: &mut S,
193        execution: &W,
194    ) -> Result<DigestMemo<'a, P>, PassError<S::Error, P::Error>> {
195        let field = self.field;
196        let at = self.at;
197        let mut outputs = execution.vector();
198        outputs
199            .try_reserve_exact(self.pending.len())
200            .map_err(|cause| StreamError::Processing(Error::resource(cause, field, at)))?;
201        stream::run_async(source, &mut BatchSink(&mut self), execution, field, at).await?;
202        self.finish(outputs, execution)
203            .map_err(StreamError::Callback)
204    }
205
206    /// Completes algorithm-dependent derived inputs, once per distinct full
207    /// identity. The callback receives the first ORIGINAL request ordinal,
208    /// preserving the prepare mapping after identity sorting/deduplication.
209    ///
210    /// TS101733 ยง6.5.1's `PoE` preimage is eight hashes under the selected
211    /// algorithm, so different algorithms cannot share one literal byte stream.
212    /// All states, sticky stream errors, output widths and finalization use
213    /// exactly the same kernels as [`Self::run`]. No raw-output constructor or
214    /// unchecked digest/identity pair is exposed.
215    pub fn run_requests<S: RequestSource<'a, P>, W: Work>(
216        mut self,
217        source: &mut S,
218        execution: &W,
219    ) -> PassResult<DigestMemo<'a, P>, S::Error, P::Error> {
220        let mut outputs = execution.vector();
221        outputs
222            .try_reserve_exact(self.pending.len())
223            .map_err(|cause| {
224                StreamError::Processing(Error::resource(cause, self.field, self.at))
225            })?;
226        for pending in &mut self.pending {
227            execution.charge_work(1).map_err(|cause| {
228                StreamError::Processing(Error::resource(cause, self.field, self.at))
229            })?;
230            let mut selected = SelectedSource {
231                source,
232                ordinal: pending.ordinal,
233                provider: self.provider,
234                identity: &pending.identity,
235            };
236            stream::run(
237                &mut selected,
238                &mut RelatedSink {
239                    provider: self.provider,
240                    state: &mut pending.state,
241                },
242                execution,
243                self.field,
244                self.at,
245            )?;
246        }
247        self.finish(outputs, execution)
248            .map_err(StreamError::Callback)
249    }
250
251    pub(crate) fn feed<W: Work>(
252        &mut self,
253        bytes: &[u8],
254        execution: &W,
255    ) -> Result<(), DigestError<P::Error>> {
256        for pending in &mut self.pending {
257            execution.charge_work(1).map_err(|reason| {
258                DigestError::Processing(Error::resource(reason, self.field, self.at))
259            })?;
260            self.provider
261                .update(&mut pending.state, bytes, execution)
262                .map_err(DigestError::Callback)?;
263        }
264        Ok(())
265    }
266
267    pub(crate) fn finish<W: Work>(
268        self,
269        mut outputs: OwnedVec<DigestOutput<'a, P>, W>,
270        execution: &W,
271    ) -> Result<DigestMemo<'a, P>, DigestError<P::Error>> {
272        let refusal =
273            |reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
274        execution
275            .charge_work(core::mem::size_of_val(&*self.pending) as u64)
276            .map_err(refusal)?;
277        for pending in self.pending {
278            execution.charge_work(1).map_err(refusal)?;
279            let output = self
280                .provider
281                .finish(pending.state, execution)
282                .map_err(DigestError::Callback)?;
283            if let Some(expected) = self.provider.width(&pending.identity) {
284                let actual = output.as_ref().len();
285                if actual != expected {
286                    return Err(DigestError::OutputWidth { expected, actual });
287                }
288            }
289            outputs
290                .try_push(Finalized::new(self.provider, pending.identity, output))
291                .map_err(refusal)?;
292        }
293        Ok(DigestMemo {
294            provider: self.provider,
295            outputs: outputs.into_owned(),
296            mapping: self.mapping,
297            field: self.field,
298            at: self.at,
299        })
300    }
301
302    pub(crate) fn provider(&self) -> &'a P {
303        self.provider
304    }
305
306    pub(crate) fn location(&self) -> (&'static str, u64) {
307        (self.field, self.at)
308    }
309}
310
311/// A derived byte stream indexed by the original resolved-digest request.
312/// Source errors remain independent of provider/stream-driver refusals.
313pub trait RequestSource<'provider, P: Digest<'provider>> {
314    /// Original derivation/acquisition refusal.
315    type Error;
316    /// Supplies the complete algorithm-dependent preimage, with current
317    /// transitive work/storage admission before processing each component.
318    fn stream_request<W: Work>(
319        &mut self,
320        ordinal: usize,
321        provider: &'provider P,
322        identity: &P::Identity,
323        receiver: &mut impl crate::stream::Receiver,
324        execution: &W,
325    ) -> Result<(), Self::Error>;
326}
327struct SelectedSource<'s, 'p, P: Digest<'p>, S> {
328    source: &'s mut S,
329    ordinal: usize,
330    provider: &'p P,
331    identity: &'s P::Identity,
332}
333impl<'p, P: Digest<'p>, S: RequestSource<'p, P>> Source for SelectedSource<'_, 'p, P, S> {
334    type Error = S::Error;
335    fn stream<W: Work>(
336        &mut self,
337        receiver: &mut impl crate::stream::Receiver,
338        execution: &W,
339    ) -> Result<(), Self::Error> {
340        self.source.stream_request(
341            self.ordinal,
342            self.provider,
343            self.identity,
344            receiver,
345            execution,
346        )
347    }
348}
349
350struct BatchSink<'s, 'a, P: Digest<'a>>(&'s mut DigestBatch<'a, P>);
351
352impl<'a, P: Digest<'a>> Sink for BatchSink<'_, 'a, P> {
353    type Error = DigestError<P::Error>;
354    fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
355        self.0.feed(bytes, execution)
356    }
357}
358
359/// Immutable completed outputs, sorted by the selected provider's full identity.
360pub struct DigestMemo<'a, P: Digest<'a>> {
361    provider: &'a P,
362    outputs: OwnedVec<DigestOutput<'a, P>>,
363    mapping: OwnedVec<usize>,
364    field: &'static str,
365    at: u64,
366}
367
368/// A separately completed input under an existing memo's resolved identity.
369///
370/// Signed attributes need their own digest output, while sharing the payload's
371/// already resolved identity. This result borrows that identity and owns its
372/// output; it cannot outlive the memo or become a fabricated digest identity.
373pub struct RelatedDigest<'m, 'a, P: Digest<'a>> {
374    provider: &'a P,
375    identity: &'m P::Identity,
376    output: P::Output,
377}
378
379impl<'a, P: Digest<'a>> RelatedDigest<'_, 'a, P> {
380    /// Typed finalized input with short borrows, without identity/output clones.
381    #[must_use]
382    pub fn prehash(&self) -> Prehash<'_, '_, P, P::Identity> {
383        Prehash::new(self.provider, self.identity, self.output.as_ref())
384    }
385
386    /// Borrows the provider's own output for its comparison, with provenance.
387    #[must_use]
388    pub fn finished(&self) -> Finished<'_, P, P::Identity, P::Output> {
389        Finished::new(self.provider, self.identity, &self.output)
390    }
391}
392
393struct RelatedSink<'s, 'a, P: Digest<'a>> {
394    provider: &'a P,
395    state: &'s mut P::State,
396}
397
398impl<'a, P: Digest<'a>> Sink for RelatedSink<'_, 'a, P> {
399    type Error = DigestError<P::Error>;
400
401    fn write<W: Work>(&mut self, bytes: &[u8], execution: &W) -> Result<(), Self::Error> {
402        self.provider
403            .update(self.state, bytes, execution)
404            .map_err(DigestError::Callback)
405    }
406}
407
408impl<'a, P: Digest<'a>> DigestMemo<'a, P> {
409    /// Completes another input with the original request's resolved identity.
410    ///
411    /// For CMS signed attributes, feed their original retagged ranges here;
412    /// their output is distinct from the payload output used by message-digest.
413    /// No resolver, Identity clone, state reconstruction or payload replay is
414    /// performed. An absent ordinal does not call the source or provider.
415    /// `field`/`at` locate the caller's enclosing original input structure.
416    pub fn digest_related<'m, S: Source, W: Work>(
417        &'m self,
418        ordinal: usize,
419        source: &mut S,
420        execution: &W,
421        field: &'static str,
422        at: u64,
423    ) -> PassResult<Option<RelatedDigest<'m, 'a, P>>, S::Error, P::Error> {
424        let Some(existing) = self
425            .for_request(ordinal, execution)
426            .map_err(StreamError::Callback)?
427        else {
428            return Ok(None);
429        };
430        let identity = existing.identity();
431        let mut state =
432            started(self.provider.start(identity, execution)).map_err(StreamError::Callback)?;
433        stream::run(
434            source,
435            &mut RelatedSink {
436                provider: self.provider,
437                state: &mut state,
438            },
439            execution,
440            field,
441            at,
442        )?;
443        let output = self
444            .provider
445            .finish(state, execution)
446            .map_err(|error| StreamError::Callback(DigestError::Callback(error)))?;
447        if let Some(expected) = self.provider.width(identity) {
448            let actual = output.as_ref().len();
449            if actual != expected {
450                return Err(StreamError::Callback(DigestError::OutputWidth {
451                    expected,
452                    actual,
453                }));
454            }
455        }
456        Ok(Some(RelatedDigest {
457            provider: self.provider,
458            identity,
459            output,
460        }))
461    }
462
463    /// Number of unique finalized outputs.
464    #[must_use]
465    pub fn len(&self) -> usize {
466        self.outputs.len()
467    }
468    /// Whether no completed output is held.
469    #[must_use]
470    pub fn is_empty(&self) -> bool {
471        self.outputs.is_empty()
472    }
473
474    /// Number of original requests, including identities that shared a state.
475    #[must_use]
476    pub fn request_count(&self) -> usize {
477        self.mapping.len()
478    }
479
480    /// The already finalized output for an original request ordinal.
481    ///
482    /// This constant-work link avoids resolving an algorithm again or cloning
483    /// its identity/state merely to find a shared output. Out-of-range ordinals
484    /// mean absence only after current-operation admission.
485    pub fn for_request<W: Work>(
486        &self,
487        ordinal: usize,
488        execution: &W,
489    ) -> DigestResult<Option<&DigestOutput<'a, P>>, P::Error> {
490        execution.charge_work(1).map_err(|reason| {
491            DigestError::Processing(Error::resource(reason, self.field, self.at))
492        })?;
493        let Some(index) = self.mapping.get(ordinal) else {
494            return Ok(None);
495        };
496        // Preparation assigned every ordinal to a pending state; successful
497        // finalization produced exactly one output for every such state.
498        debug_assert!(*index < self.outputs.len());
499        Ok(Some(&self.outputs[*index]))
500    }
501
502    /// Looks up a full resolved identity in the current execution account.
503    ///
504    /// Storage remains on its original account. No state/output clone, new hash
505    /// or finalization is performed. A resource refusal cannot become absence.
506    pub fn find<W: Work>(
507        &self,
508        identity: &P::Identity,
509        execution: &W,
510    ) -> DigestResult<Option<&DigestOutput<'a, P>>, P::Error> {
511        let refusal =
512            |reason| DigestError::Processing(Error::resource(reason, self.field, self.at));
513        execution.charge_work(1).map_err(refusal)?;
514        let mut from = 0;
515        let mut to = self.outputs.len();
516        while from < to {
517            execution.charge_work(1).map_err(refusal)?;
518            let middle = from + (to - from) / 2;
519            let held = &self.outputs[middle];
520            match self
521                .provider
522                .compare(held.identity(), identity, execution)
523                .map_err(DigestError::Callback)?
524            {
525                Ordering::Equal => return Ok(Some(held)),
526                Ordering::Less => from = middle + 1,
527                Ordering::Greater => to = middle,
528            }
529        }
530        Ok(None)
531    }
532}