diff --git a/Cargo.toml b/Cargo.toml index 35d8452..736b898 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bipack_ru" -version = "0.3.0" +version = "0.3.1" edition = "2021" license = "Apache-2.0" description = "binary size-effective format used in Divan smart contracts, wasm bindings, network protocols, etc." diff --git a/README.md b/README.md index 1834f5e..fc03487 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ The autodoc documentation is good enough already, so we do not repeat it here no - just ad this package to your dependencies, it is on crates.io. +## Big thanks + +- to https://github.com/jamesmunns for the brilliant postcard format which was used as a model. + # License For compliance with other modules this work is provided under APACHE 2.0 license a copy of which is included in the file `LICENSE`. \ No newline at end of file diff --git a/src/contrail.rs b/src/contrail.rs index fce9605..8295de6 100644 --- a/src/contrail.rs +++ b/src/contrail.rs @@ -18,6 +18,7 @@ pub fn create_contrail(data: &[u8]) -> Vec { result } +#[cfg(test)] mod tests { use crate::contrail::{create_contrail, is_valid_contrail}; diff --git a/src/de.rs b/src/de.rs index 52a49bd..8918508 100644 --- a/src/de.rs +++ b/src/de.rs @@ -260,7 +260,11 @@ impl<'de, 'a> MapAccess<'de> for SimpleMap<'a> { } } +#[cfg(test)] mod tests { + // use std::collections::{HashMap, HashSet}; + // use std::fmt::Debug; + use std::collections::{HashMap, HashSet}; use std::fmt::Debug; diff --git a/src/fixint.rs b/src/fixint.rs new file mode 100644 index 0000000..451b2b7 --- /dev/null +++ b/src/fixint.rs @@ -0,0 +1,189 @@ +//! # Fixed Size Integers +//! +//! In some cases, the use of variably length encoded data may not be +//! preferrable. These modules, for use with `#[serde(with = ...)]` +//! "opt out" of variable length encoding. +//! +//! Support explicitly not provided for `usize` or `isize`, as +//! these types would not be portable between systems of different +//! pointer widths. +//! +//! Although all data in Postcard is typically encoded in little-endian +//! order, these modules provide a choice to the user to encode the data +//! in either little or big endian form, which may be useful for zero-copy +//! applications. + +use serde::{Deserialize, Serialize, Serializer}; + +/// Use with the `#[serde(with = "bipack_ru::fixint::le")]` field attribute. +/// Disables smartint serialization/deserialization for the specified integer +/// field. The integer will always be serialized in the same way as a fixed +/// size array, in **Little Endian** order on the wire. +/// +/// ```rust +/// # use serde::Serialize; +/// #[derive(Serialize)] +/// pub struct DefinitelyLittleEndian { +/// #[serde(with = "bipack_ru::fixint::le")] +/// x: u16, +/// } +/// ``` +pub mod le { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::LE; + + /// Serialize the integer value as a little-endian fixed-size array. + pub fn serialize(val: &T, serializer: S) -> Result + where + S: Serializer, + T: Copy, + LE: Serialize, + { + LE(*val).serialize(serializer) + } + + /// Deserialize the integer value from a little-endian fixed-size array. + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: Deserializer<'de>, + LE: Deserialize<'de>, + { + LE::::deserialize(deserializer).map(|x| x.0) + } +} + +/// Use with the `#[serde(with = "bipack_ru::fixint::be")]` field attribute. +/// Disables smartint serialization/deserialization for the specified integer +/// field. The integer will always be serialized in the same way as a fixed +/// size array, in **Big Endian** order on the wire. +/// +/// ```rust +/// # use serde::Serialize; +/// #[derive(Serialize)] +/// pub struct DefinitelyBigEndian { +/// #[serde(with = "bipack_ru::fixint::be")] +/// x: u16, +/// } +/// ``` +pub mod be { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + use super::BE; + + /// Serialize the integer value as a big-endian fixed-size array. + pub fn serialize(val: &T, serializer: S) -> Result + where + S: Serializer, + T: Copy, + BE: Serialize, + { + BE(*val).serialize(serializer) + } + + /// Deserialize the integer value from a big-endian fixed-size array. + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: Deserializer<'de>, + BE: Deserialize<'de>, + { + BE::::deserialize(deserializer).map(|x| x.0) + } +} + +#[doc(hidden)] +pub struct LE(T); + +#[doc(hidden)] +pub struct BE(T); + +macro_rules! impl_fixint { + ($( $int:ty ),*) => { + $( + impl Serialize for LE<$int> { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.to_le_bytes().serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for LE<$int> { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + <_ as Deserialize>::deserialize(deserializer) + .map(<$int>::from_le_bytes) + .map(Self) + } + } + + impl Serialize for BE<$int> { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.to_be_bytes().serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for BE<$int> { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + <_ as Deserialize>::deserialize(deserializer) + .map(<$int>::from_be_bytes) + .map(Self) + } + } + )* + }; +} + +impl_fixint![i16, i32, i64, i128, u16, u32, u64, u128]; + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + use crate::de::from_bytes; + use crate::ser::to_bytes; + + #[test] + fn test_little_endian() { + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] + pub struct DefinitelyLE { + #[serde(with = "crate::fixint::le")] + x: u16, + } + + let input = DefinitelyLE { x: 0xABCD }; + let serialized = to_bytes(&input).unwrap(); + assert_eq!(serialized, &[0xCD, 0xAB]); + + let deserialized: DefinitelyLE = from_bytes(&serialized).unwrap(); + assert_eq!(deserialized, input); + } + + #[test] + fn test_big_endian() { + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] + pub struct DefinitelyBE { + #[serde(with = "crate::fixint::be")] + x: u16, + } + + let input = DefinitelyBE { x: 0xABCD }; + let serialized = to_bytes(&input).unwrap(); + assert_eq!(serialized, &[0xAB, 0xCD]); + + let deserialized: DefinitelyBE = from_bytes(&serialized).unwrap(); + assert_eq!(deserialized, input); + } +} diff --git a/src/lib.rs b/src/lib.rs index 186a001..6457c38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,11 +118,12 @@ pub mod bipack_source; pub mod bipack_sink; pub mod tools; pub mod bipack; -mod error; -mod ser; -mod de; -mod crc; -mod contrail; +pub mod error; +pub mod ser; +pub mod de; +pub mod crc; +pub mod contrail; +pub mod fixint; #[cfg(test)] mod tests { diff --git a/src/ser.rs b/src/ser.rs index 0bf38ea..45cf01f 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,12 +1,7 @@ -use std::collections::HashMap; -use std::string::FromUtf8Error; - use serde::{ser, Serialize}; use crate::bipack_sink::{BipackSink, IntoU64}; -use crate::bipack_source::{BipackError, BipackSource, SliceSource}; use crate::error::{Error, Result}; -use crate::tools::{to_dump, to_hex}; pub struct Serializer { // This string starts empty and JSON is appended as values are serialized. @@ -159,7 +154,8 @@ impl<'a> ser::Serializer for &'a mut Serializer { } fn serialize_tuple(self, len: usize) -> Result { - self.serialize_seq(Some(len)) + Ok(self) + // self.serialize_seq(Some(len)) } fn serialize_tuple_struct( @@ -342,81 +338,93 @@ impl<'a> ser::SerializeStructVariant for &'a mut Serializer { } } -#[test] -fn test_struct() -> std::result::Result<(), BipackError> { - #[derive(Serialize)] - struct Test { - int: u32, - seq: Vec<&'static str>, +#[cfg(test)] +mod test { + use std::collections::HashMap; + use std::string::FromUtf8Error; + + use serde::Serialize; + + use crate::bipack_source::{BipackError, BipackSource, SliceSource}; + use crate::error; + use crate::ser::to_bytes; + use crate::tools::{to_dump, to_hex}; + + #[test] + fn test_struct() -> std::result::Result<(), BipackError> { + #[derive(Serialize)] + struct Test { + int: u32, + seq: Vec<&'static str>, + } + + let test = Test { + int: 17, + seq: vec!["a", "b"], + }; + let x = to_bytes(&test).unwrap(); + println!("!:\n{}", to_dump(&x)); + // let y = x.clone(); + // let z = x.clone(); + let mut src = SliceSource::from(&x); + assert_eq!(test.int, src.get_unsigned()? as u32); + assert_eq!(test.seq.len(), src.get_unsigned()? as usize); + assert_eq!(test.seq[0], src.get_str()?); + assert_eq!(test.seq[1], src.get_str()?); + Ok(()) } - let test = Test { - int: 17, - seq: vec!["a", "b"], - }; - let x = to_bytes(&test).unwrap(); - println!("!:\n{}", to_dump(&x)); - // let y = x.clone(); - // let z = x.clone(); - let mut src = SliceSource::from(&x); - assert_eq!(test.int, src.get_unsigned()? as u32); - assert_eq!(test.seq.len(), src.get_unsigned()? as usize); - assert_eq!(test.seq[0], src.get_str()?); - assert_eq!(test.seq[1], src.get_str()?); - Ok(()) -} + #[test] + fn test_enum() -> std::result::Result<(), FromUtf8Error> { + #[derive(Serialize)] + enum E { + Unit, + Unit2, + Newtype(u32), + Tuple(u32, u32), + Struct { a: u32 }, + } -#[test] -fn test_enum() -> std::result::Result<(), FromUtf8Error> { - #[derive(Serialize)] - enum E { - Unit, - Unit2, - Newtype(u32), - Tuple(u32, u32), - Struct { a: u32 }, + let u = E::Unit; + println!("u:{}", to_dump(to_bytes(&u).unwrap().as_slice())); + assert_eq!("00", to_hex(to_bytes(&u).unwrap())?); + let u2 = E::Unit2; + println!("u:{}", to_dump(to_bytes(&u2).unwrap().as_slice())); + let nt = E::Newtype(17); + println!("u:{}", to_dump(to_bytes(&nt).unwrap().as_slice())); + assert_eq!("08 44", to_hex(to_bytes(&nt).unwrap())?); + let t = E::Tuple(7, 17); + println!("u:{}", to_dump(to_bytes(&t).unwrap().as_slice())); + assert_eq!("0c 1c 44", to_hex(to_bytes(&t).unwrap())?); + let t = E::Struct { a: 17 }; + println!("u:{}", to_dump(to_bytes(&t).unwrap().as_slice())); + assert_eq!("10 44", to_hex(to_bytes(&t).unwrap())?); + // let expected = r#""Unit""#; + // assert_eq!(to_string(&u).unwrap(), expected); + // + // let n = E::Newtype(1); + // let expected = r#"{"Newtype":1}"#; + // assert_eq!(to_string(&n).unwrap(), expected); + // + // let t = E::Tuple(1, 2); + // let expected = r#"{"Tuple":[1,2]}"#; + // assert_eq!(to_string(&t).unwrap(), expected); + // + // let s = E::Struct { a: 1 }; + // let expected = r#"{"Struct":{"a":1}}"#; + // assert_eq!(to_string(&s).unwrap(), expected); + Ok(()) } - let u = E::Unit; - println!("u:{}",to_dump(to_bytes(&u).unwrap().as_slice())); - assert_eq!("00",to_hex(to_bytes(&u).unwrap())?); - let u2 = E::Unit2; - println!("u:{}",to_dump(to_bytes(&u2).unwrap().as_slice())); - let nt = E::Newtype(17); - println!("u:{}",to_dump(to_bytes(&nt).unwrap().as_slice())); - assert_eq!("08 44",to_hex(to_bytes(&nt).unwrap())?); - let t = E::Tuple(7,17); - println!("u:{}",to_dump(to_bytes(&t).unwrap().as_slice())); - assert_eq!("0c 1c 44",to_hex(to_bytes(&t).unwrap())?); - let t = E::Struct { a: 17 }; - println!("u:{}",to_dump(to_bytes(&t).unwrap().as_slice())); - assert_eq!("10 44",to_hex(to_bytes(&t).unwrap())?); - // let expected = r#""Unit""#; - // assert_eq!(to_string(&u).unwrap(), expected); - // - // let n = E::Newtype(1); - // let expected = r#"{"Newtype":1}"#; - // assert_eq!(to_string(&n).unwrap(), expected); - // - // let t = E::Tuple(1, 2); - // let expected = r#"{"Tuple":[1,2]}"#; - // assert_eq!(to_string(&t).unwrap(), expected); - // - // let s = E::Struct { a: 1 }; - // let expected = r#"{"Struct":{"a":1}}"#; - // assert_eq!(to_string(&s).unwrap(), expected); - Ok(()) -} - -#[test] -fn test_map() -> Result<()> { - - let mut src = HashMap::new(); - src.insert("foo", 1); - src.insert("foo", 42); - src.insert("bar", 1); - src.insert("baz", 17); - let packed = to_bytes(&src)?; - println!("{}", to_dump(&packed)); - Ok(()) + #[test] + fn test_map() -> error::Result<()> { + let mut src = HashMap::new(); + src.insert("foo", 1); + src.insert("foo", 42); + src.insert("bar", 1); + src.insert("baz", 17); + let packed = to_bytes(&src)?; + println!("{}", to_dump(&packed)); + Ok(()) + } } \ No newline at end of file