diff --git a/.github/workflows/rustdoc.yml b/.github/workflows/rustdoc.yml index 1a99f8d9..a635be8d 100644 --- a/.github/workflows/rustdoc.yml +++ b/.github/workflows/rustdoc.yml @@ -13,6 +13,6 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-07-26 + toolchain: nightly-2024-11-28 # tokio/net required to workaround https://github.com/tokio-rs/tokio/issues/6165 - run: RUSTDOCFLAGS="--deny=warnings --cfg=docsrs" cargo doc --all-features --features tokio/net diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5b5ee297..754e1713 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,9 +38,9 @@ jobs: --target thumbv7m-none-eabi --features async,defmt-03,embedded-io/defmt,embedded-io-async/defmt - msrv-1-81: + msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.81 + - uses: dtolnay/rust-toolchain@1.83 - run: cargo test --workspace --all-features diff --git a/README.md b/README.md index 63c34762..f9c6a184 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ on crates.io. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.81 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.83 and up. It *might* 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. diff --git a/embedded-can/CHANGELOG.md b/embedded-can/CHANGELOG.md index 6ef70c03..15bfc1a5 100644 --- a/embedded-can/CHANGELOG.md +++ b/embedded-can/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - Added `core::error::Error` implementations for every custom `impl Error` -- Increased MSRV to 1.81 due to `core::error::Error` +- Increased MSRV to 1.83 due to `core::error::Error` & mutable reference in const context. - Bumped `defmt` to v1 - `defmt-03` feature is now named `defmt` diff --git a/embedded-can/Cargo.toml b/embedded-can/Cargo.toml index 7a638db0..cd07bcde 100644 --- a/embedded-can/Cargo.toml +++ b/embedded-can/Cargo.toml @@ -2,7 +2,7 @@ name = "embedded-can" version = "0.4.1" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" description = "HAL traits for Controller Area Network (CAN) devices." categories = ["embedded", "hardware-support", "no-std"] diff --git a/embedded-can/README.md b/embedded-can/README.md index 96afb5ec..18a62d77 100644 --- a/embedded-can/README.md +++ b/embedded-can/README.md @@ -19,7 +19,7 @@ This project is developed and maintained by the [HAL team](https://github.com/ru ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.81 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.83 and up. It *might* 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. diff --git a/embedded-hal-bus/CHANGELOG.md b/embedded-hal-bus/CHANGELOG.md index a3680bc2..c70e8537 100644 --- a/embedded-hal-bus/CHANGELOG.md +++ b/embedded-hal-bus/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -- Your change here! +- Increased MSRV to 1.83 due to mutable reference in const context. ## [v0.3.0] - 2025-01-21 diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index 7f404e3d..e4996982 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -6,7 +6,7 @@ categories = ["embedded", "hardware-support", "no-std"] description = "Bus/Device connection mechanisms for embedded-hal, a Hardware Abstraction Layer (HAL) for embedded systems" documentation = "https://docs.rs/embedded-hal-bus" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" keywords = ["hal", "IO"] license = "MIT OR Apache-2.0" name = "embedded-hal-bus" diff --git a/embedded-hal-nb/CHANGELOG.md b/embedded-hal-nb/CHANGELOG.md index 63314c7c..4d529361 100644 --- a/embedded-hal-nb/CHANGELOG.md +++ b/embedded-hal-nb/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - Added `core::error::Error` implementations for every custom `impl Error` -- Increased MSRV to 1.81 due to `core::error::Error` +- Increased MSRV to 1.83 due to `core::error::Error` & mutable reference in const context. ## [v1.0.0] - 2023-12-28 diff --git a/embedded-hal-nb/Cargo.toml b/embedded-hal-nb/Cargo.toml index 43a6cd7b..9f6d7dae 100644 --- a/embedded-hal-nb/Cargo.toml +++ b/embedded-hal-nb/Cargo.toml @@ -2,7 +2,7 @@ name = "embedded-hal-nb" version = "1.0.0" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" categories = ["embedded", "hardware-support", "no-std"] description = "Non-blocking Hardware Abstraction Layer (HAL) for embedded systems using the `nb` crate." diff --git a/embedded-hal/CHANGELOG.md b/embedded-hal/CHANGELOG.md index e919e25a..1135a308 100644 --- a/embedded-hal/CHANGELOG.md +++ b/embedded-hal/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - Added `core::error::Error` implementations for every custom `impl Error` -- Increased MSRV to 1.81 due to `core::error::Error` +- Increased MSRV to 1.83 due to `core::error::Error` & mutable reference in const context. ## [v1.0.0] - 2023-12-28 diff --git a/embedded-hal/Cargo.toml b/embedded-hal/Cargo.toml index 011f3cc9..faadf4b6 100644 --- a/embedded-hal/Cargo.toml +++ b/embedded-hal/Cargo.toml @@ -8,7 +8,7 @@ categories = ["asynchronous", "embedded", "hardware-support", "no-std"] description = " A Hardware Abstraction Layer (HAL) for embedded systems " documentation = "https://docs.rs/embedded-hal" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" keywords = ["hal", "IO"] license = "MIT OR Apache-2.0" name = "embedded-hal" diff --git a/embedded-io-adapters/CHANGELOG.md b/embedded-io-adapters/CHANGELOG.md index 558fb965..98a22138 100644 --- a/embedded-io-adapters/CHANGELOG.md +++ b/embedded-io-adapters/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- Add `Cursor` type, an `embedded-io` equivalent of `std::io::Cursor`. +- Add `alloc` and `defmt` Cargo features. +- Increased MSRV to 1.83 due to `core::error::Error` & mutable reference in const context. + ## 0.7.0 - 2025-09-30 - Update to embedded-io and embedded-io-async 0.7 diff --git a/embedded-io-adapters/Cargo.toml b/embedded-io-adapters/Cargo.toml index 6a2130a1..ede48d7c 100644 --- a/embedded-io-adapters/Cargo.toml +++ b/embedded-io-adapters/Cargo.toml @@ -2,7 +2,7 @@ name = "embedded-io-adapters" version = "0.7.0" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" description = "Adapters between the `embedded-io` traits and other I/O traits" repository = "https://github.com/rust-embedded/embedded-hal" readme = "README.md" @@ -13,7 +13,9 @@ categories = [ ] [features] -std = ["embedded-io/std"] +std = ["alloc", "embedded-io/std"] +alloc = ["embedded-io/alloc"] +defmt = ["dep:defmt", "embedded-io/defmt"] 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"] @@ -21,9 +23,10 @@ futures-03 = ["std", "dep:futures", "dep:embedded-io-async", "embedded-io-async? embedded-io = { version = "0.7", path = "../embedded-io" } embedded-io-async = { version = "0.7", path = "../embedded-io-async", optional = true } +defmt = { version = "1", 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 } [package.metadata.docs.rs] -features = ["std", "tokio-1", "futures-03"] +features = ["std", "defmt", "tokio-1", "futures-03"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io-adapters/src/cursor.rs b/embedded-io-adapters/src/cursor.rs new file mode 100644 index 00000000..0c52601c --- /dev/null +++ b/embedded-io-adapters/src/cursor.rs @@ -0,0 +1,704 @@ +// Parts of this file were copied from [`std::io::Cursor`]. +// +// [`std::io::Cursor`]: https://doc.rust-lang.org/std/io/struct.Cursor.html + +use core::cmp; + +use embedded_io::{ + BufRead, ErrorKind, ErrorType, Read, ReadReady, Seek, SeekFrom, Write, WriteReady, +}; + +/// A `Cursor` wraps an in-memory buffer and provides it with a [`Seek`] implementation. +/// +/// `Cursor`s are used with in-memory buffers, anything implementing [`AsRef<[u8]>`], +/// to allow them to implement [`Read`] and/or [`Write`], allowing these buffers to be used +/// anywhere you might use a reader or writer that does actual I/O. +/// +/// The standard library implements some I/O traits on various types which +/// are commonly used as a buffer, like `Cursor>` and `Cursor<&[u8]>`. +/// +/// This is the `embedded-io` equivalent of [`std::io::Cursor`]. +/// +/// # Examples +/// +/// We may want to write bytes to a [`Write`], but not consume the buffer: +/// +/// ```rust +/// use embedded_io::Write; +/// use embedded_io_adapters::{Cursor, CursorError}; +/// +/// let mut buf = [0u8; 10]; +/// let mut cursor = Cursor::new(&mut buf[..]); +/// +/// cursor.write_all(b"some bytes").unwrap(); +/// // Internal buffer is now at capacity so writing more bytes fails. +/// assert_eq!(cursor.write_all(b"some more bytes"), Err(CursorError::Full)); +/// +/// assert_eq!(cursor.into_inner(), b"some bytes"); +/// ``` +/// +/// [`std::io::Cursor`]: https://doc.rust-lang.org/std/io/struct.Cursor.html +#[derive(Debug, Default, Eq, PartialEq)] +pub struct Cursor { + inner: T, + pos: u64, +} + +impl Cursor { + /// Creates a new cursor wrapping the provided underlying in-memory buffer. + /// + /// Cursor initial position is `0` even if the underlying buffer is not empty. + #[inline] + pub const fn new(inner: T) -> Cursor { + Cursor { inner, pos: 0 } + } + + /// Consumes this cursor, returning the underlying value. + #[inline] + pub fn into_inner(self) -> T { + self.inner + } + + /// Gets a reference to the underlying value in this cursor. + #[inline] + pub const fn get_ref(&self) -> &T { + &self.inner + } + + /// Gets a mutable reference to the underlying value in this cursor. + /// + /// Care should be taken to avoid modifying the internal I/O state of the + /// underlying value as it may corrupt this cursor's position. + #[inline] + pub const fn get_mut(&mut self) -> &mut T { + &mut self.inner + } + + /// Returns the current position of this cursor. + #[inline] + pub const fn position(&self) -> u64 { + self.pos + } + + /// Sets the position of this cursor. + #[inline] + pub const fn set_position(&mut self, pos: u64) { + self.pos = pos; + } +} + +impl Cursor +where + T: AsRef<[u8]>, +{ + /// Splits the underlying slice at the cursor position and returns them. + /// + /// # Examples + /// + /// ``` + /// use embedded_io_adapters::Cursor; + /// + /// let mut buff = Cursor::new(vec![1, 2, 3, 4, 5]); + /// + /// assert_eq!(buff.split(), ([].as_slice(), [1, 2, 3, 4, 5].as_slice())); + /// + /// buff.set_position(2); + /// assert_eq!(buff.split(), ([1, 2].as_slice(), [3, 4, 5].as_slice())); + /// + /// buff.set_position(6); + /// assert_eq!(buff.split(), ([1, 2, 3, 4, 5].as_slice(), [].as_slice())); + /// ``` + pub fn split(&self) -> (&[u8], &[u8]) { + let slice = self.inner.as_ref(); + let pos = self.pos.min(slice.len() as u64); + slice.split_at(pos as usize) + } +} + +impl Cursor +where + T: AsMut<[u8]>, +{ + /// Splits the underlying slice at the cursor position and returns them mutably. + /// + /// # Examples + /// + /// ``` + /// use embedded_io_adapters::Cursor; + /// + /// let mut buff = Cursor::new(vec![1, 2, 3, 4, 5]); + /// + /// assert_eq!(buff.split_mut(), ([].as_mut_slice(), [1, 2, 3, 4, 5].as_mut_slice())); + /// + /// buff.set_position(2); + /// assert_eq!(buff.split_mut(), ([1, 2].as_mut_slice(), [3, 4, 5].as_mut_slice())); + /// + /// buff.set_position(6); + /// assert_eq!(buff.split_mut(), ([1, 2, 3, 4, 5].as_mut_slice(), [].as_mut_slice())); + /// ``` + pub fn split_mut(&mut self) -> (&mut [u8], &mut [u8]) { + let slice = self.inner.as_mut(); + let pos = self.pos.min(slice.len() as u64); + slice.split_at_mut(pos as usize) + } + + /// Write to the fixed-size inner buffer at the current position. + fn write_impl(&mut self, buf: &[u8]) -> Result { + let slice = self.inner.as_mut(); + let pos = cmp::min(self.pos, slice.len() as u64) as usize; + let remaining = &mut slice[pos..]; + let len = cmp::min(buf.len(), remaining.len()); + + if !buf.is_empty() && len == 0 { + return Err(CursorError::Full); + } + + remaining[..len].copy_from_slice(&buf[..len]); + self.pos += len as u64; + Ok(len) + } +} + +impl Clone for Cursor +where + T: Clone, +{ + #[inline] + fn clone(&self) -> Self { + Cursor { + inner: self.inner.clone(), + pos: self.pos, + } + } + + #[inline] + fn clone_from(&mut self, other: &Self) { + self.inner.clone_from(&other.inner); + self.pos = other.pos; + } +} + +impl ErrorType for Cursor { + type Error = CursorError; +} + +/// Errors that could be returned by `Cursor`. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum CursorError { + /// An invalid seek was attempted (e.g., seeking to a negative position). + InvalidSeek, + /// The cursor's buffer is full and cannot accept more data. + Full, +} + +impl embedded_io::Error for CursorError { + fn kind(&self) -> ErrorKind { + match self { + CursorError::InvalidSeek => ErrorKind::InvalidInput, + CursorError::Full => ErrorKind::WriteZero, + } + } +} + +impl core::fmt::Display for CursorError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + CursorError::InvalidSeek => write!(f, "invalid seek to a negative position"), + CursorError::Full => write!(f, "cursor buffer is full"), + } + } +} + +impl core::error::Error for CursorError {} + +impl Read for Cursor +where + T: AsRef<[u8]>, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + let slice = self.split().1; + let amt = cmp::min(buf.len(), slice.len()); + + buf[..amt].copy_from_slice(&slice[..amt]); + + self.pos += amt as u64; + Ok(amt) + } +} + +impl BufRead for Cursor +where + T: AsRef<[u8]>, +{ + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(self.split().1) + } + + fn consume(&mut self, amt: usize) { + self.pos += amt as u64; + } +} + +impl Seek for Cursor +where + T: AsRef<[u8]>, +{ + fn seek(&mut self, style: SeekFrom) -> Result { + let (base_pos, offset) = match style { + SeekFrom::Start(n) => { + self.pos = n; + return Ok(n); + } + SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), + SeekFrom::Current(n) => (self.pos, n), + }; + + let Some(new_pos) = base_pos.checked_add_signed(offset) else { + return Err(CursorError::InvalidSeek); + }; + + self.pos = new_pos; + Ok(new_pos) + } +} + +impl ReadReady for Cursor +where + T: AsRef<[u8]>, +{ + fn read_ready(&mut self) -> Result { + Ok(true) + } +} + +impl Write for Cursor<&mut [u8]> { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_impl(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl WriteReady for Cursor<&mut [u8]> { + fn write_ready(&mut self) -> Result { + Ok(true) + } +} + +impl Write for Cursor<[u8; N]> { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_impl(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl WriteReady for Cursor<[u8; N]> { + fn write_ready(&mut self) -> Result { + Ok(true) + } +} + +#[cfg(feature = "alloc")] +mod alloc_impl { + use alloc::vec::Vec; + + use embedded_io::{Write, WriteReady}; + + use super::{cmp, Cursor, CursorError}; + + #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] + impl Write for Cursor> { + fn write(&mut self, buf: &[u8]) -> Result { + write_vec(&mut self.pos, &mut self.inner, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] + impl WriteReady for Cursor> { + fn write_ready(&mut self) -> Result { + Ok(true) + } + } + + #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] + impl Write for Cursor<&mut Vec> { + fn write(&mut self, buf: &[u8]) -> Result { + write_vec(&mut self.pos, self.inner, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] + impl WriteReady for Cursor<&mut Vec> { + fn write_ready(&mut self) -> Result { + Ok(true) + } + } + + /// Write to a growable `Vec` buffer at the given position. + fn write_vec(pos: &mut u64, vec: &mut Vec, buf: &[u8]) -> Result { + let p = *pos as usize; + + // Zero-fill gap if position is past the end. + if p > vec.len() { + vec.resize(p, 0); + } + + // If position is at the end, just append. + if p == vec.len() { + vec.extend_from_slice(buf); + } else { + // Overwrite existing bytes first, then extend if needed. + let overlap = cmp::min(buf.len(), vec.len() - p); + vec[p..p + overlap].copy_from_slice(&buf[..overlap]); + + if buf.len() > overlap { + vec.extend_from_slice(&buf[overlap..]); + } + } + + *pos += buf.len() as u64; + Ok(buf.len()) + } +} + +#[cfg(feature = "alloc")] +mod box_impl { + use alloc::boxed::Box; + + use embedded_io::{Write, WriteReady}; + + use super::Cursor; + + #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] + impl Write for Cursor> { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_impl(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + } + + #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] + impl WriteReady for Cursor> { + fn write_ready(&mut self) -> Result { + Ok(true) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_and_position() { + let cursor = Cursor::new([1, 2, 3]); + assert_eq!(cursor.position(), 0); + assert_eq!(cursor.get_ref(), &[1, 2, 3]); + } + + #[test] + fn set_position() { + let mut cursor = Cursor::new([1, 2, 3]); + cursor.set_position(2); + assert_eq!(cursor.position(), 2); + } + + #[test] + fn into_inner() { + let cursor = Cursor::new([1, 2, 3]); + assert_eq!(cursor.into_inner(), [1, 2, 3]); + } + + #[test] + fn get_mut() { + let mut cursor = Cursor::new([1, 2, 3]); + cursor.get_mut()[0] = 10; + assert_eq!(cursor.get_ref(), &[10, 2, 3]); + } + + #[test] + fn split() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + assert_eq!(cursor.split(), (&[][..], &[1, 2, 3, 4, 5][..])); + cursor.set_position(2); + assert_eq!(cursor.split(), (&[1, 2][..], &[3, 4, 5][..])); + cursor.set_position(5); + assert_eq!(cursor.split(), (&[1, 2, 3, 4, 5][..], &[][..])); + // Position beyond end. + cursor.set_position(100); + assert_eq!(cursor.split(), (&[1, 2, 3, 4, 5][..], &[][..])); + } + + #[test] + fn read_basic() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + let mut buf = [0; 3]; + assert_eq!(cursor.read(&mut buf).unwrap(), 3); + assert_eq!(buf, [1, 2, 3]); + assert_eq!(cursor.position(), 3); + + assert_eq!(cursor.read(&mut buf).unwrap(), 2); + assert_eq!(buf[..2], [4, 5]); + assert_eq!(cursor.position(), 5); + + // Reading at end returns 0. + assert_eq!(cursor.read(&mut buf).unwrap(), 0); + } + + #[test] + fn read_single_byte() { + let mut cursor = Cursor::new([42]); + let mut buf = [0; 1]; + assert_eq!(cursor.read(&mut buf).unwrap(), 1); + assert_eq!(buf[0], 42); + } + + #[test] + fn buf_read() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + assert_eq!(cursor.fill_buf().unwrap(), &[1, 2, 3, 4, 5]); + cursor.consume(2); + assert_eq!(cursor.position(), 2); + assert_eq!(cursor.fill_buf().unwrap(), &[3, 4, 5]); + } + + #[test] + fn seek_start() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + assert_eq!(cursor.seek(SeekFrom::Start(3)).unwrap(), 3); + assert_eq!(cursor.position(), 3); + } + + #[test] + fn seek_end() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + assert_eq!(cursor.seek(SeekFrom::End(-2)).unwrap(), 3); + assert_eq!(cursor.position(), 3); + + assert_eq!(cursor.seek(SeekFrom::End(0)).unwrap(), 5); + assert_eq!(cursor.position(), 5); + } + + #[test] + fn seek_current() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + cursor.set_position(2); + assert_eq!(cursor.seek(SeekFrom::Current(2)).unwrap(), 4); + assert_eq!(cursor.position(), 4); + + assert_eq!(cursor.seek(SeekFrom::Current(-1)).unwrap(), 3); + assert_eq!(cursor.position(), 3); + } + + #[test] + fn seek_invalid() { + let mut cursor = Cursor::new([1, 2, 3, 4, 5]); + // Seeking to negative position. + assert_eq!( + cursor.seek(SeekFrom::End(-10)).unwrap_err(), + CursorError::InvalidSeek + ); + assert_eq!( + cursor.seek(SeekFrom::Current(-1)).unwrap_err(), + CursorError::InvalidSeek + ); + } + + #[test] + fn write_to_slice() { + let mut buf = [0u8; 5]; + let mut cursor = Cursor::new(&mut buf[..]); + assert_eq!(cursor.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(cursor.position(), 3); + assert_eq!(cursor.write(&[4, 5]).unwrap(), 2); + assert_eq!(cursor.position(), 5); + assert_eq!(buf, [1, 2, 3, 4, 5]); + } + + #[test] + fn write_slice_full() { + let mut buf = [0u8; 3]; + let mut cursor = Cursor::new(&mut buf[..]); + cursor.write_all(&[1, 2, 3]).unwrap(); + assert_eq!(cursor.write(&[4]).unwrap_err(), CursorError::Full); + } + + #[test] + fn write_to_array() { + let mut cursor = Cursor::new([0u8; 5]); + assert_eq!(cursor.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(cursor.position(), 3); + assert_eq!(cursor.write(&[4, 5]).unwrap(), 2); + assert_eq!(cursor.position(), 5); + assert_eq!(cursor.into_inner(), [1, 2, 3, 4, 5]); + } + + #[test] + fn write_array_full() { + let mut cursor = Cursor::new([0u8; 3]); + cursor.write_all(&[1, 2, 3]).unwrap(); + assert_eq!(cursor.write(&[4]).unwrap_err(), CursorError::Full); + } + + #[test] + fn read_ready() { + let mut cursor = Cursor::new([1, 2, 3]); + assert!(cursor.read_ready().unwrap()); + } + + #[test] + fn write_ready_slice() { + let mut buf = [0u8; 3]; + let mut cursor = Cursor::new(&mut buf[..]); + assert!(cursor.write_ready().unwrap()); + } + + #[test] + fn flush() { + let mut buf = [0u8; 3]; + let mut cursor = Cursor::new(&mut buf[..]); + assert!(cursor.flush().is_ok()); + } + + #[test] + fn clone() { + let cursor = Cursor::new([1, 2, 3]); + let cloned = cursor.clone(); + assert_eq!(cursor.get_ref(), cloned.get_ref()); + assert_eq!(cursor.position(), cloned.position()); + } + + #[test] + fn default() { + let cursor: Cursor<[u8; 0]> = Cursor::default(); + assert_eq!(cursor.position(), 0); + assert_eq!(cursor.get_ref(), &[]); + } + + #[test] + fn error_kind() { + use embedded_io::Error; + assert_eq!(CursorError::InvalidSeek.kind(), ErrorKind::InvalidInput); + assert_eq!(CursorError::Full.kind(), ErrorKind::WriteZero); + } + + #[cfg(feature = "alloc")] + mod alloc_tests { + use alloc::boxed::Box; + use alloc::string::ToString; + use alloc::vec; + use alloc::vec::Vec; + + use super::*; + + #[test] + fn write_to_vec() { + let mut cursor = Cursor::new(Vec::new()); + assert_eq!(cursor.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(cursor.write(&[4, 5]).unwrap(), 2); + assert_eq!(cursor.into_inner(), vec![1, 2, 3, 4, 5]); + } + + #[test] + fn write_to_vec_with_seek() { + let mut cursor = Cursor::new(vec![0, 0, 0, 0, 0]); + cursor.set_position(2); + assert_eq!(cursor.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(cursor.into_inner(), vec![0, 0, 1, 2, 3]); + } + + #[test] + fn write_to_vec_extend() { + let mut cursor = Cursor::new(vec![1, 2]); + cursor.set_position(1); + // Overwrite one byte and extend. + assert_eq!(cursor.write(&[10, 20, 30]).unwrap(), 3); + assert_eq!(cursor.into_inner(), vec![1, 10, 20, 30]); + } + + #[test] + fn write_to_vec_zero_fill_gap() { + let mut cursor = Cursor::new(vec![1u8]); + cursor.set_position(2); + // Write past end - gap should be zero-filled. + assert_eq!(cursor.write(&[3]).unwrap(), 1); + assert_eq!(cursor.into_inner(), vec![1, 0, 3]); + } + + #[test] + fn write_to_mut_vec() { + let mut vec = Vec::new(); + let mut cursor = Cursor::new(&mut vec); + cursor.write_all(&[1, 2, 3]).unwrap(); + drop(cursor); + assert_eq!(vec, vec![1, 2, 3]); + } + + #[test] + fn write_to_boxed_slice() { + let mut cursor = Cursor::new(vec![0u8; 5].into_boxed_slice()); + assert_eq!(cursor.write(&[1, 2, 3]).unwrap(), 3); + assert_eq!(cursor.position(), 3); + assert_eq!(&*cursor.into_inner(), &[1, 2, 3, 0, 0]); + } + + #[test] + fn write_boxed_slice_full() { + let mut cursor = Cursor::new(vec![0u8; 2].into_boxed_slice()); + cursor.write_all(&[1, 2]).unwrap(); + assert_eq!(cursor.write(&[3]).unwrap_err(), CursorError::Full); + } + + #[test] + fn write_ready_vec() { + let mut cursor = Cursor::new(Vec::new()); + assert!(cursor.write_ready().unwrap()); + } + + #[test] + fn write_ready_boxed_slice() { + let mut cursor = Cursor::new(Box::<[u8]>::from(vec![0u8; 3])); + assert!(cursor.write_ready().unwrap()); + } + + #[test] + fn read_from_vec() { + let mut cursor = Cursor::new(vec![1, 2, 3]); + let mut buf = [0; 2]; + assert_eq!(cursor.read(&mut buf).unwrap(), 2); + assert_eq!(buf, [1, 2]); + } + + #[test] + fn read_from_boxed_slice() { + let mut cursor = Cursor::new(vec![1, 2, 3].into_boxed_slice()); + let mut buf = [0; 2]; + assert_eq!(cursor.read(&mut buf).unwrap(), 2); + assert_eq!(buf, [1, 2]); + } + + #[test] + fn error_display() { + assert!(!CursorError::InvalidSeek.to_string().is_empty()); + assert!(!CursorError::Full.to_string().is_empty()); + } + } +} diff --git a/embedded-io-adapters/src/lib.rs b/embedded-io-adapters/src/lib.rs index c5e6710a..8d125694 100644 --- a/embedded-io-adapters/src/lib.rs +++ b/embedded-io-adapters/src/lib.rs @@ -3,8 +3,14 @@ #![warn(missing_docs)] #![doc = include_str!("../README.md")] +#[cfg(feature = "alloc")] +extern crate alloc; + +mod cursor; pub mod fmt; +pub use cursor::{Cursor, CursorError}; + #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod std; diff --git a/embedded-io-async/Cargo.toml b/embedded-io-async/Cargo.toml index 5148ebfa..fa1d594c 100644 --- a/embedded-io-async/Cargo.toml +++ b/embedded-io-async/Cargo.toml @@ -2,7 +2,7 @@ name = "embedded-io-async" version = "0.7.0" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" description = "Async embedded IO traits" repository = "https://github.com/rust-embedded/embedded-hal" readme = "README.md" diff --git a/embedded-io/CHANGELOG.md b/embedded-io/CHANGELOG.md index 0fc0c11d..4c83168a 100644 --- a/embedded-io/CHANGELOG.md +++ b/embedded-io/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- Increased MSRV to 1.83 due to `core::error::Error` & mutable reference in const context. + ## 0.7.1 - 2025-09-30 - Do not require `Read` and `Write` to be implemented for `ReadReady` and `WriteReady`. diff --git a/embedded-io/Cargo.toml b/embedded-io/Cargo.toml index e02ecdc4..6eaf54b5 100644 --- a/embedded-io/Cargo.toml +++ b/embedded-io/Cargo.toml @@ -2,7 +2,7 @@ name = "embedded-io" version = "0.7.1" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" description = "Embedded IO traits" repository = "https://github.com/rust-embedded/embedded-hal" readme = "README.md" diff --git a/embedded-io/README.md b/embedded-io/README.md index 2b09d9c0..a2725871 100644 --- a/embedded-io/README.md +++ b/embedded-io/README.md @@ -27,7 +27,7 @@ targets. ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.81 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.83 and up. It *might* 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.