Skip to main content

structured_xades/digest/
batch.rs

1//! Shared native digest fan-out over already prepared XML declarations.
2
3use super::{
4    Access, Agreement, Digest, Error, Location, OwnedVec, Prepared, PreparedValue, Reference,
5    StreamError, Work, Xml, binary, compare_resolved_finished, stream,
6};
7use structured_cms::digest::{DigestBatch, DigestError};
8
9enum Value {
10    Answer(Agreement),
11    Request {
12        ordinal: usize,
13        expected: OwnedVec<u8>,
14    },
15}
16struct Declared<N, D, G> {
17    reference: Reference<N, D, G>,
18    value: Value,
19}
20
21/// One source pass for multiple original XML declarations. Complete native
22/// identities move into the existing common batch, which sorts/deduplicates
23/// them semantically. No identity/state/output clone or second dispatcher.
24pub struct Batch<'p, P: Digest<'p>, N, D, G> {
25    provider: &'p P,
26    declarations: OwnedVec<Declared<N, D, G>>,
27    native: Option<DigestBatch<'p, P>>,
28}
29
30fn map_error<SE, PE>(cause: DigestError<PE>) -> Error<SE, PE> {
31    match cause {
32        DigestError::Processing(cause) => Error::Stream(cause),
33        DigestError::Callback(cause) => Error::Provider(cause),
34        DigestError::OutputWidth { expected, actual } => Error::OutputWidth { expected, actual },
35        DigestError::Unstreamable => Error::Unstreamable,
36    }
37}
38
39impl<'p, P: Digest<'p>, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq> Batch<'p, P, N, D, G> {
40    /// Consumes declarations whose original XML syntax, widths and provider
41    /// policy have ALREADY been checked. Do not prepare a subset and start it
42    /// before validating other declarations in the same operation.
43    pub fn prepare<W: Work>(
44        provider: &'p P,
45        prepared: OwnedVec<Prepared<'p, P, N, D, G>>,
46        work: &W,
47    ) -> Result<Self, Error<core::convert::Infallible, P::Error>> {
48        let mut identities = work.vector();
49        let mut declarations = work.vector();
50        identities
51            .try_reserve_exact(prepared.len())
52            .map_err(Error::Resource)?;
53        declarations
54            .try_reserve_exact(prepared.len())
55            .map_err(Error::Resource)?;
56        work.charge_work(core::mem::size_of_val(&*prepared) as u64)
57            .map_err(Error::Resource)?;
58        for prepared in prepared {
59            if !core::ptr::eq(prepared.provider, provider) {
60                return Err(Error::ForeignDigest);
61            }
62            let value = match prepared.value {
63                PreparedValue::Answer(answer) => Value::Answer(answer),
64                PreparedValue::Ready { identity, expected } => {
65                    let ordinal = identities.len();
66                    identities.try_push(identity).map_err(Error::Resource)?;
67                    Value::Request { ordinal, expected }
68                }
69            };
70            declarations
71                .try_push(Declared {
72                    reference: prepared.reference,
73                    value,
74                })
75                .map_err(Error::Resource)?;
76        }
77        let batch = if identities.is_empty() {
78            None
79        } else {
80            Some(
81                DigestBatch::prepare(
82                    provider,
83                    identities.into_owned(),
84                    work,
85                    "xml.digest.batch",
86                    0,
87                )
88                .map_err(map_error)?,
89            )
90        };
91        Ok(Self {
92            provider,
93            declarations: declarations.into_owned(),
94            native: batch,
95        })
96    }
97
98    /// Checks original generations before source I/O and after await. Only the
99    /// exact private request mapping supplies each actual output/identity. All
100    /// declarations survive, including unsupported/malformed readings; a failed
101    /// source or callback returns no successful partial answer collection.
102    ///
103    /// # Panics
104    /// An internal preparation invariant fails if a ready declaration lacks
105    /// its original native batch or private request-to-output mapping.
106    pub async fn run<
107        X: Xml<Id = N, Document = D, Generation = G>,
108        S: stream::AsyncSource,
109        W: Work,
110    >(
111        self,
112        xml: &X,
113        source: &mut S,
114        work: &W,
115    ) -> binary::BinaryResult<OwnedVec<Agreement>, X, Error<S::Error, P::Error>> {
116        let mut answers = work.vector();
117        for declared in &self.declarations {
118            let location = Location {
119                snapshot: declared.reference.snapshot,
120                node: declared.reference.value,
121            };
122            Access::new(xml, location.snapshot, work)
123                .inspect(location.node, |_| ())
124                .map_err(|cause| binary::Error::Access { location, cause })?;
125        }
126        let Some(first) = self.declarations.first() else {
127            return Ok(answers.into_owned());
128        };
129        let location = Location {
130            snapshot: first.reference.snapshot,
131            node: first.reference.value,
132        };
133        answers
134            .try_reserve_exact(self.declarations.len())
135            .map_err(|cause| binary::Error::Callback {
136                location,
137                cause: Error::Resource(cause),
138            })?;
139        let memo = match self.native {
140            Some(batch) => Some(batch.run_async(source, work).await.map_err(|cause| {
141                binary::Error::Callback {
142                    location,
143                    cause: match cause {
144                        StreamError::Source(cause) => Error::Source(cause),
145                        StreamError::Callback(cause) => map_error(cause),
146                        StreamError::Processing(cause) => Error::Stream(cause),
147                    },
148                }
149            })?),
150            None => None,
151        };
152        for declared in self.declarations {
153            let access = Access::new(xml, declared.reference.snapshot, work);
154            let location = Location {
155                snapshot: declared.reference.snapshot,
156                node: declared.reference.value,
157            };
158            access
159                .inspect(location.node, |_| ())
160                .map_err(|cause| binary::Error::Access { location, cause })?;
161            let answer = match declared.value {
162                Value::Answer(answer) => answer,
163                Value::Request { ordinal, expected } => {
164                    let actual = memo
165                        .as_ref()
166                        .expect("ready declarations initialize their private batch")
167                        .for_request(ordinal, work)
168                        .map_err(|cause| binary::Error::Callback {
169                            location,
170                            cause: map_error(cause),
171                        })?
172                        .expect("each ready declaration has the original private request mapping");
173                    // Its identity was MOVED from this exact prepared declaration
174                    // into this mapping. No separately supplied identity or width
175                    // can manufacture this pairing, including equal-width hashes.
176                    compare_resolved_finished(
177                        &access,
178                        declared.reference,
179                        self.provider,
180                        actual.identity(),
181                        actual.finished(),
182                        &expected,
183                    )
184                    .map_err(|cause| {
185                        cause.map_callback(|cause| cause.map_source(|never| match never {}))
186                    })?
187                }
188            };
189            answers
190                .try_push(answer)
191                .map_err(|cause| binary::Error::Callback {
192                    location,
193                    cause: Error::Resource(cause),
194                })?;
195        }
196        Ok(answers.into_owned())
197    }
198}