Skip to main content

structured_asn1/
memory.rs

1//! Shared storage accounting for independently owned results.
2//!
3//! The caller creates the trusted account before processing input. Containers
4//! retain that account independently of any parser and release their charge
5//! only when the backing allocation is deallocated. Input buffers supplied by
6//! the caller are outside these allocations; borrowed views do not copy them.
7
8use alloc::sync::Arc;
9use core::fmt;
10use core::ops::{Deref, DerefMut};
11use core::sync::atomic::{AtomicUsize, Ordering};
12
13use allocator_api2::collections::TryReserveErrorKind;
14use allocator_api2::vec::Vec;
15
16mod allocator;
17mod map;
18mod sort;
19mod string;
20mod work;
21pub use map::{HashWork, OwnedMap};
22pub use sort::SortError;
23pub use string::OwnedString;
24pub use work::{Execution, Work};
25
26/// A failed storage operation, distinct from malformed input.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum MemoryError {
29    /// The shared capacity allowance cannot cover this allocation's peak.
30    LimitExceeded,
31    /// The underlying allocator could not supply storage.
32    OutOfMemory,
33    /// The requested capacity cannot be represented by an allocation layout.
34    CapacityOverflow,
35    /// An aggregate or borrowed parser-session work allowance is exhausted.
36    WorkExceeded,
37}
38
39impl fmt::Display for MemoryError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.write_str(match self {
42            Self::LimitExceeded => "memory budget spent",
43            Self::OutOfMemory => "allocator could not supply storage",
44            Self::CapacityOverflow => "storage capacity overflow",
45            Self::WorkExceeded => "work budget spent",
46        })
47    }
48}
49
50#[cfg(feature = "std")]
51impl std::error::Error for MemoryError {}
52
53/// Caller-owned accounting state, constructed without allocating memory.
54///
55/// The caller supplies its shared ownership separately, during trusted setup.
56/// This keeps account creation out of document-driven allocation paths.
57#[derive(Debug)]
58pub struct MemoryAccount {
59    limit: usize,
60    used: AtomicUsize,
61    work_limit: usize,
62    work: AtomicUsize,
63}
64
65impl MemoryAccount {
66    /// An immutable capacity allowance, in bytes of allocator-visible storage.
67    #[must_use]
68    pub const fn new(limit: usize, work_limit: usize) -> Self {
69        Self {
70            limit,
71            used: AtomicUsize::new(0),
72            work_limit,
73            work: AtomicUsize::new(0),
74        }
75    }
76
77    fn reserve(&self, bytes: usize) -> core::result::Result<Reservation<'_>, MemoryError> {
78        let mut used = self.used.load(Ordering::Relaxed);
79        loop {
80            debug_assert!(used <= self.limit);
81            if bytes > self.limit - used {
82                return Err(MemoryError::LimitExceeded);
83            }
84            match self.used.compare_exchange_weak(
85                used,
86                used + bytes,
87                Ordering::Relaxed,
88                Ordering::Relaxed,
89            ) {
90                Ok(_) => {
91                    return Ok(Reservation {
92                        account: self,
93                        bytes,
94                    });
95                }
96                Err(current) => used = current,
97            }
98        }
99    }
100
101    fn release(&self, bytes: usize) {
102        let previous = self.used.fetch_sub(bytes, Ordering::Relaxed);
103        debug_assert!(previous >= bytes);
104    }
105}
106
107struct Reservation<'a> {
108    account: &'a MemoryAccount,
109    bytes: usize,
110}
111
112impl Reservation<'_> {
113    fn commit(mut self) {
114        self.bytes = 0;
115    }
116}
117
118impl Drop for Reservation<'_> {
119    fn drop(&mut self) {
120        self.account.release(self.bytes);
121    }
122}
123
124/// A shared storage domain retained by owned results.
125///
126/// Cloning this handle shares existing state and allocates no new block.
127#[derive(Debug, Clone)]
128pub struct Memory {
129    account: Arc<MemoryAccount>,
130}
131
132impl Memory {
133    /// Uses caller-supplied shared state; this constructor allocates nothing.
134    #[must_use]
135    pub fn new(account: Arc<MemoryAccount>) -> Self {
136        Self { account }
137    }
138
139    /// The immutable allowance in bytes.
140    #[must_use]
141    pub fn limit(&self) -> usize {
142        self.account.limit
143    }
144
145    /// Currently reserved bytes, including pending allocations and growth peaks.
146    #[must_use]
147    pub fn used(&self) -> usize {
148        self.account.used.load(Ordering::Relaxed)
149    }
150
151    /// Whether two handles share exactly the same account, without allocation.
152    #[must_use]
153    pub fn same_account(&self, other: &Self) -> bool {
154        Arc::ptr_eq(&self.account, &other.account)
155    }
156
157    /// Cumulative work already admitted, including operations that later failed.
158    #[must_use]
159    pub fn work_used(&self) -> usize {
160        self.account.work.load(Ordering::Relaxed)
161    }
162
163    /// Currently available aggregate work units, without reserving them.
164    /// Concurrent consumption can reduce this observation; every operation
165    /// must still use [`Self::charge_work`] before execution.
166    #[must_use]
167    pub fn work_remaining(&self) -> usize {
168        let spent = self.account.work.load(Ordering::Relaxed);
169        debug_assert!(spent <= self.account.work_limit);
170        self.account.work_limit - spent
171    }
172
173    /// Charges work before performing it; storage release never refunds work.
174    ///
175    /// An amount unrepresentable on this target is refused before any work,
176    /// preserving the existing u64 scanning API on 32-bit targets.
177    pub fn charge_work(&self, amount: u64) -> core::result::Result<(), MemoryError> {
178        let amount = usize::try_from(amount).map_err(|_| MemoryError::WorkExceeded)?;
179        let mut spent = self.account.work.load(Ordering::Relaxed);
180        loop {
181            debug_assert!(spent <= self.account.work_limit);
182            if amount > self.account.work_limit - spent {
183                return Err(MemoryError::WorkExceeded);
184            }
185            match self.account.work.compare_exchange_weak(
186                spent,
187                spent + amount,
188                Ordering::Relaxed,
189                Ordering::Relaxed,
190            ) {
191                Ok(_) => return Ok(()),
192                Err(current) => spent = current,
193            }
194        }
195    }
196
197    /// Reserves storage for an adapter-owned allocation before allocating it.
198    ///
199    /// Keep the returned guard until after deallocation, including rollback on
200    /// failure. Reserve the complete actual capacity; a growth must retain the
201    /// old guard while admitting the replacement. This bridge does not allocate.
202    pub fn reserve(&self, bytes: usize) -> core::result::Result<MemoryReservation, MemoryError> {
203        let reservation = self.account.reserve(bytes)?;
204        reservation.commit();
205        Ok(MemoryReservation {
206            memory: self.clone(),
207            bytes,
208        })
209    }
210
211    /// An empty owned collection sharing this domain, without allocating storage.
212    #[must_use]
213    pub fn vector<T>(&self) -> OwnedVec<T> {
214        Work::vector(self)
215    }
216
217    /// An empty accounted hash map, without allocating storage.
218    #[must_use]
219    pub fn map<K: HashWork, V>(&self) -> OwnedMap<K, V> {
220        OwnedMap::new(self.clone())
221    }
222
223    /// Empty accounted UTF-8 text, without allocating storage.
224    #[must_use]
225    pub fn string(&self) -> OwnedString {
226        OwnedString::new(self)
227    }
228}
229
230/// A reservation retained alongside an adapter-owned allocation.
231///
232/// Its drop releases the reserved charge. The adapter must deallocate its block
233/// first; dropping this guard earlier would misreport the allocation's lifetime.
234#[derive(Debug)]
235#[must_use = "retain the reservation until its allocation has been deallocated"]
236pub struct MemoryReservation {
237    memory: Memory,
238    bytes: usize,
239}
240
241impl Drop for MemoryReservation {
242    fn drop(&mut self) {
243        self.memory.account.release(self.bytes);
244    }
245}
246
247/// An owned collection whose capacity remains charged until deallocation.
248///
249/// It exposes slices rather than the underlying growable container, so callers
250/// cannot bypass fallible growth. Moving it retains accounting; copying values
251/// requires a fresh reservation. Clearing values does not release capacity.
252/// The default [`Memory`] execution owner is independent of parser lifetime;
253/// a collection using [`Execution`] borrows its parser's work ceiling too.
254pub struct OwnedVec<T, W: Work = Memory> {
255    values: Vec<T, allocator::AccountAllocator<W>>,
256}
257
258impl<T> OwnedVec<T> {
259    /// Temporarily attaches the current operation's work ceilings.
260    ///
261    /// The execution must use this allocation's existing account. On mismatch
262    /// the original collection is returned unchanged. On success the pointer,
263    /// values, capacity, charge and owning account handle move without copying,
264    /// allocation or new work. Previously admitted work is neither reset nor
265    /// charged again. Call [`OwnedVec::into_owned`] to retain the allocation
266    /// independently after the callback, including after a refused mutation.
267    pub fn with_execution<W: Work>(self, execution: &W) -> Result<OwnedVec<T, W>, Self> {
268        if !self.memory().same_account(execution.memory()) {
269            return Err(self);
270        }
271        Ok(OwnedVec {
272            values: allocator::scope(self.values, execution),
273        })
274    }
275}
276
277impl<T, W: Work> OwnedVec<T, W> {
278    /// Finishes bounded construction as an independently owned result.
279    ///
280    /// Transfers the existing allocation and its original account without
281    /// copying elements, reallocating, changing capacity or reserving again.
282    /// Subsequent operations use that aggregate account; the parser-local
283    /// ceiling governed construction and is no longer attached. Borrows in
284    /// `T` retain their own lifetimes.
285    ///
286    /// # Examples
287    /// ```
288    /// use std::sync::Arc;
289    /// use structured_asn1::{Parser, memory::{Memory, MemoryAccount, Work}};
290    /// let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
291    /// let result;
292    /// {
293    ///     let parser = Parser::ber(memory.clone());
294    ///     let reader = parser.read(&[], 0)?;
295    ///     let mut scratch = reader.execution().vector();
296    ///     scratch.try_push(7_u8).unwrap();
297    ///     result = scratch.into_owned();
298    /// }
299    /// assert_eq!(&*result, &[7]);
300    /// assert!(memory.used() > 0);
301    /// drop(result);
302    /// assert_eq!(memory.used(), 0);
303    /// # Ok::<(), structured_asn1::Error>(())
304    /// ```
305    #[must_use]
306    pub fn into_owned(self) -> OwnedVec<T> {
307        OwnedVec {
308            values: allocator::retain(self.values),
309        }
310    }
311}
312
313impl<T: fmt::Debug, W: Work> fmt::Debug for OwnedVec<T, W> {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        self.values.fmt(f)
316    }
317}
318
319impl<T: PartialEq, W: Work> PartialEq for OwnedVec<T, W> {
320    fn eq(&self, other: &Self) -> bool {
321        **self == **other
322    }
323}
324
325impl<T: Eq, W: Work> Eq for OwnedVec<T, W> {}
326
327impl<T: PartialEq, W: Work, const N: usize> PartialEq<[T; N]> for OwnedVec<T, W> {
328    fn eq(&self, other: &[T; N]) -> bool {
329        &**self == other
330    }
331}
332
333impl<T, W: Work> OwnedVec<T, W> {
334    /// The account retained by this collection, for related adapter operations.
335    #[must_use]
336    pub fn memory(&self) -> &Memory {
337        self.values.allocator().memory()
338    }
339    /// The allocated capacity in elements (zero-sized types allocate no bytes).
340    #[must_use]
341    pub fn capacity(&self) -> usize {
342        self.values.capacity()
343    }
344
345    /// Reserves capacity before any allocation, accounting for growth's peak.
346    pub fn try_reserve_exact(
347        &mut self,
348        additional: usize,
349    ) -> core::result::Result<(), MemoryError> {
350        self.values.allocator().clear_failure();
351        self.values
352            .try_reserve_exact(additional)
353            .map_err(|error| match error.kind() {
354                TryReserveErrorKind::CapacityOverflow => MemoryError::CapacityOverflow,
355                TryReserveErrorKind::AllocError { .. } => self.values.allocator().failure(),
356            })
357    }
358
359    /// Reserves amortized growth, charging the actual capacity and growth peak.
360    pub fn try_reserve(&mut self, additional: usize) -> core::result::Result<(), MemoryError> {
361        self.values.allocator().clear_failure();
362        self.values
363            .try_reserve(additional)
364            .map_err(|error| match error.kind() {
365                TryReserveErrorKind::CapacityOverflow => MemoryError::CapacityOverflow,
366                TryReserveErrorKind::AllocError { .. } => self.values.allocator().failure(),
367            })
368    }
369
370    /// Appends one value, acquiring any required storage before moving it.
371    pub fn try_push(&mut self, value: T) -> core::result::Result<(), MemoryError> {
372        self.values.allocator().work().charge_work(1)?;
373        if self.values.len() == self.values.capacity() {
374            self.try_reserve(1)?;
375        }
376        // Vec guarantees push cannot allocate while capacity is available.
377        self.values.push(value);
378        Ok(())
379    }
380
381    /// Drops the last value, retaining the allocated capacity.
382    pub fn pop(&mut self) -> Option<T> {
383        self.values.pop()
384    }
385
386    /// Drops every value, retaining the allocated capacity and its charge.
387    pub fn clear(&mut self) {
388        self.values.clear();
389    }
390
391    /// Sorts in place without allocating or recursion.
392    ///
393    /// Comparisons and moves are admitted before execution. The comparator
394    /// must admit its own data-dependent work before examining values. On
395    /// refusal, values remain a permutation of the input, partially ordered.
396    pub fn try_sort_unstable_by<E>(
397        &mut self,
398        compare: impl FnMut(&T, &T) -> core::result::Result<core::cmp::Ordering, E>,
399    ) -> core::result::Result<(), SortError<E>> {
400        let work = self.values.allocator().work().clone();
401        sort::sort(&mut self.values, &work, compare)
402    }
403}
404
405impl<T: Copy, W: Work> OwnedVec<T, W> {
406    /// Copies a slice after reserving storage and cumulative work in this domain.
407    pub fn try_extend_from_slice(&mut self, values: &[T]) -> core::result::Result<(), MemoryError> {
408        let work = values.len().max(core::mem::size_of_val(values));
409        self.values.allocator().work().charge_work(work as u64)?;
410        if self.values.capacity() == 0 {
411            self.try_reserve_exact(values.len())?;
412        } else {
413            self.try_reserve(values.len())?;
414        }
415        self.values.extend_from_slice(values);
416        Ok(())
417    }
418    /// Copies values into independently charged storage in the same domain.
419    pub fn try_copy(&self) -> core::result::Result<Self, MemoryError> {
420        let mut copy = self.values.allocator().work().vector();
421        let work = self.values.len().max(core::mem::size_of_val(&**self));
422        self.values.allocator().work().charge_work(work as u64)?;
423        copy.try_reserve_exact(self.values.len())?;
424        // Reservation above guarantees the Copy specialization cannot allocate.
425        copy.values.extend_from_slice(&self.values);
426        Ok(copy)
427    }
428}
429
430impl<T, W: Work> Deref for OwnedVec<T, W> {
431    type Target = [T];
432    fn deref(&self) -> &[T] {
433        &self.values
434    }
435}
436
437impl<T, W: Work> DerefMut for OwnedVec<T, W> {
438    fn deref_mut(&mut self) -> &mut [T] {
439        &mut self.values
440    }
441}
442
443impl<'a, T, W: Work> IntoIterator for &'a OwnedVec<T, W> {
444    type Item = &'a T;
445    type IntoIter = core::slice::Iter<'a, T>;
446    fn into_iter(self) -> Self::IntoIter {
447        self.iter()
448    }
449}
450
451impl<'a, T, W: Work> IntoIterator for &'a mut OwnedVec<T, W> {
452    type Item = &'a mut T;
453    type IntoIter = core::slice::IterMut<'a, T>;
454    fn into_iter(self) -> Self::IntoIter {
455        self.iter_mut()
456    }
457}
458
459/// An owning iterator that retains capacity accounting until storage is freed.
460pub struct OwnedIter<T, W: Work = Memory> {
461    values: allocator_api2::vec::IntoIter<T, allocator::AccountAllocator<W>>,
462}
463
464impl<T, W: Work> Iterator for OwnedIter<T, W> {
465    type Item = T;
466    fn next(&mut self) -> Option<T> {
467        self.values.next()
468    }
469    fn size_hint(&self) -> (usize, Option<usize>) {
470        self.values.size_hint()
471    }
472}
473
474impl<T, W: Work> DoubleEndedIterator for OwnedIter<T, W> {
475    fn next_back(&mut self) -> Option<T> {
476        self.values.next_back()
477    }
478}
479
480impl<T, W: Work> ExactSizeIterator for OwnedIter<T, W> {}
481impl<T, W: Work> core::iter::FusedIterator for OwnedIter<T, W> {}
482
483impl<T, W: Work> IntoIterator for OwnedVec<T, W> {
484    type Item = T;
485    type IntoIter = OwnedIter<T, W>;
486    fn into_iter(self) -> Self::IntoIter {
487        OwnedIter {
488            values: self.values.into_iter(),
489        }
490    }
491}
492
493#[cfg(test)]
494mod tests;