Skip to main content

structured_pades/
range.rs

1//! Asynchronous original-range access, with bounded accounted scratch.
2//!
3//! This boundary performs no filesystem, JavaScript, network or blocking I/O.
4//! A source owner supplies those operations and a stable immutable snapshot.
5
6use crate::Session;
7use crate::byte_machine::{Decoder, Step};
8use crate::bytes::Form;
9use crate::error::{Error, ErrorKind, ReadFailure, Syntax};
10use crate::source::{Location, Source};
11use structured_asn1::Span;
12use structured_asn1::memory::{Execution, MemoryError, OwnedVec, Work};
13use structured_asn1::range::Window;
14
15pub use structured_asn1::range::{RangeSource, ReadError, Slice, Stamp};
16
17/// A source processing failure with an honest original file location.
18#[derive(Debug)]
19pub enum InputError<E> {
20    /// Original execution denied work or scratch capacity.
21    Resource {
22        /// Original requested file range or cached/EOF point.
23        location: Location,
24        /// Preserved memory or work admission failure.
25        cause: MemoryError,
26    },
27    /// The owner changed identity, generation or length. Prior evidence cannot
28    /// be reused; this is not an unreadable PDF field.
29    Changed {
30        /// Original requested file range or cached/EOF point.
31        location: Location,
32    },
33    /// The acquisition violated its advertised length or output contract.
34    Contract {
35        /// Original range that was not delivered as advertised.
36        location: Location,
37    },
38    /// Original opaque source failure.
39    Cause {
40        /// Original requested file range.
41        location: Location,
42        /// Unmodified caller/source error, separate from its physical context.
43        cause: E,
44    },
45}
46
47/// One reusable original-file window. Parser lookahead and seek operations
48/// share this buffer; large file/stream bodies need not be materialized.
49/// Dropping a suspended read releases its scratch with the actual allocation.
50pub struct RangeReader<'q, S: RangeSource> {
51    session: &'q Session,
52    domain: Source,
53    window: Window<'q, S>,
54}
55
56impl<'q, S: RangeSource> RangeReader<'q, S> {
57    /// Creates bounded scratch under the original AND current execution.
58    /// Construction performs no acquisition and never copies the source.
59    pub fn new<W: Work>(
60        source: &'q S,
61        session: &'q Session,
62        operation: &'q W,
63    ) -> Result<Self, InputError<S::Error>> {
64        Self::in_source(source, session, operation, Source::File)
65    }
66
67    pub(crate) fn in_source<W: Work>(
68        source: &'q S,
69        session: &'q Session,
70        operation: &'q W,
71        domain: Source,
72    ) -> Result<Self, InputError<S::Error>> {
73        let stamp = source.stamp();
74        Self::window(
75            source,
76            session,
77            operation,
78            domain,
79            stamp,
80            Span {
81                start: 0,
82                length: stamp.length,
83            },
84        )
85    }
86
87    pub(crate) fn in_region<W: Work>(
88        source: &'q S,
89        session: &'q Session,
90        operation: &'q W,
91        domain: Source,
92        bounds: Span,
93    ) -> Result<Self, InputError<S::Error>> {
94        Self::window(source, session, operation, domain, source.stamp(), bounds)
95    }
96
97    fn window<W: Work>(
98        source: &'q S,
99        session: &'q Session,
100        operation: &'q W,
101        domain: Source,
102        stamp: Stamp<S::Document, S::Generation>,
103        bounds: Span,
104    ) -> Result<Self, InputError<S::Error>> {
105        let window = Window::new(source, stamp, bounds, session.query(operation))
106            .map_err(|error| located(domain, error))?;
107        Ok(Self {
108            session,
109            domain,
110            window,
111        })
112    }
113
114    /// Original immutable view captured before parsing.
115    #[must_use]
116    pub fn stamp(&self) -> Stamp<S::Document, S::Generation> {
117        self.window.stamp()
118    }
119
120    /// Current original/source operation boundary, without a retained clone.
121    #[must_use]
122    pub fn execution(&self) -> Execution<'q> {
123        self.window.execution()
124    }
125
126    pub(crate) fn session(&self) -> &'q Session {
127        self.session
128    }
129
130    pub(crate) fn location(&self, start: u64, length: u64) -> Location {
131        Location::new(self.domain, start, length).expect("bounded original-domain range")
132    }
133
134    // Comparing two distant encoded strings must not alternate one cache
135    // between two pages for every byte. A second bounded window borrows the
136    // SAME source snapshot/execution and owns only its actual scratch charge.
137    pub(crate) fn fork(&self) -> Result<Self, InputError<S::Error>> {
138        Ok(Self {
139            session: self.session,
140            domain: self.domain,
141            window: self
142                .window
143                .fork()
144                .map_err(|error| located(self.domain, error))?,
145        })
146    }
147
148    /// Reads one original byte, suspending only on a missing window. EOF is
149    /// separate from source failure, changed snapshot or exhausted work.
150    ///
151    /// # Panics
152    /// Panics if an offset inside the bounded resident window cannot index it.
153    pub async fn byte_at(&mut self, offset: u64) -> Result<Option<u8>, InputError<S::Error>> {
154        self.window
155            .byte_at(offset)
156            .await
157            .map_err(|error| located(self.domain, error))
158    }
159
160    pub(crate) async fn chunk_at(
161        &mut self,
162        offset: u64,
163        end: u64,
164    ) -> Result<&[u8], InputError<S::Error>> {
165        let domain = self.domain;
166        self.window
167            .chunk_at(offset, end)
168            .await
169            .map_err(|error| located(domain, error))
170    }
171
172    /// Decodes an original name/string BODY (without its delimiters) through
173    /// the same grammar as resident [`crate::bytes::Bytes`]. It neither parses
174    /// the enclosing PDF nor proves that the caller selected a signed field.
175    /// All output has actual-capacity ownership; failure returns no prefix.
176    pub async fn decode_bytes(
177        &mut self,
178        body: Span,
179        form: Form,
180    ) -> Result<OwnedVec<u8, Execution<'q>>, ReadFailure<S::Error>> {
181        self.decode_bytes_limit(body, form, usize::MAX).await
182    }
183
184    pub(crate) async fn decode_bytes_limit(
185        &mut self,
186        body: Span,
187        form: Form,
188        maximum: usize,
189    ) -> Result<OwnedVec<u8, Execution<'q>>, ReadFailure<S::Error>> {
190        let bounds = self.window.bounds();
191        let enclosure = self.location(bounds.start, bounds.length);
192        self.check(enclosure)?;
193        let Some(end) = body.start.checked_add(body.length) else {
194            return Err(Error {
195                kind: ErrorKind::Foreign,
196                location: enclosure,
197            }
198            .into());
199        };
200        if body.start < bounds.start || end > bounds.end() {
201            return Err(Error {
202                kind: ErrorKind::Foreign,
203                location: enclosure,
204            }
205            .into());
206        }
207        let mut output = self.execution().vector();
208        let mut decoder = Decoder::new(form);
209        let mut at = body.start;
210        loop {
211            let input = if at < end {
212                self.byte_at(at).await?
213            } else {
214                None
215            };
216            match decoder.step(input) {
217                Step::More => {
218                    debug_assert!(input.is_some());
219                    at += 1;
220                }
221                Step::Byte { value, consumed } => {
222                    if output.len() == maximum {
223                        return Err(Error {
224                            kind: ErrorKind::Syntax(Syntax::String),
225                            location: self.location(at, 0),
226                        }
227                        .into());
228                    }
229                    if consumed {
230                        at += 1;
231                    }
232                    output.try_push(value).map_err(|cause| Error {
233                        kind: ErrorKind::Resource(cause),
234                        location: self.location(at, 0),
235                    })?;
236                }
237                Step::End => {
238                    self.check(self.location(at, 0))?;
239                    return Ok(output);
240                }
241                Step::Malformed => {
242                    if input.is_some() {
243                        at += 1;
244                    }
245                    return Err(Error {
246                        kind: ErrorKind::Syntax(match form {
247                            Form::Name => Syntax::Name,
248                            Form::Literal | Form::Hexadecimal => Syntax::String,
249                        }),
250                        location: self.location(at, 0),
251                    }
252                    .into());
253                }
254            }
255        }
256    }
257
258    pub(crate) fn check(&self, location: Location) -> Result<(), InputError<S::Error>> {
259        debug_assert_eq!(location.source(), self.domain);
260        self.window
261            .check(location.span())
262            .map_err(|error| located(self.domain, error))
263    }
264}
265
266fn located<E>(domain: Source, error: structured_asn1::range::InputError<E>) -> InputError<E> {
267    use structured_asn1::range::InputError as RangeError;
268    let location = |range: Span| {
269        Location::new(domain, range.start, range.length).expect("bounded source range")
270    };
271    match error {
272        RangeError::Resource { range, cause } => InputError::Resource {
273            location: location(range),
274            cause,
275        },
276        RangeError::Changed { range } => InputError::Changed {
277            location: location(range),
278        },
279        RangeError::Contract { range } => InputError::Contract {
280            location: Location::new(domain, range.start, 0).expect("original invalid-range point"),
281        },
282        RangeError::Cause { range, cause } => InputError::Cause {
283            location: location(range),
284            cause,
285        },
286    }
287}