Skip to content
This repository has been archived by the owner on Mar 15, 2024. It is now read-only.

Commit

Permalink
* feat(dev): implements Device<u8, _> for Read + Write + Seek objects (
Browse files Browse the repository at this point in the history
…#16)

* feat(io): provide read_exact and write_all functions

* feat(dev): add UnexpectedEof and WriteZero errors
  • Loading branch information
RatCornu committed Jan 12, 2024
1 parent 97439a1 commit 8f30d33
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 37 deletions.
21 changes: 20 additions & 1 deletion src/dev/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ use core::fmt::Display;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, PartialEq, Eq)]
pub enum DevError {
/// `OutOfBounds(structure, value, (lower_bound, upper_bound))`: the given `struct` has a `value` not between the given bounds
/// `OutOfBounds(structure, value, (lower_bound, upper_bound))`: the given `struct` has a `value` not between the given
/// bounds.
OutOfBounds(&'static str, i128, (i128, i128)),

/// An error returned when an operation could not be completed because an “end of file” was reached prematurely.
///
/// This typically means that an operation could only succeed if it read a particular number of bytes but only a smaller number
/// of bytes could be read.
UnexpectedEof,

/// An error returned when an operation could not be completed because a call to [`write`](crate::io::Write::write) returned
/// `Ok(0)`.
WriteZero,
}

impl Display for DevError {
Expand All @@ -22,6 +33,14 @@ impl Display for DevError {
"Out of Bounds: the {structure} has a value {value} not between the lower bound {lower_bound} and the upper bound {upper_bound}"
)
},
Self::UnexpectedEof => write!(
formatter,
"Unexpected End of File: an operation could not be completed because an \"end of file\" was reached prematurely"
),
Self::WriteZero => write!(
formatter,
"Write Zero: An error returned when an operation could not be completed because a call to write returned Ok(0)"
),
}
}
}
Expand Down
81 changes: 66 additions & 15 deletions src/dev/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ use core::ptr::{addr_of, slice_from_raw_parts};
#[cfg(feature = "std")]
use std::fs::File;
#[cfg(feature = "std")]
use std::io::{ErrorKind, Read, Seek, Write};
use std::io::ErrorKind;

use self::sector::Address;
use self::size::Size;
use crate::dev::error::DevError;
use crate::error::Error;
use crate::io::{Base, Read, Seek, SeekFrom, Write};

