Skip to main content

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}