Skip to content

Commit

Permalink
add container_as_blob (#63)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Boog900 authored Jan 17, 2024
1 parent 57cdb13 commit ebb9bcd
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
150 changes: 150 additions & 0 deletions src/container_as_blob.rs
Original file line number Diff line number Diff line change
@@ -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<u8>);
}

pub fn deserialize<'de, D, T>(d: D) -> Result<Vec<T>, 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<S, T>(t: &[T], s: S) -> Result<S::Ok, S::Error>
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<const N: usize> 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<u8>) {
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<u8>) {
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);
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#![forbid(unsafe_code)]

#[cfg(feature = "container_as_blob")]
pub mod container_as_blob;
mod de;
mod error;
mod ser;
Expand Down

0 comments on commit ebb9bcd

Please sign in to comment.