diff --git a/Cargo.toml b/Cargo.toml index c1b7673..40bbff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,11 @@ rust-version = "1.68" default = ["std"] std = ["serde/std"] alloc = ["serde/alloc"] +heapless = ["dep:heapless"] [dependencies] serde = { version = "1.0.166", default-features = false } +heapless = { version = "0.8", default-features = false, optional = true } [dev-dependencies] bincode = { version = "2", features = ["serde"] } diff --git a/README.md b/README.md index e0a0e9f..d482507 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ in `serde_bytes::ByteBuf`. Additionally this crate supports the Serde `with` attribute to enable efficient handling of `&[u8]` and `Vec` in structs without needing a wrapper type. +This approach also works with `heapless::Vec`, which is useful +where the size of data has a maxmimum bound but isn't an exact size. Use +the `heapless` feature to enable support. You can also opt into special +handling in this case by wrapping the `heapless::Vec` with +`serde_bytes::HeaplessByteBuf`. + ## Example ```rust diff --git a/src/de.rs b/src/de.rs index 27c35e6..e92710b 100644 --- a/src/de.rs +++ b/src/de.rs @@ -7,6 +7,9 @@ use serde::Deserializer; #[cfg(any(feature = "std", feature = "alloc"))] use crate::ByteBuf; +#[cfg(feature = "heapless")] +use crate::HeaplessByteBuf; + #[cfg(any(feature = "std", feature = "alloc"))] use core::cmp; @@ -53,6 +56,16 @@ impl<'de> Deserialize<'de> for Vec { } } +#[cfg(feature = "heapless")] +impl<'de, const N: usize> Deserialize<'de> for heapless::Vec { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(HeaplessByteBuf::into_inner) + } +} + impl<'de: 'a, 'a> Deserialize<'de> for &'a Bytes { fn deserialize(deserializer: D) -> Result where @@ -114,6 +127,17 @@ impl<'de> Deserialize<'de> for ByteBuf { } } +#[cfg(feature = "heapless")] +impl<'de, const N: usize> Deserialize<'de> for HeaplessByteBuf { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // Via the serde::Deserialize impl for ByteBuf. + serde::Deserialize::deserialize(deserializer) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl<'de: 'a, 'a> Deserialize<'de> for Cow<'a, [u8]> { fn deserialize(deserializer: D) -> Result diff --git a/src/heapless_bytebuf.rs b/src/heapless_bytebuf.rs new file mode 100644 index 0000000..e7c9400 --- /dev/null +++ b/src/heapless_bytebuf.rs @@ -0,0 +1,271 @@ +use core::borrow::{Borrow, BorrowMut}; +use core::cmp::Ordering; +use core::convert::TryFrom; +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::ops::{Deref, DerefMut}; + +use heapless::Vec; + +use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; + +use crate::Bytes; + +/// Wrapper around `heapless::Vec` to serialize and deserialize efficiently. +/// +/// ``` +/// use std::collections::HashMap; +/// use std::io; +/// +/// use serde_bytes::HeaplessByteBuf; +/// +/// fn deserialize_bytebufs() -> Result<(), bincode::error::DecodeError> { +/// let example_data = [2, 2, 3, 116, 119, 111, 1, 3, 111, 110, 101]; +/// +/// let map: HashMap>; +/// (map, _) = bincode::serde::decode_from_slice( +/// &example_data, +/// bincode::config::standard(), +/// )?; +/// +/// println!("{:?}", map); +/// +/// Ok(()) +/// } +/// # +/// # fn main() { +/// # deserialize_bytebufs().unwrap(); +/// # } +/// ``` +#[derive(Clone, Default, Eq, Ord)] +pub struct HeaplessByteBuf { + bytes: Vec, +} + +impl HeaplessByteBuf { + /// Construct a `HeaplessByteBuf`. + pub fn new() -> Self { + HeaplessByteBuf { bytes: Vec::new() } + } + + /// Wrap existing bytes in a `HeaplessByteBuf`. + pub fn from>>(bytes: T) -> Self { + HeaplessByteBuf { + bytes: bytes.into(), + } + } + + /// Wrap existing bytes in a `HeaplessByteBuf`. + pub fn try_from_slice(bytes: &[u8]) -> Result> { + let bytes = Vec::from_slice(bytes).map_err(|_| HeaplessByteBufFullError::)?; + + Ok(HeaplessByteBuf { bytes }) + } + + /// Wrap existing bytes in a `HeaplessByteBuf`. + #[cfg(any(feature = "std", feature = "alloc"))] + pub fn try_from>>( + vec: T, + ) -> Result> { + let std_vec = vec.into(); + let bytes = Vec::from_slice(&std_vec).map_err(|_| HeaplessByteBufFullError::)?; + + Ok(HeaplessByteBuf { bytes }) + } + + /// Unwrap the vector of byte underlying this `HeaplessByteBuf`. + pub fn into_inner(self) -> Vec { + self.bytes + } + + #[doc(hidden)] + #[allow(clippy::should_implement_trait)] + pub fn into_iter(self) -> as IntoIterator>::IntoIter { + self.bytes.into_iter() + } +} + +#[derive(Debug)] +pub struct HeaplessByteBufFullError; + +impl Display for HeaplessByteBufFullError { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + write!(f, "heapless::Vec<{N}> is too small") + } +} + +impl core::error::Error for HeaplessByteBufFullError {} + +impl Debug for HeaplessByteBuf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.bytes, f) + } +} + +impl AsRef<[u8]> for HeaplessByteBuf { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl AsMut<[u8]> for HeaplessByteBuf { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } +} + +impl Deref for HeaplessByteBuf { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +impl DerefMut for HeaplessByteBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.bytes + } +} + +impl Borrow for HeaplessByteBuf { + fn borrow(&self) -> &Bytes { + Bytes::new(&self.bytes) + } +} + +impl BorrowMut for HeaplessByteBuf { + fn borrow_mut(&mut self) -> &mut Bytes { + unsafe { &mut *(&mut self.bytes as &mut [u8] as *mut [u8] as *mut Bytes) } + } +} + +#[cfg(any(feature = "std", feature = "alloc"))] +impl TryFrom> for HeaplessByteBuf { + type Error = HeaplessByteBufFullError; + + fn try_from(bytes: alloc::vec::Vec) -> Result { + HeaplessByteBuf::::try_from(bytes) + } +} + +impl PartialEq for HeaplessByteBuf +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn eq(&self, other: &Rhs) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + +impl PartialOrd for HeaplessByteBuf +where + Rhs: ?Sized + AsRef<[u8]>, +{ + fn partial_cmp(&self, other: &Rhs) -> Option { + self.as_ref().partial_cmp(other.as_ref()) + } +} + +impl Hash for HeaplessByteBuf { + fn hash(&self, state: &mut H) { + self.bytes.hash(state); + } +} + +impl IntoIterator for HeaplessByteBuf { + type Item = u8; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.into_iter() + } +} + +impl<'a, const N: usize> IntoIterator for &'a HeaplessByteBuf { + type Item = &'a u8; + type IntoIter = <&'a [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter() + } +} + +impl<'a, const N: usize> IntoIterator for &'a mut HeaplessByteBuf { + type Item = &'a mut u8; + type IntoIter = <&'a mut [u8] as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.bytes.iter_mut() + } +} + +impl Serialize for HeaplessByteBuf { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.bytes) + } +} + +struct HeaplessByteBufVisitor; + +impl<'de, const N: usize> Visitor<'de> for HeaplessByteBufVisitor { + type Value = HeaplessByteBuf; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("byte array") + } + + fn visit_seq(self, mut visitor: V) -> Result, V::Error> + where + V: SeqAccess<'de>, + { + let mut bytes = Vec::::new(); + + while let Some(b) = visitor.next_element()? { + let result = bytes.push(b); + + // Push can only fail if the sequence is too large for the Vec + if result.is_err() { + let expected: &str = &format!("{N}"); + return Err(V::Error::invalid_length(N + 1, &expected)); + } + } + + Ok(HeaplessByteBuf::from(bytes)) + } + + fn visit_bytes(self, v: &[u8]) -> Result, E> + where + E: Error, + { + HeaplessByteBuf::try_from_slice(v).map_err(|_| { + let expected: &str = &format!("{N}"); + Error::invalid_length(v.len(), &expected) + }) + } + + #[cfg(any(feature = "std", feature = "alloc"))] + fn visit_byte_buf(self, v: alloc::vec::Vec) -> Result, E> + where + E: Error, + { + let len = v.len(); + HeaplessByteBuf::try_from(v).map_err(|_| { + let expected: &str = &format!("{N}"); + Error::invalid_length(len, &expected) + }) + } +} + +impl<'de, const N: usize> Deserialize<'de> for HeaplessByteBuf { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(HeaplessByteBufVisitor::) + } +} diff --git a/src/lib.rs b/src/lib.rs index b6f6655..0fe2e47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,12 @@ //! efficient handling of `&[u8]` and `Vec` in structs without needing a //! wrapper type. //! +//! This approach also works with `heapless::Vec`, which is useful +//! where the size of data has a maxmimum bound but isn't an exact size. Use +//! the `heapless` feature to enable support. You can also opt into special +//! handling in this case by wrapping the `heapless::Vec` with +//! `serde_bytes::HeaplessByteBuf`. +//! //! ``` //! # use serde_derive::{Deserialize, Serialize}; //! use serde::{Deserialize, Serialize}; @@ -27,6 +33,10 @@ //! //! #[serde(with = "serde_bytes")] //! byte_array: [u8; 314], +//! +//! #[cfg(feature = "heapless")] +//! #[serde(with = "serde_bytes")] +//! heapless_byte_buf: heapless::Vec, //! } //! ``` @@ -51,6 +61,9 @@ mod ser; #[cfg(any(feature = "std", feature = "alloc"))] mod bytebuf; +#[cfg(feature = "heapless")] +mod heapless_bytebuf; + #[cfg(feature = "alloc")] extern crate alloc; @@ -64,6 +77,9 @@ pub use crate::ser::Serialize; #[cfg(any(feature = "std", feature = "alloc"))] pub use crate::bytebuf::ByteBuf; +#[cfg(feature = "heapless")] +pub use crate::heapless_bytebuf::HeaplessByteBuf; + /// Serde `serialize_with` function to serialize bytes efficiently. /// /// This function can be used with either of the following Serde attributes: @@ -85,6 +101,10 @@ pub use crate::bytebuf::ByteBuf; /// /// #[serde(with = "serde_bytes")] /// byte_array: [u8; 314], +/// +/// #[cfg(feature = "heapless")] +/// #[serde(with = "serde_bytes")] +/// heapless_byte_buf: heapless::Vec, /// } /// ``` pub fn serialize(bytes: &T, serializer: S) -> Result @@ -113,6 +133,10 @@ where /// /// #[serde(with = "serde_bytes")] /// byte_array: [u8; 314], +/// +/// #[cfg(feature = "heapless")] +/// #[serde(with = "serde_bytes")] +/// heapless_payload: heapless::Vec, /// } /// ``` pub fn deserialize<'de, T, D>(deserializer: D) -> Result diff --git a/src/ser.rs b/src/ser.rs index 37889a8..8dce8df 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -4,6 +4,9 @@ use serde::Serializer; #[cfg(any(feature = "std", feature = "alloc"))] use crate::ByteBuf; +#[cfg(feature = "heapless")] +use crate::HeaplessByteBuf; + #[cfg(feature = "alloc")] use alloc::borrow::Cow; #[cfg(all(feature = "std", not(feature = "alloc")))] @@ -69,6 +72,16 @@ impl Serialize for ByteArray { } } +#[cfg(feature = "heapless")] +impl Serialize for heapless::Vec { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl Serialize for ByteBuf { fn serialize(&self, serializer: S) -> Result @@ -79,6 +92,16 @@ impl Serialize for ByteBuf { } } +#[cfg(feature = "heapless")] +impl Serialize for HeaplessByteBuf { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self) + } +} + #[cfg(any(feature = "std", feature = "alloc"))] impl<'a> Serialize for Cow<'a, [u8]> { fn serialize(&self, serializer: S) -> Result diff --git a/tests/test_derive.rs b/tests/test_derive.rs index 019a7cb..3f8b854 100644 --- a/tests/test_derive.rs +++ b/tests/test_derive.rs @@ -5,6 +5,9 @@ use serde_derive::{Deserialize, Serialize}; use serde_test::{assert_tokens, Token}; use std::borrow::Cow; +#[cfg(feature = "heapless")] +use serde_bytes::HeaplessByteBuf; + #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Test<'a> { #[serde(with = "serde_bytes")] @@ -57,6 +60,14 @@ struct Test<'a> { #[serde(with = "serde_bytes")] opt_cow_slice: Option>, + + #[cfg(feature = "heapless")] + #[serde(with = "serde_bytes")] + heapless_byte_buf: HeaplessByteBuf<314>, + + #[cfg(feature = "heapless")] + #[serde(with = "serde_bytes")] + heapless_vec: heapless::Vec, } #[derive(Serialize)] @@ -86,6 +97,12 @@ fn test() { opt_array: Some([0; 314]), opt_bytearray: Some(ByteArray::new([0; 314])), opt_cow_slice: Some(Cow::Borrowed(b"...")), + #[cfg(feature = "heapless")] + heapless_byte_buf: HeaplessByteBuf::try_from(b"...".as_ref()) + .expect("Size of tested `HeaplessByteBuf`to be >= size of test data"), + #[cfg(feature = "heapless")] + heapless_vec: heapless::Vec::from_slice(b"...".as_ref()) + .expect("Size of tested `heapless::Vec` to be >= size of test data"), }; assert_tokens( @@ -93,7 +110,10 @@ fn test() { &[ Token::Struct { name: "Test", + #[cfg(not(feature = "heapless"))] len: 17, + #[cfg(feature = "heapless")] + len: 19, }, Token::Str("slice"), Token::BorrowedBytes(b"..."), @@ -134,6 +154,14 @@ fn test() { Token::Str("opt_cow_slice"), Token::Some, Token::BorrowedBytes(b"..."), + #[cfg(feature = "heapless")] + Token::Str("heapless_byte_buf"), + #[cfg(feature = "heapless")] + Token::BorrowedBytes(b"..."), + #[cfg(feature = "heapless")] + Token::Str("heapless_vec"), + #[cfg(feature = "heapless")] + Token::BorrowedBytes(b"..."), Token::StructEnd, ], ); diff --git a/tests/test_partialeq.rs b/tests/test_partialeq.rs index a0423cf..be40cec 100644 --- a/tests/test_partialeq.rs +++ b/tests/test_partialeq.rs @@ -2,6 +2,9 @@ use serde_bytes::{ByteArray, ByteBuf, Bytes}; +#[cfg(feature = "heapless")] +use serde_bytes::HeaplessByteBuf; + fn _bytes_eq_slice(bytes: &Bytes, slice: &[u8]) -> bool { bytes == slice } @@ -10,6 +13,11 @@ fn _bytebuf_eq_vec(bytebuf: ByteBuf, vec: Vec) -> bool { bytebuf == vec } +#[cfg(feature = "heapless")] +fn _heapless_bytebuf_eq_vec(bytebuf: HeaplessByteBuf, vec: Vec) -> bool { + bytebuf == vec +} + fn _bytes_eq_bytestring(bytes: &Bytes) -> bool { bytes == b"..." } diff --git a/tests/test_serde.rs b/tests/test_serde.rs index 6b864b9..d15043a 100644 --- a/tests/test_serde.rs +++ b/tests/test_serde.rs @@ -1,6 +1,9 @@ use serde_bytes::{ByteArray, ByteBuf, Bytes}; use serde_test::{assert_de_tokens, assert_ser_tokens, assert_tokens, Token}; +#[cfg(feature = "heapless")] +use serde_bytes::HeaplessByteBuf; + #[test] fn test_bytes() { let empty = Bytes::new(&[]); @@ -73,3 +76,40 @@ fn test_bytearray() { assert_ser_tokens(&bytes, &[Token::ByteBuf(b"ABC")]); assert_de_tokens(&bytes, &[Token::BorrowedStr("ABC")]); } + +#[test] +#[cfg(feature = "heapless")] +fn test_heapless_byte_buf() { + let empty = HeaplessByteBuf::<3>::new(); + assert_tokens(&empty, &[Token::BorrowedBytes(b"")]); + assert_tokens(&empty, &[Token::Bytes(b"")]); + assert_tokens(&empty, &[Token::ByteBuf(b"")]); + assert_de_tokens(&empty, &[Token::Seq { len: None }, Token::SeqEnd]); + assert_de_tokens(&empty, &[Token::Seq { len: Some(0) }, Token::SeqEnd]); + + let buf = HeaplessByteBuf::<3>::try_from(vec![65, 66, 67]) + .expect("Size of tested `HeaplessByteBuf`to be >= size of test data"); + assert_tokens(&buf, &[Token::BorrowedBytes(b"ABC")]); + assert_tokens(&buf, &[Token::Bytes(b"ABC")]); + assert_tokens(&buf, &[Token::ByteBuf(b"ABC")]); + assert_de_tokens( + &buf, + &[ + Token::Seq { len: None }, + Token::U8(65), + Token::U8(66), + Token::U8(67), + Token::SeqEnd, + ], + ); + assert_de_tokens( + &buf, + &[ + Token::Seq { len: Some(3) }, + Token::U8(65), + Token::U8(66), + Token::U8(67), + Token::SeqEnd, + ], + ); +}