structured_asn1/memory/work.rs
1//! Borrowed parser execution for transitive scratch work.
2
3use super::{HashWork, Memory, MemoryError, OwnedMap, OwnedVec, allocator};
4use crate::limits::BudgetView;
5use crate::{Encoding, Parser, Reader, ScopedReader};
6
7mod private {
8 pub trait Sealed {}
9}
10
11/// An execution domain used by accounted collections and schema processing.
12///
13/// [`Memory`] owns an independent aggregate domain. [`Execution`] additionally
14/// borrows a parser's cumulative ceiling. The sealed implementations ensure
15/// that allocator and collection admission cannot bypass either boundary.
16pub trait Work: Clone + private::Sealed {
17 /// Storage ownership retained by the allocator independently of borrowed work.
18 type Retained: core::fmt::Debug;
19
20 /// Keeps an allocation's account alive even if its collection is forgotten.
21 /// Owned [`Memory`] already holds that account; borrowed [`Execution`]
22 /// supplies one owning handle per collection without allocating a new account.
23 fn retain_storage(&self) -> Self::Retained;
24
25 /// Moves the existing account owner when finishing independently owned storage.
26 /// The sealed implementations preserve the account without cloning it.
27 fn into_storage_owner(self, retained: Self::Retained) -> Memory;
28
29 /// Attaches current work while moving an existing same-account owner.
30 /// The caller verifies account identity before transferring storage.
31 fn with_storage_owner(&self, memory: Memory) -> (Self, Self::Retained);
32
33 /// The account retaining allocated capacity.
34 fn memory(&self) -> &Memory;
35
36 /// Admits work before execution, against every applicable ceiling.
37 fn charge_work(&self, amount: u64) -> Result<(), MemoryError>;
38
39 /// Opens original input while borrowing this operation only for decoding.
40 /// Parsed elements and scalar views retain the longer original input
41 /// lifetime; no result needs to keep a completed operation alive merely
42 /// because it borrows input. Source and current ceilings still apply to
43 /// every read, scratch allocation and copy before that work.
44 /// A cursor itself cannot escape a local operation:
45 ///
46 /// ```compile_fail
47 /// use structured_asn1::{Encoding, Parser, ScopedReader, memory::Work};
48 /// fn escape<'source>(source: &'source Parser, data: &'source [u8])
49 /// -> ScopedReader<'source, 'source>
50 /// {
51 /// let operation = Parser::der(source.memory().clone());
52 /// let query = operation.read(&[], 900).unwrap();
53 /// let execution = query.execution();
54 /// execution.read_scoped_source(source, data, 400, Encoding::Der, 0).unwrap()
55 /// }
56 /// ```
57 fn read_scoped_source<'source: 'operation, 'operation>(
58 &'operation self,
59 parser: &'source Parser,
60 data: &'source [u8],
61 base: u64,
62 encoding: Encoding,
63 depth: u32,
64 ) -> crate::Result<ScopedReader<'source, 'operation>>;
65
66 /// Nested form of [`Work::read_scoped_source`], retaining actual outer+1
67 /// depth. Only decoding borrows this operation; returned input views do not.
68 fn read_scoped_nested<'source: 'operation, 'operation>(
69 &'operation self,
70 parser: &'source Parser,
71 data: &'source [u8],
72 base: u64,
73 encoding: Encoding,
74 outer: u32,
75 ) -> crate::Result<ScopedReader<'source, 'operation>> {
76 let depth = outer.checked_add(1).ok_or_else(|| {
77 crate::Error::new(
78 crate::ErrorCode::LimitExceeded(crate::Limit::Depth),
79 base,
80 crate::Path::default(),
81 )
82 })?;
83 self.read_scoped_source(parser, data, base, encoding, depth)
84 }
85
86 /// Opens original nested bytes without discarding this execution boundary.
87 /// Memory retains its existing source-plus-aggregate admission; Execution
88 /// additionally preserves the current root operation before any decode.
89 /// Source parser/data/base/encoding and actual `outer + 1` depth remain
90 /// original. A transition replaces a previous foreign source ceiling,
91 /// never the operation owner. A fresh document uses depth zero through
92 /// [`Work::read_source`], rather than inventing a nested carrier level.
93 ///
94 /// `Self: 'source` retains the real budget lifetimes, not the borrow of a
95 /// temporary Execution wrapper. The returned reader owns its account handle;
96 /// no parser/diagnostic clone, new domain or budget reset is introduced.
97 /// It still cannot outlive either actual parser budget:
98 ///
99 /// ```compile_fail
100 /// use std::sync::Arc;
101 /// use structured_asn1::{Parser, Encoding, memory::{Memory,MemoryAccount,Work}};
102 /// let memory = Memory::new(Arc::new(MemoryAccount::new(4096,4096)));
103 /// let source = Parser::ber(memory.clone());
104 /// let mut lazy;
105 /// {
106 /// let operation = Parser::ber(memory);
107 /// let query = operation.read(&[],900).unwrap();
108 /// lazy = query.execution().read_nested(&source,&[2,1,7],400,Encoding::Ber,0).unwrap();
109 /// }
110 /// lazy.next_element().unwrap();
111 /// ```
112 ///
113 /// ```compile_fail
114 /// use std::sync::Arc;
115 /// use structured_asn1::{Parser, Encoding, memory::{Memory,MemoryAccount,Work}};
116 /// let memory = Memory::new(Arc::new(MemoryAccount::new(4096,4096)));
117 /// let operation = Parser::ber(memory.clone());
118 /// let query = operation.read(&[],900).unwrap();
119 /// let mut lazy;
120 /// {
121 /// let source = Parser::ber(memory);
122 /// lazy = query.execution().read_nested(&source,&[2,1,7],400,Encoding::Ber,0).unwrap();
123 /// }
124 /// lazy.next_element().unwrap();
125 /// ```
126 fn read_nested<'source>(
127 &self,
128 parser: &'source Parser,
129 data: &'source [u8],
130 base: u64,
131 encoding: Encoding,
132 outer: u32,
133 ) -> crate::Result<Reader<'source>>
134 where
135 Self: 'source;
136
137 /// Opens an original source window at its actual ASN.1 depth.
138 ///
139 /// This has the same source/account and lifetime guarantees as
140 /// [`Work::read_nested`], without adding a carrier level. A fresh document
141 /// begins at zero; an already nested window must supply its original depth.
142 /// Current operation ceilings remain in force even when a retained query
143 /// uses a new source parser bounded by the retained input size.
144 fn read_source<'source>(
145 &self,
146 parser: &'source Parser,
147 data: &'source [u8],
148 base: u64,
149 encoding: Encoding,
150 depth: u32,
151 ) -> crate::Result<Reader<'source>>
152 where
153 Self: 'source;
154
155 /// Creates empty accounted scratch retaining this execution boundary.
156 ///
157 /// No allocation occurs until a fallible growth operation. A collection
158 /// borrowing [`Execution`] cannot outlive that parser or execution account.
159 ///
160 /// # Examples
161 ///
162 /// ```
163 /// use std::sync::Arc;
164 /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
165 /// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
166 /// let reader = parser.read(&[], 0).unwrap();
167 /// let mut scratch = reader.execution().vector();
168 /// scratch.try_push(42_u8)?;
169 /// assert_eq!(&*scratch, &[42]);
170 /// # Ok::<(), structured_asn1::memory::MemoryError>(())
171 /// ```
172 #[must_use]
173 fn vector<T>(&self) -> OwnedVec<T, Self> {
174 OwnedVec {
175 values: allocator_api2::vec::Vec::new_in(allocator::AccountAllocator::new(
176 self.clone(),
177 )),
178 }
179 }
180
181 /// Creates a bounded scratch map in this storage and execution domain.
182 /// Hash/probes, table growth and value movement retain the same ceilings.
183 #[must_use]
184 fn map<K: HashWork, V>(&self) -> OwnedMap<K, V, Self> {
185 OwnedMap::new(self.clone())
186 }
187}
188
189impl private::Sealed for Memory {}
190
191impl Work for Memory {
192 type Retained = ();
193
194 fn retain_storage(&self) {}
195
196 fn into_storage_owner(self, (): ()) -> Memory {
197 self
198 }
199
200 fn with_storage_owner(&self, memory: Memory) -> (Self, ()) {
201 (memory, ())
202 }
203
204 fn memory(&self) -> &Memory {
205 self
206 }
207
208 fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
209 Memory::charge_work(self, amount)
210 }
211
212 fn read_scoped_source<'source: 'operation, 'operation>(
213 &'operation self,
214 parser: &'source Parser,
215 data: &'source [u8],
216 base: u64,
217 encoding: Encoding,
218 depth: u32,
219 ) -> crate::Result<ScopedReader<'source, 'operation>> {
220 parser.read_scoped_source_in(data, base, encoding, depth, None, self)
221 }
222
223 fn read_nested<'source>(
224 &self,
225 parser: &'source Parser,
226 data: &'source [u8],
227 base: u64,
228 encoding: Encoding,
229 outer: u32,
230 ) -> crate::Result<Reader<'source>>
231 where
232 Self: 'source,
233 {
234 parser.read_nested_in(data, base, encoding, outer, self)
235 }
236
237 fn read_source<'source>(
238 &self,
239 parser: &'source Parser,
240 data: &'source [u8],
241 base: u64,
242 encoding: Encoding,
243 depth: u32,
244 ) -> crate::Result<Reader<'source>>
245 where
246 Self: 'source,
247 {
248 parser.read_source_in(data, base, encoding, depth, self)
249 }
250}
251
252/// A borrowed aggregate account and original parser-session work ceiling.
253///
254/// Obtain this from [`crate::Reader::execution`]. Copies allocate nothing and
255/// acquire no shared ownership. Scratch storage and work use the reader's
256/// current account; rebinding the account never resets the parser ceiling.
257///
258/// A parser-bound allocation cannot survive its borrowed owner:
259///
260/// ```compile_fail
261/// use std::sync::Arc;
262/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
263/// let mut scratch;
264/// {
265/// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
266/// let reader = parser.read(&[], 0).unwrap();
267/// scratch = reader.execution().vector();
268/// scratch.try_push(42_u8).unwrap();
269/// }
270/// drop(scratch);
271/// ```
272#[derive(Debug, Clone, Copy)]
273pub struct Execution<'a> {
274 memory: &'a Memory,
275 budget: BudgetView<'a>,
276}
277
278impl<'a> Execution<'a> {
279 pub(crate) fn new(memory: &'a Memory, budget: BudgetView<'a>) -> Self {
280 Self { memory, budget }
281 }
282}
283
284impl private::Sealed for Execution<'_> {}
285
286impl Work for Execution<'_> {
287 type Retained = Memory;
288
289 fn retain_storage(&self) -> Memory {
290 self.memory.clone()
291 }
292
293 fn into_storage_owner(self, retained: Memory) -> Memory {
294 retained
295 }
296
297 fn with_storage_owner(&self, memory: Memory) -> (Self, Memory) {
298 (*self, memory)
299 }
300
301 fn memory(&self) -> &Memory {
302 self.memory
303 }
304
305 fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
306 self.budget
307 .work_in(amount, self.memory)
308 .map_err(|_| MemoryError::WorkExceeded)
309 }
310
311 fn read_scoped_source<'source: 'operation, 'operation>(
312 &'operation self,
313 parser: &'source Parser,
314 data: &'source [u8],
315 base: u64,
316 encoding: Encoding,
317 depth: u32,
318 ) -> crate::Result<ScopedReader<'source, 'operation>> {
319 parser.read_scoped_source_in(
320 data,
321 base,
322 encoding,
323 depth,
324 Some(self.budget.operation()),
325 self.memory,
326 )
327 }
328
329 fn read_nested<'source>(
330 &self,
331 parser: &'source Parser,
332 data: &'source [u8],
333 base: u64,
334 encoding: Encoding,
335 outer: u32,
336 ) -> crate::Result<Reader<'source>>
337 where
338 Self: 'source,
339 {
340 parser.read_nested_operation_in(
341 data,
342 base,
343 encoding,
344 outer,
345 self.budget.operation(),
346 self.memory,
347 )
348 }
349
350 fn read_source<'source>(
351 &self,
352 parser: &'source Parser,
353 data: &'source [u8],
354 base: u64,
355 encoding: Encoding,
356 depth: u32,
357 ) -> crate::Result<Reader<'source>>
358 where
359 Self: 'source,
360 {
361 parser.read_query_budget_in(
362 data,
363 base,
364 encoding,
365 depth,
366 self.budget.operation(),
367 self.memory,
368 )
369 }
370}