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