Skip to main content

sark_core/body/credit/
mod.rs

1use std::convert::Infallible;
2
3use o3::base::cell::region;
4use o3::buffer::segments;
5
6pub mod raw;
7mod seal;
8pub(in crate::body::credit) use seal::Seal;
9
10/// An unforgeable zero-sized account identity or its private persisted form.
11pub trait Origin: Seal + Copy {}
12
13impl Origin for raw::Stored {}
14impl<'id> Origin for region::Identity<'id> {}
15
16#[derive(Clone, Copy, PartialEq, Eq, Debug)]
17pub struct Amount {
18    bytes: usize,
19    chunks: segments::Count,
20}
21
22impl Amount {
23    pub const fn new(bytes: usize, chunks: segments::Count) -> Self {
24        Self { bytes, chunks }
25    }
26
27    pub const fn bytes(self) -> usize {
28        self.bytes
29    }
30
31    pub const fn chunks(self) -> segments::Count {
32        self.chunks
33    }
34
35    pub const fn is_empty(self) -> bool {
36        self.bytes == 0 && self.chunks.is_empty()
37    }
38
39    fn try_add(self, added: Self, scope: Scope) -> Result<Self, Error> {
40        let bytes = self
41            .bytes
42            .checked_add(added.bytes)
43            .ok_or(Error::ByteOverflow {
44                scope,
45                current: self.bytes,
46                added: added.bytes,
47            })?;
48        let chunks = self
49            .chunks
50            .checked_add(added.chunks)
51            .ok_or(Error::ChunkOverflow {
52                scope,
53                current: self.chunks,
54                added: added.chunks,
55            })?;
56        Ok(Self { bytes, chunks })
57    }
58
59    fn try_fit(self, max: Limits, scope: Scope) -> Result<Self, Error> {
60        if self.bytes > max.bytes {
61            return Err(Error::ByteLimit {
62                scope,
63                actual: self.bytes,
64                limit: max.bytes,
65            });
66        }
67        if u128::from(self.chunks.into_raw()) > max.chunks as u128 {
68            return Err(Error::ChunkLimit {
69                scope,
70                actual: self.chunks,
71                limit: max.chunks,
72            });
73        }
74        Ok(self)
75    }
76
77    fn try_sub(self, removed: Self, scope: Scope) -> Result<Self, Error> {
78        let bytes = self
79            .bytes
80            .checked_sub(removed.bytes)
81            .ok_or(Error::ByteUnderflow {
82                scope,
83                current: self.bytes,
84                removed: removed.bytes,
85            })?;
86        let chunks = self
87            .chunks
88            .checked_sub(removed.chunks)
89            .ok_or(Error::ChunkUnderflow {
90                scope,
91                current: self.chunks,
92                removed: removed.chunks,
93            })?;
94        Ok(Self { bytes, chunks })
95    }
96}
97
98impl Default for Amount {
99    fn default() -> Self {
100        Self::new(0, segments::Count::EMPTY)
101    }
102}
103
104/// Ownership disposition leaving pre-admission accounting.
105#[derive(Clone, Copy, PartialEq, Eq)]
106pub enum Disposition {
107    Discarded,
108    Admitted,
109}
110
111/// A committed body mutation exposed to lifecycle evidence.
112#[derive(PartialEq, Eq)]
113pub enum Mutation {
114    Head,
115    Reserved(Amount),
116    Released {
117        credit: Amount,
118        disposition: Disposition,
119    },
120    BuildReady,
121}
122
123impl Mode for Mutation {
124    fn committed(change: Mutation) -> Option<Self> {
125        Some(change)
126    }
127
128    fn credit(bytes: usize, chunks: segments::Count) -> Amount {
129        Amount::new(bytes, chunks)
130    }
131}
132
133/// Constructs the committed mutation of one event for the receiver's event type.
134///
135/// A statically disabled topology reports `None` from `committed`, so its
136/// uninhabited `Change` value is never built and no recording call can occur.
137pub trait Mode: Sized {
138    /// Wraps a mutation this receiver has already built, or `None` when this
139    /// topology records nothing.
140    fn committed(change: Mutation) -> Option<Self>;
141
142    fn head() -> Option<Self> {
143        Self::committed(Mutation::Head)
144    }
145
146    fn reserved(credit: Amount) -> Option<Self> {
147        (!credit.is_empty())
148            .then_some(Mutation::Reserved(credit))
149            .and_then(Self::committed)
150    }
151
152    fn released(credit: Amount, cause: Disposition) -> Option<Self> {
153        (!credit.is_empty())
154            .then_some(Mutation::Released {
155                credit,
156                disposition: cause,
157            })
158            .and_then(Self::committed)
159    }
160
161    fn build_ready() -> Option<Self> {
162        Self::committed(Mutation::BuildReady)
163    }
164
165    fn credit(bytes: usize, chunks: segments::Count) -> Amount;
166}
167
168impl Mode for Infallible {
169    fn committed(_: Mutation) -> Option<Self> {
170        None
171    }
172
173    fn credit(_: usize, _: segments::Count) -> Amount {
174        Amount::default()
175    }
176}
177
178/// Receives committed body mutations with statically selected evidence.
179pub trait Evidence {
180    type Change: Mode;
181
182    fn record(&mut self, change: Self::Change);
183
184    fn head(&mut self) {
185        if let Some(change) = Self::Change::head() {
186            self.record(change);
187        }
188    }
189
190    fn credit(bytes: usize, chunks: segments::Count) -> Amount
191    where
192        Self: Sized,
193    {
194        Self::Change::credit(bytes, chunks)
195    }
196
197    fn reserved(&mut self, credit: Amount) {
198        if let Some(change) = Self::Change::reserved(credit) {
199            self.record(change);
200        }
201    }
202
203    fn released(&mut self, credit: Amount, kind: Disposition) {
204        if let Some(change) = Self::Change::released(credit, kind) {
205            self.record(change);
206        }
207    }
208
209    fn build_ready(&mut self) {
210        if let Some(change) = Self::Change::build_ready() {
211            self.record(change);
212        }
213    }
214}
215
216impl<E: Evidence + ?Sized> Evidence for &mut E {
217    type Change = E::Change;
218
219    fn record(&mut self, change: Self::Change) {
220        E::record(self, change);
221    }
222}
223
224/// Zero-sized evidence for disabled production topologies.
225///
226#[derive(Clone, Copy, Default)]
227pub struct Disabled;
228
229impl Disabled {
230    pub const fn new() -> Self {
231        Self
232    }
233}
234
235impl Evidence for Disabled {
236    type Change = Infallible;
237
238    /// `Infallible` admits no value: eliminating it is the whole proof, and it
239    /// compiles to no instructions.
240    fn record(&mut self, change: Infallible) {
241        match change {}
242    }
243}
244
245/// Remaining pre-admission credit detached from its ingress state.
246#[must_use = "released credit must be recorded with its final disposition"]
247#[derive(Debug)]
248pub struct Release<O: Origin = raw::Stored> {
249    pub(in crate::body) amount: Amount,
250    _origin: O,
251}
252
253/// Committed connection-credit disposition after a body is consumed.
254#[must_use = "a rejected release must be explicitly abandoned"]
255pub enum Discard<O: Origin = raw::Stored> {
256    Applied,
257    Rejected { release: Release<O>, error: Error },
258}
259
260impl<O: Origin> Discard<O> {
261    /// Abandons rejected credit and returns its original settlement error.
262    pub fn abandon<E: Evidence>(self, evidence: E) -> Result<(), Error> {
263        match self {
264            Self::Applied => Ok(()),
265            Self::Rejected { release, error } => {
266                release.discard(evidence);
267                Err(error)
268            }
269        }
270    }
271}
272
273impl<O: Origin> Release<O> {
274    pub(in crate::body) const fn new(amount: Amount, origin: O) -> Self {
275        Self {
276            amount,
277            _origin: origin,
278        }
279    }
280
281    pub fn try_discard<E: Evidence>(
282        mut self,
283        ledger: &mut raw::Ledger<'_, O>,
284        mut evidence: E,
285    ) -> Discard<O> {
286        let change = match ledger.amount().try_drain(&mut self.amount) {
287            Ok(change) => change,
288            Err(error) => {
289                return Discard::Rejected {
290                    release: self,
291                    error,
292                };
293            }
294        };
295        let freed = change.commit();
296        evidence.released(freed, Disposition::Discarded);
297        Discard::Applied
298    }
299
300    /// Records abandonment when the connection ledger can no longer be settled.
301    pub fn discard<E: Evidence>(self, mut evidence: E) {
302        evidence.released(self.amount, Disposition::Discarded);
303    }
304}
305
306/// Byte and owner-count ceilings for one credit domain.
307#[derive(Clone, Copy, PartialEq, Eq)]
308pub struct Limits {
309    pub bytes: usize,
310    pub chunks: usize,
311}
312
313impl Limits {
314    pub const fn new(bytes: usize, chunks: usize) -> Self {
315        Self { bytes, chunks }
316    }
317}
318
319#[derive(Clone, Copy, PartialEq, Eq, Debug)]
320pub enum Scope {
321    Request,
322    Connection,
323}
324
325#[derive(Clone, Copy, PartialEq, Eq, Debug)]
326pub enum Error {
327    ByteOverflow {
328        scope: Scope,
329        current: usize,
330        added: usize,
331    },
332    ChunkOverflow {
333        scope: Scope,
334        current: segments::Count,
335        added: segments::Count,
336    },
337    ByteLimit {
338        scope: Scope,
339        actual: usize,
340        limit: usize,
341    },
342    ChunkLimit {
343        scope: Scope,
344        actual: segments::Count,
345        limit: usize,
346    },
347    ByteUnderflow {
348        scope: Scope,
349        current: usize,
350        removed: usize,
351    },
352    ChunkUnderflow {
353        scope: Scope,
354        current: segments::Count,
355        removed: segments::Count,
356    },
357}
358
359/// A fully validated body-credit mutation with exclusive commit authority.
360pub(in crate::body) struct Change<'a> {
361    request: &'a mut Amount,
362    connection: &'a mut Amount,
363    next_request: Amount,
364    next_connection: Amount,
365    changed: Amount,
366}
367
368impl Change<'_> {
369    pub(in crate::body) fn commit(self) -> Amount {
370        *self.request = self.next_request;
371        *self.connection = self.next_connection;
372        self.changed
373    }
374}
375
376impl Amount {
377    pub(in crate::body) fn try_commit<'a>(
378        &'a mut self,
379        req: &'a mut Amount,
380        amount: Amount,
381        rmax: Limits,
382        cmax: Limits,
383    ) -> Result<Change<'a>, Error> {
384        let next = req.try_add(amount, Scope::Request)?;
385        let total = self.try_add(amount, Scope::Connection)?;
386        let next = next.try_fit(rmax, Scope::Request)?;
387        let total = total.try_fit(cmax, Scope::Connection)?;
388
389        Ok(Change {
390            request: req,
391            connection: self,
392            next_request: next,
393            next_connection: total,
394            changed: amount,
395        })
396    }
397
398    pub(in crate::body) fn try_release<'a>(
399        &'a mut self,
400        req: &'a mut Amount,
401        amount: Amount,
402    ) -> Result<Change<'a>, Error> {
403        let next = req.try_sub(amount, Scope::Request)?;
404        let total = self.try_sub(amount, Scope::Connection)?;
405        Ok(Change {
406            request: req,
407            connection: self,
408            next_request: next,
409            next_connection: total,
410            changed: amount,
411        })
412    }
413
414    pub(in crate::body) fn try_drain<'a>(
415        &'a mut self,
416        req: &'a mut Amount,
417    ) -> Result<Change<'a>, Error> {
418        let freed = *req;
419        self.try_release(req, freed)
420    }
421}
422
423const _: () = {
424    assert!(core::mem::size_of::<Amount>() == 2 * core::mem::size_of::<usize>());
425    assert!(core::mem::size_of::<Release>() == core::mem::size_of::<Amount>());
426    assert!(
427        core::mem::size_of::<Release<region::Identity<'static>>>()
428            == core::mem::size_of::<Amount>()
429    );
430    assert!(core::mem::size_of::<Disabled>() == 0);
431};