use super::{Binding, Counter, CounterResult, Error, Original, bind, named};
use crate::binding::{Selected, SignatureXml};
use crate::container::{self, ContainerError, DS, Layout, XADES};
use crate::properties;
use crate::timestamp::{self, InputError};
use crate::unsigned;
use crate::xml::{Access, AccessError, Children, Xml};
use structured_asn1::memory::{MemoryError, OwnedVec, Work};
#[derive(Debug)]
pub enum WalkError<E, VE, N> {
Access(AccessError<E, N>),
Container(ContainerError<E, N>),
Property(properties::Error<E, N>),
Counter(Error<E, N>),
Resource {
node: N,
cause: MemoryError,
},
Visitor(VE),
}
pub type WalkResult<T, X, VE> = Result<T, WalkError<<X as Xml>::Error, VE, <X as Xml>::Id>>;
struct Frame<'a, 'xml, X: Xml, W: Work> {
layout: Layout<X>,
value: X::Id,
children: Children<'a, 'xml, X, W>,
}
pub fn walk<X: SignatureXml, W: Work, R, VE>(
access: &Access<'_, X, W>,
root: &Selected<X>,
mut visitor: impl FnMut(&Binding<X>, Option<&Layout<X>>) -> Result<R, VE>,
) -> WalkResult<OwnedVec<R>, X, VE> {
let layout = root.containers();
if access.snapshot() != layout.snapshot() {
return Err(WalkError::Access(AccessError::Changed {
node: layout.signature(),
}));
}
let mut frames = access
.query(layout.signature(), |_, _, work| Ok(work.vector()))
.map_err(WalkError::Access)?;
let mut results = access
.query(layout.signature(), |_, _, work| Ok(work.vector()))
.map_err(WalkError::Access)?;
if let Some(frame) = frame(access, *layout)? {
frames
.try_push(frame)
.map_err(|cause| WalkError::Resource {
node: layout.signature(),
cause,
})?;
}
while let Some(current) = frames.last_mut() {
let Some(next) = current.children.next() else {
frames.pop();
continue;
};
let node =
next.map_err(|cause| WalkError::Counter(Error::Input(InputError::Child(cause))))?;
if !named(access, node, XADES, "CounterSignature").map_err(WalkError::Counter)? {
continue;
}
let original = member(access, current.layout.signature(), current.value, node)
.map_err(WalkError::Counter)?;
let checked = bind(access, original).map_err(WalkError::Counter)?;
let child = checked.original().child();
let nested = match container::locate(access, child) {
Ok(layout) => Some(layout),
Err(ContainerError::Missing {
parent,
local: "QualifyingProperties",
}) => {
if !access
.equal_id(parent, child)
.map_err(|cause| WalkError::Resource { node: child, cause })?
{
return Err(WalkError::Container(ContainerError::Missing {
parent,
local: "QualifyingProperties",
}));
}
None
}
Err(cause) => return Err(WalkError::Container(cause)),
};
let pending = nested
.map(|layout| frame(access, layout))
.transpose()?
.flatten();
let result = visitor(&checked, nested.as_ref()).map_err(WalkError::Visitor)?;
access
.query(child, |_, _, _| Ok(()))
.map_err(WalkError::Access)?;
results
.try_push(result)
.map_err(|cause| WalkError::Resource { node, cause })?;
if let Some(pending) = pending {
frames
.try_push(pending)
.map_err(|cause| WalkError::Resource { node: child, cause })?;
}
}
access
.query(layout.signature(), |_, _, _| Ok(()))
.map_err(WalkError::Access)?;
Ok(results.into_owned())
}
fn frame<'a, 'xml, X: Xml, W: Work, VE>(
access: &'a Access<'xml, X, W>,
layout: Layout<X>,
) -> WalkResult<Option<Frame<'a, 'xml, X, W>>, X, VE> {
let [parent, _] =
unsigned::containers(access, layout.unsigned()).map_err(WalkError::Property)?;
let Some(parent) = parent else {
return Ok(None);
};
let value = timestamp::singleton(access, layout.signature(), DS, "SignatureValue")
.map_err(|cause| WalkError::Counter(Error::Input(cause)))?;
Ok(Some(Frame {
layout,
value,
children: access.children(parent),
}))
}
pub(super) fn member<X: Xml, W: Work>(
access: &Access<'_, X, W>,
parent: X::Id,
value: X::Id,
node: X::Id,
) -> CounterResult<Original<X>, X> {
if !named(access, node, XADES, "CounterSignature")? {
return Err(Error::Input(InputError::Unexpected { node }));
}
let mut child = None;
for next in access.children(node) {
let next = next.map_err(|cause| Error::Input(InputError::Child(cause)))?;
if !named(access, next, DS, "Signature")? {
return Err(Error::Input(InputError::Unexpected { node: next }));
}
if child.is_some() {
return Err(Error::Input(InputError::Duplicate { node: next }));
}
child = Some(next);
}
let child = child.ok_or(Error::Input(InputError::Missing {
node,
local: "Signature",
}))?;
Ok(Counter {
snapshot: access.snapshot(),
parent,
value,
node,
child,
})
}