use super::{Cursor, FIELD};
use crate::{Error, Octets, signer::SignerInfo};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Reader, Span};
pub enum Visit<'s, 'a> {
Signer {
signer: &'s SignerInfo<'a>,
signature: &'s Octets<'a>,
parent: Span,
},
Malformed {
source: Error,
parent: Span,
},
}
#[derive(Debug)]
pub enum WalkError<E> {
Processing(Error),
Callback {
parent: Span,
at: u64,
source: E,
},
}
pub fn walk<'a, R, E>(
root: &SignerInfo<'a>,
parent: &Reader<'a>,
mut visitor: impl FnMut(Visit<'_, 'a>, &Reader<'a>) -> core::result::Result<R, E>,
) -> core::result::Result<OwnedVec<R>, WalkError<E>> {
let execution = parent.execution();
let mut ancestors = execution.vector();
let mut results = execution.vector();
let mut current = Frame {
owned: None,
cursor: Cursor::default(),
};
loop {
let (signer, reader) = match ¤t.owned {
Some((signer, reader)) => (signer, reader),
None => (root, parent),
};
let candidate = current.cursor.next(signer, reader);
let Some(candidate) = candidate else {
if ancestors.is_empty() {
break;
}
execution.charge_work(1).map_err(|reason| {
WalkError::Processing(Error::resource(reason, FIELD, signer.span().start))
})?;
if let Some(frame) = ancestors.pop() {
current = frame;
}
continue;
};
let parent_span = signer.span();
match candidate {
Ok((child, child_reader)) => {
let at = child.span().start;
let refusal = |reason| WalkError::Processing(Error::resource(reason, FIELD, at));
let descend = child.unsigned_attributes().is_some();
if descend {
ancestors.try_reserve(1).map_err(refusal)?;
}
results.try_reserve(1).map_err(refusal)?;
let result = visitor(
Visit::Signer {
signer: &child,
signature: signer.signature(),
parent: parent_span,
},
&child_reader,
)
.map_err(|source| WalkError::Callback {
parent: parent_span,
at,
source,
})?;
results.try_push(result).map_err(refusal)?;
if descend {
ancestors.try_push(current).map_err(refusal)?;
current = Frame {
owned: Some((child, child_reader)),
cursor: Cursor::default(),
};
}
}
Err(error) if error.is_terminal() => return Err(WalkError::Processing(error)),
Err(error) => {
let at = error.offset();
let refusal = |reason| WalkError::Processing(Error::resource(reason, FIELD, at));
results.try_reserve(1).map_err(refusal)?;
let result = visitor(
Visit::Malformed {
source: error,
parent: parent_span,
},
reader,
)
.map_err(|source| WalkError::Callback {
parent: parent_span,
at,
source,
})?;
results.try_push(result).map_err(refusal)?;
}
}
}
Ok(results.into_owned())
}
struct Frame<'a> {
owned: Option<(SignerInfo<'a>, Reader<'a>)>,
cursor: Cursor,
}