Skip to main content

structured_asn1/
range.rs

1//! Bounded asynchronous original-range acquisition under one execution.
2
3use crate::Span;
4use crate::memory::{Execution, MemoryError, OwnedVec, Work};
5use crate::source::Snapshot;
6use core::future::{Future, ready};
7
8/// One immutable original byte source, including its exact length.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Stamp<D, G> {
11    /// Source-owner identity and immutable generation.
12    pub snapshot: Snapshot<D, G>,
13    /// Exact byte length of this view.
14    pub length: u64,
15}
16
17/// Failure inside caller-owned range acquisition.
18#[derive(Debug)]
19pub enum ReadError<E> {
20    /// Original typed resource refusal, independent of syntax and EOF.
21    Resource(MemoryError),
22    /// Original opaque acquisition cause.
23    Cause(E),
24}
25
26/// A stable range source that may suspend during bounded acquisition.
27///
28/// The window admits requested copy bytes before dispatch. Implementations
29/// fund additional processing/storage through `work`, write only `output`,
30/// and return its written length. Short reads and zero at EOF are allowed.
31/// Whole-source materialization is not an implementation detail of this API.
32pub trait RangeSource {
33    /// Stable, constant-time comparable source-owner identity.
34    type Document: Copy + Eq;
35    /// Constant-time comparable immutable generation.
36    type Generation: Copy + Eq;
37    /// Original acquisition failure.
38    type Error;
39
40    /// Constant-time metadata, without allocation, traversal or I/O.
41    fn stamp(&self) -> Stamp<Self::Document, Self::Generation>;
42
43    /// Fills a bounded original range; no executor, `Send` or heap future bound.
44    fn read_at<'q, W: Work + 'q>(
45        &'q self,
46        offset: u64,
47        output: &'q mut [u8],
48        work: &'q W,
49    ) -> impl Future<Output = Result<usize, ReadError<Self::Error>>> + 'q;
50}
51
52/// A borrowed resident source using the same original-range contract.
53pub struct Slice<'a, D, G> {
54    bytes: &'a [u8],
55    stamp: Stamp<D, G>,
56}
57
58impl<'a, D, G> Slice<'a, D, G> {
59    /// Binds original bytes to their caller-owned source identity.
60    #[must_use]
61    pub fn new(bytes: &'a [u8], snapshot: Snapshot<D, G>) -> Self {
62        Self {
63            bytes,
64            stamp: Stamp {
65                snapshot,
66                length: bytes.len() as u64,
67            },
68        }
69    }
70}
71
72impl<D: Copy + Eq, G: Copy + Eq> RangeSource for Slice<'_, D, G> {
73    type Document = D;
74    type Generation = G;
75    type Error = core::convert::Infallible;
76
77    fn stamp(&self) -> Stamp<D, G> {
78        self.stamp
79    }
80
81    fn read_at<'q, W: Work + 'q>(
82        &'q self,
83        offset: u64,
84        output: &'q mut [u8],
85        _work: &'q W,
86    ) -> impl Future<Output = Result<usize, ReadError<Self::Error>>> + 'q {
87        let count = if let Ok(offset) = usize::try_from(offset)
88            && let Some(input) = self.bytes.get(offset..)
89        {
90            let count = input.len().min(output.len());
91            output[..count].copy_from_slice(&input[..count]);
92            count
93        } else {
94            0
95        };
96        ready(Ok(count))
97    }
98}
99
100/// Failure at an original source range, without inventing format coordinates.
101#[derive(Debug)]
102pub enum InputError<E> {
103    /// Original source/current execution refusal.
104    Resource {
105        /// Requested original range or EOF point.
106        range: Span,
107        /// Preserved work or capacity cause.
108        cause: MemoryError,
109    },
110    /// Identity, generation or length changed.
111    Changed {
112        /// Requested original range or point.
113        range: Span,
114    },
115    /// Invalid bounds, premature EOF or an impossible returned count.
116    Contract {
117        /// Requested original range or point.
118        range: Span,
119    },
120    /// Original opaque acquisition failure.
121    Cause {
122        /// Requested original range.
123        range: Span,
124        /// Preserved cause.
125        cause: E,
126    },
127}
128
129// Cache granularity, not a document-size policy or acceptance ceiling.
130const WINDOW: usize = 4096;
131const ZERO_WINDOW: [u8; WINDOW] = [0; WINDOW];
132
133/// A bounded resident window over one exact source view and execution.
134/// Cache hits still check source identity and current admission. Scratch is
135/// released with this owner; retaining metadata never prepays a later query.
136pub struct Window<'q, S: RangeSource> {
137    source: &'q S,
138    stamp: Stamp<S::Document, S::Generation>,
139    bounds: Span,
140    work: Execution<'q>,
141    bytes: OwnedVec<u8, Execution<'q>>,
142    start: u64,
143    filled: usize,
144    aligned: bool,
145    quantum: usize,
146}
147
148impl<'q, S: RangeSource> Window<'q, S> {
149    /// Opens a bounded region of an already selected immutable view.
150    ///
151    /// # Panics
152    /// Only if the internal fixed scratch size no longer fits target `usize`.
153    /// The selected input length is bounded by that fixed size before conversion.
154    pub fn new(
155        source: &'q S,
156        stamp: Stamp<S::Document, S::Generation>,
157        bounds: Span,
158        work: Execution<'q>,
159    ) -> Result<Self, InputError<S::Error>> {
160        let Some(end) = bounds.start.checked_add(bounds.length) else {
161            return Err(InputError::Contract { range: bounds });
162        };
163        if end > stamp.length {
164            return Err(InputError::Contract { range: bounds });
165        }
166        Self::guard(source, stamp, &work, bounds)?;
167        let mut bytes = work.vector();
168        let size = usize::try_from(bounds.length.min(WINDOW as u64))
169            .expect("bounded window length fits usize");
170        bytes
171            .try_extend_from_slice(&ZERO_WINDOW[..size])
172            .map_err(|cause| InputError::Resource {
173                range: bounds,
174                cause,
175            })?;
176        Ok(Self {
177            source,
178            stamp,
179            bounds,
180            work,
181            bytes,
182            start: 0,
183            filled: 0,
184            aligned: true,
185            quantum: WINDOW,
186        })
187    }
188
189    /// The selected source/current operation, without changing account ownership.
190    #[must_use]
191    pub fn execution(&self) -> Execution<'q> {
192        self.work
193    }
194
195    /// Selected immutable source metadata.
196    #[must_use]
197    pub fn stamp(&self) -> Stamp<S::Document, S::Generation> {
198        self.stamp
199    }
200
201    /// Original bounded source region.
202    #[must_use]
203    pub fn bounds(&self) -> Span {
204        self.bounds
205    }
206
207    /// Independent bounded cache with the same snapshot/execution. Comparing
208    /// distant regions does not evict one window for every compared byte.
209    pub fn fork(&self) -> Result<Self, InputError<S::Error>> {
210        let mut fork = Self::new(self.source, self.stamp, self.bounds, self.work)?;
211        fork.aligned = self.aligned;
212        fork.quantum = self.quantum;
213        Ok(fork)
214    }
215
216    /// Requires the original view and current admission before cached/EOF work.
217    pub fn check(&self, range: Span) -> Result<(), InputError<S::Error>> {
218        Self::guard(self.source, self.stamp, &self.work, range)
219    }
220
221    fn guard(
222        source: &S,
223        stamp: Stamp<S::Document, S::Generation>,
224        work: &Execution<'_>,
225        range: Span,
226    ) -> Result<(), InputError<S::Error>> {
227        work.charge_work(1)
228            .map_err(|cause| InputError::Resource { range, cause })?;
229        if source.stamp() != stamp {
230            return Err(InputError::Changed { range });
231        }
232        Ok(())
233    }
234
235    async fn refill(
236        &mut self,
237        start: u64,
238        length: usize,
239        complete: bool,
240    ) -> Result<(), InputError<S::Error>> {
241        // Invalidated before the first mutable borrow: cancellation or any
242        // later failure cannot expose a partially overwritten old cache page.
243        self.filled = 0;
244        let mut used = 0;
245        loop {
246            let wanted = (length - used).min(self.quantum);
247            let range = Span {
248                start: start + used as u64,
249                length: wanted as u64,
250            };
251            self.check(range)?;
252            self.work
253                .charge_work(wanted as u64)
254                .map_err(|cause| InputError::Resource { range, cause })?;
255            let count = self
256                .source
257                .read_at(
258                    range.start,
259                    &mut self.bytes[used..used + wanted],
260                    &self.work,
261                )
262                .await
263                .map_err(|error| match error {
264                    ReadError::Resource(cause) => InputError::Resource { range, cause },
265                    ReadError::Cause(cause) => InputError::Cause { range, cause },
266                })?;
267            self.check(range)?;
268            if count == 0 || count > wanted {
269                return Err(InputError::Contract { range });
270            }
271            if count < wanted {
272                // A short read adapts acquisition granularity, not semantics.
273                // Subsequent copies are admitted for the smaller requested
274                // buffer instead of repeatedly prepaying a whole 4KiB page.
275                self.quantum = count;
276                self.aligned = false;
277            }
278            used += count;
279            if !complete || used == length {
280                self.start = start;
281                self.filled = used;
282                return Ok(());
283            }
284        }
285    }
286
287    /// Reads one original byte, suspending only when acquisition is required.
288    ///
289    /// # Panics
290    /// Panics if an internally bounded resident offset cannot index its window.
291    pub async fn byte_at(&mut self, offset: u64) -> Result<Option<u8>, InputError<S::Error>> {
292        let point = Span {
293            start: offset,
294            length: 0,
295        };
296        self.check(point)?;
297        if offset < self.bounds.start {
298            return Err(InputError::Contract { range: point });
299        }
300        let end = self.bounds.end();
301        if offset >= end {
302            return Ok(None);
303        }
304        if offset >= self.start && offset - self.start < self.filled as u64 {
305            let index = usize::try_from(offset - self.start).expect("cached window index");
306            return Ok(Some(self.bytes[index]));
307        }
308        loop {
309            let mut start = if self.aligned {
310                offset / WINDOW as u64 * WINDOW as u64
311            } else {
312                offset
313            };
314            if start < self.bounds.start {
315                start = self.bounds.start;
316            }
317            let wanted = usize::try_from((end - start).min(self.bytes.len() as u64))
318                .expect("bounded requested length");
319            self.refill(start, wanted, false).await?;
320            let index = usize::try_from(offset - start).expect("query is inside window");
321            if index < self.filled {
322                return Ok(Some(self.bytes[index]));
323            }
324            // After an aligned short read, retry exactly at the queried byte.
325            // Any positive result completes it: at most two acquisitions.
326            debug_assert!(!self.aligned);
327        }
328    }
329
330    /// Borrows the next bounded original chunk, without an intermediate copy.
331    ///
332    /// # Panics
333    /// Panics if a resident range cannot index its bounded window.
334    pub async fn chunk_at(&mut self, offset: u64, end: u64) -> Result<&[u8], InputError<S::Error>> {
335        let range = Span {
336            start: offset,
337            length: 0,
338        };
339        if end > self.bounds.end() || offset < self.bounds.start || offset > end {
340            return Err(InputError::Contract { range });
341        }
342        if offset == end {
343            self.check(range)?;
344            return Ok(&[]);
345        }
346        self.byte_at(offset).await?;
347        let start = usize::try_from(offset - self.start).expect("cached offset");
348        let count = usize::try_from((end - offset).min((self.filled - start) as u64))
349            .expect("bounded chunk");
350        Ok(&self.bytes[start..start + count])
351    }
352
353    /// Borrows a bounded chunk ending at `end` for reverse metadata scans.
354    /// Reverse refill completes one bounded page despite short reads. This
355    /// preserves every byte and linear acquisition/admission during scans.
356    ///
357    /// # Panics
358    /// Panics if an internally bounded resident range cannot index its window.
359    pub async fn chunk_before(
360        &mut self,
361        end: u64,
362        minimum: u64,
363    ) -> Result<&[u8], InputError<S::Error>> {
364        let point = Span {
365            start: end,
366            length: 0,
367        };
368        if minimum < self.bounds.start || end > self.bounds.end() || minimum > end {
369            return Err(InputError::Contract { range: point });
370        }
371        if end == minimum {
372            self.check(point)?;
373            return Ok(&[]);
374        }
375        self.check(point)?;
376        let last = end - 1;
377        if last < self.start || last - self.start >= self.filled as u64 {
378            let start = minimum.max(end - end.min(self.bytes.len() as u64));
379            let length = usize::try_from(end - start).expect("bounded reverse page");
380            self.refill(start, length, true).await?;
381        }
382        let start =
383            usize::try_from(minimum.max(self.start) - self.start).expect("resident reverse start");
384        let stop = usize::try_from(end - self.start).expect("resident reverse end");
385        Ok(&self.bytes[start..stop])
386    }
387}
388
389#[cfg(test)]
390mod tests;