Skip to main content

structured_xades/counter/
walk.rs

1//! Iterative original countersignature traversal with actual `XMLDSig` receipts.
2
3use super::{Binding, Counter, CounterResult, Error, Original, bind, named};
4use crate::binding::{Selected, SignatureXml};
5use crate::container::{self, ContainerError, DS, Layout, XADES};
6use crate::properties;
7use crate::timestamp::{self, InputError};
8use crate::unsigned;
9use crate::xml::{Access, AccessError, Children, Xml};
10use structured_asn1::memory::{MemoryError, OwnedVec, Work};
11
12/// Original traversal/verification or visitor cause. Refusal never returns a
13/// successful prefix, though external visitor side effects cannot be rolled back.
14#[derive(Debug)]
15pub enum WalkError<E, VE, N> {
16    /// Original source or current-operation admission refusal.
17    Access(AccessError<E, N>),
18    /// Malformed direct child container, without a fallback descendant search.
19    Container(ContainerError<E, N>),
20    /// Unsigned container grammar failure.
21    Property(properties::Error<E, N>),
22    /// Original counter schema or actual selected-child verification refusal.
23    Counter(Error<E, N>),
24    /// Frame/result storage admission failed before growth.
25    Resource {
26        /// Original enclosing signature/property.
27        node: N,
28        /// Unchanged current execution refusal.
29        cause: MemoryError,
30    },
31    /// Exact visitor error. No later sibling or descendant is visited.
32    Visitor(VE),
33}
34
35/// Complete traversal result using original adapter causes and node identities.
36pub type WalkResult<T, X, VE> = Result<T, WalkError<<X as Xml>::Error, VE, <X as Xml>::Id>>;
37
38struct Frame<'a, 'xml, X: Xml, W: Work> {
39    layout: Layout<X>,
40    value: X::Id,
41    children: Children<'a, 'xml, X, W>,
42}
43
44/// Visits every direct/nested enveloped countersignature in depth-first original
45/// sibling order. Each child must have an actual successful `XMLDSig` receipt
46/// covering its own parent's complete original `SignatureValue`. Plain `XMLDSig`
47/// children are leaves; children with `XAdES` containers expose those original
48/// containers to the visitor for their independent property/profile checks.
49///
50/// The walker does not recursively call itself, retain movable-frame borrows,
51/// clone parent catalogues, reverify signatures or grant a child B level/trust.
52/// O(depth) frames are held on accounted heap storage. A visitor result can own
53/// admitted facts or original identities, but cannot borrow its temporary child
54/// binding/layout. All results and frames are dropped on terminal refusal.
55///
56/// ```compile_fail
57/// use structured_asn1::memory::{OwnedVec, Work};
58/// use structured_xades::{binding::{Selected, SignatureXml}, counter, xml::Access};
59/// fn escape<'a, X: SignatureXml, W: Work>(
60///     access: &Access<'_, X, W>, root: &Selected<X>,
61/// ) -> counter::WalkResult<OwnedVec<&'a counter::Binding<X>>, X, ()> {
62///     counter::walk(access, root, |child, _| Ok(child))
63/// }
64/// ```
65///
66/// EN319132-1 §5.2.7.2 permits an enveloped `XMLDSig` counter; the B-LTA material
67/// obligation in §6.3(aa) includes every incorporated counter, not just siblings.
68/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
69pub fn walk<X: SignatureXml, W: Work, R, VE>(
70    access: &Access<'_, X, W>,
71    root: &Selected<X>,
72    mut visitor: impl FnMut(&Binding<X>, Option<&Layout<X>>) -> Result<R, VE>,
73) -> WalkResult<OwnedVec<R>, X, VE> {
74    let layout = root.containers();
75    if access.snapshot() != layout.snapshot() {
76        return Err(WalkError::Access(AccessError::Changed {
77            node: layout.signature(),
78        }));
79    }
80    let mut frames = access
81        .query(layout.signature(), |_, _, work| Ok(work.vector()))
82        .map_err(WalkError::Access)?;
83    let mut results = access
84        .query(layout.signature(), |_, _, work| Ok(work.vector()))
85        .map_err(WalkError::Access)?;
86    if let Some(frame) = frame(access, *layout)? {
87        frames
88            .try_push(frame)
89            .map_err(|cause| WalkError::Resource {
90                node: layout.signature(),
91                cause,
92            })?;
93    }
94    while let Some(current) = frames.last_mut() {
95        let Some(next) = current.children.next() else {
96            frames.pop();
97            continue;
98        };
99        let node =
100            next.map_err(|cause| WalkError::Counter(Error::Input(InputError::Child(cause))))?;
101        if !named(access, node, XADES, "CounterSignature").map_err(WalkError::Counter)? {
102            continue;
103        }
104        let original = member(access, current.layout.signature(), current.value, node)
105            .map_err(WalkError::Counter)?;
106        let checked = bind(access, original).map_err(WalkError::Counter)?;
107        let child = checked.original().child();
108        let nested = match container::locate(access, child) {
109            Ok(layout) => Some(layout),
110            // Only absence of direct qualifying properties makes a plain
111            // XMLDSig leaf. Every other schema/resource/source error is terminal.
112            Err(ContainerError::Missing {
113                parent,
114                local: "QualifyingProperties",
115            }) => {
116                if !access
117                    .equal_id(parent, child)
118                    .map_err(|cause| WalkError::Resource { node: child, cause })?
119                {
120                    return Err(WalkError::Container(ContainerError::Missing {
121                        parent,
122                        local: "QualifyingProperties",
123                    }));
124                }
125                None
126            }
127            Err(cause) => return Err(WalkError::Container(cause)),
128        };
129        let pending = nested
130            .map(|layout| frame(access, layout))
131            .transpose()?
132            .flatten();
133        let result = visitor(&checked, nested.as_ref()).map_err(WalkError::Visitor)?;
134        // A visitor may retain owned data but cannot silently change the source
135        // generation and still return a successful result or start another node.
136        access
137            .query(child, |_, _, _| Ok(()))
138            .map_err(WalkError::Access)?;
139        results
140            .try_push(result)
141            .map_err(|cause| WalkError::Resource { node, cause })?;
142        if let Some(pending) = pending {
143            frames
144                .try_push(pending)
145                .map_err(|cause| WalkError::Resource { node: child, cause })?;
146        }
147    }
148    access
149        .query(layout.signature(), |_, _, _| Ok(()))
150        .map_err(WalkError::Access)?;
151    Ok(results.into_owned())
152}
153
154fn frame<'a, 'xml, X: Xml, W: Work, VE>(
155    access: &'a Access<'xml, X, W>,
156    layout: Layout<X>,
157) -> WalkResult<Option<Frame<'a, 'xml, X, W>>, X, VE> {
158    let [parent, _] =
159        unsigned::containers(access, layout.unsigned()).map_err(WalkError::Property)?;
160    let Some(parent) = parent else {
161        return Ok(None);
162    };
163    let value = timestamp::singleton(access, layout.signature(), DS, "SignatureValue")
164        .map_err(|cause| WalkError::Counter(Error::Input(cause)))?;
165    Ok(Some(Frame {
166        layout,
167        value,
168        children: access.children(parent),
169    }))
170}
171
172// Membership in the direct unsigned cursor was already established by the
173// walker. Share this field kernel with the independently selected read route.
174pub(super) fn member<X: Xml, W: Work>(
175    access: &Access<'_, X, W>,
176    parent: X::Id,
177    value: X::Id,
178    node: X::Id,
179) -> CounterResult<Original<X>, X> {
180    if !named(access, node, XADES, "CounterSignature")? {
181        return Err(Error::Input(InputError::Unexpected { node }));
182    }
183    let mut child = None;
184    for next in access.children(node) {
185        let next = next.map_err(|cause| Error::Input(InputError::Child(cause)))?;
186        if !named(access, next, DS, "Signature")? {
187            return Err(Error::Input(InputError::Unexpected { node: next }));
188        }
189        if child.is_some() {
190            return Err(Error::Input(InputError::Duplicate { node: next }));
191        }
192        child = Some(next);
193    }
194    let child = child.ok_or(Error::Input(InputError::Missing {
195        node,
196        local: "Signature",
197    }))?;
198    Ok(Counter {
199        snapshot: access.snapshot(),
200        parent,
201        value,
202        node,
203        child,
204    })
205}