diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-09-26 13:36:29 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-09-26 13:37:40 +0200 |
commit | d86988b24efbd1e76962801b89bf65f26f4e554d (patch) | |
tree | 37fd6f1a15be3cf480257e9bf6bb6689b0022112 /buffered-reader | |
parent | 1c6b7bccdfdcbfcf1e0711a98d607cd6804717bc (diff) |
buffered-reader: Add a new reader, BufferedReaderReserve
- This buffered reader prevents the reader from reading the last X
bytes of the underlying buffered reader.
Diffstat (limited to 'buffered-reader')
-rw-r--r-- | buffered-reader/src/lib.rs | 4 | ||||
-rw-r--r-- | buffered-reader/src/reserve.rs | 297 |
2 files changed, 300 insertions, 1 deletions
diff --git a/buffered-reader/src/lib.rs b/buffered-reader/src/lib.rs index 4eb0f419..7d3e475e 100644 --- a/buffered-reader/src/lib.rs +++ b/buffered-reader/src/lib.rs @@ -14,6 +14,7 @@ use std::fmt; mod generic; mod memory; mod limitor; +mod reserve; mod dup; mod eof; #[cfg(feature = "compression-deflate")] @@ -24,6 +25,7 @@ mod decompress_bzip2; pub use self::generic::BufferedReaderGeneric; pub use self::memory::BufferedReaderMemory; pub use self::limitor::BufferedReaderLimitor; +pub use self::reserve::BufferedReaderReserve; pub use self::dup::BufferedReaderDup; pub use self::eof::BufferedReaderEOF; #[cfg(feature = "compression-deflate")] @@ -83,7 +85,7 @@ pub trait BufferedReader<C> : io::Read + fmt::Debug { let result = self.data(amount); if let Ok(buffer) = result { if buffer.len() < amount { - return Err(Error::new(ErrorKind::UnexpectedEof, "unepxected EOF")); + return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")); } } return result; diff --git a/buffered-reader/src/reserve.rs b/buffered-reader/src/reserve.rs new file mode 100644 index 00000000..c2787845 --- /dev/null +++ b/buffered-reader/src/reserve.rs @@ -0,0 +1,297 @@ +use std::io; + +use super::*; + +/// A `BufferedReaderReserve` allows a reader to read everything +/// except for the last N bytes (the reserve) from the underlying +/// `BufferedReader`. +/// +/// Note: because the `BufferedReaderReserve` doesn't generally know +/// how much data can be read from the underlying `BufferedReader`, +/// it causes at least N bytes to by buffered. +pub struct BufferedReaderReserve<'a, C> { + reader: Box<'a + BufferedReader<C>>, + reserve: usize, + + cookie: C, +} + +impl<'a, C> fmt::Debug for BufferedReaderReserve<'a, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("BufferedReaderReserve") + .field("reserve", &self.reserve) + .field("reader", &self.reader) + .finish() + } +} + +impl<'a> BufferedReaderReserve<'a, ()> { + /// Instantiate a new `BufferedReaderReserve`. `reader` is the + /// source to wrap. `n` is the number of bytes that will not be + /// returned to the reader. + pub fn new(reader: Box<'a + BufferedReader<()>>, reserve: usize) -> Self { + Self::with_cookie(reader, reserve, ()) + } +} + +impl<'a, C> BufferedReaderReserve<'a, C> { + /// Like `new()`, but sets a cookie, which can be retrieved using + /// the `cookie_ref` and `cookie_mut` methods, and set using the + /// `cookie_set` method. + pub fn with_cookie(reader: Box<'a + BufferedReader<C>>, + reserve: usize, cookie: C) + -> BufferedReaderReserve<'a, C> { + BufferedReaderReserve { + reader: reader, + reserve: reserve, + cookie: cookie, + } + } +} + +impl<'a, C> io::Read for BufferedReaderReserve<'a, C> { + fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> { + let to_read = { + let data = self.reader.data(buf.len() + self.reserve)?; + if data.len() > self.reserve { + data.len() - self.reserve + } else { + return Ok(0); + } + }; + + return self.reader.read(&mut buf[0..to_read]); + } +} + +impl<'a, C> BufferedReader<C> for BufferedReaderReserve<'a, C> { + fn buffer(&self) -> &[u8] { + let buf = self.reader.buffer(); + if buf.len() > self.reserve { + &buf[..buf.len() - self.reserve] + } else { + b"" + } + } + + /// Return the buffer. Ensure that it contains at least `amount` + /// bytes. + fn data(&mut self, amount: usize) -> Result<&[u8], io::Error> { + let data = self.reader.data(amount + self.reserve)?; + if data.len() <= self.reserve { + // EOF. + Ok(b"") + } else { + // More than enough. + Ok(&data[..data.len() - self.reserve]) + } + } + + fn consume(&mut self, amount: usize) -> &[u8] { + assert!(amount <= self.buffer().len()); + + // consume may return more than amount. If it does, make sure + // it doesn't return any of the reserve. + let data = self.reader.consume(amount); + assert!(data.len() >= amount); + + if data.len() > amount { + // We got more than `amount`. We need to be careful to + // not return data from the reserve. But, we also know + // that `amount` does not include data from the reserve. + if data.len() > amount + self.reserve { + return &data[..data.len() - self.reserve]; + } + } + &data[..amount] + } + + fn data_consume(&mut self, amount: usize) -> Result<&[u8], io::Error> { + let amount = cmp::min(amount, self.data(amount)?.len()); + Ok(self.consume(amount)) + } + + fn data_consume_hard(&mut self, amount: usize) -> Result<&[u8], io::Error> { + self.data_hard(amount)?; + Ok(self.consume(amount)) + } + + fn get_mut(&mut self) -> Option<&mut BufferedReader<C>> { + Some(&mut self.reader) + } + + fn get_ref(&self) -> Option<&BufferedReader<C>> { + Some(&self.reader) + } + + fn into_inner<'b>(self: Box<Self>) -> Option<Box<BufferedReader<C> + 'b>> + where Self: 'b { + Some(self.reader) + } + + fn cookie_set(&mut self, cookie: C) -> C { + use std::mem; + + mem::replace(&mut self.cookie, cookie) + } + + fn cookie_ref(&self) -> &C { + &self.cookie + } + + fn cookie_mut(&mut self) -> &mut C { + &mut self.cookie + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn data() { + use ::BufferedReaderMemory; + + // orig is the original buffer + // + // cursor is the BufferedReaderReserve's position in orig. + // + // to_read is how much to read. + // + // total is the total to_read that be read from orig. + // + // cursor / reserve \ + // orig: [ | to_read | | ] + // \ total / + // + fn read_chunk<'a, R: BufferedReader<C>, C>( + orig: &[u8], r: &mut R, to_read: usize, cursor: usize, total: usize, + mode: usize) + { + // Use data. + let data_len = { + let data = r.data(to_read).unwrap(); + assert_eq!(data, &orig[cursor..cursor + data.len()]); + data.len() + }; + assert!(data_len <= total - cursor); + assert_eq!(r.buffer().len(), data_len); + + // Use data_hard. + let data_hard_len = { + let data_hard = r.data_hard(to_read).unwrap(); + assert_eq!(data_hard, &orig[cursor..cursor + data_hard.len()]); + data_hard.len() + }; + assert!(data_len <= data_hard_len); + assert!(data_hard_len <= total - cursor); + assert_eq!(r.buffer().len(), data_hard_len); + + + + // Make sure data_hard fails when requesting too much + // data. + assert!(r.data_hard(total - cursor + 1).is_err()); + + // And that a failing data_hard does not move the cursor. + let data_len = { + let data = r.data(to_read).unwrap(); + assert_eq!(data, &orig[cursor..cursor + data.len()]); + data.len() + }; + assert!(data_len <= total - cursor); + assert_eq!(r.buffer().len(), data_len); + + + // Likewise for data_consume_hard. + assert!(r.data_consume_hard(total - cursor + 1).is_err()); + + // And that a failing data_hard does not move the cursor. + let data_len = { + let data = r.data(to_read).unwrap(); + assert_eq!(data, &orig[cursor..cursor + data.len()]); + data.len() + }; + assert!(data_len <= total - cursor); + assert_eq!(r.buffer().len(), data_len); + + + + // Consume the chunk. + match mode { + 0 => { + // Use consume. + let l = r.consume(to_read).len(); + assert!(to_read <= l); + assert!(l <= total - cursor); + } + 1 => { + // Use data_consume. + let data_len = { + let data = r.data_consume(to_read).unwrap(); + assert_eq!(data, &orig[cursor..cursor + data.len()]); + data.len() + }; + assert!(data_len <= total - cursor); + assert!(r.buffer().len() <= total - cursor - to_read); + } + 2 => { + // Use data_consume_hard. + let data_len = { + let data = r.data_consume_hard(to_read).unwrap(); + assert_eq!(data, &orig[cursor..cursor + data.len()]); + data.len() + }; + assert!(data_len <= total - cursor); + assert!(r.buffer().len() <= total - cursor - to_read); + } + _ => panic!("Invalid mode"), + } + } + + fn test(orig: &[u8], mode: usize, reserve: usize, + mid1: usize, mid2: usize) { + let total = orig.len() - reserve; + + let mut r = BufferedReaderReserve::new( + Box::new(BufferedReaderMemory::new(orig)), reserve); + + // Read the first chunk. + read_chunk(orig, &mut r, mid1, 0, total, mode); + + // Read the second chunk. + read_chunk(orig, &mut r, mid2 - mid1, mid1, total, mode); + + // Read the remaining bit. + read_chunk(orig, &mut r, total - mid2, mid2, total, mode); + + // And, we should be at EOF. + assert_eq!(r.data(100).unwrap().len(), 0); + assert_eq!(r.buffer().len(), 0); + assert!(r.data_hard(100).is_err()); + assert_eq!(r.data_hard(0).unwrap().len(), 0); + + let mut g = Box::new(r).into_inner().unwrap(); + read_chunk(orig, &mut g, + orig.len() - total, total, orig.len(), + mode); + } + + // 26 letters. + let orig : &[u8] = b"abcdefghijklmnopqrstuvwxyz"; + + // We break up the above into four pieces: three chunks, and + // the reserved area. + for mode in 0..3 { + for reserve in 0..orig.len() { + let total = orig.len() - reserve; + + for mid1 in 0..total { + for mid2 in mid1..total { + test(orig, mode, reserve, mid1, mid2); + } + } + } + } + } +} |