Skip to content

Commit

Permalink
Export std::io instead of always using thin reimplementation
Browse files Browse the repository at this point in the history
This allows most implementers of `BinRead` to use std features
on the reader instead of restricting them to the subset of
features exposed by the no_std reimplementation. This also
appears to have been the original intent of the `binread::io`
module, per the module-level comment.

This is a breaking change because `iter_bytes` is called `bytes`
in std and consumes the reader, so this has been changed in the
binread implementation to conform to that API.

This commit also fixes the earlier binread implementations not
handling errors as std does; this is now fixed (by copying
from std).

(Hopefully rust-lang/rust#48331 will be addressed someday and then
this code can disappear entirely.)
  • Loading branch information
csnover committed Mar 9, 2021
1 parent c543fe6 commit be4273a
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 83 deletions.
8 changes: 7 additions & 1 deletion binread/src/io/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum Repr {
}

#[non_exhaustive]
#[derive(Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorKind {
NotFound,
PermissionDenied,
Expand Down Expand Up @@ -44,4 +44,10 @@ impl Error {
repr: Repr::Simple(kind)
}
}

pub fn kind(&self) -> ErrorKind {
match self.repr {
Repr::Simple(kind) => kind,
}
}
}
85 changes: 4 additions & 81 deletions binread/src/io/mod.rs
Original file line number Diff line number Diff line change
@@ -1,91 +1,14 @@
//! A swappable version of [std::io](std::io) that works in `no_std + alloc` environments.
//! If the feature flag `std` is enabled (as it is by default), this will just re-export types from `std::io`.

pub mod prelude;
pub mod cursor;
pub mod error;

#[cfg(feature = "std")]
pub use std::io::{Error, ErrorKind};

#[cfg(not(feature = "std"))]
pub use error::{Error, ErrorKind};

#[cfg(feature = "std")]
pub use std::io::Result;

#[cfg(not(feature = "std"))]
pub type Result<T> = core::result::Result<T, Error>;

/// A simplified version of [std::io::Read](std::io::Read) for use in no_std environments
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;

fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
if let Ok(n) = self.read(buf) {
if n == buf.len() {
return Ok(())
}
}

Err(Error::new(ErrorKind::UnexpectedEof, "Out of bytes in reader"))
}

fn iter_bytes(&mut self) -> Bytes<'_, Self>
where Self: Sized,
{
Bytes {
inner: self
}
}
}

pub struct Bytes<'a, R: Read> {
inner: &'a mut R
}

impl<'a, R: Read> Iterator for Bytes<'a, R> {
type Item = Result<u8>;

fn next(&mut self) -> Option<Self::Item> {
let mut byte = [0u8];
Some(
self.inner.read_exact(&mut byte)
.map(|_| byte[0])
)
}
}

#[cfg(feature = "std")]
pub use std::io::SeekFrom;

mod no_std;
#[cfg(not(feature = "std"))]
#[derive(Debug, Clone, Copy)]
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}

pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
}
pub use no_std::*;

#[cfg(feature = "std")]
impl<R: std::io::Read> Read for R {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.read(buf)
}
}

#[cfg(feature = "std")]
impl<S: std::io::Seek> Seek for S {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
self.seek(pos)
}
}

#[cfg(feature = "std")]
pub use std::io::Cursor;

#[cfg(not(feature = "std"))]
pub use cursor::Cursor;
pub use std::io::{Bytes, Cursor, Error, ErrorKind, Read, Result, Seek, SeekFrom};
92 changes: 92 additions & 0 deletions binread/src/io/no_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
pub use super::{cursor::Cursor, error::{Error, ErrorKind}};

pub type Result<T> = core::result::Result<T, Error>;

/// A simplified version of [std::io::Read](std::io::Read) for use in no_std environments
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;

fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
if !buf.is_empty() {
Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
} else {
Ok(())
}
}

fn bytes(self) -> Bytes<Self>
where
Self: Sized,
{
Bytes { inner: self }
}

fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{
self
}
}

impl<R: Read + ?Sized> Read for &mut R {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
(**self).read(buf)
}

#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
(**self).read_exact(buf)
}
}

#[derive(Debug)]
pub struct Bytes<R: Read> {
inner: R
}

