From 02db603b46a0f057bb5075e53a6cb69ad4d6f9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Jacobs?= Date: Fri, 14 Jun 2024 17:22:23 +0200 Subject: [PATCH 1/2] embedded-io-adapters: Add adapters for embedded-storage and embedded-storage-async --- embedded-io-adapters/CHANGELOG.md | 2 +- embedded-io-adapters/Cargo.toml | 6 + embedded-io-adapters/README.md | 5 +- embedded-io-adapters/src/embedded_storage.rs | 174 ++++++++++++++++++ .../src/embedded_storage_async.rs | 139 ++++++++++++++ embedded-io-adapters/src/lib.rs | 26 ++- 6 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 embedded-io-adapters/src/embedded_storage.rs create mode 100644 embedded-io-adapters/src/embedded_storage_async.rs diff --git a/embedded-io-adapters/CHANGELOG.md b/embedded-io-adapters/CHANGELOG.md index b540b67b4..7bc6b75be 100644 --- a/embedded-io-adapters/CHANGELOG.md +++ b/embedded-io-adapters/CHANGELOG.md @@ -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 diff --git a/embedded-io-adapters/Cargo.toml b/embedded-io-adapters/Cargo.toml index 89721f1bb..ae5c4a89e 100644 --- a/embedded-io-adapters/Cargo.toml +++ b/embedded-io-adapters/Cargo.toml @@ -16,6 +16,9 @@ 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" } @@ -23,6 +26,9 @@ embedded-io-async = { version = "0.6.1", path = "../embedded-io-async", optional 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 = { version = "0.3.1", optional = true } +embedded-storage-async = { version = "0.4.1", optional = true } +defmt-03 = { package = "defmt", version = "0.3", optional = true } [package.metadata.docs.rs] features = ["std", "tokio-1", "futures-03"] diff --git a/embedded-io-adapters/README.md b/embedded-io-adapters/README.md index a069d1fac..b71da48e3 100644 --- a/embedded-io-adapters/README.md +++ b/embedded-io-adapters/README.md @@ -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 diff --git a/embedded-io-adapters/src/embedded_storage.rs b/embedded-io-adapters/src/embedded_storage.rs new file mode 100644 index 000000000..1463cc607 --- /dev/null +++ b/embedded-io-adapters/src/embedded_storage.rs @@ -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 { + position: u32, + inner: T, +} + +impl FromEmbeddedStorage { + /// 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 FromEmbeddedStorage { + /// 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 embedded_io::ErrorType for FromEmbeddedStorage { + type Error = StorageIOError; +} + +impl + ?Sized, E: Into> embedded_io::Read + for FromEmbeddedStorage +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(self.position, buf).map_err(|e| e.into())?; + self.position += buf.len() as u32; + Ok(buf.len()) + } +} + +impl + ?Sized, E: Into> embedded_io::Write + for FromEmbeddedStorage +{ + fn write(&mut self, buf: &[u8]) -> Result { + 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 + ?Sized, E: Into> embedded_io::Seek + for FromEmbeddedStorage +{ + fn seek(&mut self, pos: SeekFrom) -> Result { + 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 { + capacity: usize, + inner: T, +} + +impl ToEmbeddedStorage { + /// 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 ToEmbeddedStorage { + /// 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 ReadStorage for ToEmbeddedStorage { + 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 Storage for ToEmbeddedStorage { + 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 From 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), + } + } +} diff --git a/embedded-io-adapters/src/embedded_storage_async.rs b/embedded-io-adapters/src/embedded_storage_async.rs new file mode 100644 index 000000000..1287e0a9e --- /dev/null +++ b/embedded-io-adapters/src/embedded_storage_async.rs @@ -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 { + position: u32, + inner: T, +} + +impl FromEmbeddedStorage { + /// 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 FromEmbeddedStorage { + /// 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 embedded_io_async::ErrorType for FromEmbeddedStorage { + type Error = StorageIOError; +} + +impl + ?Sized, E: Into> Read for FromEmbeddedStorage { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner + .read(self.position, buf) + .await + .map_err(|e| e.into())?; + self.position += buf.len() as u32; + Ok(buf.len()) + } +} + +impl + ?Sized, E: Into> Write for FromEmbeddedStorage { + async fn write(&mut self, buf: &[u8]) -> Result { + 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 + ?Sized, E: Into> Seek for FromEmbeddedStorage { + async fn seek(&mut self, pos: SeekFrom) -> Result { + 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 { + capacity: usize, + inner: T, +} + +impl ToEmbeddedStorage { + /// 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 ToEmbeddedStorage { + /// 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 ReadStorage for ToEmbeddedStorage { + 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 Storage for ToEmbeddedStorage { + 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(()) + } +} diff --git a/embedded-io-adapters/src/lib.rs b/embedded-io-adapters/src/lib.rs index f9d0f1a5c..317c82e8b 100644 --- a/embedded-io-adapters/src/lib.rs +++ b/embedded-io-adapters/src/lib.rs @@ -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; From ad887b67f8b878aa0639e11053f3d44629c0e084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Jacobs?= Date: Tue, 18 Jun 2024 15:54:03 +0200 Subject: [PATCH 2/2] embedded-io-adapters: use git version of embedded-storage until a new release is available --- embedded-io-adapters/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embedded-io-adapters/Cargo.toml b/embedded-io-adapters/Cargo.toml index ae5c4a89e..51d21c2d4 100644 --- a/embedded-io-adapters/Cargo.toml +++ b/embedded-io-adapters/Cargo.toml @@ -26,8 +26,8 @@ embedded-io-async = { version = "0.6.1", path = "../embedded-io-async", optional 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 = { version = "0.3.1", optional = true } -embedded-storage-async = { version = "0.4.1", 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]