diff --git a/src/lib.rs b/src/lib.rs index 845cb8e0..8a0e187c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -343,6 +343,9 @@ The feature `bits` enables the `bitvec` crate to use when reading and writing, w This however slows down the reading and writing process if your code doesn't use `bits` and the `bit_offset` in `from_bytes`. +# NoSeek +Unseekable streams such as [TcpStream](https://doc.rust-lang.org/std/net/struct.TcpStream.html) are supported through the [NoSeek](noseek::NoSeek) wrapper. + */ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] @@ -377,6 +380,7 @@ pub mod attributes; pub mod ctx; pub mod error; mod impls; +pub mod noseek; pub mod prelude; pub mod reader; pub mod writer; diff --git a/src/noseek.rs b/src/noseek.rs new file mode 100644 index 00000000..fc9264e0 --- /dev/null +++ b/src/noseek.rs @@ -0,0 +1,124 @@ +//! Wrapper type that provides a fake [`Seek`] implementation. + +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec}; + +use crate::no_std_io::{Read, Result, Seek, SeekFrom, Write}; +use no_std_io::io::ErrorKind; + +/// A wrapper that provides a limited implementation of +/// [`Seek`] for unseekable [`Read`] and [`Write`] streams. +/// +/// This is useful when reading or writing from unseekable streams where deku +/// does not *actually* need to seek to successfully parse or write the data. +/// +/// This implementation was taken from [binrw](https://docs.rs/binrw/0.14.0/binrw/io/struct.NoSeek.html) +pub struct NoSeek { + /// The original stream. + inner: T, + /// The virtual position of the seekable stream. + pos: u64, +} + +impl NoSeek { + /// Creates a new seekable wrapper for the given value. + pub fn new(inner: T) -> Self { + NoSeek { inner, pos: 0 } + } + + /// Gets a mutable reference to the underlying value. + pub fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + /// Gets a reference to the underlying value. + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Consumes this wrapper, returning the underlying value. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl Seek for NoSeek { + fn seek(&mut self, pos: SeekFrom) -> Result { + match pos { + SeekFrom::Start(n) if self.pos == n => Ok(n), + SeekFrom::Current(0) => Ok(self.pos), + // https://github.com/rust-lang/rust/issues/86442 + #[cfg(feature = "std")] + _ => Err(std::io::Error::new( + ErrorKind::Other, + "seek on unseekable file", + )), + #[cfg(not(feature = "std"))] + _ => panic!("seek on unseekable file"), + } + } + + #[cfg(feature = "std")] + fn stream_position(&mut self) -> Result { + Ok(self.pos) + } +} + +impl Read for NoSeek { + fn read(&mut self, buf: &mut [u8]) -> Result { + let n = self.inner.read(buf)?; + self.pos += n as u64; + Ok(n) + } + + #[cfg(feature = "std")] + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> Result { + let n = self.inner.read_vectored(bufs)?; + self.pos += n as u64; + Ok(n) + } + + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + let n = self.inner.read_to_end(buf)?; + self.pos += n as u64; + Ok(n) + } + + #[cfg(feature = "std")] + fn read_to_string(&mut self, buf: &mut String) -> Result { + let n = self.inner.read_to_string(buf)?; + self.pos += n as u64; + Ok(n) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + self.inner.read_exact(buf)?; + self.pos += buf.len() as u64; + Ok(()) + } +} + +impl Write for NoSeek { + fn write(&mut self, buf: &[u8]) -> Result { + let n = self.inner.write(buf)?; + self.pos += n as u64; + Ok(n) + } + + fn flush(&mut self) -> Result<()> { + self.inner.flush() + } + + #[cfg(feature = "std")] + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> Result { + let n = self.inner.write_vectored(bufs)?; + self.pos += n as u64; + Ok(n) + } + + fn write_all(&mut self, buf: &[u8]) -> Result<()> { + self.inner.write_all(buf)?; + self.pos += buf.len() as u64; + Ok(()) + } +} diff --git a/tests/test_from_reader.rs b/tests/test_from_reader.rs index 1e7e87dc..a366884d 100644 --- a/tests/test_from_reader.rs +++ b/tests/test_from_reader.rs @@ -1,3 +1,4 @@ +use deku::noseek::NoSeek; use deku::prelude::*; use no_std_io::io::Seek; @@ -32,6 +33,12 @@ fn test_from_reader_struct() { let (amt_read, ret_read) = TestDeku::from_reader((&mut c, total_read)).unwrap(); assert_eq!(amt_read, 16); assert_eq!(TestDeku(0b1010), ret_read); + + let inner = &c.into_inner(); + let mut s = NoSeek::new(inner.as_slice()); + let (amt_read, ret_read) = TestDeku::from_reader((&mut s, 0)).unwrap(); + assert_eq!(amt_read, 4); + assert_eq!(TestDeku(0b0110), ret_read); } #[cfg(feature = "bits")] @@ -57,4 +64,11 @@ fn test_from_reader_enum() { let (amt_read, ret_read) = TestDeku::from_reader((&mut c, first_amt_read)).unwrap(); assert_eq!(amt_read, 6 + first_amt_read); assert_eq!(TestDeku::VariantB(0b10), ret_read); + + c.rewind(); + let inner = &c.into_inner(); + let mut s = NoSeek::new(inner.as_slice()); + let (amt_read, ret_read) = TestDeku::from_reader((&mut s, 0)).unwrap(); + assert_eq!(first_amt_read, 8); + assert_eq!(TestDeku::VariantA(0b0110), ret_read); } diff --git a/tests/test_seek.rs b/tests/test_seek.rs index 6d3d6116..a75354dc 100644 --- a/tests/test_seek.rs +++ b/tests/test_seek.rs @@ -1,4 +1,4 @@ -use deku::prelude::*; +use deku::{noseek::NoSeek, prelude::*}; use hexlit::hex; use rstest::*; @@ -25,7 +25,6 @@ pub struct Test { )] fn test_seek(input: &[u8], expected: Test) { let input = input.to_vec(); - let mut cursor = std::io::Cursor::new(input.clone()); let (_, ret_read) = Test::from_reader((&mut cursor, 0)).unwrap(); @@ -115,3 +114,36 @@ fn test_seek_ctx_end(input: &[u8], expected: SeekCtxBeforeEnd) { let _ = ret_read.to_writer(&mut writer, ()).unwrap(); assert_eq!(buf, input); } + +#[derive(DekuRead, DekuWrite, Debug, PartialEq, Eq)] +#[deku(seek_from_start = "0")] +pub struct SeekCtxNoSeek { + byte: u8, +} + +#[rstest(input, expected, + case(&hex!("03"), SeekCtxNoSeek { byte: 0x03 }), + case(&hex!("ff"), SeekCtxNoSeek { byte: 0xff }), +)] +fn test_seek_ctx_no_seek(input: &[u8], expected: SeekCtxNoSeek) { + use std::io::Cursor; + let input = input.to_vec(); + + let mut cursor = std::io::Cursor::new(input.clone()); + let mut reader = Reader::new(&mut cursor); + let ret_read = SeekCtxNoSeek::from_reader_with_ctx(&mut reader, ()).unwrap(); + + assert_eq!(ret_read, expected); + + let mut buf = vec![]; + let mut cursor = Cursor::new(&mut buf); + let mut writer = Writer::new(&mut cursor); + let _ = ret_read.to_writer(&mut writer, ()).unwrap(); + assert_eq!(buf, input); + + let mut buf = vec![]; + let mut cursor = NoSeek::new(&mut buf); + let mut writer = Writer::new(&mut cursor); + let _ = ret_read.to_writer(&mut writer, ()).unwrap(); + assert_eq!(buf, input); +} diff --git a/tests/test_struct.rs b/tests/test_struct.rs index 92ed213e..c0844822 100644 --- a/tests/test_struct.rs +++ b/tests/test_struct.rs @@ -198,6 +198,11 @@ fn test_big_endian() { let new_bytes = a.to_bytes().unwrap(); assert_eq!(bytes, &*new_bytes); + let bytes = &[0x11, 0x22, 0x33]; + let a = A::from_bytes((bytes, 0)).unwrap().1; + let new_bytes = a.to_bytes().unwrap(); + assert_eq!(bytes, &*new_bytes); + #[derive(PartialEq, Debug, DekuRead, DekuWrite)] pub struct B { #[deku(bytes = "2", endian = "big")]