Skip to main content

structured_pades/
session.rs

1//! Trusted source-session setup and the shared original/current work boundary.
2
3use crate::error::{Error, ErrorKind, Limit, Result};
4use crate::source::Location;
5use core::cell::Cell;
6use structured_asn1::memory::{Execution, Memory, Work};
7use structured_asn1::{Encoding, Parser};
8
9/// Caller-selected mechanism ceilings. These do not select algorithm trust,
10/// document permissions or a `PAdES` acceptance policy.
11#[derive(Debug, Clone, Copy)]
12pub struct Limits {
13    /// Shared source work and ASN.1 limits for carried CMS/PKIX material. PDF
14    /// container nesting also obeys this setup's maximum depth.
15    pub processing: structured_asn1::Limits,
16    /// Maximum cumulative PDF object admissions.
17    pub max_objects: u64,
18    /// Maximum cumulative PDF revision admissions.
19    pub max_revisions: u32,
20    /// Maximum cumulative decoded stream output, independently of live capacity.
21    pub max_decoded: u64,
22}
23
24impl Default for Limits {
25    fn default() -> Self {
26        Self {
27            processing: structured_asn1::Limits::default(),
28            max_objects: 500_000,
29            max_revisions: 1_000,
30            max_decoded: 64 * 1024 * 1024,
31        }
32    }
33}
34
35/// Caller-owned lifetime of one PDF source session and its original work
36/// ceiling. The existing shared parser owns the same execution boundary used
37/// by genuinely carried DER CMS/PKIX values; PDF bytes are not parsed as ASN.1.
38/// Source and current-operation work compose through the existing shared
39/// execution kernel, without another account, reset or allocator wrapper.
40pub struct Session {
41    parser: Parser,
42    limits: Limits,
43    objects: Cell<u64>,
44    revisions: Cell<u64>,
45    decoded: Cell<u64>,
46}
47
48impl Session {
49    /// Constructs trusted session state without allocation or source traversal.
50    #[must_use]
51    pub fn new(limits: Limits, memory: Memory) -> Self {
52        Self {
53            parser: Parser::new(Encoding::Der, limits.processing, memory),
54            limits,
55            objects: Cell::new(0),
56            revisions: Cell::new(0),
57            decoded: Cell::new(0),
58        }
59    }
60
61    /// Immutable caller-selected mechanism ceilings.
62    #[must_use]
63    pub fn limits(&self) -> Limits {
64        self.limits
65    }
66
67    /// Original source work and allocation account, without a new cursor or
68    /// account-owner clone. Every data-dependent operation must admit work.
69    #[must_use]
70    pub fn execution(&self) -> Execution<'_> {
71        self.parser.execution_in(self.parser.memory())
72    }
73
74    /// Attaches the current operation while retaining the original source
75    /// ceiling. Cross-source queries preserve the original root operation;
76    /// sharing an account does not exempt either ceiling.
77    #[must_use]
78    pub fn query<'a, W: Work>(&'a self, operation: &'a W) -> Execution<'a> {
79        operation.execution_for(&self.parser)
80    }
81
82    pub(crate) fn carried<'a, W: Work + 'a>(
83        &'a self,
84        bytes: &'a [u8],
85        operation: &W,
86    ) -> structured_asn1::Result<structured_asn1::Reader<'a>> {
87        operation.read_source(&self.parser, bytes, 0, Encoding::Der, 0)
88    }
89
90    pub(crate) fn owns(&self, reader: &structured_asn1::Reader<'_>) -> bool {
91        core::ptr::eq(core::ptr::from_ref(&self.parser), reader.parser())
92    }
93
94    pub(crate) fn decoded_remaining(&self) -> u64 {
95        self.limits.max_decoded - self.decoded.get()
96    }
97
98    /// Counts an object before parsing or allocating its representation.
99    pub fn object(&self, location: Location) -> Result<()> {
100        Self::admit(
101            &self.objects,
102            1,
103            self.limits.max_objects,
104            Limit::Objects,
105            location,
106        )
107    }
108
109    /// Counts a revision before traversing its cross-reference section.
110    pub fn revision(&self, location: Location) -> Result<()> {
111        Self::admit(
112            &self.revisions,
113            1,
114            u64::from(self.limits.max_revisions),
115            Limit::Revisions,
116            location,
117        )
118    }
119
120    /// Counts potential decoded output before running a decoder. This output
121    /// allowance is cumulative; discarding storage never refunds completed work.
122    pub fn decode(&self, amount: u64, location: Location) -> Result<()> {
123        Self::admit(
124            &self.decoded,
125            amount,
126            self.limits.max_decoded,
127            Limit::Decoded,
128            location,
129        )
130    }
131
132    fn admit(
133        counter: &Cell<u64>,
134        amount: u64,
135        ceiling: u64,
136        limit: Limit,
137        location: Location,
138    ) -> Result<()> {
139        let spent = counter.get();
140        debug_assert!(spent <= ceiling);
141        if amount > ceiling - spent {
142            return Err(Error {
143                kind: ErrorKind::Limit(limit),
144                location,
145            });
146        }
147        counter.set(spent + amount);
148        Ok(())
149    }
150}