Skip to main content

readcon_core/
stream.rs

1//! Frames parsed from a byte stream with about one frame in memory.
2
3use crate::error::ParseError;
4use crate::iterators::ConFrameIterator;
5use crate::types::ConFrame;
6use std::io::{self, Read};
7use std::path::Path;
8
9/// Error type of [`ConFrameReader`]: a [`ParseError`], or an input or
10/// output error from the source.
11pub type StreamError = Box<dyn std::error::Error + Send + Sync>;
12
13/// Bytes requested from the source when the buffer has no whole frame.
14pub const DEFAULT_READ_SIZE: usize = 1 << 20;
15
16/// Parses frames from any [`Read`], such as a gzip or Zstandard decoder.
17///
18/// The buffer keeps the bytes not yet returned and room for one read.
19/// Before each read it moves those bytes to its front, and it grows only
20/// to fit them plus that read.
21/// Each read asks for `read_size` bytes.
22/// The request grows to an eighth of the unread bytes when that exceeds
23/// `read_size`, so a large frame needs few reads.
24/// The capacity therefore stays near the largest frame, with the lines
25/// the parser looks at after it, plus one read.
26/// For a frame longer than eight reads, the read term is up to an eighth
27/// of that frame.
28///
29/// Frames and errors are those of [`ConFrameIterator::from_bytes`] over
30/// the whole stream, up to and including the first error.
31/// When the source fails, the reader returns the frames that `from_bytes`
32/// returns over the bytes read before the failure, up to its first error,
33/// and then the source error.
34/// After an error the reader returns `None`.
35pub struct ConFrameReader<R> {
36    src: R,
37    /// Initialized storage, with the unread bytes in `buf[start..end]`.
38    buf: Vec<u8>,
39    start: usize,
40    end: usize,
41    read_size: usize,
42    eof: bool,
43    failed: Option<io::Error>,
44    done: bool,
45}
46
47impl ConFrameReader<Box<dyn Read + Send>> {
48    /// A reader over `path`, decompressed by
49    /// [`crate::compression::open_decoded`].
50    pub fn open(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
51        Ok(Self::new(crate::compression::open_decoded(path)?))
52    }
53}
54
55impl<R: Read> ConFrameReader<R> {
56    /// A reader that asks `src` for [`DEFAULT_READ_SIZE`] bytes at a time.
57    pub fn new(src: R) -> Self {
58        Self::with_read_size(src, DEFAULT_READ_SIZE)
59    }
60
61    /// Like [`Self::new`], asking the source for `read_size` bytes (at
62    /// least 1) whenever the buffer has no whole frame.
63    pub fn with_read_size(src: R, read_size: usize) -> Self {
64        Self {
65            src,
66            buf: Vec::new(),
67            start: 0,
68            end: 0,
69            read_size: read_size.max(1),
70            eof: false,
71            failed: None,
72            done: false,
73        }
74    }
75
76    /// Skip one frame without parsing its atoms, as
77    /// [`ConFrameIterator::forward`] does.
78    pub fn forward(&mut self) -> Option<Result<(), StreamError>> {
79        self.step(|it| it.forward().map(|r| r.map(|()| ((), it.byte_pos()))))
80    }
81
82    /// Run `op` on a fresh iterator over the buffered whole lines.
83    /// Its result is final once the stream has ended, or once the lines
84    /// after its cursor fix every look-ahead decision.
85    /// Otherwise read more.
86    ///
87    /// After a source error no more bytes arrive, so the buffer is parsed
88    /// as a whole input.
89    /// A frame from it is returned, and an error or the end of the buffer
90    /// gives way to the source error.
91    fn step<T>(
92        &mut self,
93        mut op: impl FnMut(&mut ConFrameIterator<'_>) -> Option<Result<(T, usize), ParseError>>,
94    ) -> Option<Result<T, StreamError>> {
95        let mut cut = false;
96        while !self.done {
97            let ended = self.eof || self.failed.is_some();
98            let rest = &self.buf[self.start..self.end];
99            let text = if ended {
100                rest
101            } else {
102                memchr::memrchr(b'\n', rest).map_or(&rest[..0], |i| &rest[..=i])
103            };
104            // Once a frame was cut by the buffer end, a skip walk checks
105            // each longer buffer first, so a frame larger than one read is
106            // not parsed in full once per read.
107            if ended || !cut || {
108                let mut probe = ConFrameIterator::from_bytes(text);
109                probe.forward();
110                settled(text, probe.byte_pos())
111            } {
112                let mut it = ConFrameIterator::from_bytes(text);
113                let result = op(&mut it);
114                if ended || settled(text, it.byte_pos()) {
115                    match result {
116                        Some(Ok((value, used))) => {
117                            self.start += used;
118                            return Some(Ok(value));
119                        }
120                        other if self.failed.is_none() => {
121                            self.done = true;
122                            return other.map(|r| r.map(|(value, _)| value).map_err(Into::into));
123                        }
124                        _ => {}
125                    }
126                }
127            }
128            cut = true;
129            if let Some(e) = self.failed.take() {
130                self.done = true;
131                return Some(Err(e.into()));
132            }
133            self.fill();
134        }
135        None
136    }
137
138    /// Move the unread bytes to the front, and grow the buffer to fit them
139    /// plus one read.
140    /// Then read until that read is full, the source ends, or it fails.
141    /// The bytes read before a failure are kept.
142    fn fill(&mut self) {
143        self.buf.copy_within(self.start..self.end, 0);
144        self.end -= self.start;
145        self.start = 0;
146        let target = self.end + self.read_size.max(self.end / 8);
147        if target > self.buf.len() {
148            self.buf.reserve_exact(target - self.buf.len());
149            self.buf.resize(target, 0);
150        }
151        while self.end < target {
152            match self.src.read(&mut self.buf[self.end..target]) {
153                Ok(0) => {
154                    self.eof = true;
155                    return;
156                }
157                Ok(n) => self.end += n,
158                Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
159                Err(e) => {
160                    self.failed = Some(e);
161                    return;
162                }
163            }
164        }
165    }
166}
167
168/// Whether `text` has, after `pos`, any whitespace-only lines, then a
169/// line with text and one more line.
170/// The parser reads no further past its cursor.
171/// A scan for trailing whitespace stops at the line with text.
172/// A section probe after a peeked blank reads two lines.
173/// It stops early when the first of them is whitespace.
174/// `text` ends in a newline, so every piece is a whole line.
175fn settled(text: &[u8], pos: usize) -> bool {
176    let mut lines = text[pos.min(text.len())..].split_inclusive(|&b| b == b'\n');
177    lines
178        .find(|line| !line.iter().all(u8::is_ascii_whitespace))
179        .is_some()
180        && lines.next().is_some()
181}
182
183impl<R: Read> Iterator for ConFrameReader<R> {
184    type Item = Result<ConFrame, StreamError>;
185
186    fn next(&mut self) -> Option<Self::Item> {
187        self.step(|it| it.next_with_end())
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    /// A zero-atom frame of `len` bytes, padded by its user line.
196    fn zero_atom_frame(len: usize) -> Vec<u8> {
197        let tail = "\n{\"con_spec_version\":2}\n10.000000 10.000000 10.000000\n\
198                    90.000000 90.000000 90.000000\n\n\n0\n\n\n";
199        let mut frame = vec![b'U'; len - tail.len()];
200        frame.extend_from_slice(tail.as_bytes());
201        frame
202    }
203
204    /// The buffer after the first frame of `frame` then `after`.
205    fn after_first(frame: &[u8], after: &[u8]) -> (usize, usize) {
206        let text = [frame, after].concat();
207        let mut reader = ConFrameReader::new(&text[..]);
208        let want = ConFrameIterator::from_bytes(frame).next().unwrap().unwrap();
209        assert_eq!(reader.next().unwrap().unwrap(), want);
210        assert_eq!(reader.start, frame.len());
211        (reader.buf.len(), reader.buf.capacity())
212    }
213
214    #[test]
215    fn a_frame_over_one_read_grows_the_buffer_to_about_one_frame_and_one_read() {
216        let small = zero_atom_frame(100).repeat(2);
217        for len in [(1 << 20) + 57, (1 << 21) + 57] {
218            let frame = zero_atom_frame(len);
219            for after in [&small[..], &[][..]] {
220                let (filled, capacity) = after_first(&frame, after);
221                assert!(filled <= capacity);
222                assert!(
223                    capacity <= len + DEFAULT_READ_SIZE,
224                    "frame {len}, {} bytes after it: capacity {capacity}",
225                    after.len()
226                );
227            }
228        }
229    }
230
231    #[test]
232    fn a_frame_of_many_reads_grows_the_buffer_by_an_eighth_at_most() {
233        let read_size = 4096;
234        let len = 64 * read_size + 57;
235        let text = [zero_atom_frame(len), zero_atom_frame(100)]
236            .concat()
237            .repeat(2);
238        let mut reader = ConFrameReader::with_read_size(&text[..], read_size);
239        for _ in 0..4 {
240            assert!(reader.next().unwrap().is_ok());
241            assert!(reader.buf.capacity() <= len + len / 8 + read_size);
242        }
243        assert!(reader.next().is_none());
244    }
245}