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 ¤t.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}