impl<R: Read> Iterator for Bytes<R> {
type Item = Result<u8>;

fn next(&mut self) -> Option<Result<u8>> {
let mut byte = 0;
loop {
return match self.inner.read(core::slice::from_mut(&mut byte)) {
Ok(0) => None,
Ok(..) => Some(Ok(byte)),
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => Some(Err(e)),
};
}
}
}

#[derive(Debug, Clone, Copy)]
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}

pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
}

impl<S: Seek + ?Sized> Seek for &mut S {
#[inline]
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
(**self).seek(pos)
}
}
2 changes: 1 addition & 1 deletion binread/src/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl BinRead for Vec<NonZeroU8> {
fn read_options<R: Read + Seek>(reader: &mut R, _: &ReadOptions, _: Self::Args) -> BinResult<Self>
{
reader
.iter_bytes()
.bytes()
.take_while(|x| !matches!(x, Ok(0)))
.map(|x| Ok(x.map(|byte| unsafe { NonZeroU8::new_unchecked(byte) })?))
.collect()
Expand Down
2 changes: 2 additions & 0 deletions binread/tests/io/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(not(feature = "std"))]
mod no_std;
87 changes: 87 additions & 0 deletions binread/tests/io/no_std.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use binread::io::{Cursor, Error, ErrorKind, Read, Result};

#[derive(Debug)]
struct MalfunctioningEddie<'data> {
error: Option<Error>,
data: Cursor<&'data [u8]>,
}

impl <'data> MalfunctioningEddie<'data> {
fn new(data: &'data[u8]) -> Self {
Self {
error: None,
data: Cursor::new(data),
}
}

fn nice_to_meet_you(&mut self) {
self.error = Some(Error::new(ErrorKind::BrokenPipe, ""));
}

fn what_a_surprise(&mut self) {
self.error = Some(Error::new(ErrorKind::Interrupted, ""));
}
}

impl Read for MalfunctioningEddie<'_> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if let Some(error) = self.error.take() {
Err(error)
} else {
self.data.read(buf)
}
}
}

#[test]
fn bytes() {
let mut cursor = MalfunctioningEddie::new(b"\0\x01\x02\x03\x04\x05");
{
let mut bytes = cursor.by_ref().bytes();
assert!(matches!(bytes.next(), Some(Ok(0))));
assert!(matches!(bytes.next(), Some(Ok(1))));
}

// Interrupted error should cause a retry
cursor.what_a_surprise();
{
let mut bytes = cursor.by_ref().bytes();
assert!(matches!(bytes.next(), Some(Ok(2))));
}

// Reads through Bytes should have advanced the underlying stream
let mut raw_read_data = [0u8; 2];
assert_eq!(cursor.read(&mut raw_read_data).unwrap(), 2);
assert_eq!(raw_read_data, [3, 4]);

// Errors other than Interrupted should be returned
cursor.nice_to_meet_you();
let mut bytes = cursor.bytes();
assert_eq!(bytes.next().unwrap().unwrap_err().kind(), ErrorKind::BrokenPipe);
}

#[test]
fn read_exact() {
let mut cursor = MalfunctioningEddie::new(b"\0\x01\x02\x03\x04\x05");

let mut raw_read_data = [0u8; 2];
cursor.read_exact(&mut raw_read_data).unwrap();
assert_eq!(raw_read_data, [0, 1]);

// Interrupted error should cause a retry
cursor.what_a_surprise();
cursor.read_exact(&mut raw_read_data).unwrap();
assert_eq!(raw_read_data, [2, 3]);

// Errors other than Interrupted should be returned
cursor.nice_to_meet_you();
assert_eq!(cursor.read_exact(&mut raw_read_data).unwrap_err().kind(), ErrorKind::BrokenPipe);

// Read through a mutable reference should work as if it were directly on
// the cursor
cursor.by_ref().read_exact(&mut raw_read_data).unwrap();
assert_eq!(raw_read_data, [4, 5]);

// EOF reads should not succeed
assert_eq!(cursor.read_exact(&mut raw_read_data).unwrap_err().kind(), ErrorKind::UnexpectedEof);
}
1 change: 1 addition & 0 deletions binread/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod io;

0 comments on commit be4273a

Please sign in to comment.