diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index eef50ebc..d1e087a7 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -10,7 +10,7 @@ extern crate alloc; mod impls; pub use embedded_io::{ - Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteReady, + Error, ErrorKind, ErrorType, OperationError, ReadReady, SeekFrom, WriteReady, }; /// Async reader. @@ -63,18 +63,18 @@ pub trait Read: ErrorType { /// /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned /// future that hasn't completed yet, some bytes might have already been read, which will get lost. - async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), OperationError> { 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)), + Err(e) => return Err(OperationError::Other(e)), } } if buf.is_empty() { Ok(()) } else { - Err(ReadExactError::UnexpectedEof) + Err(OperationError::UnexpectedEof) } } } @@ -94,6 +94,36 @@ pub trait BufRead: ErrorType { /// 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); + + /// Skips all bytes until the delimiter `byte` or EOF is reached. + /// + /// This function will read (and discard) bytes from the underlying stream, waiting until the + /// delimiter is found, or an EOF condition is reached. + /// + /// If successful, this function will return the total number of bytes read, + /// including the delimiter byte. + async fn skip_until(&mut self, delim: u8) -> Result> { + let mut read: usize = 0; + loop { + let (done, used) = { + let available = self.fill_buf().await?; + + if available.is_empty() { + return Err(OperationError::UnexpectedEof); + } + + match available.iter().position(|p| *p == delim) { + Some(i) => (true, i + 1), + None => (false, available.len()), + } + }; + self.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } + } } /// Async writer. diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index ae5ff485..192c6f12 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -227,17 +227,17 @@ impl ErrorType for &mut T { type Error = T::Error; } -/// Error returned by [`Read::read_exact`] +/// Error returned by [`Read::read_exact`] and [`BufRead::skip_until`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt-03", derive(defmt::Format))] -pub enum ReadExactError { - /// An EOF error was encountered before reading the exact amount of requested bytes. +pub enum OperationError { + /// An EOF error was encountered before the operation could complete. UnexpectedEof, /// Error returned by the inner Read. Other(E), } -impl From for ReadExactError { +impl From for OperationError { fn from(err: E) -> Self { Self::Other(err) } @@ -245,25 +245,25 @@ impl From for ReadExactError { #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl From> for std::io::Error { - fn from(err: ReadExactError) -> Self { +impl From> for std::io::Error { + fn from(err: OperationError) -> Self { match err { - ReadExactError::UnexpectedEof => std::io::Error::new( + OperationError::UnexpectedEof => std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "UnexpectedEof".to_owned(), ), - ReadExactError::Other(e) => std::io::Error::new(e.kind(), format!("{e:?}")), + OperationError::Other(e) => std::io::Error::new(e.kind(), format!("{e:?}")), } } } -impl fmt::Display for ReadExactError { +impl fmt::Display for OperationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } -impl core::error::Error for ReadExactError {} +impl core::error::Error for OperationError {} /// Errors that could be returned by `Write` on `&mut [u8]`. #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -340,18 +340,18 @@ pub trait Read: ErrorType { /// If you are using [`ReadReady`] to avoid blocking, you should not use this function. /// `ReadReady::read_ready()` returning true only guarantees the first call to `read()` will /// not block, so this function may still block in subsequent calls. - fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), OperationError> { while !buf.is_empty() { match self.read(buf) { Ok(0) => break, Ok(n) => buf = &mut buf[n..], - Err(e) => return Err(ReadExactError::Other(e)), + Err(e) => return Err(OperationError::Other(e)), } } if buf.is_empty() { Ok(()) } else { - Err(ReadExactError::UnexpectedEof) + Err(OperationError::UnexpectedEof) } } } @@ -371,6 +371,36 @@ pub trait BufRead: ErrorType { /// 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); + + /// Skips all bytes until the delimiter `byte` or EOF is reached. + /// + /// This function will read (and discard) bytes from the underlying stream, blocking until the + /// delimiter is found, or an EOF condition is reached. + /// + /// If successful, this function will return the total number of bytes read, + /// including the delimiter byte. + fn skip_until(&mut self, delim: u8) -> Result> { + let mut read: usize = 0; + loop { + let (done, used) = { + let available = self.fill_buf()?; + + if available.is_empty() { + return Err(OperationError::UnexpectedEof); + } + + match available.iter().position(|p| *p == delim) { + Some(i) => (true, i + 1), + None => (false, available.len()), + } + }; + self.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } + } } /// Blocking writer.