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}