From ebb9bcd056fb7769082d2b419af05d2e85865cc7 Mon Sep 17 00:00:00 2001 From: Boog900 Date: Wed, 17 Jan 2024 00:42:28 +0000 Subject: [PATCH] add `container_as_blob` (#63) * add `container_as_blob`. equivalent to `KV_SERIALIZE_CONTAINER_POD_AS_BLOB` * clippy & fmt & update changelog/ readme * update readme and remove `container_as_blob` from default features * move position in changelog --- CHANGELOG.md | 4 ++ Cargo.toml | 9 +++ README.md | 5 ++ src/container_as_blob.rs | 150 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 5 files changed, 170 insertions(+) create mode 100644 src/container_as_blob.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b74877..9d5e658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `container_as_blob` ([#63](https://github.com/monero-rs/monero-epee-bin-serde/pull/63)) + ### Changed - Allow optional values ([#59](https://github.com/monero-rs/monero-epee-bin-serde/pull/59)) diff --git a/Cargo.toml b/Cargo.toml index 5fb71a1..792b51b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,22 @@ repository = "https://github.com/monero-rs/monero-epee-bin-serde" rust-version = "1.63.0" description = "A serde format for the binary encoding defined within the epee helper library of Monero." +[features] +default = [] +# Enables use of the `container_as_blob` #[serde(with = )] which is equivelent to: +# KV_SERIALIZE_CONTAINER_POD_AS_BLOB in monero +container_as_blob = ["dep:serde_bytes"] + [dependencies] byteorder = "1" serde = "1" +serde_bytes = { version = "0.11", optional = true } + [dev-dependencies] hex = "0.4" hex-literal = "0.4" monero = "0.19" +rand = "0.8" serde = { version = "1", features = ["derive"] } serde_with = "3" diff --git a/README.md b/README.md index f85eef8..0841093 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ This crate implements the binary encoding defined in the `epee` helper library o [0]: https://github.com/monero-project/monero/blob/0a1ddc2eff854f3e932203a95b65a9f1efd60eef/contrib/epee/include/storages/portable_storage_from_bin.h [1]: https://github.com/monero-project/monero/blob/0a1ddc2eff854f3e932203a95b65a9f1efd60eef/contrib/epee/include/storages/portable_storage_to_bin.h +## Regressions + +Due to limitations of `serde` and weirdness in `epee` you need to wrap sequences like `Vec` in `#[serde(default = "Vec::new")]` and +optionally you can add `#[serde(skip_serializing_if = "Vec::is_empty")]` as `epee` does not serialize empty sequences. + ## License Licensed under either of diff --git a/src/container_as_blob.rs b/src/container_as_blob.rs new file mode 100644 index 0000000..5545051 --- /dev/null +++ b/src/container_as_blob.rs @@ -0,0 +1,150 @@ +use serde::{de::Error as DeError, Deserialize, Deserializer, Serializer}; +use serde_bytes::ByteBuf; + +pub trait ContainerAble { + const SIZE: usize; + + /// Returns `Self` from bytes. + /// + /// `bytes` is guaranteed to be [`Self::SIZE`] long. + fn from_bytes(bytes: &[u8]) -> Self; + + fn push_bytes(&self, buf: &mut Vec); +} + +pub fn deserialize<'de, D, T>(d: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: ContainerAble, +{ + let bytes: ByteBuf = Deserialize::deserialize(d)?; + + if bytes.len() % T::SIZE != 0 { + return Err(DeError::invalid_length( + bytes.len(), + &"A number divisible by the fields size.", + )); + } + + let ret = bytes + .windows(T::SIZE) + .step_by(T::SIZE) + .map(T::from_bytes) + .collect(); + Ok(ret) +} + +pub fn serialize(t: &[T], s: S) -> Result +where + S: Serializer, + T: ContainerAble, +{ + let mut bytes = Vec::with_capacity(t.len() * T::SIZE); + t.iter().for_each(|tt| tt.push_bytes(&mut bytes)); + + s.serialize_bytes(&bytes) +} + +impl ContainerAble for [u8; N] { + const SIZE: usize = N; + + fn from_bytes(bytes: &[u8]) -> Self { + bytes.try_into().unwrap() + } + + fn push_bytes(&self, buf: &mut Vec) { + buf.extend_from_slice(self) + } +} + +macro_rules! int_container_able { + ($int:ty ) => { + impl ContainerAble for $int { + const SIZE: usize = std::mem::size_of::<$int>(); + + fn from_bytes(bytes: &[u8]) -> Self { + <$int>::from_le_bytes(bytes.try_into().unwrap()) + } + + fn push_bytes(&self, buf: &mut Vec) { + buf.extend_from_slice(&self.to_le_bytes()) + } + } + }; +} + +int_container_able!(u16); +int_container_able!(u32); +int_container_able!(u64); +int_container_able!(u128); + +int_container_able!(i8); +int_container_able!(i16); +int_container_able!(i32); +int_container_able!(i64); +int_container_able!(i128); + +#[cfg(test)] +mod tests { + use rand::random; + use serde::{Deserialize, Serialize}; + + use crate::{container_as_blob, from_bytes, to_bytes}; + + #[test] + fn ser_deser() { + macro_rules! create_test_struct { + ($($typ:ident,)+; $($name: ident: $ty:tt,)+ ) => { + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct Test { + $( + #[serde(with = "container_as_blob")] + #[serde(default = "Vec::new")] + #[serde(skip_serializing_if = "Vec::is_empty")] + $typ: Vec<$typ>, + )+ + $( + #[serde(with = "container_as_blob")] + #[serde(default = "Vec::new")] + #[serde(skip_serializing_if = "Vec::is_empty")] + $name: Vec<$ty>, + )+ + } + + impl Test { + fn random() -> Test { + Test { + $($typ: (0_u8.. random()).map(|_| random()).collect(),)+ + $($name: (0_u8.. random()).map(|_| random()).collect(),)+ + } + } + } + + } + } + + create_test_struct!( + u16, + u32, + u64, + u128, + + i8, + i16, + i32, + i64, + i128,; + + arr_32: [u8; 32], + arr_64: [u8; 16], + ); + + let t = Test::random(); + + let bytes = to_bytes(&t).unwrap(); + + let tt = from_bytes(bytes).unwrap(); + + assert_eq!(t, tt); + } +} diff --git a/src/lib.rs b/src/lib.rs index 451129b..9331bd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ #![forbid(unsafe_code)] +#[cfg(feature = "container_as_blob")] +pub mod container_as_blob; mod de; mod error; mod ser;