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/// Complete payload acquisition that may suspend between bounded chunks.
37/// The same sticky receiver and cause precedence govern synchronous sources.
38/// No executor, heap allocation, `Send` bound or blocking adapter is required.
39pub trait AsyncSource {
40 /// Original acquisition failure, distinct from callback refusal.
41 type Error;
42
43 /// Feeds every chunk once and returns success only after complete EOF.
44 fn stream<'q, W: Work + 'q, R: Receiver + 'q>(
45 &'q mut self,
46 receiver: &'q mut R,
47 execution: &'q W,
48 ) -> impl core::future::Future<Output = Result<(), Self::Error>> + 'q;
49}
50
51/// Feeds original ranges, including the SET tag and original signedAttrs
52/// remainder returned by [`crate::attribute::Attributes::signed_bytes`].
53/// No concatenation or canonical re-encoding occurs; each metadata step is
54/// admitted in the current operation before dispatch.
55impl<const N: usize> Source for [&[u8]; N] {
56 type Error = RangesError;
57
58 fn stream<W: Work>(
59 &mut self,
60 receiver: &mut impl Receiver,
61 execution: &W,
62 ) -> Result<(), Self::Error> {
63 for bytes in self.iter() {
64 execution.charge_work(1).map_err(RangesError::Resource)?;
65 receiver.write(bytes).map_err(RangesError::Receiver)?;
66 }
67 Ok(())
68 }
69}
70
71/// Range acquisition has no ASN.1 source location of its own.
72#[derive(Debug)]
73pub enum RangesError {
74 /// Current work admission failed before walking a range.
75 Resource(structured_asn1::memory::MemoryError),
76 /// The driver holds the original sink refusal.
77 Receiver(Refused),
78}
79
80/// An injected processing sink, such as a set of digest/raw verification states.
81pub trait Sink {
82 /// The callback's original failure, preserving provider/resource provenance.
83 type Error;
84
85 /// Processes a chunk after admitting its complete transitive operation.
86 ///
87 /// The driver pays dispatch; the callback pays actual hashing, copying,
88 /// opaque state growth and fan-out before execution. No Clone/Send/Sync
89 /// bound or particular cryptographic engine is required.
90 fn write<W: Work>(
91 &mut self,
92 bytes: &[u8],
93 execution: &W,
94 ) -> core::result::Result<(), Self::Error>;
95}
96
97/// Why a payload pass could not finish.
98#[derive(Debug)]
99pub enum StreamError<SE, CE> {
100 /// The source did not reach successful EOF.
101 Source(SE),
102 /// A sink callback refused, with its original cause.
103 Callback(CE),
104 /// Common processing refused resources before dispatch.
105 Processing(Error),
106}
107
108enum Failure<E> {
109 Callback(E),
110 Processing(Error),
111}
112
113struct Guard<'a, S: Sink, W: Work> {
114 sink: &'a mut S,
115 execution: &'a W,
116 field: &'static str,
117 at: u64,
118 failure: Option<Failure<S::Error>>,
119}
120
121impl<S: Sink, W: Work> Receiver for Guard<'_, S, W> {
122 fn write(&mut self, bytes: &[u8]) -> core::result::Result<(), Refused> {
123 if self.failure.is_some() {
124 return Err(Refused);
125 }
126 if let Err(reason) = self.execution.charge_work(1) {
127 self.failure = Some(Failure::Processing(Error::resource(
128 reason, self.field, self.at,
129 )));
130 return Err(Refused);
131 }
132 if let Err(error) = self.sink.write(bytes, self.execution) {
133 self.failure = Some(Failure::Callback(error));
134 return Err(Refused);
135 }
136 Ok(())
137 }
138}
139
140/// Feeds one source once and preserves the first sink refusal without cloning.
141///
142/// `field`/`at` locate processing in the caller's enclosing structure; they do
143/// not invent an ASN.1 position for detached bytes. A callback refusal takes
144/// precedence over a later source failure caused by that refusal. A partial
145/// source never returns success, even if a prefix reached the sink.
146pub fn run<S: Source, C: Sink, W: Work>(
147 source: &mut S,
148 sink: &mut C,
149 execution: &W,
150 field: &'static str,
151 at: u64,
152) -> core::result::Result<(), StreamError<S::Error, C::Error>> {
153 execution
154 .charge_work(1)
155 .map_err(|reason| StreamError::Processing(Error::resource(reason, field, at)))?;
156 let mut guard = Guard {
157 sink,
158 execution,
159 field,
160 at,
161 failure: None,
162 };
163 let acquired = source.stream(&mut guard, execution);
164 match guard.failure {
165 Some(Failure::Callback(error)) => Err(StreamError::Callback(error)),
166 Some(Failure::Processing(error)) => Err(StreamError::Processing(error)),
167 None => acquired.map_err(StreamError::Source),
168 }
169}
170
171/// Suspends source acquisition while preserving the same sticky callback
172/// refusal, admission order and complete-EOF boundary as [`run`]. Dropping the
173/// future drops the borrowed driver, without producing a partial receipt.
174pub async fn run_async<S: AsyncSource, C: Sink, W: Work>(
175 source: &mut S,
176 sink: &mut C,
177 execution: &W,
178 field: &'static str,
179 at: u64,
180) -> Result<(), StreamError<S::Error, C::Error>> {
181 execution
182 .charge_work(1)
183 .map_err(|cause| StreamError::Processing(Error::resource(cause, field, at)))?;
184 let mut guard = Guard {
185 sink,
186 execution,
187 field,
188 at,
189 failure: None,
190 };
191 let acquired = source.stream(&mut guard, execution).await;
192 match guard.failure {
193 Some(Failure::Callback(error)) => Err(StreamError::Callback(error)),
194 Some(Failure::Processing(error)) => Err(StreamError::Processing(error)),
195 None => acquired.map_err(StreamError::Source),
196 }
197}