use crate::binding::{Bound, SignatureXml};
use crate::reference::{Parent, VisitError};
use crate::xml::{Access, AccessError, Snapshot, Xml};
use structured_asn1::memory::{MemoryError, Work};
mod participants;
pub use participants::{
ParticipantBinding, ParticipantHashes, ParticipantProblem, check_participants,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashClass {
Md5,
OtherKnown,
Intrinsic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashOrigin<N> {
SignatureMessage {
instruction: N,
method: Option<N>,
},
MaskGeneration {
instruction: N,
method: Option<N>,
},
Reference {
instruction: N,
method: Option<N>,
parent: Parent<N>,
},
}
impl<N: Copy> HashOrigin<N> {
#[must_use]
pub fn node(self) -> N {
match self {
Self::SignatureMessage { instruction, .. }
| Self::MaskGeneration { instruction, .. }
| Self::Reference { instruction, .. } => instruction,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashUseState {
Complete(HashClass),
Unsupported,
Unprocessed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HashUse<N> {
pub signature: N,
pub origin: HashOrigin<N>,
pub state: HashUseState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashCompleteness<N> {
Complete,
Unsupported(N),
Unprocessed(N),
}
pub trait HashXml: SignatureXml {
fn visit_hash_uses<VE, W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
signature: Self::Id,
execution: &W,
visitor: impl FnMut(HashUse<Self::Id>) -> Result<(), VE>,
) -> Result<HashCompleteness<Self::Id>, VisitError<Self::Error, VE>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashFailure {
ProhibitedMd5,
Unsupported,
Unprocessed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashProblem<N> {
Use {
origin: HashOrigin<N>,
cause: HashFailure,
},
Catalogue {
node: N,
cause: HashFailure,
},
}
pub struct Hashes<N, D, G> {
snapshot: Snapshot<D, G>,
signature: N,
}
impl<N: Copy, D: Copy, G: Copy> Hashes<N, D, G> {
#[must_use]
pub fn snapshot(&self) -> Snapshot<D, G> {
self.snapshot
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
}
pub enum HashBinding<N, D, G> {
Bound(Hashes<N, D, G>),
Unproven(HashProblem<N>),
}
#[derive(Debug)]
pub enum HashError<E, N> {
Access(AccessError<E, N>),
Foreign {
node: N,
},
Resource {
node: N,
cause: MemoryError,
},
Projection {
node: N,
},
}
pub type HashResult<X> = Result<
HashBinding<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
HashError<<X as Xml>::Error, <X as Xml>::Id>,
>;
enum Stop<N> {
Problem(HashProblem<N>),
Resource(N, MemoryError),
Foreign(N),
Projection(N),
}
pub fn check<X: HashXml, W: Work>(
bound: &Bound<X::Id, X::Document, X::Generation>,
access: &Access<'_, X, W>,
) -> HashResult<X> {
let layout = bound.containers();
let signature = layout.signature();
if layout.snapshot() != access.snapshot() {
return Err(HashError::Foreign { node: signature });
}
check_selected(signature, access)
}
fn check_selected<X: HashXml, W: Work>(
signature: X::Id,
access: &Access<'_, X, W>,
) -> HashResult<X> {
let mut message = false;
let visited = access.query_fallible(signature, |xml, snapshot, work| {
xml.visit_hash_uses(snapshot, signature, work, |actual| {
let node = actual.origin.node();
work.charge_work(1)
.map_err(|cause| Stop::Resource(node, cause))?;
if !access
.equal_id(actual.signature, signature)
.map_err(|cause| Stop::Resource(node, cause))?
{
return Err(Stop::Foreign(node));
}
if matches!(actual.origin, HashOrigin::SignatureMessage { .. }) {
if message {
return Err(Stop::Projection(node));
}
message = true;
}
let cause = match actual.state {
HashUseState::Complete(HashClass::Intrinsic) => {
if !matches!(actual.origin, HashOrigin::SignatureMessage { .. }) {
return Err(Stop::Projection(node));
}
return Ok(());
}
HashUseState::Complete(HashClass::OtherKnown) => return Ok(()),
HashUseState::Complete(HashClass::Md5) => HashFailure::ProhibitedMd5,
HashUseState::Unsupported => HashFailure::Unsupported,
HashUseState::Unprocessed => HashFailure::Unprocessed,
};
Err(Stop::Problem(HashProblem::Use {
origin: actual.origin,
cause,
}))
})
});
let completeness = match visited {
Ok(complete) => complete,
Err(AccessError::Changed { node }) => {
return Err(HashError::Access(AccessError::Changed { node }));
}
Err(AccessError::Resource { node, cause }) => {
return Err(HashError::Access(AccessError::Resource { node, cause }));
}
Err(AccessError::Xml(VisitError::Processing(cause))) => {
return Err(HashError::Access(AccessError::Xml(cause)));
}
Err(AccessError::Xml(VisitError::Visitor(stop))) => {
return match stop {
Stop::Problem(problem) => Ok(HashBinding::Unproven(problem)),
Stop::Resource(node, cause) => Err(HashError::Resource { node, cause }),
Stop::Foreign(node) => Err(HashError::Foreign { node }),
Stop::Projection(node) => Err(HashError::Projection { node }),
};
}
};
match completeness {
HashCompleteness::Complete => {}
HashCompleteness::Unsupported(node) => {
return Ok(HashBinding::Unproven(HashProblem::Catalogue {
node,
cause: HashFailure::Unsupported,
}));
}
HashCompleteness::Unprocessed(node) => {
return Ok(HashBinding::Unproven(HashProblem::Catalogue {
node,
cause: HashFailure::Unprocessed,
}));
}
}
if !message {
return Err(HashError::Projection { node: signature });
}
Ok(HashBinding::Bound(Hashes {
snapshot: access.snapshot(),
signature,
}))
}