use super::{
ArchiveError, AsnDigest, AsyncObjects, Authorities, Checked, Content, Decision, DigestBatch,
DigestMemo, EvidenceRecord, Execution, Group, Historical, Instant, LeapTable, Membership,
OwnedVec, PassResult, PlanFailure, ProfileFailure, ProfileInput, RangeSource, ReadFailure,
Reader, SignatureProvider, StreamError, TokenExtension, VerificationError, Work,
};
use crate::{
manifest::Slot,
references::{Catalogue, DigestReading, Unchecked},
};
use core::convert::Infallible;
use structured_xades::{
binary::{self, OwnedBinaryXml},
certificate::DigestProvider,
digest,
xml::{Access, Xml},
};
#[derive(Debug)]
pub enum ManifestInputError<IE, XE, N> {
Input(IE),
Xml(XE),
Resource {
node: N,
cause: structured_asn1::memory::MemoryError,
},
MissingAlgorithm,
}
type ObjectError<X, S, PE> = ManifestInputError<
ReadFailure<
<S as RangeSource>::Error,
<S as RangeSource>::Document,
<S as RangeSource>::Generation,
>,
binary::Error<
<X as Xml>::Error,
digest::Error<Infallible, PE>,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
<X as Xml>::Id,
>;
#[derive(Debug)]
pub enum ManifestError<AE, CE, XE> {
Archive(AE),
Check(CE),
Xml(XE),
}
struct Objects<'q, 'p, 'c, 'v, 's, X: Xml, S: RangeSource, W: Work, P: AsnDigest<'p>> {
catalogue: &'c Catalogue<'v, 's, X::Id, X::Document, X::Generation, S>,
group: &'q Group<'v, 's, S>,
xml: &'q X,
answers: &'q mut OwnedVec<DigestReading, W>,
prepared: &'q [Option<Prepared<'p, P, X>>],
}
type Prepared<'p, P, X> =
digest::Prepared<'p, P, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
fn unchecked_answers<W: Work>(
count: usize,
work: &W,
) -> Result<OwnedVec<DigestReading, W>, structured_asn1::memory::MemoryError> {
let mut answers = work.vector();
answers.try_reserve_exact(count)?;
for _ in 0..count {
answers.try_push(DigestReading::Unchecked(Unchecked::NotExecuted))?;
}
Ok(answers)
}
impl<'p, X, S, P, W0> AsyncObjects<'p, P> for Objects<'_, 'p, '_, '_, '_, X, S, W0, P>
where
X: OwnedBinaryXml,
S: RangeSource,
W0: Work,
P: AsnDigest<'p> + DigestProvider<'p, X>,
{
type Error = ObjectError<X, S, P::Error>;
fn len(&self) -> usize {
self.group.members.len()
}
async fn run<W: Work>(
&mut self,
index: usize,
batch: DigestBatch<'p, P>,
work: &W,
) -> PassResult<DigestMemo<'p, P>, Self::Error, P::Error> {
let id = self.group.members[index];
let member = self.group.archive.original(id, work).map_err(|cause| {
StreamError::Source(ManifestInputError::Input(ReadFailure::Processing {
member: id,
cause,
}))
})?;
let mut source = member
.source(work)
.map_err(|cause| StreamError::Source(ManifestInputError::Input(cause)))?;
let memo = batch
.run_async(&mut source, work)
.await
.map_err(|cause| match cause {
StreamError::Source(cause) => StreamError::Source(ManifestInputError::Input(cause)),
StreamError::Callback(cause) => StreamError::Callback(cause),
StreamError::Processing(cause) => StreamError::Processing(cause),
})?;
let object = self.catalogue.reading().objects()[index];
let answer = match (object.method, object.value) {
(Slot::One(_), Slot::One(_)) => {
let actual = memo
.for_request(0, work)
.map_err(StreamError::Callback)?
.ok_or(StreamError::Source(ManifestInputError::MissingAlgorithm))?;
let prepared = self.prepared[index]
.as_ref()
.ok_or(StreamError::Source(ManifestInputError::MissingAlgorithm))?;
DigestReading::Checked(
prepared
.check_finished(self.xml, actual.finished(), work)
.map_err(|cause| StreamError::Source(ManifestInputError::Xml(cause)))?,
)
}
(Slot::One(_), _) => DigestReading::Unchecked(Unchecked::Value),
_ => DigestReading::Unchecked(Unchecked::Method),
};
work.charge_work(1).map_err(|cause| {
StreamError::Source(ManifestInputError::Resource {
node: object.node,
cause,
})
})?;
self.answers[index] = answer;
Ok(memo)
}
}
pub struct ManifestChecked<'a, 'c, 'v, 's, N, D, G, S: RangeSource> {
catalogue: &'c Catalogue<'v, 's, N, D, G, S>,
checked: Checked<'a, 'a, 'v, 's, S>,
answers: OwnedVec<DigestReading>,
}
impl<'a, 'c, 'v, 's, N: Copy, D: Copy + Eq, G: Copy + Eq, S: RangeSource>
ManifestChecked<'a, 'c, 'v, 's, N, D, G, S>
{
#[must_use]
pub fn catalogue(&self) -> &'c Catalogue<'v, 's, N, D, G, S> {
self.catalogue
}
#[must_use]
pub fn evidence(&self) -> &Checked<'a, 'a, 'v, 's, S> {
&self.checked
}
#[must_use]
pub fn object_digests(&self) -> &[DigestReading] {
&self.answers
}
}
impl<'v, 's, S: RangeSource> Content<'v, 's, S> {
#[expect(
clippy::too_many_arguments,
reason = "the existing common ERS provider, authority, UTC and scoped profile/history inputs remain explicit"
)]
#[expect(
clippy::result_large_err,
reason = "retain the original XML and ERS source-map failures without requiring allocation to report refusal"
)]
pub async fn check_manifest_evidence<'a, 'c, X, P, PE, A, RE, HE, FE>(
&'a self,
record: &'a EvidenceRecord<'a>,
parent: &Reader<'a>,
provider: &'a P,
authorities: &'a A,
utc: &LeapTable<'a>,
reference: Instant,
catalogue: &'c Catalogue<'v, 's, X::Id, X::Document, X::Generation, S>,
xml: &X,
recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
historical: impl FnMut(Historical<'_, 'a>, &Execution<'_>) -> Result<Decision, HE>,
profile: impl for<'view, 'scope> FnMut(
usize,
ProfileInput<'view, 'a, 'a, 'scope>,
) -> Result<(), ProfileFailure<FE>>,
) -> Result<
Option<ManifestChecked<'a, 'c, 'v, 's, X::Id, X::Document, X::Generation, S>>,
ManifestError<
ArchiveError<S::Error>,
VerificationError<ObjectError<X, S, PE>, PE, RE, A::Error, HE, FE>,
structured_xades::xml::AccessError<X::Error, X::Id>,
>,
>
where
X: OwnedBinaryXml,
P: for<'scope> SignatureProvider<'scope, Error = PE>
+ for<'scope> DigestProvider<'scope, X>,
A: Authorities<'a>,
{
let work = parent.execution();
let resource =
|cause| ManifestError::Archive(crate::error::resource(self.enclosure(), cause));
let plan = self
.evidence_plan(record, parent, catalogue.base().archive())
.map_err(|cause| match cause {
PlanFailure::Archive(cause) => ManifestError::Archive(cause),
PlanFailure::Plan(cause) => ManifestError::Check(VerificationError::Plan(cause)),
})?;
let access = Access::new(xml, catalogue.reading().snapshot(), &work);
let root = catalogue.reading().root();
access.inspect(root, |_| ()).map_err(ManifestError::Xml)?;
let Some(selected) = catalogue
.signature_member(&work)
.map_err(ManifestError::Archive)?
else {
return Ok(None);
};
if selected.id() != self.member().id() {
return Err(ManifestError::Archive(ArchiveError::Foreign {
range: self.enclosure(),
}));
}
let Some(group) = catalogue.group(&work).map_err(|cause| {
ManifestError::Check(VerificationError::Acquisition(StreamError::Source(
ManifestInputError::Input(cause),
)))
})?
else {
return Ok(None);
};
let mut answers = unchecked_answers(catalogue.objects().len(), &work).map_err(resource)?;
let mut prepared = work.vector();
prepared
.try_reserve_exact(catalogue.objects().len())
.map_err(resource)?;
for object in catalogue.reading().objects() {
let value = match (object.method, object.value) {
(Slot::One(method), Slot::One(value)) => Some(
digest::prepare_xml(
xml,
digest::Reference {
snapshot: catalogue.reading().snapshot(),
method,
value,
},
provider,
&work,
)
.map_err(|cause| {
ManifestError::Check(VerificationError::Acquisition(StreamError::Source(
ManifestInputError::Xml(cause),
)))
})?,
),
_ => None,
};
prepared.try_push(value).map_err(resource)?;
}
let objects = Objects {
catalogue,
group: &group,
xml,
answers: &mut answers,
prepared: &prepared,
};
let report = plan
.verify_async_with_profile(
provider,
parent,
authorities,
utc,
reference,
objects,
Membership::Group,
recognizes,
historical,
profile,
)
.await
.map_err(ManifestError::Check)?;
self.member()
.archive()
.admit(&work, self.enclosure())
.map_err(ManifestError::Archive)?;
access.inspect(root, |_| ()).map_err(ManifestError::Xml)?;
Ok(Some(ManifestChecked {
catalogue,
checked: Checked {
evidence: self.member().id(),
objects: group,
report,
},
answers: answers.into_owned(),
}))
}
}