1use crate::{
4 content::Content,
5 error::Error as ArchiveError,
6 zip::{Archive, MemberId, ReadFailure},
7};
8use structured_asn1::{
9 Reader,
10 memory::{Execution, OwnedVec, Work},
11 range::RangeSource,
12};
13use structured_cms::{
14 digest::{DigestBatch, DigestMemo, PassResult},
15 evidence::{
16 AsyncObjects, EvidenceRecord, Membership,
17 verify::{Authorities, Decision, Historical, Report, VerificationError},
18 },
19 provider::SignatureProvider,
20 stream::StreamError,
21 timestamp::{ProfileFailure, ProfileInput, TokenExtension},
22};
23use structured_pkix::{
24 algorithm::digest::AsnDigest,
25 temporal::{Instant, LeapTable},
26};
27
28mod manifest;
29pub use manifest::{ManifestChecked, ManifestError, ManifestInputError};
30
31enum PlanFailure<E> {
32 Archive(ArchiveError<E>),
33 Plan(structured_cms::evidence::verify::PlanError),
34}
35
36pub struct Group<'v, 's, S: RangeSource> {
39 archive: &'v Archive<'s, S>,
40 members: OwnedVec<MemberId<S::Document, S::Generation>>,
41}
42impl<'v, 's, S: RangeSource> Group<'v, 's, S> {
43 pub fn new<W: Work>(
46 archive: &'v Archive<'s, S>,
47 members: OwnedVec<MemberId<S::Document, S::Generation>>,
48 work: &W,
49 ) -> crate::zip::Acquisition<Self, S> {
50 for id in &members {
51 let member = archive
52 .original(*id, work)
53 .map_err(|cause| ReadFailure::Processing { member: *id, cause })?;
54 member.source(work)?;
55 }
56 Ok(Self { archive, members })
57 }
58 #[must_use]
60 pub fn members(&self) -> &[MemberId<S::Document, S::Generation>] {
61 &self.members
62 }
63}
64
65struct Objects<'g, 'v, 's, S: RangeSource>(&'g Group<'v, 's, S>);
66impl<'p, P: AsnDigest<'p>, S: RangeSource> AsyncObjects<'p, P> for Objects<'_, '_, '_, S> {
67 type Error = ReadFailure<S::Error, S::Document, S::Generation>;
68 fn len(&self) -> usize {
69 self.0.members.len()
70 }
71 async fn run<W: Work>(
72 &mut self,
73 index: usize,
74 batch: DigestBatch<'p, P>,
75 work: &W,
76 ) -> PassResult<DigestMemo<'p, P>, Self::Error, P::Error> {
77 let id = self.0.members[index];
78 let member =
79 self.0.archive.original(id, work).map_err(|cause| {
80 StreamError::Source(ReadFailure::Processing { member: id, cause })
81 })?;
82 let mut source = member.source(work).map_err(StreamError::Source)?;
83 batch.run_async(&mut source, work).await
84 }
85}
86
87pub struct Checked<'a, 'd, 'v, 's, S: RangeSource> {
90 evidence: MemberId<S::Document, S::Generation>,
91 objects: Group<'v, 's, S>,
92 report: Report<'a, 'd>,
93}
94impl<'a, 'd, 'v, 's, S: RangeSource> Checked<'a, 'd, 'v, 's, S> {
95 #[must_use]
97 pub fn evidence(&self) -> MemberId<S::Document, S::Generation> {
98 self.evidence
99 }
100 #[must_use]
102 pub fn objects(&self) -> &Group<'v, 's, S> {
103 &self.objects
104 }
105 #[must_use]
107 pub fn report(&self) -> &Report<'a, 'd> {
108 &self.report
109 }
110}
111
112#[derive(Debug)]
114pub enum Error<SE, PE, RE, AE, HE, FE, D, G> {
115 Archive(ArchiveError<SE>),
117 Check(VerificationError<ReadFailure<SE, D, G>, PE, RE, AE, HE, FE>),
119}
120
121impl<'v, 's, S: RangeSource> Content<'v, 's, S> {
122 #[expect(
123 clippy::result_large_err,
124 reason = "retained source errors must survive allocation refusal without allocating an error box"
125 )]
126 fn evidence_plan<'a>(
127 &'a self,
128 record: &'a EvidenceRecord<'a>,
129 parent: &Reader<'a>,
130 archive: &Archive<'_, S>,
131 ) -> Result<structured_cms::evidence::verify::InputPlan<'a, 'a>, PlanFailure<S::Error>> {
132 let work = parent.execution();
133 let original = record.original().element();
134 self.require_original(original.as_bytes(), original.span().start, parent, &work)
135 .map_err(PlanFailure::Archive)?;
136 if !core::ptr::eq(self.member().archive(), archive)
137 || original.as_bytes().len() != self.bytes().len()
138 || original.span().start != 0
139 {
140 return Err(PlanFailure::Archive(ArchiveError::Foreign {
141 range: self.enclosure(),
142 }));
143 }
144 record.input_plan(parent).map_err(PlanFailure::Plan)
145 }
146 #[expect(
150 clippy::too_many_arguments,
151 reason = "source-bound ERS keeps common provider, selected authorities, UTC and three scoped callbacks explicit"
152 )]
153 #[expect(
154 clippy::result_large_err,
155 reason = "preserve complete original diagnostic maps without allocating on a resource failure"
156 )]
157 pub async fn check_evidence<'a, P, PE, A, RE, HE, FE>(
158 &'a self,
159 record: &'a EvidenceRecord<'a>,
160 parent: &Reader<'a>,
161 provider: &'a P,
162 authorities: &'a A,
163 utc: &LeapTable<'a>,
164 reference: Instant,
165 objects: Group<'v, 's, S>,
166 membership: Membership,
167 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
168 historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
169 profile: impl for<'view, 'scope> FnMut(
170 usize,
171 ProfileInput<'view, 'a, 'a, 'scope>,
172 ) -> Result<(), ProfileFailure<FE>>,
173 ) -> Result<
174 Checked<'a, 'a, 'v, 's, S>,
175 Error<S::Error, PE, RE, A::Error, HE, FE, S::Document, S::Generation>,
176 >
177 where
178 P: for<'scope> SignatureProvider<'scope, Error = PE>,
179 A: Authorities<'a>,
180 {
181 let work = parent.execution();
182 let plan = self
183 .evidence_plan(record, parent, objects.archive)
184 .map_err(|cause| match cause {
185 PlanFailure::Archive(cause) => Error::Archive(cause),
186 PlanFailure::Plan(cause) => Error::Check(VerificationError::Plan(cause)),
187 })?;
188 let report = plan
189 .verify_async_with_profile(
190 provider,
191 parent,
192 authorities,
193 utc,
194 reference,
195 Objects(&objects),
196 membership,
197 recognizes,
198 historical,
199 profile,
200 )
201 .await
202 .map_err(Error::Check)?;
203 self.member()
204 .archive()
205 .admit(&work, self.enclosure())
206 .map_err(Error::Archive)?;
207 Ok(Checked {
208 evidence: self.member().id(),
209 objects,
210 report,
211 })
212 }
213}