Skip to content

Commit

Permalink
Add NoSeek (#487)
Browse files Browse the repository at this point in the history
* Add NoSeek

* Add NoSeek to implement Seek for types that won't
need to seek, but have the trait impl.

* fixup! Add NoSeek

add more docs
  • Loading branch information
wcampbell0x2a authored Oct 29, 2024
1 parent b3be1e3 commit cbad152
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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;
Expand Down
124 changes: 124 additions & 0 deletions src/noseek.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
/// The original stream.
inner: T,
/// The virtual position of the seekable stream.
pos: u64,
}

impl<T> NoSeek<T> {
/// 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<T> Seek for NoSeek<T> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
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<u64> {
Ok(self.pos)
}
}

impl<T: Read> Read for NoSeek<T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
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<usize> {
let n = self.inner.read_vectored(bufs)?;
self.pos += n as u64;
Ok(n)
}

fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
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<usize> {
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<T: Write> Write for NoSeek<T> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
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<usize> {
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(())
}
}
14 changes: 14 additions & 0 deletions tests/test_from_reader.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use deku::noseek::NoSeek;
use deku::prelude::*;
use no_std_io::io::Seek;

Expand Down Expand Up @@ -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")]
Expand All @@ -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);
}
36 changes: 34 additions & 2 deletions tests/test_seek.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use deku::prelude::*;
use deku::{noseek::NoSeek, prelude::*};
use hexlit::hex;
use rstest::*;

Expand All @@ -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();

Expand Down Expand Up @@ -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);
}
5 changes: 5 additions & 0 deletions tests/test_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down

0 comments on commit cbad152

Please sign in to comment.