use crate::container::{Containers, Layout};
use crate::xml::{Access, AccessError, Snapshot, Xml};
use structured_asn1::memory::Work;
pub const SIGNED_PROPERTIES: &str = "http://uri.etsi.org/01903#SignedProperties";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
Valid,
Invalid,
Unchecked,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Coverage {
ElementContent,
Transformed,
}
#[derive(Debug, Clone, Copy)]
pub struct Reference<'a, N> {
pub node: N,
pub target: Option<N>,
pub kind: Option<&'a str>,
pub status: Status,
pub coverage: Coverage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Target<N> {
LocalBareName(N),
Missing,
Other,
}
pub trait SignatureXml: Xml {
fn signature_status<W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
signature: Self::Id,
execution: &W,
) -> Result<Status, Self::Error>;
fn reference_count<W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
signature: Self::Id,
execution: &W,
) -> Result<usize, Self::Error>;
fn reference<R, W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
signature: Self::Id,
ordinal: usize,
execution: &W,
visitor: impl for<'view> FnOnce(Reference<'view, Self::Id>) -> R,
) -> Result<R, Self::Error>;
fn qualifying_target<W: Work>(
&self,
snapshot: Snapshot<Self::Document, Self::Generation>,
qualifying: Self::Id,
execution: &W,
) -> Result<Target<Self::Id>, Self::Error>;
}
#[derive(Debug)]
pub enum BindingError<E, N> {
Access(AccessError<E, N>),
Signature {
node: N,
status: Status,
},
Target {
node: N,
},
Missing {
node: N,
},
Reference {
node: N,
},
References {
node: N,
},
}
#[derive(Debug)]
pub struct Bound<N, D, G> {
containers: Containers<N, D, G>,
reference: N,
ordinal: usize,
}
impl<N: Copy, D: Copy, G: Copy> Bound<N, D, G> {
#[must_use]
pub fn containers(&self) -> &Containers<N, D, G> {
&self.containers
}
#[must_use]
pub fn reference(&self) -> N {
self.reference
}
#[must_use]
pub fn ordinal(&self) -> usize {
self.ordinal
}
}
pub type Selected<X> = Bound<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
pub type BindingResult<T, X> = Result<T, BindingError<<X as Xml>::Error, <X as Xml>::Id>>;
#[derive(Clone, Copy)]
pub(crate) enum Purpose<N> {
Properties(N),
Counter(N),
}
pub(crate) fn covering_reference<X: SignatureXml, W: Work>(
access: &Access<'_, X, W>,
signature: X::Id,
purpose: Purpose<X::Id>,
) -> BindingResult<(X::Id, usize), X> {
let status = access
.query(signature, |xml, snapshot, work| {
xml.signature_status(snapshot, signature, work)
})
.map_err(BindingError::Access)?;
if status != Status::Valid {
return Err(BindingError::Signature {
node: signature,
status,
});
}
let count = access
.query(signature, |xml, snapshot, work| {
xml.reference_count(snapshot, signature, work)
})
.map_err(BindingError::Access)?;
let (wanted, minimum) = match purpose {
Purpose::Properties(target) => (target, 2),
Purpose::Counter(target) => (target, 1),
};
if count < minimum {
return Err(BindingError::References { node: signature });
}
let mut selected = None;
let mut uncovered = None;
for ordinal in 0..count {
let (node, target, status, coverage, eligible) = access
.query(signature, |xml, snapshot, work| {
xml.reference(snapshot, signature, ordinal, work, |r| {
let eligible = match purpose {
Purpose::Properties(_) => match r.kind {
Some(kind) => access.equal(kind, SIGNED_PROPERTIES),
None => Ok(false),
},
Purpose::Counter(_) => Ok(true),
};
(r.node, r.target, r.status, r.coverage, eligible)
})
})
.map_err(BindingError::Access)?;
let eligible = eligible
.map_err(|cause| BindingError::Access(AccessError::Resource { node, cause }))?;
if !eligible {
continue;
}
let matches = match target {
Some(target) => access
.equal_id(target, wanted)
.map_err(|cause| BindingError::Access(AccessError::Resource { node, cause }))?,
None => false,
};
if !matches {
if let Purpose::Counter(_) = purpose {
continue;
}
return Err(BindingError::Reference { node });
}
if status != Status::Valid {
return Err(BindingError::Reference { node });
}
if coverage != Coverage::ElementContent {
uncovered = Some(node);
continue;
}
if selected.is_none() {
selected = Some((node, ordinal));
}
}
if let Some(value) = selected {
return Ok(value);
}
if let Some(node) = uncovered {
return Err(BindingError::Reference { node });
}
Err(BindingError::Missing { node: wanted })
}
pub fn bind<X: SignatureXml, W: Work>(
access: &Access<'_, X, W>,
layout: &Layout<X>,
) -> BindingResult<Selected<X>, X> {
check_target(access, layout)?;
let (reference, ordinal) = covering_reference(
access,
layout.signature(),
Purpose::Properties(layout.signed()),
)?;
Ok(Bound {
containers: *layout,
reference,
ordinal,
})
}
pub(crate) fn check_target<X: SignatureXml, W: Work>(
access: &Access<'_, X, W>,
layout: &Layout<X>,
) -> BindingResult<(), X> {
let signature = layout.signature();
if access.snapshot() != layout.snapshot() {
return Err(BindingError::Access(AccessError::Changed {
node: signature,
}));
}
let target = access
.query(layout.qualifying(), |xml, snapshot, work| {
xml.qualifying_target(snapshot, layout.qualifying(), work)
})
.map_err(BindingError::Access)?;
let matched = match target {
Target::LocalBareName(target) => access.equal_id(target, signature).map_err(|cause| {
BindingError::Access(AccessError::Resource {
node: layout.qualifying(),
cause,
})
})?,
Target::Missing | Target::Other => false,
};
if !matched {
return Err(BindingError::Target {
node: layout.qualifying(),
});
}
Ok(())
}