structured_asn1/memory/work.rs
1//! Borrowed parser execution for transitive scratch work.
2
3use super::{Memory, MemoryError, OwnedVec, allocator};
4use crate::limits::Budget;
5
6mod private {
7 pub trait Sealed {}
8}
9
10/// An execution domain used by accounted collections and schema processing.
11///
12/// [`Memory`] owns an independent aggregate domain. [`Execution`] additionally
13/// borrows a parser's cumulative ceiling. The sealed implementations ensure
14/// that allocator and collection admission cannot bypass either boundary.
15pub trait Work: Clone + private::Sealed {
16 /// Storage ownership retained by the allocator independently of borrowed work.
17 type Retained: core::fmt::Debug;
18
19 /// Keeps an allocation's account alive even if its collection is forgotten.
20 /// Owned [`Memory`] already holds that account; borrowed [`Execution`]
21 /// supplies one owning handle per collection without allocating a new account.
22 fn retain_storage(&self) -> Self::Retained;
23
24 /// The account retaining allocated capacity.
25 fn memory(&self) -> &Memory;
26
27 /// Admits work before execution, against every applicable ceiling.
28 fn charge_work(&self, amount: u64) -> Result<(), MemoryError>;
29
30 /// Creates empty accounted scratch retaining this execution boundary.
31 ///
32 /// No allocation occurs until a fallible growth operation. A collection
33 /// borrowing [`Execution`] cannot outlive that parser or execution account.
34 ///
35 /// # Examples
36 ///
37 /// ```
38 /// use std::sync::Arc;
39 /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
40 /// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
41 /// let reader = parser.read(&[], 0).unwrap();
42 /// let mut scratch = reader.execution().vector();
43 /// scratch.try_push(42_u8)?;
44 /// assert_eq!(&*scratch, &[42]);
45 /// # Ok::<(), structured_asn1::memory::MemoryError>(())
46 /// ```
47 #[must_use]
48 fn vector<T>(&self) -> OwnedVec<T, Self> {
49 OwnedVec {
50 values: allocator_api2::vec::Vec::new_in(allocator::AccountAllocator::new(
51 self.clone(),
52 )),
53 }
54 }
55}
56
57impl private::Sealed for Memory {}
58
59impl Work for Memory {
60 type Retained = ();
61
62 fn retain_storage(&self) {}
63
64 fn memory(&self) -> &Memory {
65 self
66 }
67
68 fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
69 Memory::charge_work(self, amount)
70 }
71}
72
73/// A borrowed aggregate account and original parser-session work ceiling.
74///
75/// Obtain this from [`crate::Reader::execution`]. Copies allocate nothing and
76/// acquire no shared ownership. Scratch storage and work use the reader's
77/// current account; rebinding the account never resets the parser ceiling.
78///
79/// A parser-bound allocation cannot survive its borrowed owner:
80///
81/// ```compile_fail
82/// use std::sync::Arc;
83/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
84/// let mut scratch;
85/// {
86/// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
87/// let reader = parser.read(&[], 0).unwrap();
88/// scratch = reader.execution().vector();
89/// scratch.try_push(42_u8).unwrap();
90/// }
91/// drop(scratch);
92/// ```
93#[derive(Debug, Clone, Copy)]
94pub struct Execution<'a> {
95 memory: &'a Memory,
96 budget: &'a Budget,
97}
98
99impl<'a> Execution<'a> {
100 pub(crate) fn new(memory: &'a Memory, budget: &'a Budget) -> Self {
101 Self { memory, budget }
102 }
103}
104
105impl private::Sealed for Execution<'_> {}
106
107impl Work for Execution<'_> {
108 type Retained = Memory;
109
110 fn retain_storage(&self) -> Memory {
111 self.memory.clone()
112 }
113
114 fn memory(&self) -> &Memory {
115 self.memory
116 }
117
118 fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
119 self.budget
120 .work_in(amount, self.memory)
121 .map_err(|_| MemoryError::WorkExceeded)
122 }
123}