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