diff --git a/Cargo.toml b/Cargo.toml index 7f35b35..80a8dff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bytedata" -version = "0.1.0" +version = "0.1.1" edition = "2021" rust-version = "1.70.0" description = "Representation of a byte slice that is either static, borrowed, or shared." @@ -13,15 +13,19 @@ authors = ["TimLuq"] [dependencies] bytes_1 = { package = "bytes", version = "^1.2", optional = true, default-features = false } http-body_04 = { package = "http-body", version = "0.4.5", optional = true } +http-body_1 = { package = "http-body", version = "1", optional = true } http_02 = { package = "http", version = "0.2.4", optional = true } +http_1 = { package = "http", version = "1", optional = true } serde_1 = { package = "serde", version = "1.0.0", optional = true, default-features = false } [features] -default = ["macros"] +default = ["macros", "chunk"] macros = [] +chunk = [] alloc = ["serde_1?/alloc"] bytes_1_safe = ["bytes_1"] http-body_04 = ["dep:http-body_04", "dep:http_02", "bytes_1"] +http-body_1 = ["dep:http-body_1", "dep:http_1", "bytes_1"] std = ["alloc"] nightly = [] read_buf = ["std"] diff --git a/README.md b/README.md index a7a7fd1..99f6cb8 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,11 @@ The `SharedBytes` type is then also added as one additional representation of `B Enables runtime allocation of byte arrays on the heap. This allows for dynamic allocation of byte arrays which are exposed as `SharedBytes` and can be wrapped as `ByteData::Shared(_)`. +### chunk + +Enables runtime representation of small byte arrays inline as `ByteData::Chunk(_)`. +This allows for optimized storage of small byte arrays that are less than or equal to 12 bytes in size. + ### macros Exposes the `concat_bytes_static!` and `concat_str_static!` macros. @@ -41,3 +46,8 @@ Enables integration with the `http-body` crate (version `>=0.4.5, <0.5`). The trait `http_body::Body` is then implemented for `ByteData` and `SharedBytes` (if `alloc` feature is used). Since `http_body::Body` is the trait reexported as `hyper::HttpBody` in the `hyper` crate, this feature by extension also enables integration with `hyper`. + +### http-body_1 + +Enables integration with the `http-body` crate (version `>=1.0.0, <2`). +The trait `http_body::Body` is then implemented for `ByteData` and `SharedBytes` (if `alloc` feature is used). diff --git a/src/byte_chunk.rs b/src/byte_chunk.rs new file mode 100644 index 0000000..641155a --- /dev/null +++ b/src/byte_chunk.rs @@ -0,0 +1,155 @@ +use core::{ops::RangeBounds, slice::SliceIndex}; + +/// A chunk of bytes that is 12 bytes or less. +#[cfg_attr(docsrs, doc(cfg(feature = "chunk")))] +#[derive(Clone, Copy)] +pub struct ByteChunk { + /// The length of the chunk. + pub(crate) len: u8, + /// The data of the chunk. + pub(crate) data: [u8; Self::LEN], +} + +impl ByteChunk { + /// The maximum length of a `ByteChunk`. + pub const LEN: usize = 12; + + /// Create a `ByteChunk` from a slice. + pub const fn from_slice(data: &[u8]) -> Self { + let len = data.len(); + core::assert!(len <= Self::LEN, "chunk data too large"); + let mut chunk = ByteChunk { + len: len as u8, + data: unsafe { core::mem::zeroed() }, + }; + let mut i = 0; + while i < len { + chunk.data[i] = data[i]; + i += 1; + } + chunk + } + + /// Create a `ByteChunk` from a fixed-size array. + pub const fn from_array(data: &[u8; L]) -> Self { + core::assert!(L <= 12, "chunk data too large"); + let mut chunk = ByteChunk { + len: L as u8, + data: unsafe { core::mem::zeroed() }, + }; + let mut i = 0; + while i < L { + chunk.data[i] = data[i]; + i += 1; + } + chunk + } + + /// Create a `ByteChunk` from a fixed-size array. + #[inline] + pub const fn from_byte(data: u8) -> Self { + let mut chunk: ByteChunk = ByteChunk { + len: 1, + data: unsafe { core::mem::zeroed() }, + }; + chunk.data[0] = data; + chunk + } + + /// Get the bytes of the `ByteChunk` as a slice. + #[inline] + pub const fn as_slice(&self) -> &[u8] { + let len = self.len as usize; + if len == 0 { + return &[]; + } + unsafe { core::slice::from_raw_parts(&self.data[0], len) } + } + + /// Get the number bytes of the `ByteChunk`. + #[inline] + pub const fn len(&self) -> usize { + self.len as usize + } + + /// Check if the `ByteChunk` is empty. + #[inline] + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Slice the `ByteChunk` in place. + fn slice(&mut self, start: usize, end: usize) { + let len = self.len as usize; + if end > len || start > end { + panic!("ByteData: range out of bounds"); + } + let len = (end - start) as u8; + self.len = len; + if len != 0 && start != 0 { + let len = len as usize; + let mut i = 0usize; + while i < len { + self.data[i] = self.data[start + i]; + i += 1; + } + } + } + + /// Return as subslice of the `ByteChunk`. + pub fn sliced + SliceIndex<[u8], Output = [u8]>>( + &'_ self, + range: R, + ) -> Self { + let start = match range.start_bound() { + core::ops::Bound::Included(&s) => s, + core::ops::Bound::Excluded(&s) => s + 1, + core::ops::Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + core::ops::Bound::Included(&e) => e + 1, + core::ops::Bound::Excluded(&e) => e, + core::ops::Bound::Unbounded => self.len as usize, + }; + let mut r = *self; + Self::slice(&mut r, start, end); + r + } + + /// Return as subslice of the `ByteChunk`. + pub fn into_sliced + SliceIndex<[u8], Output = [u8]>>( + mut self, + range: R, + ) -> Self { + let start = match range.start_bound() { + core::ops::Bound::Included(&s) => s, + core::ops::Bound::Excluded(&s) => s + 1, + core::ops::Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + core::ops::Bound::Included(&e) => e + 1, + core::ops::Bound::Excluded(&e) => e, + core::ops::Bound::Unbounded => self.len as usize, + }; + Self::slice(&mut self, start, end); + self + } + + /// Slice the `ByteChunk` in place. + pub fn make_sliced + SliceIndex<[u8], Output = [u8]>>( + &'_ mut self, + range: R, + ) { + let start = match range.start_bound() { + core::ops::Bound::Included(&s) => s, + core::ops::Bound::Excluded(&s) => s + 1, + core::ops::Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + core::ops::Bound::Included(&e) => e + 1, + core::ops::Bound::Excluded(&e) => e, + core::ops::Bound::Unbounded => self.len as usize, + }; + Self::slice(self, start, end); + } +} diff --git a/src/bytedata.rs b/src/bytedata.rs index f0bea28..fd185dd 100644 --- a/src/bytedata.rs +++ b/src/bytedata.rs @@ -17,6 +17,10 @@ pub enum ByteData<'a> { Static(&'static [u8]), /// A borrowed byte slice. Borrowed(&'a [u8]), + #[cfg(feature = "chunk")] + #[cfg_attr(docsrs, doc(cfg(feature = "chunk")))] + /// A chunk of bytes that is 12 bytes or less. + Chunk(crate::byte_chunk::ByteChunk), #[cfg(feature = "alloc")] /// A shared byte slice. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] @@ -36,6 +40,30 @@ impl<'a> ByteData<'a> { Self::Static(dat) } + #[cfg(feature = "chunk")] + /// Creates a `ByteData` from a slice of bytes. The slice must be 12 bytes or less. If the slice is larger, this will panic. + #[cfg_attr(docsrs, doc(cfg(feature = "chunk")))] + #[inline] + pub const fn from_chunk_slice(dat: &[u8]) -> Self { + Self::Chunk(crate::byte_chunk::ByteChunk::from_slice(dat)) + } + + #[cfg(feature = "chunk")] + /// Creates a `ByteData` from a single byte. + #[cfg_attr(docsrs, doc(cfg(feature = "chunk")))] + #[inline] + pub const fn from_byte(b0: u8) -> Self { + Self::Chunk(crate::byte_chunk::ByteChunk::from_byte(b0)) + } + + #[cfg(feature = "chunk")] + /// Creates a `ByteData` from an array of bytes. The array must be 12 bytes or less. If the array is larger, this will panic. + #[cfg_attr(docsrs, doc(cfg(feature = "chunk")))] + #[inline] + pub const fn from_chunk(dat: &[u8; L]) -> Self { + Self::Chunk(crate::byte_chunk::ByteChunk::from_array(&dat)) + } + /// Creates a `ByteData` from a borrowed slice of bytes. #[inline] pub const fn from_borrowed(dat: &'a [u8]) -> Self { @@ -55,6 +83,10 @@ impl<'a> ByteData<'a> { #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[inline] pub fn from_owned(dat: Vec) -> Self { + #[cfg(feature = "chunk")] + if dat.len() <= 12 { + return Self::Chunk(crate::byte_chunk::ByteChunk::from_slice(&dat)); + } Self::Shared(dat.into()) } @@ -83,6 +115,8 @@ impl<'a> ByteData<'a> { match self { Self::Static(dat) => dat, Self::Borrowed(dat) => dat, + #[cfg(feature = "chunk")] + Self::Chunk(dat) => dat.as_slice(), #[cfg(feature = "alloc")] Self::Shared(dat) => dat.as_slice(), } @@ -93,6 +127,8 @@ impl<'a> ByteData<'a> { match self { Self::Static(dat) => dat.len(), Self::Borrowed(dat) => dat.len(), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => dat.len(), #[cfg(feature = "alloc")] Self::Shared(dat) => dat.len(), } @@ -103,6 +139,8 @@ impl<'a> ByteData<'a> { match self { Self::Static(dat) => dat.is_empty(), Self::Borrowed(dat) => dat.is_empty(), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => dat.is_empty(), #[cfg(feature = "alloc")] Self::Shared(dat) => dat.is_empty(), } @@ -140,6 +178,8 @@ impl<'a> ByteData<'a> { match self { Self::Static(dat) => Self::Static(&dat[range]), Self::Borrowed(dat) => Self::Borrowed(&dat[range]), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => Self::Chunk(dat.sliced(range)), #[cfg(feature = "alloc")] Self::Shared(dat) => Self::Shared(dat.sliced_range(range)), } @@ -153,6 +193,8 @@ impl<'a> ByteData<'a> { match self { Self::Static(dat) => Self::Static(&dat[range]), Self::Borrowed(dat) => Self::Borrowed(&dat[range]), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => Self::Chunk(dat.sliced(range)), #[cfg(feature = "alloc")] Self::Shared(dat) => Self::Shared(dat.into_sliced_range(range)), } @@ -166,6 +208,8 @@ impl<'a> ByteData<'a> { match self { Self::Static(dat) => *dat = &dat[range], Self::Borrowed(dat) => *dat = &dat[range], + #[cfg(feature = "chunk")] + Self::Chunk(dat) => dat.make_sliced(range), #[cfg(feature = "alloc")] Self::Shared(dat) => { dat.make_sliced_range(range); @@ -178,8 +222,14 @@ impl<'a> ByteData<'a> { #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn into_shared<'s>(self) -> ByteData<'s> { match self { + #[cfg(feature = "chunk")] + Self::Borrowed(dat) if dat.len() <= 12 => { + ByteData::Chunk(crate::byte_chunk::ByteChunk::from_slice(dat)) + } Self::Borrowed(dat) => ByteData::Shared(SharedBytes::from_slice(dat)), Self::Static(dat) => ByteData::Static(dat), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => ByteData::Chunk(dat), Self::Shared(dat) => ByteData::Shared(dat), } } @@ -194,9 +244,18 @@ impl<'a> ByteData<'a> { range: R, ) -> ByteData<'s> { match self { - Self::Borrowed(dat) => ByteData::Shared(SharedBytes::from_slice(&dat[range])), + Self::Borrowed(dat) => { + let dat = &dat[range]; + #[cfg(feature = "chunk")] + if dat.len() <= 12 { + return ByteData::Chunk(crate::byte_chunk::ByteChunk::from_slice(dat)); + } + ByteData::Shared(SharedBytes::from_slice(dat)) + } Self::Shared(dat) => ByteData::Shared(dat.into_sliced_range(range)), Self::Static(dat) => ByteData::Static(&dat[range]), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => ByteData::Chunk(dat.into_sliced(range)), } } } @@ -210,6 +269,8 @@ impl ByteData<'static> { Self::Borrowed(dat) => ByteData::Static(dat), #[cfg(feature = "alloc")] Self::Shared(dat) => ByteData::Shared(dat), + #[cfg(feature = "chunk")] + Self::Chunk(dat) => ByteData::Chunk(dat), } } } diff --git a/src/bytes_1/bytedata.rs b/src/bytes_1/bytedata.rs index f2a1733..45ed545 100644 --- a/src/bytes_1/bytedata.rs +++ b/src/bytes_1/bytedata.rs @@ -8,6 +8,8 @@ impl From> for bytes::Bytes { match dat { ByteData::Static(dat) => bytes::Bytes::from_static(dat), ByteData::Borrowed(dat) => bytes::Bytes::copy_from_slice(dat), + #[cfg(feature = "chunk")] + ByteData::Chunk(dat) => bytes::Bytes::copy_from_slice(dat.as_slice()), #[cfg(feature = "alloc")] ByteData::Shared(dat) => dat.into(), } diff --git a/src/http_body_1.rs b/src/http_body_1.rs new file mode 100644 index 0000000..a3e97ec --- /dev/null +++ b/src/http_body_1.rs @@ -0,0 +1,70 @@ +use core::{ + convert::Infallible, + pin::Pin, + task::{Context, Poll}, +}; + +use ::http_body_1 as http_body; + +use crate::ByteData; + +impl<'a> http_body::Body for ByteData<'a> { + type Data = ByteData<'a>; + type Error = Infallible; + + fn poll_frame( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + let slf = core::ops::DerefMut::deref_mut(&mut self); + if slf.is_empty() { + Poll::Ready(None) + } else if slf.len() > 65535 { + let res = slf.sliced(0..65535); + slf.make_sliced(65535..); + Poll::Ready(Some(Ok(http_body::Frame::data(res)))) + } else { + let res = core::mem::replace(slf, ByteData::empty()); + Poll::Ready(Some(Ok(http_body::Frame::data(res)))) + } + } + + fn is_end_stream(&self) -> bool { + self.is_empty() + } + + fn size_hint(&self) -> http_body::SizeHint { + http_body::SizeHint::with_exact(self.len() as u64) + } +} + +#[cfg(feature = "alloc")] +impl http_body::Body for crate::SharedBytes { + type Data = crate::SharedBytes; + type Error = Infallible; + + fn poll_frame( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + let slf = core::ops::DerefMut::deref_mut(&mut self); + if slf.is_empty() { + Poll::Ready(None) + } else if slf.len() > 65535 { + let res = slf.sliced(0, 65535); + slf.make_sliced(65535, slf.len() - 65535); + Poll::Ready(Some(Ok(http_body::Frame::data(res)))) + } else { + let res = core::mem::replace(slf, crate::SharedBytes::empty()); + Poll::Ready(Some(Ok(http_body::Frame::data(res)))) + } + } + + fn is_end_stream(&self) -> bool { + self.is_empty() + } + + fn size_hint(&self) -> http_body::SizeHint { + http_body::SizeHint::with_exact(self.len() as u64) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5c383af..7bc14e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,12 +52,20 @@ mod macros; #[cfg(feature = "macros")] pub use self::macros::*; +#[cfg(feature = "chunk")] +mod byte_chunk; +#[cfg(feature = "chunk")] +pub use byte_chunk::ByteChunk; + #[cfg(feature = "bytes_1")] mod bytes_1; #[cfg(feature = "http-body_04")] mod http_body_04; +#[cfg(feature = "http-body_1")] +mod http_body_1; + #[cfg(feature = "serde_1")] mod serde_1; diff --git a/src/serde_1/bytedata.rs b/src/serde_1/bytedata.rs index 4d2780c..6e6e533 100644 --- a/src/serde_1/bytedata.rs +++ b/src/serde_1/bytedata.rs @@ -28,10 +28,14 @@ impl<'de> serde::de::Deserialize<'de> for ByteData<'de> { Ok(ByteData::from_borrowed(v)) } - fn visit_bytes(self, _v: &[u8]) -> Result { + fn visit_bytes(self, v: &[u8]) -> Result { + #[cfg(feature = "chunk")] + if v.len() <= 12 { + return Ok(ByteData::from_chunk_slice(v)); + } #[cfg(feature = "alloc")] { - Ok(ByteData::from_shared(_v.into())) + Ok(ByteData::from_shared(v.into())) } #[cfg(not(feature = "alloc"))] { diff --git a/src/shared_bytes_builder.rs b/src/shared_bytes_builder.rs index d4606f3..272dba1 100644 --- a/src/shared_bytes_builder.rs +++ b/src/shared_bytes_builder.rs @@ -177,9 +177,7 @@ impl SharedBytesBuilder { if self.off == 8 { return &[]; } - unsafe { - core::slice::from_raw_parts(self.dat.offset(8), self.off as usize - 8) - } + unsafe { core::slice::from_raw_parts(self.dat.offset(8), self.off as usize - 8) } } /// Returns the bytes as a mut slice. @@ -187,19 +185,15 @@ impl SharedBytesBuilder { if self.off == 8 { return &mut []; } - unsafe { - core::slice::from_raw_parts_mut( - self.dat.offset(8), - self.off as usize - 8, - ) - } + unsafe { core::slice::from_raw_parts_mut(self.dat.offset(8), self.off as usize - 8) } } /// Apply a function to the unused reserved bytes. /// /// The function is passed a mutable slice of `MaybeUninit` and returns a tuple of the return value and the number of bytes filled. pub fn apply_unfilled(&mut self, f: F) -> R - where F: FnOnce(&mut [core::mem::MaybeUninit]) -> (R, usize), + where + F: FnOnce(&mut [core::mem::MaybeUninit]) -> (R, usize), { let off = self.off as isize; let data = if off == 8 { @@ -337,15 +331,17 @@ impl core::fmt::UpperHex for SharedBytesBuilder { #[cfg(feature = "read_buf")] impl SharedBytesBuilder { /// Apply a function to the unused reserved bytes. - pub fn apply_borrowed_buf<'this, R, F>(&'this mut self, f: F) -> R - where F: FnOnce(&mut std::io::BorrowedBuf<'this>) -> R, + pub fn apply_borrowed_buf<'this, R, F>(&'this mut self, f: F) -> R + where + F: FnOnce(&mut std::io::BorrowedBuf<'this>) -> R, { let off = self.off as isize; let mut bb = if off == 8 { std::io::BorrowedBuf::from(&mut [] as &mut [u8]) } else { let data = unsafe { self.dat.offset(off) as *mut core::mem::MaybeUninit }; - let data = unsafe { core::slice::from_raw_parts_mut(data, self.len as usize - off as usize) }; + let data = + unsafe { core::slice::from_raw_parts_mut(data, self.len as usize - off as usize) }; std::io::BorrowedBuf::from(data) }; let ret = f(&mut bb); diff --git a/src/stringdata.rs b/src/stringdata.rs index ca93e61..b74f0e0 100644 --- a/src/stringdata.rs +++ b/src/stringdata.rs @@ -123,6 +123,8 @@ impl<'a> StringData<'a> { match &self.data { ByteData::Static(dat) => dat.len(), ByteData::Borrowed(dat) => dat.len(), + #[cfg(feature = "chunk")] + ByteData::Chunk(dat) => dat.len(), #[cfg(feature = "alloc")] ByteData::Shared(dat) => dat.len(), }