Skip to main content

structured_cms/counter/
walk.rs

1//! Iterative, caller-selected countersignature processing over original inputs.
2
3use super::{Cursor, FIELD};
4use crate::{Error, Octets, signer::SignerInfo};
5use structured_asn1::memory::{OwnedVec, Work};
6use structured_asn1::{Reader, Span};
7
8/// One direct value, with the exact parent signature it countersigns.
9pub enum Visit<'s, 'a> {
10    /// Readable original `SignerInfo`; conformance faults remain in that model.
11    Signer {
12        /// Original parsed child, borrowed only during this visitor call.
13        signer: &'s SignerInfo<'a>,
14        /// Original logical parent signature octets, never its signed payload.
15        signature: &'s Octets<'a>,
16        /// Original parent signer range, for stable relationship identity.
17        parent: Span,
18    },
19    /// An unreadable independent value; later direct values remain accessible.
20    Malformed {
21        /// Original located processing diagnostic, with its storage ownership.
22        source: Error,
23        /// Original parent signer range.
24        parent: Span,
25    },
26}
27
28/// A traversal did not complete, independently of a visitor's semantic answers.
29#[derive(Debug)]
30pub enum WalkError<E> {
31    /// Common source-located work/storage/depth refusal.
32    Processing(Error),
33    /// Original visitor failure, including backend/resource causes.
34    Callback {
35        /// Original parent signer whose signature this value covered.
36        parent: Span,
37        /// Original child start, or the malformed value's diagnostic position.
38        at: u64,
39        /// Caller failure, never converted to invalid/unsupported/absence.
40        source: E,
41    },
42}
43
44/// Processes all nested countersignature values with an admitted iterative stack.
45///
46/// RFC5652 ยง11.4 permits repeated attributes/values and nested countersignatures.
47/// Values are visited depth first in original sibling order, with O(depth)
48/// active frames. This order implies no chronology or application priority.
49/// Only unsigned countersignatures are visited: callers must separately retain
50/// misplaced signed attributes and unreadable values for protocol diagnostics. Each visitor receives the ORIGINAL child's producing Reader;
51/// parsing its parameters must not reset enclosing depth or local work.
52///
53/// Callers select keys/providers and can run `prepare::resolve` with
54/// `Scope::CounterSignature` and the shared verification batch against the
55/// supplied parent signature. This operation asserts neither root-signature
56/// validity nor trust/identity and makes no implicit key selection or I/O.
57/// Visitors admit their own data-dependent work/storage in the supplied Reader.
58/// Returned values must not borrow the temporary child model/Reader; they may
59/// borrow original document bytes or own independently admitted evidence.
60///
61/// A semantic parse failure is delivered as a separate malformed event; a
62/// resource or visitor failure aborts and drops all temporary/collected values.
63/// Visitor external side effects cannot be rolled back by this operation.
64/// No successful partial catalogue is exposed. Retained result capacity owns
65/// its original charge independently of the parser and traversal frames.
66/// <https://www.rfc-editor.org/rfc/rfc5652#section-11.4>
67pub fn walk<'a, R, E>(
68    root: &SignerInfo<'a>,
69    parent: &Reader<'a>,
70    mut visitor: impl FnMut(Visit<'_, 'a>, &Reader<'a>) -> core::result::Result<R, E>,
71) -> core::result::Result<OwnedVec<R>, WalkError<E>> {
72    let execution = parent.execution();
73    let mut ancestors = execution.vector();
74    let mut results = execution.vector();
75    let mut current = Frame {
76        owned: None,
77        cursor: Cursor::default(),
78    };
79    loop {
80        let (signer, reader) = match &current.owned {
81            Some((signer, reader)) => (signer, reader),
82            None => (root, parent),
83        };
84        let candidate = current.cursor.next(signer, reader);
85        let Some(candidate) = candidate else {
86            if ancestors.is_empty() {
87                break;
88            }
89            execution.charge_work(1).map_err(|reason| {
90                WalkError::Processing(Error::resource(reason, FIELD, signer.span().start))
91            })?;
92            if let Some(frame) = ancestors.pop() {
93                current = frame;
94            }
95            continue;
96        };
97        let parent_span = signer.span();
98        match candidate {
99            Ok((child, child_reader)) => {
100                let at = child.span().start;
101                let refusal = |reason| WalkError::Processing(Error::resource(reason, FIELD, at));
102                let descend = child.unsigned_attributes().is_some();
103                if descend {
104                    ancestors.try_reserve(1).map_err(refusal)?;
105                }
106                results.try_reserve(1).map_err(refusal)?;
107                let result = visitor(
108                    Visit::Signer {
109                        signer: &child,
110                        signature: signer.signature(),
111                        parent: parent_span,
112                    },
113                    &child_reader,
114                )
115                .map_err(|source| WalkError::Callback {
116                    parent: parent_span,
117                    at,
118                    source,
119                })?;
120                results.try_push(result).map_err(refusal)?;
121                if descend {
122                    ancestors.try_push(current).map_err(refusal)?;
123                    current = Frame {
124                        owned: Some((child, child_reader)),
125                        cursor: Cursor::default(),
126                    };
127                }
128            }
129            Err(error) if error.is_terminal() => return Err(WalkError::Processing(error)),
130            Err(error) => {
131                let at = error.offset();
132                let refusal = |reason| WalkError::Processing(Error::resource(reason, FIELD, at));
133                results.try_reserve(1).map_err(refusal)?;
134                let result = visitor(
135                    Visit::Malformed {
136                        source: error,
137                        parent: parent_span,
138                    },
139                    reader,
140                )
141                .map_err(|source| WalkError::Callback {
142                    parent: parent_span,
143                    at,
144                    source,
145                })?;
146                results.try_push(result).map_err(refusal)?;
147            }
148        }
149    }
150    Ok(results.into_owned())
151}
152
153// None denotes only the borrowed root; descendants own exactly one active
154// decoded signer and cursor. No sibling catalogue or self-reference is stored.
155struct Frame<'a> {
156    owned: Option<(SignerInfo<'a>, Reader<'a>)>,
157    cursor: Cursor,
158}