From d053c5b6bf235626842f02fa005422772c16e9f4 Mon Sep 17 00:00:00 2001 From: debris Date: Tue, 23 Jan 2018 14:25:15 +0100 Subject: [PATCH] add default serde serialization to ethereum-types --- ethbloom/Cargo.toml | 7 ++- ethbloom/src/lib.rs | 22 ++++++++ ethereum-types/Cargo.toml | 7 ++- ethereum-types/src/hash.rs | 34 +++++++++++ ethereum-types/src/lib.rs | 5 ++ ethereum-types/src/uint.rs | 31 ++++++++++ fixed-hash/Cargo.toml | 2 +- serialize/Cargo.toml | 8 +++ serialize/src/lib.rs | 112 +++++++++++++++++++++++++++++++++++++ tests/Cargo.toml | 2 +- 10 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 serialize/Cargo.toml create mode 100644 serialize/src/lib.rs diff --git a/ethbloom/Cargo.toml b/ethbloom/Cargo.toml index 2f88ee2..882b2f0 100644 --- a/ethbloom/Cargo.toml +++ b/ethbloom/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ethbloom" version = "0.3.0" -authors = ["debris "] +authors = ["Parity Technologies "] description = "Ethereum bloom filter" license = "MIT" documentation = "https://docs.rs/ethbloom" @@ -12,12 +12,15 @@ repository = "https://github.com/debris/ethbloom" tiny-keccak = "1.4" crunchy = { version = "0.1.6", features = ["limit_256"] } fixed-hash = { version = "0.1.1", path = "../fixed-hash" } +ethereum-types-serialize = { version = "0.1", path = "../serialize", optional = true } +serde = { version = "1.0", optional = true } [dev-dependencies] rand = "0.4" rustc-hex = "1.0" [features] -default = ["std", "heapsizeof"] +default = ["std", "heapsizeof", "serialize"] std = ["fixed-hash/std"] heapsizeof = ["fixed-hash/heapsizeof"] +serialize = ["std", "ethereum-types-serialize", "serde"] diff --git a/ethbloom/src/lib.rs b/ethbloom/src/lib.rs index e86d2ef..d765934 100644 --- a/ethbloom/src/lib.rs +++ b/ethbloom/src/lib.rs @@ -40,6 +40,15 @@ extern crate crunchy; #[macro_use] extern crate fixed_hash; +#[cfg(feature="serialize")] +extern crate ethereum_types_serialize; + +#[cfg(feature="serialize")] +extern crate serde; + +#[cfg(feature="serialize")] +use serde::{Serialize, Serializer, Deserialize, Deserializer}; + use core::{ops, mem, str}; use tiny_keccak::keccak256; @@ -224,6 +233,19 @@ impl<'a> From<&'a Bloom> for BloomRef<'a> { } } +impl Serialize for Bloom { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + ethereum_types_serialize::serialize(&self.0, serializer) + } +} + +impl<'de> Deserialize<'de> for Bloom { + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + ethereum_types_serialize::deserialize_check_len(deserializer, ethereum_types_serialize::ExpectedLen::Exact(256)) + .map(|x| (&*x).into()) + } +} + #[cfg(test)] mod tests { extern crate rustc_hex; diff --git a/ethereum-types/Cargo.toml b/ethereum-types/Cargo.toml index bc54e89..18583c4 100644 --- a/ethereum-types/Cargo.toml +++ b/ethereum-types/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ethereum-types" version = "0.1.4" -authors = ["debris "] +authors = ["Parity Technologies "] license = "MIT" homepage = "https://github.com/paritytech/primitives" description = "Ethereum types" @@ -14,8 +14,11 @@ uint = { path = "../uint", version = "0.1" } fixed-hash = { path = "../fixed-hash", version = "0.1.1" } ethbloom = { path = "../ethbloom", version = "0.3" } crunchy = "0.1.5" +ethereum-types-serialize = { version = "0.1", path = "../serialize", optional = true } +serde = { version = "1.0", optional = true } [features] -default = ["std", "heapsizeof"] +default = ["std", "heapsizeof", "serialize"] std = ["uint/std", "fixed-hash/std", "ethbloom/std"] heapsizeof = ["uint/heapsizeof", "fixed-hash/heapsizeof", "ethbloom/heapsizeof"] +serialize = ["std", "ethereum-types-serialize", "serde", "ethbloom/serialize"] diff --git a/ethereum-types/src/hash.rs b/ethereum-types/src/hash.rs index 4b13a1c..e8bba04 100644 --- a/ethereum-types/src/hash.rs +++ b/ethereum-types/src/hash.rs @@ -1,5 +1,11 @@ use U256; +#[cfg(feature="serialize")] +use serde::{Serialize, Serializer, Deserialize, Deserializer}; + +#[cfg(feature="serialize")] +use ethereum_types_serialize; + construct_hash!(H32, 4); construct_hash!(H64, 8); construct_hash!(H128, 16); @@ -70,3 +76,31 @@ impl<'a> From<&'a H160> for H256 { } } +macro_rules! impl_serde { + ($name: ident, $len: expr) => { + #[cfg(feature="serialize")] + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + ethereum_types_serialize::serialize(&self.0, serializer) + } + } + + #[cfg(feature="serialize")] + impl<'de> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + ethereum_types_serialize::deserialize_check_len(deserializer, ethereum_types_serialize::ExpectedLen::Exact($len)) + .map(|x| (&*x).into()) + } + } + } +} + +impl_serde!(H32, 4); +impl_serde!(H64, 8); +impl_serde!(H128, 16); +impl_serde!(H160, 20); +impl_serde!(H256, 32); +impl_serde!(H264, 33); +impl_serde!(H512, 64); +impl_serde!(H520, 65); +impl_serde!(H1024, 128); diff --git a/ethereum-types/src/lib.rs b/ethereum-types/src/lib.rs index 897e5a2..55fdeec 100644 --- a/ethereum-types/src/lib.rs +++ b/ethereum-types/src/lib.rs @@ -12,6 +12,11 @@ extern crate uint as uint_crate; extern crate fixed_hash; extern crate ethbloom; +#[cfg(feature="serialize")] +extern crate ethereum_types_serialize; +#[cfg(feature="serialize")] +extern crate serde; + mod hash; mod uint; diff --git a/ethereum-types/src/uint.rs b/ethereum-types/src/uint.rs index 6b12c8a..af0809c 100644 --- a/ethereum-types/src/uint.rs +++ b/ethereum-types/src/uint.rs @@ -1,3 +1,9 @@ +#[cfg(feature="serialize")] +use serde::{Serialize, Serializer, Deserialize, Deserializer}; + +#[cfg(feature="serialize")] +use ethereum_types_serialize; + construct_uint!(U128, 2); construct_uint!(U256, 4); construct_uint!(U512, 8); @@ -322,3 +328,28 @@ impl From for [u8; 64] { arr } } + +macro_rules! impl_serde { + ($name: ident, $len: expr) => { + #[cfg(feature="serialize")] + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + let mut bytes = [0u8; $len * 8]; + self.to_big_endian(&mut bytes); + ethereum_types_serialize::serialize_uint(&bytes, serializer) + } + } + + #[cfg(feature="serialize")] + impl<'de> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + ethereum_types_serialize::deserialize_check_len(deserializer, ethereum_types_serialize::ExpectedLen::Between(0, $len * 8)) + .map(|x| (&*x).into()) + } + } + } +} + +impl_serde!(U128, 2); +impl_serde!(U256, 4); +impl_serde!(U512, 8); diff --git a/fixed-hash/Cargo.toml b/fixed-hash/Cargo.toml index 35c9766..a218d35 100644 --- a/fixed-hash/Cargo.toml +++ b/fixed-hash/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "fixed-hash" version = "0.1.2" -authors = ["debris "] +authors = ["Parity Technologies "] license = "MIT" homepage = "https://github.com/paritytech/primitives" description = "Fixed-size hashes" diff --git a/serialize/Cargo.toml b/serialize/Cargo.toml new file mode 100644 index 0000000..7d9ce5c --- /dev/null +++ b/serialize/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ethereum-types-serialize" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +serde = "1.0" +rustc-hex = "1.0" diff --git a/serialize/src/lib.rs b/serialize/src/lib.rs new file mode 100644 index 0000000..b4580b6 --- /dev/null +++ b/serialize/src/lib.rs @@ -0,0 +1,112 @@ +extern crate serde; +extern crate rustc_hex; + +use std::fmt; + +use serde::{de, Serializer, Deserializer}; +use rustc_hex::{ToHex, FromHex}; + +/// Serializes a slice of bytes. +pub fn serialize(bytes: &[u8], serializer: S) -> Result where + S: Serializer, +{ + let hex = ToHex::to_hex(bytes); + serializer.serialize_str(&format!("0x{}", hex)) +} + +/// Serialize a slice of bytes as uint. +/// +/// The representation will have all leading zeros trimmed. +pub fn serialize_uint(bytes: &[u8], serializer: S) -> Result where + S: Serializer, +{ + let non_zero = bytes.iter().take_while(|b| **b == 0).count(); + let bytes = &bytes[non_zero..]; + if bytes.is_empty() { + return serializer.serialize_str("0x0"); + } + + let hex = ToHex::to_hex(bytes); + let has_leading_zero = !hex.is_empty() && &hex[0..1] == "0"; + serializer.serialize_str( + &format!("0x{}", if has_leading_zero { &hex[1..] } else { &hex }) + ) +} + +/// Expected length of bytes vector. +#[derive(Debug, PartialEq, Eq)] +pub enum ExpectedLen { + /// Any length in bytes. + Any, + /// Exact length in bytes. + Exact(usize), + /// A bytes length between (min; max]. + Between(usize, usize), +} + +impl fmt::Display for ExpectedLen { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + ExpectedLen::Any => write!(fmt, "even length"), + ExpectedLen::Exact(v) => write!(fmt, "length of {}", v * 2), + ExpectedLen::Between(min, max) => write!(fmt, "length between ({}; {}]", min * 2, max * 2), + } + } +} + +/// Deserialize into vector of bytes. +pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where + D: Deserializer<'de>, +{ + deserialize_check_len(deserializer, ExpectedLen::Any) +} + +/// Deserialize into vector of bytes with additional size check. +pub fn deserialize_check_len<'de, D>(deserializer: D, len: ExpectedLen) -> Result, D::Error> where + D: Deserializer<'de>, +{ + struct Visitor { + len: ExpectedLen, + } + + impl<'a> de::Visitor<'a> for Visitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a 0x-prefixed hex string with {}", self.len) + } + + fn visit_str(self, v: &str) -> Result { + if v.len() < 2 || &v[0..2] != "0x" { + return Err(E::custom("prefix is missing")) + } + + let is_len_valid = match self.len { + // just make sure that we have all nibbles + ExpectedLen::Any => v.len() % 2 == 0, + ExpectedLen::Exact(len) => v.len() == 2 * len + 2, + ExpectedLen::Between(min, max) => v.len() <= 2 * max + 2 && v.len() > 2 * min + 2, + }; + + if !is_len_valid { + return Err(E::invalid_length(v.len() - 2, &self)) + } + + let bytes = match self.len { + ExpectedLen::Between(..) if v.len() % 2 != 0 => { + FromHex::from_hex(&*format!("0{}", &v[2..])) + }, + _ => FromHex::from_hex(&v[2..]) + }; + + bytes.map_err(|e| E::custom(&format!("invalid hex value: {:?}", e))) + } + + fn visit_string(self, v: String) -> Result { + self.visit_str(&v) + } + } + // TODO [ToDr] Use raw bytes if we switch to RLP / binencoding + // (visit_bytes, visit_bytes_buf) + deserializer.deserialize_str(Visitor { len }) +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4fe2043..e7b2b21 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tests" version = "0.1.0" -authors = ["debris "] +authors = ["Parity Technologies "] [build-dependencies] rustc_version = "0.2"