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