structured_asn1/digest.rs
1//! One incremental digest execution contract for every format.
2//!
3//! A consumer implements [`Digest`] once over its own cryptographic backend.
4//! Which algorithm a document names is a separate question answered by the
5//! format that wrote it: ASN.1 identifiers by `structured-pkix`, XML methods
6//! by `structured-xades`. Both hand their resolved identity to this contract.
7
8use crate::memory::Work;
9use core::cmp::Ordering;
10
11/// Semantic resolution, distinct from failure to execute the resolver.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Resolution<T> {
14 /// Fully resolved algorithm identity, including parameters/output semantics.
15 Supported(T),
16 /// The selected provider cannot answer this mechanism.
17 Unsupported,
18 /// The original algorithm identifier violates the selected algorithm's syntax.
19 Malformed,
20}
21
22/// Incremental digest operations of one explicitly selected provider.
23///
24/// Identities carry complete algorithm semantics, not just an identifier or a
25/// byte width. Every callback admits its complete transitive work and storage
26/// in the supplied execution before it runs. Errors are a spent ceiling or a
27/// provider fault, never a finding about the document. States may borrow the
28/// provider for `'a` and carry no `Clone`, `Send` or `Sync` requirement.
29pub trait Digest<'a> {
30 /// Fully resolved digest identity.
31 type Identity;
32 /// Digest state, independently movable from its identity object.
33 type State;
34 /// Final output, inline or retaining its own admitted allocation charge.
35 type Output: AsRef<[u8]>;
36 /// Original callback failure, including that provider's resource refusal.
37 type Error;
38
39 /// Fixed output width, or `None` for an identity without one. This is O(1)
40 /// metadata access on the resolved identity, not parsing.
41 fn width(&self, identity: &Self::Identity) -> Option<usize>;
42
43 /// Total semantic order: `Equal` means the outputs can be shared.
44 fn compare<W: Work>(
45 &self,
46 left: &Self::Identity,
47 right: &Self::Identity,
48 execution: &W,
49 ) -> Result<Ordering, Self::Error>;
50
51 /// Starts one stream for this identity without retaining a reference to the
52 /// identity object. A backend without incremental capability answers
53 /// `Unsupported`; it never buffers into a one-shot fallback.
54 fn start<W: Work>(
55 &'a self,
56 identity: &Self::Identity,
57 execution: &W,
58 ) -> Result<Resolution<Self::State>, Self::Error>;
59
60 /// Admits and processes one complete chunk exactly once.
61 fn update<W: Work>(
62 &self,
63 state: &mut Self::State,
64 bytes: &[u8],
65 execution: &W,
66 ) -> Result<(), Self::Error>;
67
68 /// Consumes a completed state once; no state cloning or repeated finish.
69 fn finish<W: Work>(
70 &self,
71 state: Self::State,
72 execution: &W,
73 ) -> Result<Self::Output, Self::Error>;
74
75 /// Compares this provider's finished output with declared bytes, admitting
76 /// the whole scan and applying the backend's comparison strategy.
77 fn matches<W: Work>(
78 &self,
79 identity: &Self::Identity,
80 output: &Self::Output,
81 expected: &[u8],
82 execution: &W,
83 ) -> Result<bool, Self::Error>;
84}
85
86/// Failure of a one-shot check, apart from every completed comparison.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum OnceError<E> {
89 /// Original provider failure, including its resource refusal.
90 Provider(E),
91 /// The provider's output contradicts the fixed width of its own identity.
92 /// This is a provider contract failure, not malformed document data.
93 OutputWidth {
94 /// Width promised by the resolved identity.
95 expected: usize,
96 /// Width actually returned.
97 actual: usize,
98 },
99}
100
101/// Hashes one contiguous input and compares it with `expected`: start, one
102/// update, finish and matches over the same contract as streamed input.
103///
104/// `Unsupported` and `Malformed` are the provider's start answer, returned
105/// before any byte is hashed.
106///
107/// # Errors
108///
109/// Returns the provider's failure, or [`OnceError::OutputWidth`] when the
110/// output contradicts the identity's fixed width.
111///
112/// # Examples
113///
114/// ```
115/// # extern crate alloc;
116/// # use alloc::sync::Arc;
117/// # use core::cmp::Ordering;
118/// # use structured_asn1::digest::{self, Digest, Resolution};
119/// # use structured_asn1::memory::{Memory, MemoryAccount, MemoryError, Work};
120/// /// A one-byte additive checksum, enough to show the call sequence.
121/// struct Sum;
122/// impl Digest<'_> for Sum {
123/// type Identity = ();
124/// type State = u8;
125/// type Output = [u8; 1];
126/// type Error = MemoryError;
127/// fn width(&self, (): &()) -> Option<usize> { Some(1) }
128/// fn compare<W: Work>(&self, (): &(), (): &(), _: &W) -> Result<Ordering, MemoryError> {
129/// Ok(Ordering::Equal)
130/// }
131/// fn start<W: Work>(&self, (): &(), w: &W) -> Result<Resolution<u8>, MemoryError> {
132/// w.charge_work(1)?;
133/// Ok(Resolution::Supported(0))
134/// }
135/// fn update<W: Work>(&self, s: &mut u8, bytes: &[u8], w: &W) -> Result<(), MemoryError> {
136/// w.charge_work(bytes.len() as u64)?;
137/// *s = bytes.iter().fold(*s, |sum, byte| sum.wrapping_add(*byte));
138/// Ok(())
139/// }
140/// fn finish<W: Work>(&self, s: u8, _: &W) -> Result<[u8; 1], MemoryError> { Ok([s]) }
141/// fn matches<W: Work>(&self, (): &(), o: &[u8; 1], e: &[u8], w: &W) -> Result<bool, MemoryError> {
142/// w.charge_work(1)?;
143/// Ok(o.as_slice() == e)
144/// }
145/// }
146/// let execution = Memory::new(Arc::new(MemoryAccount::new(0, 64)));
147/// let answer = digest::matches_once(&Sum, &(), b"\x01\x02", &[3], &execution).unwrap();
148/// assert_eq!(answer, Resolution::Supported(true));
149/// ```
150pub fn matches_once<'a, D: Digest<'a> + ?Sized, W: Work>(
151 digest: &'a D,
152 identity: &D::Identity,
153 input: &[u8],
154 expected: &[u8],
155 execution: &W,
156) -> Result<Resolution<bool>, OnceError<D::Error>> {
157 let mut state = match digest
158 .start(identity, execution)
159 .map_err(OnceError::Provider)?
160 {
161 Resolution::Supported(state) => state,
162 Resolution::Unsupported => return Ok(Resolution::Unsupported),
163 Resolution::Malformed => return Ok(Resolution::Malformed),
164 };
165 digest
166 .update(&mut state, input, execution)
167 .map_err(OnceError::Provider)?;
168 let output = digest
169 .finish(state, execution)
170 .map_err(OnceError::Provider)?;
171 if let Some(expected) = digest.width(identity) {
172 let actual = output.as_ref().len();
173 if actual != expected {
174 return Err(OnceError::OutputWidth { expected, actual });
175 }
176 }
177 digest
178 .matches(identity, &output, expected, execution)
179 .map(Resolution::Supported)
180 .map_err(OnceError::Provider)
181}