Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NoSeek #487

Merged
merged 2 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading