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