use super::{
Agreement, Catalogue, CertificateXml, CheckedCertificate, DigestError, DigestProvider,
Expected, References,
};
use crate::binary::{self, BinaryXml, Location};
use crate::xml::{Access, Xml};
use structured_asn1::memory::{OwnedVec, Work};
use structured_ltv::material::PathCertificate;
use structured_ltv::pki::{CheckedPath, SignerReceipt};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathDigest {
pub reference: usize,
pub agreement: Agreement,
pub certificate: Option<PathCertificate>,
}
pub struct PathDigests<'view, 'document, N, D, G> {
references: &'view References<N, D, G>,
path: &'view CheckedPath<'document>,
entries: OwnedVec<PathDigest>,
all_match: bool,
}
pub type PathDigestResult<'view, 'document, X, PE> = binary::BinaryResult<
PathDigests<'view, 'document, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
X,
DigestError<PE>,
>;
pub struct CheckedPathCertificate<'view, 'document, N, D, G> {
selected: CheckedCertificate<'view, 'document, N, D, G>,
digests: PathDigests<'view, 'document, N, D, G>,
}
impl<'view, 'document, N, D, G> CheckedPathCertificate<'view, 'document, N, D, G> {
#[must_use]
pub fn selected(&self) -> &CheckedCertificate<'view, 'document, N, D, G> {
&self.selected
}
#[must_use]
pub fn digests(&self) -> &PathDigests<'view, 'document, N, D, G> {
&self.digests
}
#[must_use]
pub fn is_bound(&self) -> bool {
self.selected.is_bound() && self.digests.all_match()
}
}
impl<'view, 'document, N: Copy, D: Copy, G: Copy> SignerReceipt<'view, 'document>
for CheckedPathCertificate<'view, 'document, N, D, G>
{
type Id = N;
type Document = D;
type Generation = G;
fn signature(&self) -> Location<N, D, G> {
let references = self.selected.reference();
Location {
snapshot: references.snapshot(),
node: references.signature(),
}
}
fn path(&self) -> Option<&'view CheckedPath<'document>> {
self.is_bound().then_some(self.digests.path)
}
}
pub type PathCheckResult<'view, 'document, X, PE> = super::CertificateResult<
CheckedPathCertificate<
'view,
'document,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>,
X,
PE,
>;
pub fn check_path<
'view,
'document,
'p,
X: BinaryXml + CertificateXml,
P: DigestProvider<'p, X>,
W: Work,
>(
xml: &X,
bound: &crate::binding::Bound<X::Id, X::Document, X::Generation>,
references: &'view Catalogue<X>,
path: &'view CheckedPath<'document>,
provider: &'p P,
execution: &W,
) -> PathCheckResult<'view, 'document, X, P::Error> {
let certificate = path
.members()
.last()
.expect("a checked path has a target")
.certificate();
let key = super::bind_key::<X, P::Error, W>(
xml,
bound,
references,
certificate,
path.facts().key,
execution,
)?;
let digests = match_path(xml, references, path, provider, execution)
.map_err(super::CheckError::Digest)?;
let digest = digests
.entries
.first()
.expect("certificate references are nonempty")
.agreement;
Ok(CheckedPathCertificate {
selected: CheckedCertificate {
reference: references,
certificate,
key,
digest,
},
digests,
})
}
impl<'view, 'document, N, D, G> PathDigests<'view, 'document, N, D, G> {
#[must_use]
pub fn references(&self) -> &'view References<N, D, G> {
self.references
}
#[must_use]
pub fn path(&self) -> &'view CheckedPath<'document> {
self.path
}
#[must_use]
pub fn entries(&self) -> &[PathDigest] {
&self.entries
}
#[must_use]
pub fn all_match(&self) -> bool {
self.all_match
}
}
pub fn match_path<'view, 'document, 'p, X: BinaryXml, P: DigestProvider<'p, X>, W: Work>(
xml: &X,
references: &'view Catalogue<X>,
path: &'view CheckedPath<'document>,
provider: &'p P,
execution: &W,
) -> PathDigestResult<'view, 'document, X, P::Error> {
let access = Access::new(xml, references.snapshot(), execution);
let location = Location {
snapshot: references.snapshot(),
node: references.node(),
};
access
.query(references.node(), |_, _, _| Ok(()))
.map_err(|cause| binary::Error::Access { location, cause })?;
let mut entries = execution.vector();
entries
.try_reserve_exact(references.entries().len())
.map_err(|cause| binary::Error::Callback {
location,
cause: DigestError::Resource(cause),
})?;
let mut all_match = true;
for ordinal in 0..references.entries().len() {
execution
.charge_work(1)
.map_err(|cause| binary::Error::Callback {
location,
cause: DigestError::Resource(cause),
})?;
let reference = &references.entries()[ordinal];
let answer = if let Some(answer) = super::preflight_method(&access, reference.method)
.map_err(|cause| binary::Error::Access {
location: Location {
node: reference.method,
..location
},
cause,
})? {
PathDigest {
reference: ordinal,
agreement: answer,
certificate: None,
}
} else {
binary::with_octets(
xml,
Location {
node: reference.digest,
..location
},
execution,
|expected| {
Ok::<_, core::convert::Infallible>(match_reference(
&access, reference, ordinal, path, provider, expected,
))
},
)
.map_err(|error| error.map_callback(|never| match never {}))??
};
if answer.agreement != Agreement::Matches {
all_match = false;
}
entries
.try_push(answer)
.map_err(|cause| binary::Error::Callback {
location,
cause: DigestError::Resource(cause),
})?;
}
Ok(PathDigests {
references,
path,
entries: entries.into_owned(),
all_match,
})
}
fn match_reference<'p, X: Xml, P: DigestProvider<'p, X>, W: Work>(
access: &Access<'_, X, W>,
reference: &super::Reference<X::Id>,
ordinal: usize,
path: &CheckedPath<'_>,
provider: &'p P,
expected: &[u8],
) -> binary::BinaryResult<PathDigest, X, DigestError<P::Error>> {
let identity = match super::resolve_expected(access, reference, provider, expected)? {
Expected::Ready(identity) => identity,
Expected::Answer(agreement) => {
return Ok(PathDigest {
reference: ordinal,
agreement,
certificate: None,
});
}
};
let mut answer = PathDigest {
reference: ordinal,
agreement: Agreement::Differs,
certificate: None,
};
let admit = || {
access
.execution()
.charge_work(1)
.map_err(|cause| binary::Error::Callback {
location: Location {
snapshot: access.snapshot(),
node: reference.node,
},
cause: DigestError::<P::Error>::Resource(cause),
})
};
let members = path.members();
if ordinal == 0 {
admit()?;
let target = members.last().expect("a checked path has a target");
answer.agreement = super::compare_resolved(
access,
reference,
target.certificate(),
provider,
&identity,
expected,
)?;
if answer.agreement == Agreement::Matches {
answer.certificate = Some(PathCertificate::Member(members.len() - 1));
}
return Ok(answer);
}
for (index, member) in members.iter().enumerate() {
admit()?;
match super::compare_resolved(
access,
reference,
member.certificate(),
provider,
&identity,
expected,
)? {
Agreement::Matches => {
answer.agreement = Agreement::Matches;
answer.certificate = Some(PathCertificate::Member(index));
return Ok(answer);
}
Agreement::Differs => {}
other => {
answer.agreement = other;
return Ok(answer);
}
}
}
admit()?;
if let Some(certificate) = path.anchor().certificate() {
let agreement = super::compare_resolved(
access,
reference,
certificate,
provider,
&identity,
expected,
)?;
if agreement != Agreement::Differs {
answer.agreement = agreement;
}
if agreement == Agreement::Matches {
answer.certificate = Some(PathCertificate::Anchor);
}
}
Ok(answer)
}