#![forbid(unsafe_code)]
mod escape_ascii;
pub use escape_ascii::escape_ascii;
mod deframe_crlf;
pub use deframe_crlf::deframe_crlf;
mod deframe_line;
pub use deframe_line::deframe_line;
#[cfg(feature = "futures-io")]
mod impl_futures_io;
#[cfg(feature = "futures-io")]
#[allow(unused_imports)]
pub use impl_futures_io::*;
#[cfg(feature = "tokio")]
mod impl_tokio;
#[cfg(feature = "tokio")]
#[allow(unused_imports)]
pub use impl_tokio::*;
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct NoWritableSpace {}
impl From<NoWritableSpace> for std::io::Error {
fn from(_: NoWritableSpace) -> Self {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"no writable space in buffer",
)
}
}
impl From<NoWritableSpace> for String {
fn from(_: NoWritableSpace) -> Self {
"no writable space in buffer".to_string()
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct MalformedInputError(pub String);
impl MalformedInputError {
#[must_use]
pub fn new(msg: String) -> Self {
Self(msg)
}
}
impl From<MalformedInputError> for std::io::Error {
fn from(e: MalformedInputError) -> Self {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("malformed input: {}", e.0),
)
}
}
impl From<MalformedInputError> for String {
fn from(e: MalformedInputError) -> Self {
format!("malformed input: {}", e.0)
}
}
#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FixedBuf<const SIZE: usize> {
mem: [u8; SIZE],
read_index: usize,
write_index: usize,
}
impl<const SIZE: usize> std::panic::UnwindSafe for FixedBuf<SIZE> {}
impl<const SIZE: usize> FixedBuf<SIZE> {
#[must_use]
pub const fn new() -> Self {
Self {
mem: [0_u8; SIZE],
write_index: 0,
read_index: 0,
}
}
#[must_use]
pub fn into_inner(self) -> [u8; SIZE] {
self.mem
}
#[must_use]
pub fn len(&self) -> usize {
self.write_index - self.read_index
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.write_index == self.read_index
}
pub fn clear(&mut self) {
self.read_index = 0;
self.write_index = 0;
}
#[must_use]
pub fn escape_ascii(&self) -> String {
escape_ascii(self.readable())
}
#[must_use]
pub fn mem(&self) -> &[u8] {
self.mem.as_ref()
}
#[must_use]
pub fn readable(&self) -> &[u8] {
&self.mem.as_ref()[self.read_index..self.write_index]
}
pub fn try_read_byte(&mut self) -> Option<u8> {
self.try_read_exact(1).map(|bytes| bytes[0])
}
pub fn try_read_exact(&mut self, num_bytes: usize) -> Option<&[u8]> {
let new_read_index = self.read_index + num_bytes;
if self.write_index < new_read_index {
None
} else {
let old_read_index = self.read_index;
self.read_index = new_read_index;
if self.read_index == self.write_index {
self.write_index = 0;
self.read_index = 0;
}
Some(&self.mem.as_ref()[old_read_index..new_read_index])
}
}
#[allow(clippy::missing_panics_doc)]
pub fn read_all(&mut self) -> &[u8] {
self.try_read_exact(self.len()).unwrap()
}
#[allow(clippy::missing_panics_doc)]
pub fn read_and_copy_bytes(&mut self, dest: &mut [u8]) -> usize {
let readable = self.readable();
let len = core::cmp::min(dest.len(), readable.len());
if len == 0 {
return 0;
}
let src = &readable[..len];
let copy_dest = &mut dest[..len];
copy_dest.copy_from_slice(src);
self.try_read_exact(len).unwrap();
len
}
#[allow(clippy::missing_panics_doc)]
pub fn read_and_copy_exact(&mut self, dest: &mut [u8]) -> Option<()> {
if self.len() < dest.len() {
return None;
}
assert_eq!(dest.len(), self.read_and_copy_bytes(dest));
Some(())
}
pub fn copy_once_from(
&mut self,
reader: &mut impl std::io::Read,
) -> Result<usize, std::io::Error> {
let writable = self.writable();
if writable.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"no empty space in buffer",
));
}
let num_read = reader.read(writable)?;
self.wrote(num_read);
Ok(num_read)
}
pub fn write_bytes(&mut self, data: impl AsRef<[u8]>) -> Result<usize, NoWritableSpace> {
let data = data.as_ref();
if data.is_empty() {
return Ok(0);
}
let writable = self.writable();
if writable.is_empty() {
return Err(NoWritableSpace {});
}
let len = writable.len().min(data.len());
let dest = &mut writable[..len];
let src = &data[..len];
dest.copy_from_slice(src);
self.wrote(len);
Ok(len)
}
pub fn writable(&mut self) -> &mut [u8] {
&mut self.mem.as_mut()[self.write_index..]
}
pub fn wrote(&mut self, num_bytes: usize) {
if num_bytes == 0 {
return;
}
let new_write_index = self.write_index + num_bytes;
assert!(
new_write_index <= self.mem.as_mut().len(),
"write would overflow"
);
self.write_index = new_write_index;
}
pub fn shift(&mut self) {
if self.read_index == 0 {
return;
}
self.mem
.as_mut()
.copy_within(self.read_index..self.write_index, 0);
self.write_index -= self.read_index;
self.read_index = 0;
}
#[allow(clippy::missing_panics_doc)]
pub fn deframe<F>(
&mut self,
deframer_fn: F,
) -> Result<Option<core::ops::Range<usize>>, std::io::Error>
where
F: Fn(&[u8]) -> Result<(usize, Option<core::ops::Range<usize>>), MalformedInputError>,
{
if self.is_empty() {
return Ok(None);
}
let (num_to_consume, opt_data_range) = deframer_fn(self.readable())?;
let opt_mem_range = opt_data_range.map(|data_range| {
let mem_start = self.read_index + data_range.start;
let mem_end = self.read_index + data_range.end;
mem_start..mem_end
});
self.try_read_exact(num_to_consume).unwrap();
Ok(opt_mem_range)
}
pub fn read_frame<R, F>(
&mut self,
reader: &mut R,
deframer_fn: F,
) -> Result<Option<&[u8]>, std::io::Error>
where
R: std::io::Read,
F: Fn(&[u8]) -> Result<(usize, Option<core::ops::Range<usize>>), MalformedInputError>,
{
loop {
if !self.is_empty() {
if let Some(frame_range) = self.deframe(&deframer_fn)? {
return Ok(Some(&self.mem()[frame_range]));
}
}
self.shift();
let writable = self.writable();
if writable.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"end of buffer full",
));
}
let num_read = reader.read(writable)?;
if num_read == 0 {
if self.is_empty() {
return Ok(None);
}
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"eof after reading part of a frame",
));
}
self.wrote(num_read);
}
}
#[cfg(feature = "tokio")]
pub async fn read_frame_tokio<R, F>(
&mut self,
reader: &mut R,
deframer_fn: F,
) -> Result<Option<&[u8]>, std::io::Error>
where
R: tokio::io::AsyncRead + Unpin,
F: Fn(&[u8]) -> Result<(usize, Option<core::ops::Range<usize>>), MalformedInputError>,
{
use tokio::io::AsyncReadExt;
loop {
if !self.is_empty() {
if let Some(frame_range) = self.deframe(&deframer_fn)? {
return Ok(Some(&self.mem()[frame_range]));
}
}
self.shift();
let writable = self.writable();
if writable.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"end of buffer full",
));
}
let num_read = reader.read(writable).await?;
if num_read == 0 {
if self.is_empty() {
return Ok(None);
}
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"eof after reading part of a frame",
));
}
self.wrote(num_read);
}
}
}
impl<const SIZE: usize> From<[u8; SIZE]> for FixedBuf<SIZE> {
fn from(mem: [u8; SIZE]) -> Self {
Self {
mem,
read_index: 0,
write_index: SIZE,
}
}
}
impl<const SIZE: usize> From<&[u8; SIZE]> for FixedBuf<SIZE> {
fn from(mem: &[u8; SIZE]) -> Self {
Self {
mem: *mem,
read_index: 0,
write_index: SIZE,
}
}
}
impl<const SIZE: usize> std::io::Write for FixedBuf<SIZE> {
fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
Ok(self.write_bytes(data)?)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<const SIZE: usize> std::io::Read for FixedBuf<SIZE> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
Ok(self.read_and_copy_bytes(buf))
}
}
impl<const SIZE: usize> core::fmt::Debug for FixedBuf<SIZE> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(
f,
"FixedBuf<{}>{{{} writable, {} readable: \"{}\"}}",
SIZE,
SIZE - self.write_index,
self.len(),
self.escape_ascii()
)
}
}
impl<const SIZE: usize> Default for FixedBuf<SIZE> {
fn default() -> Self {
Self::new()
}
}