structured_cms/stream.rs
1//! One payload pass with sticky source, callback and resource refusal.
2
3use crate::Error;
4use structured_asn1::memory::Work;
5
6/// A write refusal whose original cause remains held by the stream driver.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Refused;
9
10/// A source-facing chunk receiver.
11pub trait Receiver {
12 /// Delivers the next complete chunk in logical order.
13 ///
14 /// On refusal the source must stop. The driver additionally enforces sticky
15 /// refusal if a source ignores this marker, without cloning the cause.
16 fn write(&mut self, bytes: &[u8]) -> core::result::Result<(), Refused>;
17}
18
19/// Caller-supplied payload acquisition; successful return denotes complete EOF.
20///
21/// Acquisition must admit its own data-dependent work/storage before execution
22/// through the supplied domain. This trait neither fetches document references
23/// nor proves accounting inside arbitrary caller code.
24pub trait Source {
25 /// The original acquisition failure, distinct from a signature mismatch.
26 type Error;
27
28 /// Feeds every chunk once, returning an error for an incomplete source.
29 fn stream<W: Work>(
30 &mut self,
31 receiver: &mut impl Receiver,
32 execution: &W,
33 ) -> core::result::Result<(), Self::Error>;
34}
35
36/// Feeds original ranges, including the SET tag and original signedAttrs
37/// remainder returned by [`crate::attribute::Attributes::signed_bytes`].
38/// No concatenation or canonical re-encoding occurs; each metadata step is
39/// admitted in the current operation before dispatch.
40impl<const N: usize> Source for [&[u8]; N] {
41 type Error = RangesError;
42
43 fn stream<W: Work>(
44 &mut self,
45 receiver: &mut impl Receiver,
46 execution: &W,
47 ) -> Result<(), Self::Error> {
48 for bytes in self.iter() {
49 execution.charge_work(1).map_err(RangesError::Resource)?;
50 receiver.write(bytes).map_err(RangesError::Receiver)?;
51 }
52 Ok(())
53 }
54}
55
56/// Range acquisition has no ASN.1 source location of its own.
57#[derive(Debug)]
58pub enum RangesError {
59 /// Current work admission failed before walking a range.
60 Resource(structured_asn1::memory::MemoryError),
61 /// The driver holds the original sink refusal.
62 Receiver(Refused),
63}
64
65/// An injected processing sink, such as a set of digest/raw verification states.
66pub trait Sink {
67 /// The callback's original failure, preserving provider/resource provenance.
68 type Error;
69
70 /// Processes a chunk after admitting its complete transitive operation.
71 ///
72 /// The driver pays dispatch; the callback pays actual hashing, copying,
73 /// opaque state growth and fan-out before execution. No Clone/Send/Sync
74 /// bound or particular cryptographic engine is required.
75 fn write<W: Work>(
76 &mut self,
77 bytes: &[u8],
78 execution: &W,
79 ) -> core::result::Result<(), Self::Error>;
80}
81
82/// Why a payload pass could not finish.
83#[derive(Debug)]
84pub enum StreamError<SE, CE> {
85 /// The source did not reach successful EOF.
86 Source(SE),
87 /// A sink callback refused, with its original cause.
88 Callback(CE),
89 /// Common processing refused resources before dispatch.
90 Processing(Error),
91}
92
93enum Failure<E> {
94 Callback(E),
95 Processing(Error),
96}
97
98struct Guard<'a, S: Sink, W: Work> {
99 sink: &'a mut S,
100 execution: &'a W,
101 field: &'static str,
102 at: u64,
103 failure: Option<Failure<S::Error>>,
104}
105
106impl<S: Sink, W: Work> Receiver for Guard<'_, S, W> {
107 fn write(&mut self, bytes: &[u8]) -> core::result::Result<(), Refused> {
108 if self.failure.is_some() {
109 return Err(Refused);
110 }
111 if let Err(reason) = self.execution.charge_work(1) {
112 self.failure = Some(Failure::Processing(Error::resource(
113 reason, self.field, self.at,
114 )));
115 return Err(Refused);
116 }
117 if let Err(error) = self.sink.write(bytes, self.execution) {
118 self.failure = Some(Failure::Callback(error));
119 return Err(Refused);
120 }
121 Ok(())
122 }
123}
124
125/// Feeds one source once and preserves the first sink refusal without cloning.
126///
127/// `field`/`at` locate processing in the caller's enclosing structure; they do
128/// not invent an ASN.1 position for detached bytes. A callback refusal takes
129/// precedence over a later source failure caused by that refusal. A partial
130/// source never returns success, even if a prefix reached the sink.
131pub fn run<S: Source, C: Sink, W: Work>(
132 source: &mut S,
133 sink: &mut C,
134 execution: &W,
135 field: &'static str,
136 at: u64,
137) -> core::result::Result<(), StreamError<S::Error, C::Error>> {
138 execution
139 .charge_work(1)
140 .map_err(|reason| StreamError::Processing(Error::resource(reason, field, at)))?;
141 let mut guard = Guard {
142 sink,
143 execution,
144 field,
145 at,
146 failure: None,
147 };
148 let acquired = source.stream(&mut guard, execution);
149 match guard.failure {
150 Some(Failure::Callback(error)) => Err(StreamError::Callback(error)),
151 Some(Failure::Processing(error)) => Err(StreamError::Processing(error)),
152 None => acquired.map_err(StreamError::Source),
153 }
154}