use crate::binding::{Bound, Coverage, Status};
use crate::container::{DS, XADES};
use crate::properties::{DataObjectRole, Error, Reading};
use crate::reference::{Completeness, LexicalSource, Parent, ReferenceXml, Resolution, VisitError};
use crate::xml::{Access, AccessError, Name, Node, Snapshot, Xml};
use structured_asn1::memory::{OwnedVec, Work};
#[derive(Debug)]
pub enum FormatError<E, N> {
Selection {
signature: N,
index: usize,
},
Property(Error<E, N>),
Reference {
node: N,
status: Status,
},
Foreign {
node: N,
},
Metadata {
node: N,
object: N,
local: &'static str,
},
}
#[derive(Debug, Clone, Copy)]
pub struct Resolved<N> {
pub reference: N,
pub parent: Parent<N>,
pub target: Option<N>,
pub coverage: Coverage,
}
#[derive(Debug)]
pub enum CoverageError<E, N> {
Format(FormatError<E, N>),
Access(AccessError<E, N>),
Processing(E),
Lookup {
node: N,
resolution: Resolution<()>,
},
Missing {
reference: N,
},
Repeated {
reference: N,
},
Unused {
node: N,
},
ForeignCounter {
signature: N,
},
Incomplete {
node: N,
},
Unchecked {
reference: N,
},
}
#[derive(Debug)]
pub struct CoveredFormat<N, D, G> {
original: Format<N, D, G>,
reference: N,
seen: bool,
}
impl<N: Copy, D: Copy, G: Copy> CoveredFormat<N, D, G> {
#[must_use]
pub fn original(&self) -> &Format<N, D, G> {
&self.original
}
#[must_use]
pub fn reference(&self) -> N {
self.reference
}
}
pub type Covered<X, W = structured_asn1::memory::Memory> =
OwnedVec<CoveredFormat<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>, W>;
pub type CoverageResult<T, X> = Result<T, CoverageError<<X as Xml>::Error, <X as Xml>::Id>>;
pub fn check_references<X: ReferenceXml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
signed: &Reading<X>,
counter: Option<&crate::counter::Binding<X>>,
) -> CoverageResult<Covered<X>, X> {
let layout = bound.containers();
let counter_value = bind_sources(access, layout, signed, counter)?;
let mut formats = prepare_formats(access, bound, signed)?;
let mut indices = access
.query(layout.signature(), |_, _, work| Ok(work.map()))
.map_err(CoverageError::Access)?;
for index in 0..formats.len() {
admit(access, formats[index].original.node)?;
let reference = formats[index].reference;
if indices
.try_insert(reference, index)
.map_err(|cause| {
CoverageError::Access(AccessError::Resource {
node: formats[index].original.node,
cause,
})
})?
.is_some()
{
return Err(CoverageError::Repeated { reference });
}
}
let visited = access
.query(layout.signature(), |xml, snapshot, work| {
Ok(xml.visit_authenticated_references(
snapshot,
layout.signature(),
work,
|descriptor| {
let r = descriptor.reference;
if let Some(target) = r.target {
let equal = |other| {
access.equal_id(target, other).map_err(|cause| {
CoverageError::Access(AccessError::Resource {
node: r.node,
cause,
})
})
};
if equal(layout.signed())? {
return Ok(());
}
if let Some(value) = counter_value
&& equal(value)?
{
return Ok(());
}
}
if r.status != Status::Valid {
return Err(CoverageError::Unchecked { reference: r.node });
}
let Some(&index) = indices.get(&r.node, work).map_err(|cause| {
CoverageError::Access(AccessError::Resource {
node: r.node,
cause,
})
})?
else {
return Err(CoverageError::Missing { reference: r.node });
};
debug_assert!(index < formats.len());
formats[index].seen = true;
Ok(())
},
))
})
.map_err(CoverageError::Access)?;
let completeness = match visited {
Ok(value) => value,
Err(VisitError::Processing(cause)) => return Err(CoverageError::Processing(cause)),
Err(VisitError::Visitor(cause)) => return Err(cause),
};
match completeness {
Completeness::Complete => {}
Completeness::Unchecked(node) | Completeness::Incomplete(node) => {
return Err(CoverageError::Incomplete { node });
}
}
for index in 0..formats.len() {
admit(access, signed.parent())?;
if !formats[index].seen {
return Err(CoverageError::Unused {
node: formats[index].original.node,
});
}
}
Ok(formats.into_owned())
}
fn bind_sources<X: Xml, W: Work>(
access: &Access<'_, X, W>,
layout: &crate::container::Layout<X>,
signed: &Reading<X>,
counter: Option<&crate::counter::Binding<X>>,
) -> CoverageResult<Option<X::Id>, X> {
if access.snapshot() != layout.snapshot() {
return Err(CoverageError::Access(AccessError::Changed {
node: layout.signature(),
}));
}
if signed.snapshot() != layout.snapshot() {
return Err(CoverageError::Format(FormatError::Foreign {
node: signed.parent(),
}));
}
if !access
.equal_id(signed.signature(), layout.signature())
.map_err(|cause| {
CoverageError::Access(AccessError::Resource {
node: signed.parent(),
cause,
})
})?
{
return Err(CoverageError::Format(FormatError::Foreign {
node: signed.parent(),
}));
}
if let Some(counter) = counter {
if counter.original().snapshot() != layout.snapshot()
|| !access
.equal_id(counter.original().child(), layout.signature())
.map_err(|cause| {
CoverageError::Access(AccessError::Resource {
node: counter.original().node(),
cause,
})
})?
{
return Err(CoverageError::ForeignCounter {
signature: layout.signature(),
});
}
Ok(Some(counter.original().value()))
} else {
Ok(None)
}
}
fn admit<X: Xml, W: Work>(access: &Access<'_, X, W>, node: X::Id) -> CoverageResult<(), X> {
access
.query(node, |_, _, work| Ok(work.charge_work(1)))
.map_err(CoverageError::Access)?
.map_err(|cause| CoverageError::Access(AccessError::Resource { node, cause }))
}
fn prepare_formats<X: ReferenceXml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
signed: &Reading<X>,
) -> CoverageResult<Covered<X, W>, X> {
let mut formats = access
.query(signed.parent(), |_, _, work| Ok(work.vector()))
.map_err(CoverageError::Access)?;
if let Some(objects) = signed.data_objects() {
for index in 0..objects.entries().len() {
admit(access, objects.parent())?;
if objects.entries()[index].role != DataObjectRole::Format {
continue;
}
let original = read(access, signed, index).map_err(CoverageError::Format)?;
let resolved = match resolve(access, bound, &original).map_err(CoverageError::Format)? {
Resolution::Resolved(value) => value,
other => {
return Err(CoverageError::Lookup {
node: original.node,
resolution: other.map(|_| ()),
});
}
};
let node = original.node;
formats
.try_push(CoveredFormat {
original,
reference: resolved.reference,
seen: false,
})
.map_err(|cause| CoverageError::Access(AccessError::Resource { node, cause }))?;
}
}
Ok(formats)
}
pub fn resolve<X: ReferenceXml, W: Work>(
access: &Access<'_, X, W>,
bound: &Bound<X::Id, X::Document, X::Generation>,
format: &Original<X>,
) -> FormatResult<Resolution<Resolved<X::Id>>, X> {
let layout = bound.containers();
if format.snapshot != layout.snapshot() {
return Err(FormatError::Foreign { node: format.node });
}
if !access
.equal_id(format.signature, layout.signature())
.map_err(|cause| {
FormatError::Property(Error::Access(AccessError::Resource {
node: format.node,
cause,
}))
})?
{
return Err(FormatError::Foreign { node: format.node });
}
if access.snapshot() != format.snapshot {
return Err(FormatError::Property(Error::Access(AccessError::Changed {
node: format.node,
})));
}
let resolved = access
.query(format.node, |xml, snapshot, work| {
xml.resolve_reference(
snapshot,
format.signature,
format.node,
LexicalSource::Attribute(Name {
namespace: "",
local: "ObjectReference",
}),
work,
|descriptor| {
let r = descriptor.reference;
if r.status != Status::Valid {
return Err(FormatError::Reference {
node: r.node,
status: r.status,
});
}
if let Some(object) = r.target {
if access.equal_id(object, layout.signed()).map_err(|cause| {
FormatError::Property(Error::Access(AccessError::Resource {
node: r.node,
cause,
}))
})? {
return Err(FormatError::Reference {
node: r.node,
status: r.status,
});
}
object_metadata(access, format, object)?;
}
Ok(Resolved {
reference: r.node,
parent: descriptor.parent,
target: r.target,
coverage: r.coverage,
})
},
)
})
.map_err(|cause| FormatError::Property(Error::Access(cause)))?;
Ok(match resolved {
Resolution::Resolved(value) => Resolution::Resolved(value?),
Resolution::Missing => Resolution::Missing,
Resolution::Unresolved => Resolution::Unresolved,
Resolution::Unauthenticated => Resolution::Unauthenticated,
Resolution::Unsupported => Resolution::Unsupported,
})
}
fn object_metadata<X: Xml, W: Work>(
access: &Access<'_, X, W>,
format: &Original<X>,
object: X::Id,
) -> FormatResult<(), X> {
let access_error = |cause| FormatError::Property(Error::Access(cause));
if !access
.has_name(
object,
Name {
namespace: DS,
local: "Object",
},
)
.map_err(access_error)?
{
return Ok(());
}
for (node, local) in [
(Some(format.mime), "MimeType"),
(format.encoding, "Encoding"),
] {
let Some(node) = node else { continue };
access
.attribute(
object,
Name {
namespace: "",
local,
},
|expected| {
let Some(expected) = expected else {
return Ok(());
};
if simple_content_matches(access, node, expected)? {
Ok(())
} else {
Err(FormatError::Metadata {
node,
object,
local,
})
}
},
)
.map_err(access_error)??;
}
Ok(())
}
fn simple_content_matches<X: Xml, W: Work>(
access: &Access<'_, X, W>,
parent: X::Id,
expected: &str,
) -> FormatResult<bool, X> {
let access_error = |cause| FormatError::Property(Error::Access(cause));
let mut child = access.first_child(parent).map_err(access_error)?;
let mut offset = 0;
let mut same = true;
while let Some(node) = child {
let compared = access
.inspect(node, |value| match value {
Node::Element(_) => Err(FormatError::Property(Error::Unexpected { node })),
Node::Other => Ok(()),
Node::Text(text) => {
if same {
if text.len() > expected.len() - offset {
same = false;
} else {
access
.query(node, |_, _, work| {
Ok(u64::try_from(text.len())
.map_err(|_| {
structured_asn1::memory::MemoryError::WorkExceeded
})
.and_then(|length| work.charge_work(length)))
})
.map_err(access_error)?
.map_err(|cause| {
access_error(AccessError::Resource { node, cause })
})?;
let end = offset + text.len();
same = text.as_bytes() == &expected.as_bytes()[offset..end];
offset = end;
}
}
Ok(())
}
})
.map_err(access_error)?;
compared?;
child = access.next_sibling(parent, node).map_err(access_error)?;
}
Ok(same && offset == expected.len())
}
#[derive(Debug)]
pub struct Format<N, D, G> {
snapshot: Snapshot<D, G>,
signature: N,
node: N,
description: Option<N>,
identifier: Option<N>,
mime: N,
encoding: Option<N>,
}
impl<N: Copy, D: Copy, G: Copy> Format<N, D, G> {
#[must_use]
pub fn snapshot(&self) -> Snapshot<D, G> {
self.snapshot
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
#[must_use]
pub fn node(&self) -> N {
self.node
}
#[must_use]
pub fn description(&self) -> Option<N> {
self.description
}
#[must_use]
pub fn identifier(&self) -> Option<N> {
self.identifier
}
#[must_use]
pub fn mime(&self) -> N {
self.mime
}
#[must_use]
pub fn encoding(&self) -> Option<N> {
self.encoding
}
}
pub type FormatResult<T, X> = Result<T, FormatError<<X as Xml>::Error, <X as Xml>::Id>>;
pub type Original<X> = Format<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
pub fn check_encoding<X: crate::scalar::ScalarXml, W: Work>(
access: &Access<'_, X, W>,
original: &Original<X>,
) -> crate::scalar::ScalarResult<X> {
use crate::scalar::{Datatype, Error as ScalarError};
if original.snapshot != access.snapshot() {
return Err(ScalarError::Access(AccessError::Changed {
node: original.node,
}));
}
access
.query(original.node, |_, _, _| Ok(()))
.map_err(ScalarError::Access)?;
if let Some(node) = original.encoding {
crate::scalar::check(access, node, LexicalSource::SimpleContent, Datatype::AnyUri)?;
}
Ok(())
}
pub fn read<X: Xml, W: Work>(
access: &Access<'_, X, W>,
signed: &Reading<X>,
index: usize,
) -> FormatResult<Original<X>, X> {
let select = || FormatError::Selection {
signature: signed.signature(),
index,
};
let property = FormatError::Property;
if access.snapshot() != signed.snapshot() {
return Err(property(Error::Access(AccessError::Changed {
node: signed.parent(),
})));
}
access
.query(signed.parent(), |_, _, work| Ok(work.charge_work(1)))
.map_err(|cause| property(Error::Access(cause)))?
.map_err(|cause| {
property(Error::Resource {
node: signed.parent(),
cause,
})
})?;
let objects = signed.data_objects().ok_or_else(select)?;
let selected = objects.entries().get(index).ok_or_else(select)?;
if selected.role != DataObjectRole::Format {
return Err(select());
}
let node = selected.node;
let has_reference = access
.attribute(
node,
Name {
namespace: "",
local: "ObjectReference",
},
|value| value.is_some(),
)
.map_err(|cause| property(Error::Access(cause)))?;
if !has_reference {
return Err(property(Error::Missing {
parent: node,
local: "ObjectReference",
}));
}
let mut fields = [None; 4];
let mut previous = 0;
for child in access.children(node) {
let child = child.map_err(|cause| property(Error::Child(cause)))?;
let mut selected = None;
for (rank, local) in ["Description", "ObjectIdentifier", "MimeType", "Encoding"]
.iter()
.enumerate()
{
if access
.has_name(
child,
Name {
namespace: XADES,
local,
},
)
.map_err(|cause| property(Error::Access(cause)))?
{
selected = Some(rank);
break;
}
}
let rank = selected.ok_or_else(|| property(Error::Unexpected { node: child }))?;
if fields[rank].is_some() {
return Err(property(Error::Duplicate { node: child }));
}
if rank < previous {
return Err(property(Error::Order { node: child }));
}
fields[rank] = Some(child);
previous = rank;
match rank {
1 => {
crate::identifier::read(access, child).map_err(property)?;
}
2 => crate::mime::check(access, child).map_err(property)?,
_ => crate::properties::simple_content(access, child).map_err(property)?,
}
}
let mime = fields[2].ok_or_else(|| {
property(Error::Missing {
parent: node,
local: "MimeType",
})
})?;
Ok(Format {
snapshot: signed.snapshot(),
signature: signed.signature(),
node,
description: fields[0],
identifier: fields[1],
mime,
encoding: fields[3],
})
}