pub mod celled;
pub mod error;
Expand Down Expand Up @@ -224,7 +225,7 @@ pub trait Device<T: Copy, E: core::error::Error> {
..Address::forward_checked(starting_addr, length).ok_or(Error::Device(DevError::OutOfBounds(
"address",
i128::try_from(starting_addr.index() + length).unwrap_unchecked(),
(0, self.size().len().index().try_into().unwrap_unchecked()),
(0, self.size().0.into()),
)))?;
let slice = self.slice(range)?;
let ptr = slice.inner.as_ptr();
Expand Down Expand Up @@ -259,7 +260,7 @@ pub trait Device<T: Copy, E: core::error::Error> {
..Address::forward_checked(starting_addr, length).ok_or(Error::Device(DevError::OutOfBounds(
"address",
i128::try_from(starting_addr.index() + length).unwrap_unchecked(),
(0, self.size().len().index().try_into().unwrap_unchecked()),
(0, self.size().0.into()),
)))?;
let mut device_slice = self.slice(range)?;
let buffer = device_slice
Expand All @@ -278,12 +279,12 @@ macro_rules! impl_device {
impl<T: Copy, E: core::error::Error> Device<T, E> for $volume {
#[inline]
fn size(&self) -> Size {
Size(Address::from(self.len()))
Size(self.len() as u64)
}

#[inline]
fn slice(&self, addr_range: Range<Address>) -> Result<Slice<'_, T>, Error<E>> {
if Device::<T, E>::size(self) >= addr_range.end {
if Device::<T, E>::size(self) >= usize::from(addr_range.end) as u64 {
let addr_start = addr_range.start;
// SAFETY: it is not possible to manipulate addresses with a higher bit number than the device's
let range = unsafe { usize::try_from(addr_range.start.index()).unwrap_unchecked() }..unsafe {
Expand All @@ -296,8 +297,7 @@ macro_rules! impl_device {
"address",
// SAFETY: it is assumed that `usize` can always be converted to `i128`
unsafe { addr_range.end.index().try_into().unwrap_unchecked() },
// SAFETY: it is assumed that `usize` can always be converted to `i128`
(0, unsafe { <Self as Device<T, E>>::size(self).len().index().try_into().unwrap_unchecked() }),
(0, <Self as Device<T, E>>::size(self).0.into()),
)))
}
}
Expand Down Expand Up @@ -331,17 +331,62 @@ impl_device!(&mut [T]);
impl_device!(Vec<T>);
impl_device!(Box<[T]>);

impl<E: core::error::Error, T: Base<Error = E> + Read + Write + Seek> Device<u8, E> for RefCell<T> {
#[inline]
fn size(&self) -> Size {
let mut device = self.borrow_mut();
let offset = device.seek(SeekFrom::End(0)).expect("Could not seek the device at its end");
let size = device
.seek(SeekFrom::Start(offset))
.expect("Could not seek the device at its original offset");
Size(size)
}

#[inline]
fn slice(&self, addr_range: Range<Address>) -> Result<Slice<'_, u8>, Error<E>> {
let starting_addr = addr_range.start;
let len = TryInto::<usize>::try_into((addr_range.end - addr_range.start).index()).map_err(|_err| {
Error::Device(DevError::OutOfBounds(
"addr range",
// SAFETY: `usize::MAX <= i128::MAX`
unsafe { i128::try_from((addr_range.end - addr_range.start).index()).unwrap_unchecked() },
(0, i128::MAX),
))
})?;

let mut slice = alloc::vec![0; len];
let mut device = self.borrow_mut();
device
.seek(SeekFrom::Start(starting_addr.index().try_into().expect("Could not convert `usize` to `u64`")))
.and_then(|_| device.read_exact(&mut slice))?;

Ok(Slice::new_owned(slice, starting_addr))
}

#[inline]
fn commit(&mut self, commit: Commit<u8>) -> Result<(), Error<E>> {
let mut device = self.borrow_mut();

let offset = device.seek(SeekFrom::Start(commit.addr().index().try_into().expect("Could not convert `usize` to `u64`")))?;
device.write_all(commit.as_ref())?;
device.seek(SeekFrom::Start(offset))?;

Ok(())
}
}

#[cfg(feature = "std")]
impl<E: core::error::Error> Device<u8, E> for RefCell<File> {
#[inline]
fn size(&self) -> Size {
let metadata = self.borrow().metadata().expect("Could not read the file");
let size = TryInto::<Address>::try_into(metadata.len()).expect("Could not convert `usize` to `u64`");
size.into()
Size(metadata.len())
}

#[inline]
fn slice(&self, addr_range: Range<Address>) -> Result<Slice<'_, u8>, Error<E>> {
use std::io::{Read, Seek};

let starting_addr = addr_range.start;
let len = TryInto::<usize>::try_into((addr_range.end - addr_range.start).index()).map_err(|_err| {
Error::Device(DevError::OutOfBounds(
Expand All @@ -364,22 +409,28 @@ impl<E: core::error::Error> Device<u8, E> for RefCell<File> {
"address",
// SAFETY: `usize::MAX <= i128::MAX`
unsafe { i128::try_from(usize::from(starting_addr + len)).unwrap_unchecked() },
// SAFETY: the size of a file is smaller than `i128::MAX`
(0, unsafe { usize::from(Device::<u8, E>::size(self).0).try_into().unwrap_unchecked() }),
(0, Device::<u8, E>::size(self).0.into()),
)))
} else {
panic!("{err}");
Err(Error::IO(err))
}
},
}
}

#[inline]
fn commit(&mut self, commit: Commit<u8>) -> Result<(), Error<E>> {
use std::io::{Seek, Write};

let mut file = self.borrow_mut();
file.seek(std::io::SeekFrom::Start(commit.addr().index().try_into().expect("Could not convert `usize` to `u64`")))
.and_then(|_| file.write_all(commit.as_ref()))
.expect("Could not seek/write on the given file");
match file.seek(std::io::SeekFrom::Start(commit.addr().index().try_into().expect("Could not convert `usize` to `u64`"))) {
Ok(offset) => {
file.write_all(commit.as_ref()).expect("Could not write on the given file");
file.seek(std::io::SeekFrom::Start(offset)).expect("Could not seek on the given file");
},
Err(err) => panic!("{err}: Could not seek on the given file"),
}

Ok(())
}
}
Expand Down
29 changes: 8 additions & 21 deletions src/dev/size.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,27 @@

