Skip to main content

sark_core/body/
flow.rs

1use core::{marker, num::NonZero};
2
3use o3::buffer::segments;
4
5use crate::body::{
6    self, credit, ingress,
7    input::Parts,
8    stream::{Admission, Drive, Stream},
9};
10
11mod raw;
12
13enum State<B, O> {
14    Building(B),
15    Ready(O),
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum Error<E> {
20    LengthMismatch,
21    Credit(credit::Error),
22    Build(E),
23    Release { build: E, error: credit::Error },
24}
25
26pub(super) enum Push<B, E> {
27    Accepted(B),
28    Failed { build: B, error: body::Error },
29    Rejected { build: B, error: E },
30}
31
32/// Linear request construction whose ready payload replaces its builder.
33pub struct Flow<'d, B: body::Build<'d>> {
34    state: State<B, B::Output>,
35    pub(in crate::body) lifetime: marker::PhantomData<&'d ()>,
36}
37
38/// Completed output retaining its exact finished ingress and unsettled credit.
39#[must_use = "admit the output or discard its retained owner"]
40pub struct Ready<'d, B: body::Build<'d>, O: credit::Origin = credit::raw::Stored> {
41    state: ingress::State<O>,
42    output: B::Output,
43    lifetime: marker::PhantomData<&'d ()>,
44}
45
46impl<'d, B: body::Build<'d>, O: credit::Origin> Ready<'d, B, O> {
47    fn try_new<E: credit::Evidence>(
48        state: ingress::State<O>,
49        output: B::Output,
50        event: &mut E,
51    ) -> Drive<'d, B, O> {
52        let state = match state.try_ready() {
53            Ok(state) => state,
54            Err(state) => {
55                return Drive::Rejected {
56                    stream: Stream::from_parts(state, Flow::from_output(output)),
57                    error: body::Error::InputIncomplete,
58                };
59            }
60        };
61        event.build_ready();
62        Drive::Ready(Self {
63            state,
64            output,
65            lifetime: marker::PhantomData,
66        })
67    }
68
69    pub fn try_admit<E: credit::Evidence>(
70        mut self,
71        ledger: &mut credit::raw::Ledger<'_, O>,
72        mut event: E,
73    ) -> Admission<'d, B, E, O> {
74        match ingress::Turn::new(&mut self.state, ledger, &mut event)
75            .try_drain(credit::Disposition::Admitted)
76        {
77            Ok(()) => Admission::Accepted(self.output),
78            Err(error) => Admission::Rejected {
79                ready: self,
80                evidence: event,
81                error,
82            },
83        }
84    }
85
86    pub fn discard<E: credit::Evidence>(
87        self,
88        ledger: &mut credit::raw::Ledger<'_, O>,
89        event: E,
90    ) -> credit::Discard<O> {
91        self.into_release().try_discard(ledger, event)
92    }
93
94    pub fn into_release(self) -> credit::Release<O> {
95        let Self { state, output, .. } = self;
96        drop(output);
97        state.abandon()
98    }
99}
100
101impl<'d, B: body::Build<'d>> Flow<'d, B> {
102    pub const fn new(build: B) -> Self {
103        Self {
104            state: State::Building(build),
105            lifetime: marker::PhantomData,
106        }
107    }
108
109    pub(super) fn from_done(build: B, len: usize) -> Self {
110        Self::new(build.finish_input(len))
111    }
112
113    fn from_output(output: B::Output) -> Self {
114        Self {
115            state: State::Ready(output),
116            lifetime: marker::PhantomData,
117        }
118    }
119
120    pub(super) fn try_parts<const COUNT: bool, I>(
121        mut build: B,
122        parts: I,
123        bytes: usize,
124        chunks: segments::Count,
125    ) -> Push<Self, <B as body::Push<'d>>::Error>
126    where
127        B: body::Push<'d>,
128        I: Parts<'d>,
129    {
130        if I::EXACT {
131            let size = parts.dimensions();
132            if size.bytes() != bytes || COUNT && size.chunks() != chunks {
133                return Push::Failed {
134                    build: Self::new(build),
135                    error: body::Error::SegmentMismatch,
136                };
137            }
138        }
139        let mut seen = 0usize;
140        let mut count = segments::Count::EMPTY;
141        for part in parts {
142            if !I::EXACT && part.is_empty() {
143                continue;
144            }
145            if !I::EXACT {
146                let Some(total) = seen.checked_add(part.len()).filter(|total| *total <= bytes)
147                else {
148                    return Push::Failed {
149                        build: Self::new(build),
150                        error: body::Error::SegmentMismatch,
151                    };
152                };
153                seen = total;
154                if COUNT {
155                    let Some(tally) = count
156                        .checked_add(segments::Count::ONE)
157                        .filter(|tally| *tally <= chunks)
158                    else {
159                        return Push::Failed {
160                            build: Self::new(build),
161                            error: body::Error::SegmentMismatch,
162                        };
163                    };
164                    count = tally;
165                }
166            }
167            build = match body::Push::push(build, part) {
168                body::Transfer::Accepted(build) => build,
169                body::Transfer::Rejected { build, error } => {
170                    return Push::Rejected {
171                        build: Self::new(build),
172                        error,
173                    };
174                }
175            };
176        }
177        if !I::EXACT && (seen != bytes || COUNT && count != chunks) {
178            return Push::Failed {
179                build: Self::new(build),
180                error: body::Error::SegmentMismatch,
181            };
182        }
183        Push::Accepted(Self::new(build))
184    }
185
186    pub(super) fn try_push<I, E, O: credit::Origin>(
187        self,
188        turn: &mut ingress::Turn<'_, '_, E, O>,
189        parts: I,
190        bytes: usize,
191        chunks: segments::Count,
192        rmax: credit::Limits,
193        cmax: credit::Limits,
194    ) -> Push<Self, <B as body::Push<'d>>::Error>
195    where
196        B: body::Push<'d>,
197        I: Parts<'d>,
198        E: credit::Evidence,
199    {
200        let State::Building(build) = self.state else {
201            return Push::Failed {
202                build: self,
203                error: body::Error::InputFinished,
204            };
205        };
206        let next = match turn.try_advance(bytes) {
207            Ok(next) => next,
208            Err(error) => {
209                return Push::Failed {
210                    build: Self::new(build),
211                    error,
212                };
213            }
214        };
215        let amount = credit::Amount::new(bytes, chunks);
216        let next = match next.try_reserve(amount, rmax, cmax) {
217            Ok(next) => next.commit(),
218            Err(error) => {
219                return Push::Failed {
220                    build: Self::new(build),
221                    error: body::Error::Credit(error),
222                };
223            }
224        };
225
226        let transfer = Self::try_parts::<true, _>(build, parts, bytes, chunks);
227        if matches!(transfer, Push::Accepted(_)) {
228            next.commit();
229        }
230        transfer
231    }
232
233    /// Finishes with the final logical total, including previously counted input.
234    /// Discarded input may defer accounting until its carrier knows that total.
235    pub fn try_exact<E: credit::Evidence, O: credit::Origin>(
236        self,
237        turn: &mut ingress::Turn<'_, '_, E, O>,
238        len: usize,
239    ) -> body::Transfer<Self, body::Error> {
240        if let Err(error) = turn.state_mut().try_exact(len) {
241            return body::Transfer::Rejected { build: self, error };
242        }
243        self.try_finish(turn.state_mut())
244    }
245
246    pub(super) fn try_finish<O: credit::Origin>(
247        self,
248        state: &mut ingress::State<O>,
249    ) -> body::Transfer<Self, body::Error> {
250        self.try_end_with(|| state.try_end())
251    }
252
253    pub(super) fn try_complete<O: credit::Origin>(
254        self,
255        state: &mut ingress::State<O>,
256    ) -> body::Transfer<Self, body::Error> {
257        self.try_end_with(|| {
258            if state.is_finished() {
259                Ok(state.logical_len())
260            } else {
261                state.try_end()
262            }
263        })
264    }
265
266    fn try_end_with(
267        self,
268        finish: impl FnOnce() -> Result<usize, body::Error>,
269    ) -> body::Transfer<Self, body::Error> {
270        let State::Building(build) = self.state else {
271            return body::Transfer::Rejected {
272                build: self,
273                error: body::Error::InputFinished,
274            };
275        };
276        let len = match finish() {
277            Ok(len) => len,
278            Err(error) => {
279                return body::Transfer::Rejected {
280                    build: Self::new(build),
281                    error,
282                };
283            }
284        };
285        body::Transfer::Accepted(Self::new(build.finish_input(len)))
286    }
287
288    pub(super) fn try_drive<E: credit::Evidence, O: credit::Origin>(
289        self,
290        mut state: ingress::State<O>,
291        ledger: &mut credit::raw::Ledger<'_, O>,
292        event: &mut E,
293        budget: NonZero<usize>,
294    ) -> Drive<'d, B, O> {
295        let build = match self.state {
296            State::Building(build) => build,
297            State::Ready(output) => return Ready::<B, O>::try_new(state, output, event),
298        };
299        match build.step(budget) {
300            body::Step::NeedInput { build, released } => {
301                let flow = Self::new(build);
302                let result = ingress::Turn::new(&mut state, ledger, &mut *event)
303                    .try_release(released, credit::Disposition::Discarded);
304                let error = match result {
305                    Ok(()) if state.is_finished() => Error::LengthMismatch,
306                    Ok(()) => return Drive::NeedInput(Stream::from_parts(state, flow)),
307                    Err(error) => Error::Credit(error),
308                };
309                Drive::Failed {
310                    stream: Stream::from_parts(state, flow),
311                    error,
312                }
313            }
314            body::Step::Pending { build, released } => {
315                let flow = Self::new(build);
316                let result = ingress::Turn::new(&mut state, ledger, &mut *event)
317                    .try_release(released, credit::Disposition::Discarded);
318                match result {
319                    Ok(()) => Drive::Pending(Stream::from_parts(state, flow)),
320                    Err(error) => Drive::Failed {
321                        stream: Stream::from_parts(state, flow),
322                        error: Error::Credit(error),
323                    },
324                }
325            }
326            body::Step::Ready { output, released } => {
327                if let Err(error) = ingress::Turn::new(&mut state, ledger, &mut *event)
328                    .try_release(released, credit::Disposition::Discarded)
329                {
330                    return Drive::Failed {
331                        stream: Stream::from_parts(state, Self::from_output(output)),
332                        error: Error::Credit(error),
333                    };
334                }
335                Ready::<B, O>::try_new(state, output, event)
336            }
337            body::Step::Failed {
338                build,
339                error,
340                released,
341            } => {
342                let flow = Self::new(build);
343                let error = match ingress::Turn::new(&mut state, ledger, &mut *event)
344                    .try_release(released, credit::Disposition::Discarded)
345                {
346                    Ok(()) => Error::Build(error),
347                    Err(release) => Error::Release {
348                        build: error,
349                        error: release,
350                    },
351                };
352                Drive::Failed {
353                    stream: Stream::from_parts(state, flow),
354                    error,
355                }
356            }
357        }
358    }
359}
360
361const _: () = {
362    assert!(core::mem::size_of::<Error<u8>>() == 4 * core::mem::size_of::<usize>());
363};