Skip to main content

structured_asic/evidence/
manifest.rs

1//! Manifest object digests reuse the actual ERS all-algorithm object pass.
2
3use super::{
4    ArchiveError, AsnDigest, AsyncObjects, Authorities, Checked, Content, Decision, DigestBatch,
5    DigestMemo, EvidenceRecord, Execution, Group, Historical, Instant, LeapTable, Membership,
6    OwnedVec, PassResult, PlanFailure, ProfileFailure, ProfileInput, RangeSource, ReadFailure,
7    Reader, SignatureProvider, StreamError, TokenExtension, VerificationError, Work,
8};
9use crate::{
10    manifest::Slot,
11    references::{Catalogue, DigestReading, Unchecked},
12};
13use core::convert::Infallible;
14use structured_xades::{
15    binary::{self, OwnedBinaryXml},
16    certificate::DigestProvider,
17    digest,
18    xml::{Access, Xml},
19};
20
21/// Actual member input, XML reference or local result-storage refusal.
22#[derive(Debug)]
23pub enum ManifestInputError<IE, XE, N> {
24    /// Actual ZIP acquisition, extent/CRC, source or producing query failed.
25    Input(IE),
26    /// Original XML method/value, native identity or provider comparison failed.
27    Xml(XE),
28    /// Current/source metadata storage admission failed before mutation.
29    Resource {
30        /// Original owning `DataObjectReference` node.
31        node: N,
32        /// Unchanged refusal.
33        cause: structured_asn1::memory::MemoryError,
34    },
35    /// The closed ERS plan did not supply its required initial-chain digest.
36    MissingAlgorithm,
37}
38
39type ObjectError<X, S, PE> = ManifestInputError<
40    ReadFailure<
41        <S as RangeSource>::Error,
42        <S as RangeSource>::Document,
43        <S as RangeSource>::Generation,
44    >,
45    binary::Error<
46        <X as Xml>::Error,
47        digest::Error<Infallible, PE>,
48        <X as Xml>::Id,
49        <X as Xml>::Document,
50        <X as Xml>::Generation,
51    >,
52    <X as Xml>::Id,
53>;
54
55/// Exact ERS/provider/authority/history/profile refusal with XML object causes.
56#[derive(Debug)]
57pub enum ManifestError<AE, CE, XE> {
58    /// Original archive/source or caller association is not this operation's.
59    Archive(AE),
60    /// Actual common plan, object, callback or verification refusal.
61    Check(CE),
62    /// Original XML generation, owner or current-query admission refused.
63    Xml(XE),
64}
65
66struct Objects<'q, 'p, 'c, 'v, 's, X: Xml, S: RangeSource, W: Work, P: AsnDigest<'p>> {
67    catalogue: &'c Catalogue<'v, 's, X::Id, X::Document, X::Generation, S>,
68    group: &'q Group<'v, 's, S>,
69    xml: &'q X,
70    answers: &'q mut OwnedVec<DigestReading, W>,
71    prepared: &'q [Option<Prepared<'p, P, X>>],
72}
73type Prepared<'p, P, X> =
74    digest::Prepared<'p, P, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
75
76fn unchecked_answers<W: Work>(
77    count: usize,
78    work: &W,
79) -> Result<OwnedVec<DigestReading, W>, structured_asn1::memory::MemoryError> {
80    let mut answers = work.vector();
81    answers.try_reserve_exact(count)?;
82    for _ in 0..count {
83        answers.try_push(DigestReading::Unchecked(Unchecked::NotExecuted))?;
84    }
85    Ok(answers)
86}
87
88impl<'p, X, S, P, W0> AsyncObjects<'p, P> for Objects<'_, 'p, '_, '_, '_, X, S, W0, P>
89where
90    X: OwnedBinaryXml,
91    S: RangeSource,
92    W0: Work,
93    P: AsnDigest<'p> + DigestProvider<'p, X>,
94{
95    type Error = ObjectError<X, S, P::Error>;
96    fn len(&self) -> usize {
97        self.group.members.len()
98    }
99    async fn run<W: Work>(
100        &mut self,
101        index: usize,
102        batch: DigestBatch<'p, P>,
103        work: &W,
104    ) -> PassResult<DigestMemo<'p, P>, Self::Error, P::Error> {
105        let id = self.group.members[index];
106        let member = self.group.archive.original(id, work).map_err(|cause| {
107            StreamError::Source(ManifestInputError::Input(ReadFailure::Processing {
108                member: id,
109                cause,
110            }))
111        })?;
112        let mut source = member
113            .source(work)
114            .map_err(|cause| StreamError::Source(ManifestInputError::Input(cause)))?;
115        let memo = batch
116            .run_async(&mut source, work)
117            .await
118            .map_err(|cause| match cause {
119                StreamError::Source(cause) => StreamError::Source(ManifestInputError::Input(cause)),
120                StreamError::Callback(cause) => StreamError::Callback(cause),
121                StreamError::Processing(cause) => StreamError::Processing(cause),
122            })?;
123        let object = self.catalogue.reading().objects()[index];
124        let answer = match (object.method, object.value) {
125            (Slot::One(_), Slot::One(_)) => {
126                // InputPlan retains ORIGINAL chain request order. Request0 is
127                // the initial tree algorithm even when native states deduplicate.
128                let actual = memo
129                    .for_request(0, work)
130                    .map_err(StreamError::Callback)?
131                    .ok_or(StreamError::Source(ManifestInputError::MissingAlgorithm))?;
132                let prepared = self.prepared[index]
133                    .as_ref()
134                    .ok_or(StreamError::Source(ManifestInputError::MissingAlgorithm))?;
135                DigestReading::Checked(
136                    prepared
137                        .check_finished(self.xml, actual.finished(), work)
138                        .map_err(|cause| StreamError::Source(ManifestInputError::Xml(cause)))?,
139                )
140            }
141            (Slot::One(_), _) => DigestReading::Unchecked(Unchecked::Value),
142            _ => DigestReading::Unchecked(Unchecked::Method),
143        };
144        work.charge_work(1).map_err(|cause| {
145            StreamError::Source(ManifestInputError::Resource {
146                node: object.node,
147                cause,
148            })
149        })?;
150        self.answers[index] = answer;
151        Ok(memo)
152    }
153}
154
155/// Actual ERS report and every declared object digest from the SAME pass.
156/// This alone does not attest XML production, schema/critical-extension
157/// acceptance, application trust or container-level preservation continuity.
158pub struct ManifestChecked<'a, 'c, 'v, 's, N, D, G, S: RangeSource> {
159    catalogue: &'c Catalogue<'v, 's, N, D, G, S>,
160    checked: Checked<'a, 'a, 'v, 's, S>,
161    answers: OwnedVec<DigestReading>,
162}
163impl<'a, 'c, 'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource>
164    ManifestChecked<'a, 'c, 'v, 's, N, D, G, S>
165{
166    /// Exact source/manifest instruction catalogue used by this operation.
167    #[must_use]
168    pub fn catalogue(&self) -> &'c Catalogue<'v, 's, N, D, G, S> {
169        self.catalogue
170    }
171    /// Every original token/history/renewal answer from common ERS verification.
172    #[must_use]
173    pub fn evidence(&self) -> &Checked<'a, 'a, 'v, 's, S> {
174        &self.checked
175    }
176    /// Every original object result, including not-executed or malformed fields.
177    #[must_use]
178    pub fn object_digests(&self) -> &[DigestReading] {
179        &self.answers
180    }
181}
182
183impl<'v, 's, S: RangeSource> Content<'v, 's, S> {
184    /// Verifies the exact referenced member group and its declared XML hashes
185    /// through one all-chain digest pass per original object. A negative URI
186    /// group returns None with its full inspection catalogue left available;
187    /// it never becomes a successful subset. An unrelated record is caller
188    /// misuse. Original XML source provenance is a separate producing boundary.
189    #[expect(
190        clippy::too_many_arguments,
191        reason = "the existing common ERS provider, authority, UTC and scoped profile/history inputs remain explicit"
192    )]
193    #[expect(
194        clippy::result_large_err,
195        reason = "retain the original XML and ERS source-map failures without requiring allocation to report refusal"
196    )]
197    pub async fn check_manifest_evidence<'a, 'c, X, P, PE, A, RE, HE, FE>(
198        &'a self,
199        record: &'a EvidenceRecord<'a>,
200        parent: &Reader<'a>,
201        provider: &'a P,
202        authorities: &'a A,
203        utc: &LeapTable<'a>,
204        reference: Instant,
205        catalogue: &'c Catalogue<'v, 's, X::Id, X::Document, X::Generation, S>,
206        xml: &X,
207        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
208        historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
209        profile: impl for<'view, 'scope> FnMut(
210            usize,
211            ProfileInput<'view, 'a, 'a, 'scope>,
212        ) -> Result<(), ProfileFailure<FE>>,
213    ) -> Result<
214        Option<ManifestChecked<'a, 'c, 'v, 's, X::Id, X::Document, X::Generation, S>>,
215        ManifestError<
216            ArchiveError<S::Error>,
217            VerificationError<ObjectError<X, S, PE>, PE, RE, A::Error, HE, FE>,
218            structured_xades::xml::AccessError<X::Error, X::Id>,
219        >,
220    >
221    where
222        X: OwnedBinaryXml,
223        P: for<'scope> SignatureProvider<'scope, Error = PE>
224            + for<'scope> DigestProvider<'scope, X>,
225        A: Authorities<'a>,
226    {
227        let work = parent.execution();
228        let resource =
229            |cause| ManifestError::Archive(crate::error::resource(self.enclosure(), cause));
230        let plan = self
231            .evidence_plan(record, parent, catalogue.base().archive())
232            .map_err(|cause| match cause {
233                PlanFailure::Archive(cause) => ManifestError::Archive(cause),
234                PlanFailure::Plan(cause) => ManifestError::Check(VerificationError::Plan(cause)),
235            })?;
236        let access = Access::new(xml, catalogue.reading().snapshot(), &work);
237        let root = catalogue.reading().root();
238        access.inspect(root, |_| ()).map_err(ManifestError::Xml)?;
239        let Some(selected) = catalogue
240            .signature_member(&work)
241            .map_err(ManifestError::Archive)?
242        else {
243            return Ok(None);
244        };
245        if selected.id() != self.member().id() {
246            return Err(ManifestError::Archive(ArchiveError::Foreign {
247                range: self.enclosure(),
248            }));
249        }
250        let Some(group) = catalogue.group(&work).map_err(|cause| {
251            ManifestError::Check(VerificationError::Acquisition(StreamError::Source(
252                ManifestInputError::Input(cause),
253            )))
254        })?
255        else {
256            return Ok(None);
257        };
258        let mut answers = unchecked_answers(catalogue.objects().len(), &work).map_err(resource)?;
259        // XML syntax, selected-provider policy and declared widths precede ALL
260        // ERS state creation. The same prepared readings compare actual outputs
261        // later, without resolving, decoding or hashing an object a second time.
262        let mut prepared = work.vector();
263        prepared
264            .try_reserve_exact(catalogue.objects().len())
265            .map_err(resource)?;
266        for object in catalogue.reading().objects() {
267            let value = match (object.method, object.value) {
268                (Slot::One(method), Slot::One(value)) => Some(
269                    digest::prepare_xml(
270                        xml,
271                        digest::Reference {
272                            snapshot: catalogue.reading().snapshot(),
273                            method,
274                            value,
275                        },
276                        provider,
277                        &work,
278                    )
279                    .map_err(|cause| {
280                        ManifestError::Check(VerificationError::Acquisition(StreamError::Source(
281                            ManifestInputError::Xml(cause),
282                        )))
283                    })?,
284                ),
285                _ => None,
286            };
287            prepared.try_push(value).map_err(resource)?;
288        }
289        let objects = Objects {
290            catalogue,
291            group: &group,
292            xml,
293            answers: &mut answers,
294            prepared: &prepared,
295        };
296        let report = plan
297            .verify_async_with_profile(
298                provider,
299                parent,
300                authorities,
301                utc,
302                reference,
303                objects,
304                Membership::Group,
305                recognizes,
306                historical,
307                profile,
308            )
309            .await
310            .map_err(ManifestError::Check)?;
311        self.member()
312            .archive()
313            .admit(&work, self.enclosure())
314            .map_err(ManifestError::Archive)?;
315        access.inspect(root, |_| ()).map_err(ManifestError::Xml)?;
316        Ok(Some(ManifestChecked {
317            catalogue,
318            checked: Checked {
319                evidence: self.member().id(),
320                objects: group,
321                report,
322            },
323            answers: answers.into_owned(),
324        }))
325    }
326}