Skip to main content

structured_asic/references/
checks.rs

1//! Complete original reference results with one native fan-out per member.
2
3use super::{
4    Access, AccessError, Agreement, ArchiveError, Catalogue, CheckError, DigestProvider,
5    DigestReading, OwnedBinaryXml, RangeSource, Unchecked, VisitError, Work, binary, digest,
6    resource,
7};
8use structured_asn1::memory::OwnedVec;
9
10/// Every original object declaration, bound to this exact instruction catalogue.
11/// This checks hashes, not ZIP-to-XML production or manifest authentication.
12pub struct Checks<'c, 'v, 's, N, D, G, S: RangeSource> {
13    catalogue: &'c Catalogue<'v, 's, N, D, G, S>,
14    answers: OwnedVec<DigestReading>,
15}
16impl<'c, 'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource>
17    Checks<'c, 'v, 's, N, D, G, S>
18{
19    /// Exact original instruction set, including negative URI/schema outcomes.
20    #[must_use]
21    pub fn catalogue(&self) -> &'c Catalogue<'v, 's, N, D, G, S> {
22        self.catalogue
23    }
24    /// One result for EVERY original `DataObjectReference`, in original order.
25    #[must_use]
26    pub fn answers(&self) -> &[DigestReading] {
27        &self.answers
28    }
29    pub(crate) fn into_answers(self) -> OwnedVec<DigestReading> {
30        self.answers
31    }
32    /// Paid completed digest predicate only, never container/profile/trust.
33    pub fn all_match<W: Work>(&self, work: &W) -> Result<bool, ArchiveError<S::Error>> {
34        let execution = self.catalogue.base().session().query(work);
35        self.catalogue
36            .base()
37            .archive()
38            .admit(&execution, self.catalogue.base().central_span())?;
39        if self.answers.is_empty() {
40            return Ok(false);
41        }
42        for answer in &self.answers {
43            execution
44                .charge_work(1)
45                .map_err(|cause| resource(self.catalogue.base().central_span(), cause))?;
46            if *answer != DigestReading::Checked(Agreement::Matches) {
47                return Ok(false);
48            }
49        }
50        Ok(true)
51    }
52}
53
54struct Group<D, G, W: Work> {
55    member: crate::zip::MemberId<D, G>,
56    references: OwnedVec<usize, W>,
57}
58
59impl<'v, 's, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, S: RangeSource>
60    Catalogue<'v, 's, N, D, G, S>
61{
62    /// Checks all declarations without repeating URI lookup. Every method,
63    /// declared width, source/decoder and policy is preflighted before ANY hash
64    /// state. Aliases/repeated references to one original member share a single
65    /// source pass and the existing semantic native digest fan-out.
66    ///
67    /// # Panics
68    /// An internal grouping invariant fails if the same prepared original
69    /// reference is consumed by more than one source group.
70    #[expect(
71        clippy::too_many_lines,
72        reason = "all original methods and sources must finish preflight before any digest initialization; the two execution phases share the same funded plan"
73    )]
74    pub async fn check_objects<'p, X, P, W: Work>(
75        &self,
76        xml: &X,
77        provider: &'p P,
78        work: &W,
79    ) -> Result<
80        Checks<'_, 'v, 's, N, D, G, S>,
81        CheckError<X::Error, P::Error, N, D, G, S::Error, S::Document, S::Generation>,
82    >
83    where
84        X: OwnedBinaryXml<Id = N, Document = D, Generation = G>,
85        P: DigestProvider<'p, X>,
86    {
87        let execution = self.base().session().query(work);
88        let at = self.base().central_span();
89        let archive_error =
90            |cause| CheckError::Reference(AccessError::Xml(VisitError::Visitor(cause)));
91        let refusal = |cause| archive_error(resource(at, cause));
92        self.base()
93            .archive()
94            .admit(&execution, at)
95            .map_err(archive_error)?;
96        let access = Access::new(xml, self.reading().snapshot(), &execution);
97        let fence = || {
98            access
99                .inspect(self.reading().root(), |_| ())
100                .map_err(|cause| {
101                    CheckError::Reference(match cause {
102                        AccessError::Xml(cause) => AccessError::Xml(VisitError::Processing(cause)),
103                        AccessError::Changed { node } => AccessError::Changed { node },
104                        AccessError::Resource { node, cause } => {
105                            AccessError::Resource { node, cause }
106                        }
107                    })
108                })
109        };
110        fence()?;
111        let mut answers = execution.vector();
112        let mut prepared = execution.vector();
113        answers
114            .try_reserve_exact(self.objects().len())
115            .map_err(refusal)?;
116        prepared
117            .try_reserve_exact(self.objects().len())
118            .map_err(refusal)?;
119        let mut groups = execution.vector();
120        let mut by_member = execution.map();
121        for (index, object) in self.reading().objects().iter().enumerate() {
122            let instruction = match (object.method, object.value) {
123                (crate::manifest::Slot::One(method), crate::manifest::Slot::One(value)) => Some(
124                    digest::prepare_xml(
125                        xml,
126                        digest::Reference {
127                            snapshot: self.reading().snapshot(),
128                            method,
129                            value,
130                        },
131                        provider,
132                        &execution,
133                    )
134                    .map_err(|cause| {
135                        CheckError::Digest(
136                            cause.map_callback(|cause| cause.map_source(|never| match never {})),
137                        )
138                    })?,
139                ),
140                _ => None,
141            };
142            let mut answer = match (object.method, object.value) {
143                (crate::manifest::Slot::One(_), crate::manifest::Slot::One(_)) => {
144                    DigestReading::Unchecked(Unchecked::Input)
145                }
146                (crate::manifest::Slot::One(_), _) => DigestReading::Unchecked(Unchecked::Value),
147                _ => DigestReading::Unchecked(Unchecked::Method),
148            };
149            if instruction.is_some()
150                && let Some(member) = self
151                    .object_member(index, &execution)
152                    .map_err(archive_error)?
153            {
154                member.source(&execution).map_err(CheckError::Input)?;
155                let group = if let Some(index) = by_member
156                    .get(&member.id().ordinal(), &execution)
157                    .map_err(refusal)?
158                {
159                    *index
160                } else {
161                    let index = groups.len();
162                    groups
163                        .try_push(Group {
164                            member: member.id(),
165                            references: execution.vector(),
166                        })
167                        .map_err(refusal)?;
168                    by_member
169                        .try_insert(member.id().ordinal(), index)
170                        .map_err(refusal)?;
171                    index
172                };
173                groups[group].references.try_push(index).map_err(refusal)?;
174                answer = DigestReading::Unchecked(Unchecked::NotExecuted);
175            }
176            prepared.try_push(instruction).map_err(refusal)?;
177            answers.try_push(answer).map_err(refusal)?;
178        }
179        fence()?;
180        self.base()
181            .archive()
182            .admit(&execution, at)
183            .map_err(archive_error)?;
184        for group in groups {
185            let mut instructions = execution.vector();
186            instructions
187                .try_reserve_exact(group.references.len())
188                .map_err(refusal)?;
189            for index in &group.references {
190                execution.charge_work(1).map_err(refusal)?;
191                instructions
192                    .try_push(
193                        prepared[*index]
194                            .take()
195                            .expect("each original reference belongs to exactly one source group"),
196                    )
197                    .map_err(refusal)?;
198            }
199            let member = self
200                .base()
201                .archive()
202                .original(group.member, &execution)
203                .map_err(archive_error)?;
204            let mut source = member.source(&execution).map_err(CheckError::Input)?;
205            let location = binary::Location {
206                snapshot: self.reading().snapshot(),
207                node: self.reading().objects()[group.references[0]].node,
208            };
209            let batch = digest::Batch::prepare(provider, instructions.into_owned(), &execution)
210                .map_err(|cause| {
211                    CheckError::Digest(binary::Error::Callback {
212                        location,
213                        cause: cause.map_source(|never| match never {}),
214                    })
215                })?;
216            let result = batch
217                .run(xml, &mut source, &execution)
218                .await
219                .map_err(CheckError::Digest)?;
220            for (index, answer) in group.references.iter().zip(&*result) {
221                execution.charge_work(1).map_err(refusal)?;
222                answers[*index] = DigestReading::Checked(*answer);
223            }
224        }
225        self.base()
226            .archive()
227            .admit(&execution, at)
228            .map_err(archive_error)?;
229        fence()?;
230        Ok(Checks {
231            catalogue: self,
232            answers: answers.into_owned(),
233        })
234    }
235}