Skip to content

embedded-io-adapters: Add adapters for embedded-storage and embedded-storage-async #609

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion embedded-io-adapters/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

Add unreleased changes here
- Add adapters for the `embedded-storage` and `embedded-storage-async` traits.

## 0.6.1 - 2023-11-28

6 changes: 6 additions & 0 deletions embedded-io-adapters/Cargo.toml
Original file line number Diff line number Diff line change
@@ -16,13 +16,19 @@ categories = [
std = ["embedded-io/std"]
tokio-1 = ["std", "dep:tokio", "dep:embedded-io-async", "embedded-io-async?/std"]
futures-03 = ["std", "dep:futures", "dep:embedded-io-async", "embedded-io-async?/std"]
embedded-storage = ["dep:embedded-storage"]
embedded-storage-async = ["embedded-storage", "dep:embedded-storage-async", "dep:embedded-io-async"]
defmt-03 = ["dep:defmt-03"]

[dependencies]
embedded-io = { version = "0.6", path = "../embedded-io" }
embedded-io-async = { version = "0.6.1", path = "../embedded-io-async", optional = true }

futures = { version = "0.3.21", features = ["std"], default-features = false, optional = true }
tokio = { version = "1", features = ["io-util"], default-features = false, optional = true }
embedded-storage = { git = "https://github.com/rust-embedded-community/embedded-storage.git", optional = true }
embedded-storage-async = { git = "https://github.com/rust-embedded-community/embedded-storage.git", optional = true }
defmt-03 = { package = "defmt", version = "0.3", optional = true }

[package.metadata.docs.rs]
features = ["std", "tokio-1", "futures-03"]
5 changes: 4 additions & 1 deletion embedded-io-adapters/README.md
Original file line number Diff line number Diff line change
@@ -21,11 +21,13 @@ This allows using these adapters when using combinations of traits, like `Read+W
For `embedded-io`:

- [`std::io`](https://doc.rust-lang.org/stable/std/io/index.html) traits. Needs the `std` feature.
- [`embedded-storage`](https://crates.io/crates/embedded-storage) traits. Needs the `embedded-storage` feature.

For `embedded-io-async`:

- [`futures` 0.3](https://crates.io/crates/futures) traits. Needs the `futures-03` feature.
- [`tokio` 1.x](https://crates.io/crates/tokio) traits. Needs the `tokio-1` feature.
- [`embedded-storage-async`](https://crates.io/crates/embedded-storage-async) traits. Needs the `embedded-storage-async` feature.

## Minimum Supported Rust Version (MSRV)

@@ -34,7 +36,8 @@ compile with older versions but that may change in any new patch release.

See [here](../docs/msrv.md) for details on how the MSRV may be upgraded.

Enabling any of the `tokio-*` or `futures-*` Cargo features requires Rust 1.75 or higher.
Enabling any of the `tokio-*`, `futures-*` or `embedded-storage-async` Cargo features
requires Rust 1.75 or higher.

## License

174 changes: 174 additions & 0 deletions embedded-io-adapters/src/embedded_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
//! Adapters to/from `embedded_storage` traits.
// needed to prevent defmt macros from breaking, since they emit code that does `defmt::blahblah`.
#[cfg(feature = "defmt-03")]
use defmt_03 as defmt;

use embedded_io::{Error, ErrorKind, Read, Seek, SeekFrom, Write};
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
use embedded_storage::{ReadStorage, Storage};

/// Adapter from `embedded_storage` traits.
#[derive(Clone)]
pub struct FromEmbeddedStorage<T: ?Sized> {
position: u32,
inner: T,
}

impl<T> FromEmbeddedStorage<T> {
/// Create a new adapter.
pub fn new(inner: T) -> Self {
Self { position: 0, inner }
}

/// Consume the adapter, returning the inner object.
pub fn into_inner(self) -> T {
self.inner
}
}

impl<T: ?Sized> FromEmbeddedStorage<T> {
/// Borrow the inner object.
pub fn inner(&self) -> &T {
&self.inner
}

/// Mutably borrow the inner object.
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
}

impl<T: ?Sized> embedded_io::ErrorType for FromEmbeddedStorage<T> {
type Error = StorageIOError;
}

impl<T: ReadStorage<Error = E> + ?Sized, E: Into<StorageIOError>> embedded_io::Read
for FromEmbeddedStorage<T>
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.inner.read(self.position, buf).map_err(|e| e.into())?;
self.position += buf.len() as u32;
Ok(buf.len())
}
}

impl<T: Storage<Error = E> + ?Sized, E: Into<StorageIOError>> embedded_io::Write
for FromEmbeddedStorage<T>
{
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.inner.write(self.position, buf).map_err(|e| e.into())?;
self.position += buf.len() as u32;
Ok(buf.len())
}

fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}

impl<T: ReadStorage<Error = E> + ?Sized, E: Into<StorageIOError>> embedded_io::Seek
for FromEmbeddedStorage<T>
{
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
let new_position = match pos {
SeekFrom::Start(pos) => pos as i64,
SeekFrom::End(offset) => self.inner.capacity() as i64 + offset,
SeekFrom::Current(offset) => self.position as i64 + offset,
};
self.position = new_position as u32;
Ok(self.position as u64)
}
}

/// Adapter to `embedded_storage` traits.
#[derive(Clone)]
pub struct ToEmbeddedStorage<T: ?Sized> {
capacity: usize,
inner: T,
}

impl<T: Seek> ToEmbeddedStorage<T> {
/// Create a new adapter.
pub fn new(mut inner: T) -> Self {
let capacity = inner.seek(SeekFrom::End(0)).unwrap() as usize;
Self { inner, capacity }
}

/// Consume the adapter, returning the inner object.
pub fn into_inner(self) -> T {
self.inner
}
}

impl<T: ?Sized> ToEmbeddedStorage<T> {
/// Borrow the inner object.
pub fn inner(&self) -> &T {
&self.inner
}

/// Mutably borrow the inner object.
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
}

impl<T: Read + Seek + ?Sized> ReadStorage for ToEmbeddedStorage<T> {
type Error = T::Error;

fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.inner.seek(SeekFrom::Start(offset as u64))?;
let mut read = 0;
while read < bytes.len() {
read += self.inner.read(&mut bytes[read..])?;
}
Ok(())
}

fn capacity(&self) -> usize {
self.capacity
}
}

impl<T: Read + Write + Seek + ?Sized> Storage for ToEmbeddedStorage<T> {
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.inner.seek(SeekFrom::Start(offset as u64))?;
let mut written = 0;
while written < bytes.len() {
written += self.inner.write(&bytes[written..])?;
}
Ok(())
}
}

/// An error type that is implementing embedded_io::Error and that the concret
/// Storage::Error type should be able to convert to, to allow error compatibility
/// with the embedded_io layer.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
pub struct StorageIOError {
kind: ErrorKind,
}

impl StorageIOError {
/// Create a new StorageIOError.
pub fn new(kind: ErrorKind) -> Self {
Self { kind }
}
}

impl Error for StorageIOError {
fn kind(&self) -> ErrorKind {
self.kind
}
}

impl<E: NorFlashError> From<E> for StorageIOError {
fn from(value: E) -> Self {
match value.kind() {
NorFlashErrorKind::NotAligned => Self::new(ErrorKind::InvalidInput),
NorFlashErrorKind::OutOfBounds => Self::new(ErrorKind::InvalidInput),
_ => Self::new(ErrorKind::Other),
}
}
}
139 changes: 139 additions & 0 deletions embedded-io-adapters/src/embedded_storage_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//! Adapters to/from `embedded_storage_async` traits.
use embedded_io_async::{Read, Seek, SeekFrom, Write};
use embedded_storage_async::{ReadStorage, Storage};

pub use crate::embedded_storage::StorageIOError;

/// Adapter from `embedded_storage_async` traits.
#[derive(Clone)]
pub struct FromEmbeddedStorage<T: ?Sized> {
position: u32,
inner: T,
}

impl<T> FromEmbeddedStorage<T> {
/// Create a new adapter.
pub fn new(inner: T) -> Self {
Self { position: 0, inner }
}

/// Consume the adapter, returning the inner object.
pub fn into_inner(self) -> T {
self.inner
}
}

impl<T: ?Sized> FromEmbeddedStorage<T> {
/// Borrow the inner object.
pub fn inner(&self) -> &T {
&self.inner
}

/// Mutably borrow the inner object.
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
}

impl<T: ?Sized> embedded_io_async::ErrorType for FromEmbeddedStorage<T> {
type Error = StorageIOError;
}

impl<T: ReadStorage<Error = E> + ?Sized, E: Into<StorageIOError>> Read for FromEmbeddedStorage<T> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.inner
.read(self.position, buf)
.await
.map_err(|e| e.into())?;
self.position += buf.len() as u32;
Ok(buf.len())
}
}

impl<T: Storage<Error = E> + ?Sized, E: Into<StorageIOError>> Write for FromEmbeddedStorage<T> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.inner
.write(self.position, buf)
.await
.map_err(|e| e.into())?;
self.position += buf.len() as u32;
Ok(buf.len())
}

async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}

impl<T: ReadStorage<Error = E> + ?Sized, E: Into<StorageIOError>> Seek for FromEmbeddedStorage<T> {
async fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
let new_position = match pos {
SeekFrom::Start(pos) => pos as i64,
SeekFrom::End(offset) => self.inner.capacity() as i64 + offset,
SeekFrom::Current(offset) => self.position as i64 + offset,
};
self.position = new_position as u32;
Ok(self.position as u64)
}
}

/// Adapter to `embedded_storage_async` traits.
#[derive(Clone)]
pub struct ToEmbeddedStorage<T: ?Sized> {
capacity: usize,
inner: T,
}

impl<T: Seek> ToEmbeddedStorage<T> {
/// Create a new adapter.
pub async fn new(mut inner: T) -> Self {
let capacity = inner.seek(SeekFrom::End(0)).await.unwrap() as usize;
Self { inner, capacity }
}

/// Consume the adapter, returning the inner object.
pub fn into_inner(self) -> T {
self.inner
}
}

impl<T: ?Sized> ToEmbeddedStorage<T> {
/// Borrow the inner object.
pub fn inner(&self) -> &T {
&self.inner
}

/// Mutably borrow the inner object.
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
}

impl<T: Read + Seek + ?Sized> ReadStorage for ToEmbeddedStorage<T> {
type Error = T::Error;

async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.inner.seek(SeekFrom::Start(offset as u64)).await?;
let mut read = 0;
while read < bytes.len() {
read += self.inner.read(&mut bytes[read..]).await?;
}
Ok(())
}

fn capacity(&self) -> usize {
self.capacity
}
}

impl<T: Read + Write + Seek + ?Sized> Storage for ToEmbeddedStorage<T> {
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.inner.seek(SeekFrom::Start(offset as u64)).await?;
let mut written = 0;
while written < bytes.len() {
written += self.inner.write(&bytes[written..]).await?;
}
Ok(())
}
}
26 changes: 24 additions & 2 deletions embedded-io-adapters/src/lib.rs
Original file line number Diff line number Diff line change
@@ -7,11 +7,25 @@
// We don't immediately remove them to not immediately break older nightlies.
// When all features are stable, we'll remove them.
#![cfg_attr(
all(any(feature = "tokio-1", feature = "futures-03"), nightly),
all(
any(
feature = "tokio-1",
feature = "futures-03",
feature = "embedded-storage-async"
),
nightly
),
allow(stable_features)
)]
#![cfg_attr(
all(any(feature = "tokio-1", feature = "futures-03"), nightly),
all(
any(
feature = "tokio-1",
feature = "futures-03",
feature = "embedded-storage-async"
),
nightly
),
feature(async_fn_in_trait, impl_trait_projections)
)]

@@ -26,3 +40,11 @@ pub mod futures_03;
#[cfg(feature = "tokio-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio-1")))]
pub mod tokio_1;

#[cfg(feature = "embedded-storage")]
#[cfg_attr(docsrs, doc(cfg(feature = "embedded-storage")))]
pub mod embedded_storage;

#[cfg(feature = "embedded-storage-async")]
#[cfg_attr(docsrs, doc(cfg(feature = "embedded-storage-async")))]
pub mod embedded_storage_async;