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}