use core::cmp::Ordering;

use super::sector::Address;

/// Caracterize the size of a device.
///
/// For a device, it is the lowest non-accessible address.
/// Caracterize the size of a device in bytes.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Size(pub Address);
pub struct Size(pub u64);

impl PartialEq<Address> for Size {
impl PartialEq<u64> for Size {
#[inline]
fn eq(&self, other: &Address) -> bool {
fn eq(&self, other: &u64) -> bool {
self.0.eq(other)
}
}

impl PartialOrd<Address> for Size {
impl PartialOrd<u64> for Size {
#[inline]
fn partial_cmp(&self, other: &Address) -> Option<Ordering> {
fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}

impl From<Address> for Size {
impl From<u64> for Size {
#[inline]
fn from(value: Address) -> Self {
fn from(value: u64) -> Self {
Self(value)
}
}

impl Size {
/// Returns the length of the device if it exists
#[inline]
#[must_use]
pub const fn len(&self) -> Address {
self.0
}
}
50 changes: 50 additions & 0 deletions src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use derive_more::{Deref, DerefMut};

use crate::dev::error::DevError;
use crate::error::Error;

/// Base I/O trait that must be implemented for all types implementing [`Read`], [`Write`] or [`Seek`].
Expand All @@ -26,6 +27,31 @@ pub trait Read: Base {
///
/// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be read.
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>>;

/// Read the exact number of bytes required to fill buf.
///
/// See [`read_exact`](https://docs.rs/no_std_io/latest/no_std_io/io/trait.Read.html#method.read_exact) for more information.
///
/// # Errors
///
/// Returns an [`UnexpectedEof`](DevError::UnexpectedEof) if the buffer could not be entirely filled.
///
/// Otherwise, returns the same errors as [`read`](Read::read).
#[allow(clippy::indexing_slicing)]
#[inline]
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error<Self::Error>> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
},
Err(err) => return Err(err),
}
}
if buf.is_empty() { Ok(()) } else { Err(Error::Device(DevError::UnexpectedEof)) }
}
}

/// Allows for writing bytes to a destination.
Expand Down Expand Up @@ -53,6 +79,30 @@ pub trait Write: Base {
///
/// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be read.
fn flush(&mut self) -> Result<(), Error<Self::Error>>;

/// Attempts to write an entire buffer into this writer.
///
/// See [`write_all`](https://docs.rs/no_std_io/latest/no_std_io/io/trait.Write.html#method.write_all) for more information.
///
/// # Errors
///
/// Returns a [`WriteZero`](DevError::WriteZero) error if the buffer could not be written entirely.
///
/// Otherwise, returns the same errors as [`write`](Write::write).
#[allow(clippy::indexing_slicing)]
#[inline]
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error<Self::Error>> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => {
return Err(Error::Device(DevError::WriteZero));
},
Ok(n) => buf = &buf[n..],
Err(err) => return Err(err),
}
}
Ok(())
}
}

/// Enumeration of possible methods to seek within an I/O object.
Expand Down

0 comments on commit 8f30d33

Please sign in to comment.