From 092152774584e00e6ea603ae11805658800ad3b6 Mon Sep 17 00:00:00 2001 From: Mateusz Kowalczyk Date: Thu, 7 Dec 2023 13:14:40 +0900 Subject: [PATCH 1/2] Optional support for borsh serialization --- Cargo.toml | 7 ++++++- src/array_string.rs | 22 ++++++++++++++++++++++ src/arrayvec.rs | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5f4fd2c..256bff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,11 @@ categories = ["data-structures", "no-std"] [build-dependencies] +[dependencies.borsh] +version = "1.2.0" +optional = true +default-features = false + [dependencies.serde] version = "1.0" optional = true @@ -49,7 +54,7 @@ debug = true debug = true [package.metadata.docs.rs] -features = ["serde", "zeroize"] +features = ["borsh", "serde", "zeroize"] [package.metadata.release] no-dev-version = true diff --git a/src/array_string.rs b/src/array_string.rs index 90cfc09..af8439d 100644 --- a/src/array_string.rs +++ b/src/array_string.rs @@ -625,6 +625,28 @@ impl<'de, const CAP: usize> Deserialize<'de> for ArrayString } } +#[cfg(feature = "borsh")] +/// Requires crate feature `"borsh"` +impl borsh::BorshSerialize for ArrayString { + fn serialize(&self, writer: &mut W) -> borsh::io::Result<()> { + ::serialize(&*self, writer) + } +} + +#[cfg(feature = "borsh")] +/// Requires crate feature `"borsh"` +impl borsh::BorshDeserialize for ArrayString { + fn deserialize_reader(reader: &mut R) -> borsh::io::Result { + let s = ::deserialize_reader(reader)?; + ArrayString::from(&s).map_err(|_| { + borsh::io::Error::new( + borsh::io::ErrorKind::InvalidData, + format!("expected a string no more than {} bytes long", CAP), + ) + }) + } +} + impl<'a, const CAP: usize> TryFrom<&'a str> for ArrayString { type Error = CapacityError<&'a str>; diff --git a/src/arrayvec.rs b/src/arrayvec.rs index 37e151a..39361b8 100644 --- a/src/arrayvec.rs +++ b/src/arrayvec.rs @@ -1298,3 +1298,42 @@ impl<'de, T: Deserialize<'de>, const CAP: usize> Deserialize<'de> for ArrayVec(PhantomData)) } } + +#[cfg(feature = "borsh")] +/// Requires crate feature `"borsh"` +impl borsh::BorshSerialize for ArrayVec +where + T: borsh::BorshSerialize, +{ + fn serialize(&self, writer: &mut W) -> borsh::io::Result<()> { + let vs = self.as_slice(); + ::serialize(&vs.len(), writer)?; + for elem in vs { + ::serialize(elem, writer)?; + } + Ok(()) + } +} + +#[cfg(feature = "borsh")] +/// Requires crate feature `"borsh"` +impl borsh::BorshDeserialize for ArrayVec +where + T: borsh::BorshDeserialize, +{ + fn deserialize_reader(reader: &mut R) -> borsh::io::Result { + let mut values = Self::new(); + let len = ::deserialize_reader(reader)?; + for _ in 0..len { + let elem = ::deserialize_reader(reader)?; + if let Err(_) = values.try_push(elem) { + return Err(borsh::io::Error::new( + borsh::io::ErrorKind::InvalidData, + format!("expected an array with no more than {} items", CAP), + )); + } + } + + Ok(values) + } +} From ac4928e2bc7578099a05b912ffe0dd1c7f6480be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0pa=C4=8Dek?= Date: Thu, 18 Jan 2024 10:15:50 +0100 Subject: [PATCH 2/2] Improve optional support for borsh serialization - Do not allocate when deserializing ArrayString - Serialize length as u32, not as u64, to be consistent with serialization of [T] and str - Add tests --- .github/workflows/ci.yml | 4 +-- src/array_string.rs | 21 ++++++++---- src/arrayvec.rs | 11 ++---- tests/borsh.rs | 73 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 tests/borsh.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56fdb0f..6f8c77b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: matrix: include: - rust: 1.51.0 # MSRV - features: serde + features: serde, borsh experimental: false - rust: stable features: @@ -28,7 +28,7 @@ jobs: features: serde experimental: false - rust: nightly - features: serde, zeroize + features: serde, borsh, zeroize experimental: false steps: diff --git a/src/array_string.rs b/src/array_string.rs index af8439d..4afb560 100644 --- a/src/array_string.rs +++ b/src/array_string.rs @@ -637,13 +637,22 @@ impl borsh::BorshSerialize for ArrayString { /// Requires crate feature `"borsh"` impl borsh::BorshDeserialize for ArrayString { fn deserialize_reader(reader: &mut R) -> borsh::io::Result { - let s = ::deserialize_reader(reader)?; - ArrayString::from(&s).map_err(|_| { - borsh::io::Error::new( + let len = ::deserialize_reader(reader)? as usize; + if len > CAP { + return Err(borsh::io::Error::new( borsh::io::ErrorKind::InvalidData, - format!("expected a string no more than {} bytes long", CAP), - ) - }) + format!("Expected a string no more than {} bytes long", CAP), + )) + } + + let mut buf = [0u8; CAP]; + let buf = &mut buf[..len]; + reader.read_exact(buf)?; + + let s = str::from_utf8(&buf).map_err(|err| { + borsh::io::Error::new(borsh::io::ErrorKind::InvalidData, err.to_string()) + })?; + Ok(Self::from(s).unwrap()) } } diff --git a/src/arrayvec.rs b/src/arrayvec.rs index 39361b8..378b0eb 100644 --- a/src/arrayvec.rs +++ b/src/arrayvec.rs @@ -1306,12 +1306,7 @@ where T: borsh::BorshSerialize, { fn serialize(&self, writer: &mut W) -> borsh::io::Result<()> { - let vs = self.as_slice(); - ::serialize(&vs.len(), writer)?; - for elem in vs { - ::serialize(elem, writer)?; - } - Ok(()) + <[T] as borsh::BorshSerialize>::serialize(self.as_slice(), writer) } } @@ -1323,13 +1318,13 @@ where { fn deserialize_reader(reader: &mut R) -> borsh::io::Result { let mut values = Self::new(); - let len = ::deserialize_reader(reader)?; + let len = ::deserialize_reader(reader)?; for _ in 0..len { let elem = ::deserialize_reader(reader)?; if let Err(_) = values.try_push(elem) { return Err(borsh::io::Error::new( borsh::io::ErrorKind::InvalidData, - format!("expected an array with no more than {} items", CAP), + format!("Expected an array with no more than {} items", CAP), )); } } diff --git a/tests/borsh.rs b/tests/borsh.rs new file mode 100644 index 0000000..f05abcf --- /dev/null +++ b/tests/borsh.rs @@ -0,0 +1,73 @@ +#![cfg(feature = "borsh")] +use std::fmt; +extern crate arrayvec; +extern crate borsh; + +fn assert_ser(v: &T, expected_bytes: &[u8]) { + let mut actual_bytes = Vec::new(); + v.serialize(&mut actual_bytes).unwrap(); + assert_eq!(actual_bytes, expected_bytes); +} + +fn assert_roundtrip(v: &T) { + let mut bytes = Vec::new(); + v.serialize(&mut bytes).unwrap(); + let v_de = T::try_from_slice(&bytes).unwrap(); + assert_eq!(*v, v_de); +} + +mod array_vec { + use arrayvec::ArrayVec; + use super::{assert_ser, assert_roundtrip}; + + #[test] + fn test_empty() { + let vec = ArrayVec::::new(); + assert_ser(&vec, b"\0\0\0\0"); + assert_roundtrip(&vec); + } + + #[test] + fn test_full() { + let mut vec = ArrayVec::::new(); + vec.push(0xdeadbeef); + vec.push(0x123); + vec.push(0x456); + assert_ser(&vec, b"\x03\0\0\0\xef\xbe\xad\xde\x23\x01\0\0\x56\x04\0\0"); + assert_roundtrip(&vec); + } + + #[test] + fn test_with_free_capacity() { + let mut vec = ArrayVec::::new(); + vec.push(0xdeadbeef); + assert_ser(&vec, b"\x01\0\0\0\xef\xbe\xad\xde"); + assert_roundtrip(&vec); + } +} + +mod array_string { + use arrayvec::ArrayString; + use super::{assert_ser, assert_roundtrip}; + + #[test] + fn test_empty() { + let string = ArrayString::<0>::new(); + assert_ser(&string, b"\0\0\0\0"); + assert_roundtrip(&string); + } + + #[test] + fn test_full() { + let string = ArrayString::from_byte_string(b"hello world").unwrap(); + assert_ser(&string, b"\x0b\0\0\0hello world"); + assert_roundtrip(&string); + } + + #[test] + fn test_with_free_capacity() { + let string = ArrayString::<16>::from("hello world").unwrap(); + assert_ser(&string, b"\x0b\0\0\0hello world"); + assert_roundtrip(&string); + } +}