use crate::binding::{Bound, Status};
use crate::container::XADES;
use crate::identifier;
use crate::properties::{self, DataObjectRole, Error, PropertyResult};
use crate::reference::{
Completeness, Descriptor, LexicalSource, Parent, ReferenceXml, Resolution, VisitError,
};
use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
use structured_asn1::memory::{OwnedVec, Work};
#[derive(Debug)]
pub enum Objects<N> {
All(N),
References(OwnedVec<N>),
}
pub struct Reading<N, D, G> {
snapshot: Snapshot<D, G>,
signature: N,
node: N,
kind: identifier::Reading<N>,
objects: Objects<N>,
qualifiers: Option<N>,
}
impl<N: Copy, D: Copy, G: Copy> Reading<N, D, G> {
#[must_use]
pub fn node(&self) -> N {
self.node
}
#[must_use]
pub fn kind(&self) -> &identifier::Reading<N> {
&self.kind
}
#[must_use]
pub fn objects(&self) -> &Objects<N> {
&self.objects
}
#[must_use]
pub fn qualifiers(&self) -> Option<N> {
self.qualifiers
}
}
pub type Original<X> = Reading<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
pub fn read<X: Xml, W: Work>(
access: &Access<'_, X, W>,
signed: &properties::Reading<X>,
index: usize,
) -> PropertyResult<Original<X>, X> {
let node = selected(access, signed, index)?;
let mut kind = None;
let mut all = None;
let mut references = access
.query(node, |_, _, work| Ok(work.vector()))
.map_err(Error::Access)?;
let mut qualifiers = None;
for child in access.children(node) {
let child = child.map_err(Error::Child)?;
if named(access, child, "CommitmentTypeId")? {
if kind.is_some() {
return Err(Error::Duplicate { node: child });
}
if all.is_some() || !references.is_empty() || qualifiers.is_some() {
return Err(Error::Order { node: child });
}
let original = identifier::read(access, child)?;
if original.qualifier.is_some() {
return Err(Error::Forbidden {
node: original.identifier,
});
}
kind = Some(original);
} else if named(access, child, "ObjectReference")? {
if kind.is_none() || qualifiers.is_some() {
return Err(Error::Order { node: child });
}
if all.is_some() {
return Err(Error::Unexpected { node: child });
}
properties::simple_content(access, child)?;
references
.try_push(child)
.map_err(|cause| Error::Resource { node: child, cause })?;
} else if named(access, child, "AllSignedDataObjects")? {
if kind.is_none() || qualifiers.is_some() {
return Err(Error::Order { node: child });
}
if all.is_some() {
return Err(Error::Duplicate { node: child });
}
if !references.is_empty() {
return Err(Error::Unexpected { node: child });
}
if let Some(nested) = access.children(child).next() {
return Err(Error::Unexpected {
node: nested.map_err(Error::Child)?,
});
}
all = Some(child);
} else if named(access, child, "CommitmentTypeQualifiers")? {
if qualifiers.is_some() {
return Err(Error::Duplicate { node: child });
}
if kind.is_none() || (all.is_none() && references.is_empty()) {
return Err(Error::Order { node: child });
}
for qualifier in access.children(child) {
let qualifier = qualifier.map_err(Error::Child)?;
if !named(access, qualifier, "CommitmentTypeQualifier")? {
return Err(Error::Unexpected { node: qualifier });
}
}
qualifiers = Some(child);
} else {
return Err(Error::Unexpected { node: child });
}
}
let kind = kind.ok_or(Error::Missing {
parent: node,
local: "CommitmentTypeId",
})?;
let objects = if let Some(all) = all {
Objects::All(all)
} else {
if references.is_empty() {
return Err(Error::Missing {
parent: node,
local: "ObjectReference or AllSignedDataObjects",
});
}
Objects::References(references.into_owned())
};
Ok(Reading {
snapshot: signed.snapshot(),
signature: signed.signature(),
node,
kind,
objects,
qualifiers,
})
}
#[derive(Debug, Clone, Copy)]
pub struct Object<N> {
pub reference: N,
pub parent: Parent<N>,
}
pub struct Association<N, D, G> {
original: Reading<N, D, G>,
objects: OwnedVec<Object<N>>,
}
impl<N, D, G> Association<N, D, G> {
#[must_use]
pub fn original(&self) -> &Reading<N, D, G> {
&self.original
}
#[must_use]
pub fn objects(&self) -> &[Object<N>] {
&self.objects
}
}
pub type Prepared<X> = Association<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
pub fn prepare<X: ReferenceXml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
signed: &properties::Reading<X>,
index: usize,
) -> Result<Prepared<X>, AssociationError<X::Error, X::Id>> {
let original = read(access, signed, index).map_err(AssociationError::Property)?;
let objects = associate(access, bound, &original)?;
Ok(Association { original, objects })
}
#[derive(Debug)]
pub enum AssociationError<E, N> {
Property(Error<E, N>),
Processing(E),
Lookup {
node: N,
resolution: Resolution<()>,
},
Ineligible {
node: N,
},
Incomplete {
node: N,
},
}
pub fn associate<X: ReferenceXml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
original: &Original<X>,
) -> AssociationResult<OwnedVec<Object<X::Id>>, X> {
let property = AssociationError::Property;
bind_original(access, bound, original)?;
let mut output = access
.query(original.node, |_, _, work| Ok(work.vector()))
.map_err(|cause| property(Error::Access(cause)))?;
let is_properties = |r: crate::binding::Reference<'_, X::Id>| match r.target {
Some(target) => access
.equal_id(target, bound.containers().signed())
.map_err(|cause| {
property(Error::Resource {
node: r.node,
cause,
})
}),
None => Ok(false),
};
let mut retain = |descriptor: Descriptor<'_, X::Id>| {
let r = descriptor.reference;
if r.status != Status::Valid {
return Err(AssociationError::Ineligible { node: r.node });
}
output
.try_push(Object {
reference: r.node,
parent: descriptor.parent,
})
.map_err(|cause| {
property(Error::Resource {
node: r.node,
cause,
})
})
};
match &original.objects {
Objects::References(nodes) => {
for index in 0..nodes.len() {
admit(access, original.node).map_err(property)?;
let node = nodes[index];
let resolved = access
.query(node, |xml, snapshot, work| {
xml.resolve_reference(
snapshot,
original.signature,
node,
LexicalSource::SimpleContent,
work,
|descriptor| {
let r = descriptor.reference;
if r.status != Status::Valid || is_properties(r)? {
return Err(AssociationError::Ineligible { node: r.node });
}
retain(descriptor)
},
)
})
.map_err(|cause| property(Error::Access(cause)))?;
match resolved {
Resolution::Resolved(value) => value?,
other => {
return Err(AssociationError::Lookup {
node,
resolution: other.map(|_| ()),
});
}
}
}
}
Objects::All(_) => {
let visited = access
.query(original.node, |xml, snapshot, work| {
Ok(xml.visit_authenticated_references(
snapshot,
original.signature,
work,
|descriptor| {
if is_properties(descriptor.reference)? {
return Ok(());
}
retain(descriptor)
},
))
})
.map_err(|cause| property(Error::Access(cause)))?;
let complete = match visited {
Ok(value) => value,
Err(VisitError::Processing(cause)) => {
return Err(AssociationError::Processing(cause));
}
Err(VisitError::Visitor(cause)) => return Err(cause),
};
match complete {
Completeness::Complete => {}
Completeness::Unchecked(node) | Completeness::Incomplete(node) => {
return Err(AssociationError::Incomplete { node });
}
}
}
}
Ok(output.into_owned())
}
fn bind_original<X: Xml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
original: &Original<X>,
) -> AssociationResult<(), X> {
let property = AssociationError::Property;
if access.snapshot() != original.snapshot || bound.containers().snapshot() != original.snapshot
{
return Err(property(Error::Access(AccessError::Changed {
node: original.node,
})));
}
if !access
.equal_id(bound.containers().signature(), original.signature)
.map_err(|cause| {
property(Error::Resource {
node: original.node,
cause,
})
})?
{
return Err(property(Error::Access(AccessError::Changed {
node: original.node,
})));
}
Ok(())
}
pub type AssociationResult<T, X> = Result<T, AssociationError<<X as Xml>::Error, <X as Xml>::Id>>;
fn selected<X: Xml, W: Work>(
access: &Access<'_, X, W>,
signed: &properties::Reading<X>,
index: usize,
) -> PropertyResult<X::Id, X> {
if access.snapshot() != signed.snapshot() {
return Err(Error::Access(AccessError::Changed {
node: signed.parent(),
}));
}
admit(access, signed.parent())?;
let selection = || Error::Selection {
parent: signed.parent(),
index,
};
let entry = signed
.data_objects()
.and_then(|objects| objects.entries().get(index))
.ok_or_else(selection)?;
if entry.role != DataObjectRole::Commitment {
return Err(selection());
}
Ok(entry.node)
}
fn admit<X: Xml, W: Work>(access: &Access<'_, X, W>, node: X::Id) -> PropertyResult<(), X> {
access
.query(node, |_, _, work| Ok(work.charge_work(1)))
.map_err(Error::Access)?
.map_err(|cause| Error::Resource { node, cause })
}
fn named<X: Xml, W: Work>(
access: &Access<'_, X, W>,
node: X::Id,
local: &str,
) -> PropertyResult<bool, X> {
access
.has_name(
node,
Name {
namespace: XADES,
local,
},
)
.map_err(Error::Access)
}