Skip to main content

structured_pades/
object.rs

1//! Original PDF object views over flat, independently accounted storage.
2
3use crate::Session;
4use crate::arena::{Arena, Kind, NodeId};
5use crate::bytes::Form;
6use crate::error::{Error, ErrorKind, Result, Syntax, Unsupported};
7use crate::range::Stamp;
8use crate::source::Location;
9use structured_asn1::memory::{Execution, Memory, Work};
10
11/// PDF object category, independent of any application/schema interpretation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Type {
14    /// PDF null object.
15    Null,
16    /// PDF true/false object.
17    Boolean,
18    /// An integer spelling, possibly wider than a particular scalar query.
19    Integer,
20    /// A real spelling; no binary floating-point rounding is implied.
21    Real,
22    /// A PDF byte-name, with original escape spelling retained separately.
23    Name,
24    /// A literal or hexadecimal byte string.
25    String,
26    /// Original ordered array members.
27    Array,
28    /// Original dictionary key/value members.
29    Dictionary,
30    /// An indirect object reference, not an automatically resolved value.
31    Reference,
32}
33
34/// One bounded object reading. Owning buffers have independent actual-capacity
35/// charges; source queries continue to require the original session and the
36/// current operation. This does not assert a valid PDF file or `PAdES` signature.
37pub struct Value<'s, D, G> {
38    pub(crate) arena: Arena<Memory>,
39    pub(crate) root: NodeId,
40    pub(crate) session: &'s Session,
41    pub(crate) stamp: Stamp<D, G>,
42    pub(crate) end: u64,
43}
44
45impl<'s, D: Copy + Eq, G: Copy + Eq> Value<'s, D, G> {
46    /// Original source identity and generation used for every node.
47    #[must_use]
48    pub fn stamp(&self) -> Stamp<D, G> {
49        self.stamp
50    }
51
52    /// First original byte following the root value, before unrelated input.
53    #[must_use]
54    pub fn end(&self) -> u64 {
55        self.end
56    }
57
58    /// Readable format violations preserved during unique-value inspection.
59    /// An empty list alone does not establish PDF/PAdES conformance.
60    #[must_use]
61    pub fn faults(&self) -> &[crate::error::Fault] {
62        &self.arena.faults
63    }
64
65    /// Root metadata, without traversal, allocation or interpretation.
66    #[must_use]
67    pub fn root(&self) -> Object<'_, 's, D, G> {
68        Object {
69            owner: self,
70            node: self.root,
71        }
72    }
73}
74
75/// A stable view into its owning reading. Node handles cannot be constructed
76/// publicly, moved between owners or used after their owning storage drops.
77#[derive(Clone, Copy)]
78pub struct Object<'a, 's, D, G> {
79    pub(crate) owner: &'a Value<'s, D, G>,
80    pub(crate) node: NodeId,
81}
82
83impl<'a, 's, D: Copy + Eq, G: Copy + Eq> Object<'a, 's, D, G> {
84    /// Original range and coordinate domain; never a re-encoded value.
85    #[must_use]
86    pub fn location(self) -> Location {
87        self.owner.arena.nodes[self.node.0].location
88    }
89
90    /// Category already established by the bounded grammar.
91    #[must_use]
92    pub fn kind(self) -> Type {
93        match self.owner.arena.nodes[self.node.0].kind {
94            Kind::Null => Type::Null,
95            Kind::Boolean(_) => Type::Boolean,
96            Kind::Integer(_) => Type::Integer,
97            Kind::Real { .. } => Type::Real,
98            Kind::Name(_) => Type::Name,
99            Kind::String(_) => Type::String,
100            Kind::Array(_) => Type::Array,
101            Kind::Dictionary(_, _) => Type::Dictionary,
102            Kind::Reference { .. } => Type::Reference,
103        }
104    }
105
106    /// Original boolean scalar. This O(1) metadata getter does not traverse
107    /// source data or decide whether the enclosing schema accepts a boolean.
108    #[must_use]
109    pub fn boolean(self) -> Option<bool> {
110        if let Kind::Boolean(value) = self.owner.arena.nodes[self.node.0].kind {
111            Some(value)
112        } else {
113            None
114        }
115    }
116
117    /// Exact original encoded string body and spelling. Decoding is a separate
118    /// paid source query; this getter never materializes a potentially large
119    /// value or mistakes literal/hex delimiters for signature content octets.
120    #[must_use]
121    ///
122    /// # Panics
123    /// Panics if a parsed string's internally retained delimiters are invalid.
124    pub fn string_body(self) -> Option<(Form, Location)> {
125        if let Kind::String(form) = self.owner.arena.nodes[self.node.0].kind {
126            let original = self.location();
127            debug_assert!(original.span().length >= 2);
128            Some((
129                form,
130                Location::new(
131                    original.source(),
132                    original.span().start + 1,
133                    original.span().length - 2,
134                )
135                .expect("parsed string delimiters"),
136            ))
137        } else {
138            None
139        }
140    }
141
142    /// Original indirect identity, without resolution, implicit I/O or fallback
143    /// to a different generation's object.
144    #[must_use]
145    pub fn reference(self) -> Option<(u64, u16)> {
146        if let Kind::Reference { number, generation } = self.owner.arena.nodes[self.node.0].kind {
147            Some((number, generation))
148        } else {
149            None
150        }
151    }
152
153    /// Queries an integer as a nonnegative u64 under current/original work.
154    /// A wider valid integer is explicitly unsupported for THIS query; it is
155    /// not saturated, clamped or relabelled malformed during object reading.
156    pub fn unsigned(self, operation: &impl Work) -> Result<u64> {
157        let location = self.location();
158        let work = self.owner.session.query(operation);
159        work.charge_work(1).map_err(|cause| Error {
160            kind: ErrorKind::Resource(cause),
161            location,
162        })?;
163        let Kind::Integer(value) = self.owner.arena.nodes[self.node.0].kind else {
164            return Err(Error {
165                kind: ErrorKind::Syntax(Syntax::Number),
166                location,
167            });
168        };
169        let Some(magnitude) = value.magnitude else {
170            return Err(Error {
171                kind: ErrorKind::Unsupported(Unsupported::NumericRange),
172                location,
173            });
174        };
175        if value.negative && magnitude != 0 {
176            return Err(Error {
177                kind: ErrorKind::Syntax(Syntax::Number),
178                location,
179            });
180        }
181        Ok(magnitude)
182    }
183
184    /// Compares a decoded PDF name with caller bytes, paying before traversal.
185    /// PDF names have byte identity, without case/Unicode normalization.
186    pub fn name_equals(self, expected: &[u8], operation: &impl Work) -> Result<bool> {
187        let location = self.location();
188        let work = self.owner.session.query(operation);
189        work.charge_work(1).map_err(|cause| Error {
190            kind: ErrorKind::Resource(cause),
191            location,
192        })?;
193        let Kind::Name(name) = self.owner.arena.nodes[self.node.0].kind else {
194            return Ok(false);
195        };
196        if name.length != expected.len() {
197            return Ok(false);
198        }
199        work.charge_work(name.length as u64)
200            .map_err(|cause| Error {
201                kind: ErrorKind::Resource(cause),
202                location,
203            })?;
204        Ok(&self.owner.arena.names[name.start..name.start + name.length] == expected)
205    }
206
207    /// Queries a dictionary by its decoded byte-name through the paid index.
208    /// Null entries are absent; an equal repeated value retains its FIRST
209    /// original encoding and a separate format fault. Conflicts never reach
210    /// this getter. This lookup does not resolve indirect references.
211    pub fn get(self, key: &[u8], operation: &impl Work) -> Result<Option<Self>> {
212        let Kind::Dictionary(_, fields) = self.owner.arena.nodes[self.node.0].kind else {
213            return Err(Error {
214                kind: ErrorKind::Syntax(Syntax::Dictionary),
215                location: self.location(),
216            });
217        };
218        let work = self.owner.session.query(operation);
219        work.charge_work(1).map_err(|cause| Error {
220            kind: ErrorKind::Resource(cause),
221            location: self.location(),
222        })?;
223        Ok(
224            crate::dictionary::lookup(&self.owner.arena, fields, key, &work, self.location())?.map(
225                |node| Self {
226                    owner: self.owner,
227                    node,
228                },
229            ),
230        )
231    }
232
233    /// Original direct member traversal. Dictionaries expose alternating key
234    /// and value nodes in wire order, arrays expose their ordered elements.
235    /// Constructor is allocation-free; each step pays before accessing a link.
236    /// After its first resource error the iterator is fused.
237    pub fn members<'q>(self, operation: &'q impl Work) -> Option<Members<'a, 's, 'q, D, G>>
238    where
239        's: 'q,
240    {
241        let (Kind::Array(children) | Kind::Dictionary(children, _)) =
242            self.owner.arena.nodes[self.node.0].kind
243        else {
244            return None;
245        };
246        Some(Members {
247            object: self,
248            next: children.first,
249            work: self.owner.session.query(operation),
250        })
251    }
252}
253
254/// A fallible original-member iterator with no independently retained clone.
255pub struct Members<'a, 's, 'q, D, G> {
256    object: Object<'a, 's, D, G>,
257    next: Option<usize>,
258    work: Execution<'q>,
259}
260
261impl<'a, 's, D: Copy + Eq, G: Copy + Eq> Iterator for Members<'a, 's, '_, D, G> {
262    type Item = Result<Object<'a, 's, D, G>>;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        let next = self.next?;
266        if let Err(cause) = self.work.charge_work(1) {
267            self.next = None;
268            return Some(Err(Error {
269                kind: ErrorKind::Resource(cause),
270                location: self.object.location(),
271            }));
272        }
273        let link = self.object.owner.arena.links[next];
274        self.next = link.next;
275        Some(Ok(Object {
276            owner: self.object.owner,
277            node: link.value,
278        }))
279    }
280}
281
282impl<D: Copy + Eq, G: Copy + Eq> core::iter::FusedIterator for Members<'_, '_, '_, D, G> {}