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}