Skip to main content

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    /// The largest amount [`Work::charge_work`] would admit now, across every
40    /// ceiling it checks, without reserving it.
41    ///
42    /// Use it to bound work that cannot be charged step by step, such as an
43    /// external engine given a work limit up front. Concurrent consumption can
44    /// lower the observation, so the work must still be charged before it runs.
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use std::sync::Arc;
50    /// use structured_asn1::{Encoding, Limits, Parser, memory::{Memory, MemoryAccount, Work}};
51    /// let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1_000)));
52    /// let limits = Limits { max_work: 40, ..Limits::default() };
53    /// let parser = Parser::new(Encoding::Der, limits, memory);
54    /// let reader = parser.read(&[], 0)?;
55    /// let execution = reader.execution();
56    /// // The parser session is tighter than the shared account.
57    /// let remaining = execution.work_remaining();
58    /// assert_eq!(remaining, 40);
59    /// execution.charge_work(remaining as u64).unwrap();
60    /// assert!(execution.charge_work(1).is_err());
61    /// # Ok::<(), structured_asn1::Error>(())
62    /// ```
63    fn work_remaining(&self) -> usize;
64
65    /// Borrows a source's cumulative ceiling with this operation's account.
66    ///
67    /// For metadata queries that enter no ASN.1 value: no cursor, allocation,
68    /// account clone or synthetic depth is needed. A current root-operation
69    /// ceiling is preserved across foreign sources; construction itself scans
70    /// nothing. The caller admits its actual work through the returned view.
71    ///
72    /// # Examples
73    /// ```
74    /// use std::sync::Arc;
75    /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
76    /// let source = Parser::der(Memory::new(Arc::new(MemoryAccount::new(0, 10))));
77    /// let current = Memory::new(Arc::new(MemoryAccount::new(0, 10)));
78    /// current.execution_for(&source).charge_work(1)?;
79    /// assert_eq!(current.work_used(), 1);
80    /// # Ok::<(), structured_asn1::memory::MemoryError>(())
81    /// ```
82    fn execution_for<'operation>(
83        &'operation self,
84        parser: &'operation Parser,
85    ) -> Execution<'operation>;
86
87    /// Opens original input while borrowing this operation only for decoding.
88    /// Parsed elements and scalar views retain the longer original input
89    /// lifetime; no result needs to keep a completed operation alive merely
90    /// because it borrows input. Source and current ceilings still apply to
91    /// every read, scratch allocation and copy before that work.
92    /// A cursor itself cannot escape a local operation:
93    ///
94    /// ```compile_fail
95    /// use structured_asn1::{Encoding, Parser, ScopedReader, memory::Work};
96    /// fn escape<'source>(source: &'source Parser, data: &'source [u8])
97    ///     -> ScopedReader<'source, 'source>
98    /// {
99    ///     let operation = Parser::der(source.memory().clone());
100    ///     let query = operation.read(&[], 900).unwrap();
101    ///     let execution = query.execution();
102    ///     execution.read_scoped_source(source, data, 400, Encoding::Der, 0).unwrap()
103    /// }
104    /// ```
105    fn read_scoped_source<'source: 'operation, 'operation>(
106        &'operation self,
107        parser: &'source Parser,
108        data: &'source [u8],
109        base: u64,
110        encoding: Encoding,
111        depth: u32,
112    ) -> crate::Result<ScopedReader<'source, 'operation>>;
113
114    /// Nested form of [`Work::read_scoped_source`], retaining actual outer+1
115    /// depth. Only decoding borrows this operation; returned input views do not.
116    fn read_scoped_nested<'source: 'operation, 'operation>(
117        &'operation self,
118        parser: &'source Parser,
119        data: &'source [u8],
120        base: u64,
121        encoding: Encoding,
122        outer: u32,
123    ) -> crate::Result<ScopedReader<'source, 'operation>> {
124        let depth = outer.checked_add(1).ok_or_else(|| {
125            crate::Error::new(
126                crate::ErrorCode::LimitExceeded(crate::Limit::Depth),
127                base,
128                crate::Path::default(),
129            )
130        })?;
131        self.read_scoped_source(parser, data, base, encoding, depth)
132    }
133
134    /// Opens original nested bytes without discarding this execution boundary.
135    /// Memory retains its existing source-plus-aggregate admission; Execution
136    /// additionally preserves the current root operation before any decode.
137    /// Source parser/data/base/encoding and actual `outer + 1` depth remain
138    /// original. A transition replaces a previous foreign source ceiling,
139    /// never the operation owner. A fresh document uses depth zero through
140    /// [`Work::read_source`], rather than inventing a nested carrier level.
141    ///
142    /// `Self: 'source` retains the real budget lifetimes, not the borrow of a
143    /// temporary Execution wrapper. The returned reader owns its account handle;
144    /// no parser/diagnostic clone, new domain or budget reset is introduced.
145    /// It still cannot outlive either actual parser budget:
146    ///
147    /// ```compile_fail
148    /// use std::sync::Arc;
149    /// use structured_asn1::{Parser, Encoding, memory::{Memory,MemoryAccount,Work}};
150    /// let memory = Memory::new(Arc::new(MemoryAccount::new(4096,4096)));
151    /// let source = Parser::ber(memory.clone());
152    /// let mut lazy;
153    /// {
154    ///     let operation = Parser::ber(memory);
155    ///     let query = operation.read(&[],900).unwrap();
156    ///     lazy = query.execution().read_nested(&source,&[2,1,7],400,Encoding::Ber,0).unwrap();
157    /// }
158    /// lazy.next_element().unwrap();
159    /// ```
160    ///
161    /// ```compile_fail
162    /// use std::sync::Arc;
163    /// use structured_asn1::{Parser, Encoding, memory::{Memory,MemoryAccount,Work}};
164    /// let memory = Memory::new(Arc::new(MemoryAccount::new(4096,4096)));
165    /// let operation = Parser::ber(memory.clone());
166    /// let query = operation.read(&[],900).unwrap();
167    /// let mut lazy;
168    /// {
169    ///     let source = Parser::ber(memory);
170    ///     lazy = query.execution().read_nested(&source,&[2,1,7],400,Encoding::Ber,0).unwrap();
171    /// }
172    /// lazy.next_element().unwrap();
173    /// ```
174    fn read_nested<'source>(
175        &self,
176        parser: &'source Parser,
177        data: &'source [u8],
178        base: u64,
179        encoding: Encoding,
180        outer: u32,
181    ) -> crate::Result<Reader<'source>>
182    where
183        Self: 'source;
184
185    /// Opens an original source window at its actual ASN.1 depth.
186    ///
187    /// This has the same source/account and lifetime guarantees as
188    /// [`Work::read_nested`], without adding a carrier level. A fresh document
189    /// begins at zero; an already nested window must supply its original depth.
190    /// Current operation ceilings remain in force even when a retained query
191    /// uses a new source parser bounded by the retained input size.
192    fn read_source<'source>(
193        &self,
194        parser: &'source Parser,
195        data: &'source [u8],
196        base: u64,
197        encoding: Encoding,
198        depth: u32,
199    ) -> crate::Result<Reader<'source>>
200    where
201        Self: 'source;
202
203    /// Creates empty accounted scratch retaining this execution boundary.
204    ///
205    /// No allocation occurs until a fallible growth operation. A collection
206    /// borrowing [`Execution`] cannot outlive that parser or execution account.
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use std::sync::Arc;
212    /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
213    /// let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
214    /// let reader = parser.read(&[], 0).unwrap();
215    /// let mut scratch = reader.execution().vector();
216    /// scratch.try_push(42_u8)?;
217    /// assert_eq!(&*scratch, &[42]);
218    /// # Ok::<(), structured_asn1::memory::MemoryError>(())
219    /// ```
220    #[must_use]
221    fn vector<T>(&self) -> OwnedVec<T, Self> {
222        OwnedVec {
223            values: allocator_api2::vec::Vec::new_in(allocator::AccountAllocator::new(
224                self.clone(),
225            )),
226        }
227    }
228
229    /// Creates a bounded scratch map in this storage and execution domain.
230    /// Hash/probes, table growth and value movement retain the same ceilings.
231    #[must_use]
232    fn map<K: HashWork, V>(&self) -> OwnedMap<K, V, Self> {
233        OwnedMap::new(self.clone())
234    }
235}
236
237impl private::Sealed for Memory {}
238
239impl Work for Memory {
240    type Retained = ();
241
242    fn retain_storage(&self) {}
243
244    fn into_storage_owner(self, (): ()) -> Memory {
245        self
246    }
247
248    fn with_storage_owner(&self, memory: Memory) -> (Self, ()) {
249        (memory, ())
250    }
251
252    fn memory(&self) -> &Memory {
253        self
254    }
255
256    fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
257        Memory::charge_work(self, amount)
258    }
259
260    fn work_remaining(&self) -> usize {
261        Memory::work_remaining(self)
262    }
263
264    fn execution_for<'operation>(
265        &'operation self,
266        parser: &'operation Parser,
267    ) -> Execution<'operation> {
268        parser.execution_in(self)
269    }
270
271    fn read_scoped_source<'source: 'operation, 'operation>(
272        &'operation self,
273        parser: &'source Parser,
274        data: &'source [u8],
275        base: u64,
276        encoding: Encoding,
277        depth: u32,
278    ) -> crate::Result<ScopedReader<'source, 'operation>> {
279        parser.read_scoped_source_in(data, base, encoding, depth, None, self)
280    }
281
282    fn read_nested<'source>(
283        &self,
284        parser: &'source Parser,
285        data: &'source [u8],
286        base: u64,
287        encoding: Encoding,
288        outer: u32,
289    ) -> crate::Result<Reader<'source>>
290    where
291        Self: 'source,
292    {
293        parser.read_nested_in(data, base, encoding, outer, self)
294    }
295
296    fn read_source<'source>(
297        &self,
298        parser: &'source Parser,
299        data: &'source [u8],
300        base: u64,
301        encoding: Encoding,
302        depth: u32,
303    ) -> crate::Result<Reader<'source>>
304    where
305        Self: 'source,
306    {
307        parser.read_source_in(data, base, encoding, depth, self)
308    }
309}
310
311/// A borrowed aggregate account and original parser-session work ceiling.
312///
313/// Obtain this from [`crate::Reader::execution`]. Copies allocate nothing and
314/// acquire no shared ownership. Scratch storage and work use the reader's
315/// current account; rebinding the account never resets the parser ceiling.
316///
317/// A parser-bound allocation cannot survive its borrowed owner:
318///
319/// ```compile_fail
320/// use std::sync::Arc;
321/// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
322/// let mut scratch;
323/// {
324///     let parser = Parser::ber(Memory::new(Arc::new(MemoryAccount::new(1024, 1024))));
325///     let reader = parser.read(&[], 0).unwrap();
326///     scratch = reader.execution().vector();
327///     scratch.try_push(42_u8).unwrap();
328/// }
329/// drop(scratch);
330/// ```
331#[derive(Debug, Clone, Copy)]
332pub struct Execution<'a> {
333    memory: &'a Memory,
334    budget: BudgetView<'a>,
335}
336
337impl<'a> Execution<'a> {
338    pub(crate) fn new(memory: &'a Memory, budget: BudgetView<'a>) -> Self {
339        Self { memory, budget }
340    }
341}
342
343impl private::Sealed for Execution<'_> {}
344
345impl Work for Execution<'_> {
346    type Retained = Memory;
347
348    fn execution_for<'operation>(
349        &'operation self,
350        parser: &'operation Parser,
351    ) -> Execution<'operation> {
352        parser.execution_query_in(self.memory, self.budget.operation())
353    }
354
355    fn retain_storage(&self) -> Memory {
356        self.memory.clone()
357    }
358
359    fn into_storage_owner(self, retained: Memory) -> Memory {
360        retained
361    }
362
363    fn with_storage_owner(&self, memory: Memory) -> (Self, Memory) {
364        (*self, memory)
365    }
366
367    fn memory(&self) -> &Memory {
368        self.memory
369    }
370
371    fn charge_work(&self, amount: u64) -> Result<(), MemoryError> {
372        self.budget
373            .work_in(amount, self.memory)
374            .map_err(|_| MemoryError::WorkExceeded)
375    }
376
377    fn work_remaining(&self) -> usize {
378        let aggregate = self.memory.work_remaining();
379        // The aggregate remainder fits usize, so a session remainder that does
380        // not fit can never be the smaller of the two.
381        usize::try_from(self.budget.work_remaining())
382            .map_or(aggregate, |session| session.min(aggregate))
383    }
384
385    fn read_scoped_source<'source: 'operation, 'operation>(
386        &'operation self,
387        parser: &'source Parser,
388        data: &'source [u8],
389        base: u64,
390        encoding: Encoding,
391        depth: u32,
392    ) -> crate::Result<ScopedReader<'source, 'operation>> {
393        parser.read_scoped_source_in(
394            data,
395            base,
396            encoding,
397            depth,
398            Some(self.budget.operation()),
399            self.memory,
400        )
401    }
402
403    fn read_nested<'source>(
404        &self,
405        parser: &'source Parser,
406        data: &'source [u8],
407        base: u64,
408        encoding: Encoding,
409        outer: u32,
410    ) -> crate::Result<Reader<'source>>
411    where
412        Self: 'source,
413    {
414        parser.read_nested_operation_in(
415            data,
416            base,
417            encoding,
418            outer,
419            self.budget.operation(),
420            self.memory,
421        )
422    }
423
424    fn read_source<'source>(
425        &self,
426        parser: &'source Parser,
427        data: &'source [u8],
428        base: u64,
429        encoding: Encoding,
430        depth: u32,
431    ) -> crate::Result<Reader<'source>>
432    where
433        Self: 'source,
434    {
435        parser.read_query_budget_in(
436            data,
437            base,
438            encoding,
439            depth,
440            self.budget.operation(),
441            self.memory,
442        )
443    }
444}