diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 20cd030f6..bdec38346 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -15,6 +15,6 @@ jobs: with: # embedded-hal-async needs nightly. # Use a pinned version to avoid spontaneous breakages (new clippy lints are added often) - toolchain: nightly-2022-11-22 + toolchain: nightly-2023-07-03 components: clippy - - run: cargo clippy --features=embedded-hal-bus/std -- --deny=warnings \ No newline at end of file + - run: cargo clippy --features=std -- --deny=warnings \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1677ea895..e50f0c6a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,9 @@ jobs: - thumbv7m-none-eabi include: - target: x86_64-unknown-linux-gnu - features: embedded-hal-bus/std + features: std + - target: x86_64-unknown-linux-gnu + features: alloc steps: - uses: actions/checkout@v3 diff --git a/Cargo.toml b/Cargo.toml index 4322dde4e..ab7723059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,6 @@ members = [ "embedded-hal-nb", "embedded-hal-bus", "embedded-can", + "embedded-io", + "embedded-io-async", # nightly-only ] diff --git a/embedded-hal-async/src/lib.rs b/embedded-hal-async/src/lib.rs index 83410cdb9..5fe9168e7 100644 --- a/embedded-hal-async/src/lib.rs +++ b/embedded-hal-async/src/lib.rs @@ -1,11 +1,9 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] #![no_std] -#![allow(incomplete_features)] #![feature(async_fn_in_trait, impl_trait_projections)] pub mod delay; pub mod digital; pub mod i2c; -pub mod serial; pub mod spi; diff --git a/embedded-hal-async/src/serial.rs b/embedded-hal-async/src/serial.rs deleted file mode 100644 index 60ee1bc05..000000000 --- a/embedded-hal-async/src/serial.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Serial interface - -pub use embedded_hal::serial::{Error, ErrorKind, ErrorType}; - -/// Write half of a serial interface -pub trait Write: ErrorType { - /// Writes a slice, blocking until everything has been written. - /// - /// An implementation can choose to buffer the write, returning `Ok(())` - /// after the complete slice has been written to a buffer, but before all - /// words have been sent via the serial interface. To make sure that - /// everything has been sent, call [`flush`](Write::flush) after this function returns. - async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; - - /// Ensures that none of the previously written data is still buffered - async fn flush(&mut self) -> Result<(), Self::Error>; -} - -impl, Word: 'static + Copy> Write for &mut T { - async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - T::write(self, words).await - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self).await - } -} diff --git a/embedded-hal-nb/src/serial.rs b/embedded-hal-nb/src/serial.rs index e01803ab1..c069c1441 100644 --- a/embedded-hal-nb/src/serial.rs +++ b/embedded-hal-nb/src/serial.rs @@ -1,6 +1,77 @@ //! Serial interface -pub use embedded_hal::serial::{Error, ErrorKind, ErrorType}; +/// Serial error +pub trait Error: core::fmt::Debug { + /// Convert error to a generic serial error kind + /// + /// By using this method, serial errors freely defined by HAL implementations + /// can be converted to a set of generic serial errors upon which generic + /// code can act. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +/// Serial error kind +/// +/// This represents a common set of serial operation errors. HAL implementations are +/// free to define more specific or additional error types. However, by providing +/// a mapping to these common serial errors, generic code can still react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[non_exhaustive] +pub enum ErrorKind { + /// The peripheral receive buffer was overrun. + Overrun, + /// Received data does not conform to the peripheral configuration. + /// Can be caused by a misconfigured device on either end of the serial line. + FrameFormat, + /// Parity check failed. + Parity, + /// Serial line is too noisy to read valid data. + Noise, + /// A different error occurred. The original error may contain more information. + Other, +} + +impl Error for ErrorKind { + fn kind(&self) -> ErrorKind { + *self + } +} + +impl core::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), + Self::Parity => write!(f, "Parity check failed"), + Self::Noise => write!(f, "Serial line is too noisy to read valid data"), + Self::FrameFormat => write!( + f, + "Received data does not conform to the peripheral configuration" + ), + Self::Other => write!( + f, + "A different error occurred. The original error may contain more information" + ), + } + } +} + +/// Serial error type trait +/// +/// This just defines the error type, to be used by the other traits. +pub trait ErrorType { + /// Error type + type Error: Error; +} + +impl ErrorType for &mut T { + type Error = T::Error; +} /// Read half of a serial interface /// @@ -40,8 +111,7 @@ impl, Word: Copy> Write for &mut T { /// /// TODO write example of usage -impl core::fmt::Write - for dyn Write + '_ +impl core::fmt::Write for dyn Write + '_ where Word: Copy + From, { diff --git a/embedded-hal/src/lib.rs b/embedded-hal/src/lib.rs index def14dbc0..ac09042b3 100644 --- a/embedded-hal/src/lib.rs +++ b/embedded-hal/src/lib.rs @@ -81,7 +81,6 @@ pub mod delay; pub mod digital; pub mod i2c; pub mod pwm; -pub mod serial; pub mod spi; mod private { diff --git a/embedded-hal/src/serial.rs b/embedded-hal/src/serial.rs deleted file mode 100644 index ab9be722b..000000000 --- a/embedded-hal/src/serial.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Serial traits - -/// Serial error -pub trait Error: core::fmt::Debug { - /// Convert error to a generic serial error kind - /// - /// By using this method, serial errors freely defined by HAL implementations - /// can be converted to a set of generic serial errors upon which generic - /// code can act. - fn kind(&self) -> ErrorKind; -} - -impl Error for core::convert::Infallible { - fn kind(&self) -> ErrorKind { - match *self {} - } -} - -/// Serial error kind -/// -/// This represents a common set of serial operation errors. HAL implementations are -/// free to define more specific or additional error types. However, by providing -/// a mapping to these common serial errors, generic code can still react to them. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[non_exhaustive] -pub enum ErrorKind { - /// The peripheral receive buffer was overrun. - Overrun, - /// Received data does not conform to the peripheral configuration. - /// Can be caused by a misconfigured device on either end of the serial line. - FrameFormat, - /// Parity check failed. - Parity, - /// Serial line is too noisy to read valid data. - Noise, - /// A different error occurred. The original error may contain more information. - Other, -} - -impl Error for ErrorKind { - fn kind(&self) -> ErrorKind { - *self - } -} - -impl core::fmt::Display for ErrorKind { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), - Self::Parity => write!(f, "Parity check failed"), - Self::Noise => write!(f, "Serial line is too noisy to read valid data"), - Self::FrameFormat => write!( - f, - "Received data does not conform to the peripheral configuration" - ), - Self::Other => write!( - f, - "A different error occurred. The original error may contain more information" - ), - } - } -} - -/// Serial error type trait -/// -/// This just defines the error type, to be used by the other traits. -pub trait ErrorType { - /// Error type - type Error: Error; -} - -impl ErrorType for &mut T { - type Error = T::Error; -} - -/// Write half of a serial interface (blocking variant) -pub trait Write: ErrorType { - /// Writes a slice, blocking until everything has been written - /// - /// An implementation can choose to buffer the write, returning `Ok(())` - /// after the complete slice has been written to a buffer, but before all - /// words have been sent via the serial interface. To make sure that - /// everything has been sent, call [`flush`](Write::flush) after this function returns. - fn write(&mut self, buffer: &[Word]) -> Result<(), Self::Error>; - - /// Block until the serial interface has sent all buffered words - fn flush(&mut self) -> Result<(), Self::Error>; -} - -impl, Word: Copy> Write for &mut T { - fn write(&mut self, buffer: &[Word]) -> Result<(), Self::Error> { - T::write(self, buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self) - } -} diff --git a/embedded-io-async/Cargo.toml b/embedded-io-async/Cargo.toml new file mode 100644 index 000000000..e8f19af39 --- /dev/null +++ b/embedded-io-async/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "embedded-io-async" +version = "0.5.0" +edition = "2021" +description = "Async embedded IO traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] + +[features] +std = ["alloc", "embedded-io/std"] +alloc = ["embedded-io/alloc"] + +[dependencies] +embedded-io = { version = "0.5", path = "../embedded-io" } + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io-async/README.md b/embedded-io-async/README.md new file mode 100644 index 000000000..557a0866a --- /dev/null +++ b/embedded-io-async/README.md @@ -0,0 +1,35 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) +[![crates.io](https://img.shields.io/crates/v/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) +[![Documentation](https://docs.rs/embedded-io-async/badge.svg)](https://docs.rs/embedded-io-async) + +# `embedded-io-async` + +Async IO traits for embedded systems. + +This crate contains asynchronous versions of the [`embedded-io`](https://crates.io/crates/embedded-io) traits and shares its scope and design goals. + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +## Minimum Supported Rust Version (MSRV) + +This crate requires Rust nightly newer than `nightly-2022-11-22`, due to requiring support for +`async fn` in traits (AFIT), which is not stable yet. + +Keep in mind Rust nightlies can make backwards-incompatible changes to unstable features +at any time. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io-async/src/impls/boxx.rs b/embedded-io-async/src/impls/boxx.rs new file mode 100644 index 000000000..677bf328d --- /dev/null +++ b/embedded-io-async/src/impls/boxx.rs @@ -0,0 +1,44 @@ +use crate::{BufRead, Read, Seek, SeekFrom, Write}; +use alloc::boxed::Box; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Read for Box { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf).await + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl BufRead for Box { + #[inline] + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self).await + } + + #[inline] + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Box { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Seek for Box { + #[inline] + async fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos).await + } +} diff --git a/embedded-io-async/src/impls/mod.rs b/embedded-io-async/src/impls/mod.rs new file mode 100644 index 000000000..e79b9b8bf --- /dev/null +++ b/embedded-io-async/src/impls/mod.rs @@ -0,0 +1,7 @@ +mod slice_mut; +mod slice_ref; + +#[cfg(feature = "alloc")] +mod boxx; +#[cfg(feature = "alloc")] +mod vec; diff --git a/embedded-io-async/src/impls/slice_mut.rs b/embedded-io-async/src/impls/slice_mut.rs new file mode 100644 index 000000000..4a195e3f4 --- /dev/null +++ b/embedded-io-async/src/impls/slice_mut.rs @@ -0,0 +1,22 @@ +use crate::Write; +use core::mem; + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +impl Write for &mut [u8] { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } +} diff --git a/embedded-io-async/src/impls/slice_ref.rs b/embedded-io-async/src/impls/slice_ref.rs new file mode 100644 index 000000000..a6a4ba807 --- /dev/null +++ b/embedded-io-async/src/impls/slice_ref.rs @@ -0,0 +1,37 @@ +use crate::{BufRead, Read}; + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } +} + +impl BufRead for &[u8] { + #[inline] + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} diff --git a/embedded-io-async/src/impls/vec.rs b/embedded-io-async/src/impls/vec.rs new file mode 100644 index 000000000..c24405e39 --- /dev/null +++ b/embedded-io-async/src/impls/vec.rs @@ -0,0 +1,12 @@ +use alloc::vec::Vec; + +use crate::Write; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Vec { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } +} diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs new file mode 100644 index 000000000..d5a934ae2 --- /dev/null +++ b/embedded-io-async/src/lib.rs @@ -0,0 +1,130 @@ +#![feature(async_fn_in_trait, impl_trait_projections)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod impls; + +pub use embedded_io::{ + Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteAllError, WriteReady, +}; + +/// Async reader. +/// +/// Semantics are the same as [`std::io::Read`], check its documentation for details. +pub trait Read: ErrorType { + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + async fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read the exact number of bytes required to fill `buf`. + async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + while !buf.is_empty() { + match self.read(buf).await { + Ok(0) => break, + Ok(n) => buf = &mut buf[n..], + Err(e) => return Err(ReadExactError::Other(e)), + } + } + if !buf.is_empty() { + Err(ReadExactError::UnexpectedEof) + } else { + Ok(()) + } + } +} + +/// Async buffered reader. +/// +/// Semantics are the same as [`std::io::BufRead`], check its documentation for details. +pub trait BufRead: ErrorType { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + fn consume(&mut self, amt: usize); +} + +/// Async writer. +/// +/// Semantics are the same as [`std::io::Write`], check its documentation for details. +pub trait Write: ErrorType { + /// Write a buffer into this writer, returning how many bytes were written. + async fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + /// Write an entire buffer into this writer. + async fn write_all(&mut self, buf: &[u8]) -> Result<(), WriteAllError> { + let mut buf = buf; + while !buf.is_empty() { + match self.write(buf).await { + Ok(0) => return Err(WriteAllError::WriteZero), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(WriteAllError::Other(e)), + } + } + Ok(()) + } +} + +/// Async seek within streams. +/// +/// Semantics are the same as [`std::io::Seek`], check its documentation for details. +pub trait Seek: ErrorType { + /// Seek to an offset, in bytes, in a stream. + async fn seek(&mut self, pos: SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + async fn rewind(&mut self) -> Result<(), Self::Error> { + self.seek(SeekFrom::Start(0)).await?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + async fn stream_position(&mut self) -> Result { + self.seek(SeekFrom::Current(0)).await + } +} + +impl Read for &mut T { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf).await + } +} + +impl BufRead for &mut T { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self).await + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +impl Write for &mut T { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} + +impl Seek for &mut T { + #[inline] + async fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos).await + } +} diff --git a/embedded-io/CHANGELOG.md b/embedded-io/CHANGELOG.md new file mode 100644 index 000000000..a91aabe37 --- /dev/null +++ b/embedded-io/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +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 + +- Added `ReadReady`, `WriteReady` traits. They allow peeking whether the I/O handle is ready to read/write, so they allow using the traits in a non-blocking way. +- Moved `embedded_io::blocking` to the crate root. +- Split async traits to the `embedded-io-async` crate. +- Split async trait adapters to separate crates. +- Rename trait `Io` to `ErrorKind`, for consistency with `embedded-hal`. + +## 0.4.0 - 2022-11-25 + +- Switch all traits to use [`async_fn_in_trait`](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html) (AFIT). Requires `nightly-2022-11-22` or newer. + +## 0.3.1 - 2022-10-26 + +- Fix compilation on recent nightlies (#5) + +## 0.3.0 - 2022-05-19 + +- `FromFutures` adapter now requires `futures` Cargo feature. (breaking change) +- Add `FromTokio` adapter. +- Add blanket impls for `&mut T`, `Box`. +- Add impl `Read`, `BufRead` for `&[u8]` +- Add impl `Write` for `&mut [u8]` +- Add impl `Write` for `Vec` +- impl `std::error::Error` for `ReadExactError`, `WriteFmtError`. + +## 0.2.0 - 2022-05-07 + +- First release \ No newline at end of file diff --git a/embedded-io/Cargo.toml b/embedded-io/Cargo.toml new file mode 100644 index 000000000..d1f82254f --- /dev/null +++ b/embedded-io/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "embedded-io" +version = "0.5.0" +edition = "2021" +description = "Embedded IO traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] + +[features] +std = ["alloc"] +alloc = [] + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io/LICENSE-APACHE b/embedded-io/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/embedded-io/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embedded-io/LICENSE-MIT b/embedded-io/LICENSE-MIT new file mode 100644 index 000000000..e00608fbd --- /dev/null +++ b/embedded-io/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The embedded-io authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embedded-io/README.md b/embedded-io/README.md new file mode 100644 index 000000000..2563bdbf7 --- /dev/null +++ b/embedded-io/README.md @@ -0,0 +1,39 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io.svg)](https://crates.io/crates/embedded-io) +[![crates.io](https://img.shields.io/crates/v/embedded-io.svg)](https://crates.io/crates/embedded-io) +[![Documentation](https://docs.rs/embedded-io/badge.svg)](https://docs.rs/embedded-io) + +# `embedded-io` + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +Input/Output traits for embedded systems. + +Rust's `std::io` traits are not available in `no_std` targets, mainly because `std::io::Error` +requires allocation. This crate contains replacement equivalent traits, usable in `no_std` +targets. + +The only difference with `std::io` is `Error` is an associated type. This allows each implementor +to return its own error type, while avoiding `dyn` or `Box`. This is how errors are handled in [`embedded-hal`](https://github.com/rust-embedded/embedded-hal/). + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.59 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. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io/src/adapters.rs b/embedded-io/src/adapters.rs new file mode 100644 index 000000000..729f8dc0d --- /dev/null +++ b/embedded-io/src/adapters.rs @@ -0,0 +1,161 @@ +//! Adapters to/from `std::io` traits. +//! +//! To interoperate with `std::io`, wrap a type in one of these +//! adapters. +//! +//! There are no separate adapters for `Read`/`ReadBuf`/`Write` traits. Instead, a single +//! adapter implements the right traits based on what the inner type implements. +//! This allows using these adapters when using `Read+Write`, for example. + +use crate::SeekFrom; + +/// Adapter from `std::io` traits. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[derive(Clone)] +pub struct FromStd { + inner: T, +} + +impl FromStd { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromStd { + /// 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 crate::ErrorType for FromStd { + type Error = std::io::Error; +} + +impl crate::Read for FromStd { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf) + } +} + +impl crate::BufRead for FromStd { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.inner.consume(amt) + } +} + +impl crate::Write for FromStd { + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.write(buf) + } + fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.flush() + } +} + +impl crate::Seek for FromStd { + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + self.inner.seek(pos.into()) + } +} + +/// Adapter to `std::io` traits. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub struct ToStd { + inner: T, +} + +impl ToStd { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl ToStd { + /// 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 std::io::Read for ToStd { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf).map_err(to_std_error) + } +} + +impl std::io::Write for ToStd { + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.write(buf).map_err(to_std_error) + } + fn flush(&mut self) -> Result<(), std::io::Error> { + self.inner.flush().map_err(to_std_error) + } +} + +impl std::io::Seek for ToStd { + fn seek(&mut self, pos: std::io::SeekFrom) -> Result { + self.inner.seek(pos.into()).map_err(to_std_error) + } +} + +fn to_std_error(err: T) -> std::io::Error { + let kind = std::io::ErrorKind::Other; + std::io::Error::new(kind, format!("{:?}", err)) +} + +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl crate::Error for std::io::Error { + fn kind(&self) -> crate::ErrorKind { + crate::ErrorKind::Other + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for std::io::SeekFrom { + fn from(pos: SeekFrom) -> Self { + match pos { + SeekFrom::Start(n) => std::io::SeekFrom::Start(n), + SeekFrom::End(n) => std::io::SeekFrom::End(n), + SeekFrom::Current(n) => std::io::SeekFrom::Current(n), + } + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for SeekFrom { + fn from(pos: std::io::SeekFrom) -> SeekFrom { + match pos { + std::io::SeekFrom::Start(n) => SeekFrom::Start(n), + std::io::SeekFrom::End(n) => SeekFrom::End(n), + std::io::SeekFrom::Current(n) => SeekFrom::Current(n), + } + } +} diff --git a/embedded-io/src/impls/boxx.rs b/embedded-io/src/impls/boxx.rs new file mode 100644 index 000000000..6e61ebf8d --- /dev/null +++ b/embedded-io/src/impls/boxx.rs @@ -0,0 +1,63 @@ +use crate::{BufRead, ErrorType, Read, ReadReady, Seek, Write, WriteReady}; +use alloc::boxed::Box; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ErrorType for Box { + type Error = T::Error; +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Read for Box { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl BufRead for Box { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self) + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Seek for Box { + #[inline] + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + T::seek(self, pos) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ReadReady for Box { + #[inline] + fn read_ready(&mut self) -> Result { + T::read_ready(self) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl WriteReady for Box { + #[inline] + fn write_ready(&mut self) -> Result { + T::write_ready(self) + } +} diff --git a/embedded-io/src/impls/mod.rs b/embedded-io/src/impls/mod.rs new file mode 100644 index 000000000..e79b9b8bf --- /dev/null +++ b/embedded-io/src/impls/mod.rs @@ -0,0 +1,7 @@ +mod slice_mut; +mod slice_ref; + +#[cfg(feature = "alloc")] +mod boxx; +#[cfg(feature = "alloc")] +mod vec; diff --git a/embedded-io/src/impls/slice_mut.rs b/embedded-io/src/impls/slice_mut.rs new file mode 100644 index 000000000..b89b72182 --- /dev/null +++ b/embedded-io/src/impls/slice_mut.rs @@ -0,0 +1,31 @@ +use crate::{ErrorType, Write}; +use core::mem; + +impl ErrorType for &mut [u8] { + type Error = core::convert::Infallible; +} + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/embedded-io/src/impls/slice_ref.rs b/embedded-io/src/impls/slice_ref.rs new file mode 100644 index 000000000..6332d70dd --- /dev/null +++ b/embedded-io/src/impls/slice_ref.rs @@ -0,0 +1,41 @@ +use crate::{BufRead, ErrorType, Read}; + +impl ErrorType for &[u8] { + type Error = core::convert::Infallible; +} + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } +} + +impl BufRead for &[u8] { + #[inline] + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} diff --git a/embedded-io/src/impls/vec.rs b/embedded-io/src/impls/vec.rs new file mode 100644 index 000000000..3b279c564 --- /dev/null +++ b/embedded-io/src/impls/vec.rs @@ -0,0 +1,21 @@ +use crate::{ErrorType, Write}; +use alloc::vec::Vec; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ErrorType for Vec { + type Error = core::convert::Infallible; +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs new file mode 100644 index 000000000..b71c43f5a --- /dev/null +++ b/embedded-io/src/lib.rs @@ -0,0 +1,340 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use core::fmt; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "std")] +pub mod adapters; + +mod impls; + +/// Enumeration of possible methods to seek within an I/O object. +/// +/// Semantics are the same as [`std::io::SeekFrom`], check its documentation for details. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SeekFrom { + /// Sets the offset to the provided number of bytes. + Start(u64), + /// Sets the offset to the size of this object plus the specified number of bytes. + End(i64), + /// Sets the offset to the current position plus the specified number of bytes. + Current(i64), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +/// Possible kinds of errors. +pub enum ErrorKind { + /// Unspecified error kind. + Other, +} + +/// Error trait. +/// +/// This trait allows generic code to do limited inspecting of errors, +/// to react differently to different kinds. +pub trait Error: core::fmt::Debug { + /// Get the kind of this error. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +impl Error for ErrorKind { + fn kind(&self) -> ErrorKind { + *self + } +} + +/// Base trait for all IO traits, defining the error type. +/// +/// All IO operations of all traits return the error defined in this trait. +/// +/// Having a shared trait instead of having every trait define its own +/// `Error` associated type enforces all impls on the same type use the same error. +/// This is very convenient when writing generic code, it means you have to +/// handle a single error type `T::Error`, instead of `::Error` and `::Error` +/// which might be different types. +pub trait ErrorType { + /// Error type of all the IO operations on this type. + type Error: Error; +} + +impl crate::ErrorType for &mut T { + type Error = T::Error; +} + +/// Error returned by [`Read::read_exact`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ReadExactError { + /// An EOF error was encountered before reading the exact amount of requested bytes. + UnexpectedEof, + /// Error returned by the inner Read. + Other(E), +} + +impl From for ReadExactError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for ReadExactError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ReadExactError {} + +/// Error returned by [`Write::write_fmt`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum WriteFmtError { + /// [`Write::write`] wrote zero bytes + WriteZero, + /// An error was encountered while formatting. + FmtError, + /// Error returned by the inner Write. + Other(E), +} + +impl From for WriteFmtError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for WriteFmtError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for WriteFmtError {} + +/// Error returned by [`Write::write_all`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum WriteAllError { + /// [`Write::write`] wrote zero bytes + WriteZero, + /// Error returned by the inner Write. + Other(E), +} + +impl From for WriteAllError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for WriteAllError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for WriteAllError {} + +/// Blocking reader. +/// +/// Semantics are the same as [`std::io::Read`], check its documentation for details. +pub trait Read: crate::ErrorType { + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read the exact number of bytes required to fill `buf`. + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => buf = &mut buf[n..], + Err(e) => return Err(ReadExactError::Other(e)), + } + } + if !buf.is_empty() { + Err(ReadExactError::UnexpectedEof) + } else { + Ok(()) + } + } +} + +/// Blocking buffered reader. +/// +/// Semantics are the same as [`std::io::BufRead`], check its documentation for details. +pub trait BufRead: crate::ErrorType { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + fn consume(&mut self, amt: usize); +} + +/// Blocking writer. +/// +/// Semantics are the same as [`std::io::Write`], check its documentation for details. +pub trait Write: crate::ErrorType { + /// Write a buffer into this writer, returning how many bytes were written. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + fn flush(&mut self) -> Result<(), Self::Error>; + + /// Write an entire buffer into this writer. + fn write_all(&mut self, mut buf: &[u8]) -> Result<(), WriteAllError> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => return Err(WriteAllError::WriteZero), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(WriteAllError::Other(e)), + } + } + Ok(()) + } + + /// Write a formatted string into this writer, returning any error encountered. + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<(), WriteFmtError> { + // Create a shim which translates a Write to a fmt::Write and saves + // off I/O errors. instead of discarding them + struct Adapter<'a, T: Write + ?Sized + 'a> { + inner: &'a mut T, + error: Result<(), WriteAllError>, + } + + impl fmt::Write for Adapter<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adapter { + inner: self, + error: Ok(()), + }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => match output.error { + // check if the error came from the underlying `Write` or not + Err(e) => match e { + WriteAllError::WriteZero => Err(WriteFmtError::WriteZero), + WriteAllError::Other(e) => Err(WriteFmtError::Other(e)), + }, + Ok(()) => Err(WriteFmtError::FmtError), + }, + } + } +} + +/// Blocking seek within streams. +/// +/// Semantics are the same as [`std::io::Seek`], check its documentation for details. +pub trait Seek: crate::ErrorType { + /// Seek to an offset, in bytes, in a stream. + fn seek(&mut self, pos: crate::SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + fn rewind(&mut self) -> Result<(), Self::Error> { + self.seek(crate::SeekFrom::Start(0))?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + fn stream_position(&mut self) -> Result { + self.seek(crate::SeekFrom::Current(0)) + } +} + +/// Get whether a reader is ready. +/// +/// This allows using a [`Read`] or [`BufRead`] in a nonblocking fashion, i.e. trying to read +/// only when it is ready. +pub trait ReadReady: crate::ErrorType { + /// Get whether the reader is ready for immediately reading. + /// + /// This usually means that there is either some bytes have been received and are buffered and ready to be read, + /// or that the reader is at EOF. + /// + /// If this returns `true`, it's guaranteed that the next call to [`Read::read`] or [`BufRead::fill_buf`] will not block. + fn read_ready(&mut self) -> Result; +} + +/// Get whether a writer is ready. +/// +/// This allows using a [`Write`] in a nonblocking fashion, i.e. trying to write +/// only when it is ready. +pub trait WriteReady: crate::ErrorType { + /// Get whether the writer is ready for immediately writing. + /// + /// This usually means that there is free space in the internal transmit buffer. + /// + /// If this returns `true`, it's guaranteed that the next call to [`Write::write`] will not block. + fn write_ready(&mut self) -> Result; +} + +impl Read for &mut T { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf) + } +} + +impl BufRead for &mut T { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self) + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +impl Write for &mut T { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } +} + +impl Seek for &mut T { + #[inline] + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + T::seek(self, pos) + } +} + +impl ReadReady for &mut T { + #[inline] + fn read_ready(&mut self) -> Result { + T::read_ready(self) + } +} + +impl WriteReady for &mut T { + #[inline] + fn write_ready(&mut self) -> Result { + T::write_ready(self) + } +}