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}