From dc8dde381c0ee8b57afe2091dca392756909c944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 2 Jan 2020 17:25:40 +0100 Subject: [PATCH 1/3] Expose to and from hex. --- primitive-types/impls/serde/src/serialize.rs | 125 +++++++++++++------ 1 file changed, 87 insertions(+), 38 deletions(-) diff --git a/primitive-types/impls/serde/src/serialize.rs b/primitive-types/impls/serde/src/serialize.rs index 59d117fd0..a1fbe4cc1 100644 --- a/primitive-types/impls/serde/src/serialize.rs +++ b/primitive-types/impls/serde/src/serialize.rs @@ -11,7 +11,33 @@ use std::fmt; static CHARS: &[u8] = b"0123456789abcdef"; -fn to_hex<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { +/// Serialize given bytes to a 0x-prefixed hex string. +/// +/// If `skip_leading_zero` initial 0s will not be printed out, +/// unless the byte string is empty, in which case `0x0` will be returned. +/// The results are consistent with `serialize_uint` output if the flag is +/// on and `serialize_raw` if the flag is off. +pub fn to_hex(bytes: &[u8], skip_leading_zero: bool) -> String { + let bytes = if skip_leading_zero { + let non_zero = bytes.iter().take_while(|b| **b == 0).count(); + let bytes = &bytes[non_zero..]; + if bytes.is_empty() { + return "0x0".into() + } else { + bytes + } + } else if bytes.is_empty() { + return "0x".into() + } else { + bytes + }; + + let mut slice = vec![0u8; (bytes.len() + 1) * 2]; + to_hex_raw(&mut slice, bytes, skip_leading_zero) + .into() +} + +fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { assert!(v.len() > 1 + bytes.len() * 2); v[0] = b'0'; @@ -36,6 +62,49 @@ fn to_hex<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str unsafe { std::str::from_utf8_unchecked(&v[0..idx]) } } +/// Decode given hex string into a vector of bytes. +/// +/// Returns an error if the string is not prefixed with `0x` +/// or non-hex characters are present. +pub fn from_hex(v: &str) -> Result, String> { + if !v.starts_with("0x") { + return Err("0x prefix is missing".into()); + } + + let bytes_len = v.len() - 2; + let mut modulus = bytes_len % 2; + let mut bytes = vec![0u8; (bytes_len + 1) / 2]; + let mut buf = 0; + let mut pos = 0; + for (idx, byte) in v.bytes().enumerate().skip(2) { + buf <<= 4; + + match byte { + b'A'..=b'F' => buf |= byte - b'A' + 10, + b'a'..=b'f' => buf |= byte - b'a' + 10, + b'0'..=b'9' => buf |= byte - b'0', + b' ' | b'\r' | b'\n' | b'\t' => { + buf >>= 4; + continue; + } + b => { + let ch = char::from(b); + return Err(format!("invalid hex character: {}, at {}", ch, idx)); + } + } + + modulus += 1; + if modulus == 2 { + modulus = 0; + bytes[pos] = buf; + pos += 1; + } + } + + Ok(bytes) +} + + /// Serializes a slice of bytes. pub fn serialize_raw(slice: &mut [u8], bytes: &[u8], serializer: S) -> Result where @@ -44,7 +113,7 @@ where if bytes.is_empty() { serializer.serialize_str("0x") } else { - serializer.serialize_str(to_hex(slice, bytes, false)) + serializer.serialize_str(to_hex_raw(slice, bytes, false)) } } @@ -69,7 +138,7 @@ where if bytes.is_empty() { serializer.serialize_str("0x0") } else { - serializer.serialize_str(to_hex(slice, bytes, true)) + serializer.serialize_str(to_hex_raw(slice, bytes, true)) } } @@ -107,41 +176,7 @@ where } fn visit_str(self, v: &str) -> Result { - if !v.starts_with("0x") { - return Err(E::custom("prefix is missing")); - } - - let bytes_len = v.len() - 2; - let mut modulus = bytes_len % 2; - let mut bytes = vec![0u8; (bytes_len + 1) / 2]; - let mut buf = 0; - let mut pos = 0; - for (idx, byte) in v.bytes().enumerate().skip(2) { - buf <<= 4; - - match byte { - b'A'..=b'F' => buf |= byte - b'A' + 10, - b'a'..=b'f' => buf |= byte - b'a' + 10, - b'0'..=b'9' => buf |= byte - b'0', - b' ' | b'\r' | b'\n' | b'\t' => { - buf >>= 4; - continue; - } - b => { - let ch = char::from(b); - return Err(E::custom(&format!("invalid hex character: {}, at {}", ch, idx))); - } - } - - modulus += 1; - if modulus == 2 { - modulus = 0; - bytes[pos] = buf; - pos += 1; - } - } - - Ok(bytes) + from_hex(v).map_err(E::custom) } fn visit_string(self, v: String) -> Result { @@ -229,6 +264,7 @@ where #[cfg(test)] mod tests { + use super::*; extern crate serde_derive; use self::serde_derive::{Deserialize, Serialize}; @@ -278,4 +314,17 @@ mod tests { let deserialized: Bytes = serde_json::from_str(&data).unwrap(); assert!(deserialized.0.is_empty()) } + + #[test] + fn should_encode_to_and_from_hex() { + assert_eq!(to_hex(&[0, 1, 2], true), "0x102"); + assert_eq!(to_hex(&[0, 1, 2], false), "0x000102"); + assert_eq!(to_hex(&[0], true), "0x0"); + assert_eq!(to_hex(&[], true), "0x0"); + assert_eq!(to_hex(&[], false), "0x"); + assert_eq!(to_hex(&[0], false), "0x00"); + assert_eq!(from_hex("0x0102"), Ok(vec![1, 2])); + assert_eq!(from_hex("0x102"), Ok(vec![1, 2])); + assert_eq!(from_hex("0xf"), Ok(vec![0xf])); + } } From 65dce99f95d21e582239149455dc5261e7c06a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 2 Jan 2020 17:25:59 +0100 Subject: [PATCH 2/3] Bump minor version. --- ethbloom/Cargo.toml | 2 +- ethereum-types/Cargo.toml | 2 +- primitive-types/Cargo.toml | 2 +- primitive-types/impls/serde/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ethbloom/Cargo.toml b/ethbloom/Cargo.toml index 56a092776..952f043c3 100644 --- a/ethbloom/Cargo.toml +++ b/ethbloom/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" tiny-keccak = { version = "2.0", features = ["keccak"] } crunchy = { version = "0.2.2", default-features = false, features = ["limit_256"] } fixed-hash = { path = "../fixed-hash", version = "0.5", default-features = false } -impl-serde = { path = "../primitive-types/impls/serde", version = "0.2", default-features = false, optional = true } +impl-serde = { path = "../primitive-types/impls/serde", version = "0.3", default-features = false, optional = true } impl-rlp = { path = "../primitive-types/impls/rlp", version = "0.2", default-features = false } [dev-dependencies] diff --git a/ethereum-types/Cargo.toml b/ethereum-types/Cargo.toml index eb1a85e20..8153ea823 100644 --- a/ethereum-types/Cargo.toml +++ b/ethereum-types/Cargo.toml @@ -12,7 +12,7 @@ ethbloom = { path = "../ethbloom", version = "0.8", default-features = false } fixed-hash = { path = "../fixed-hash", version = "0.5", default-features = false, features = ["byteorder", "rustc-hex"] } uint-crate = { path = "../uint", package = "uint", version = "0.8", default-features = false } primitive-types = { path = "../primitive-types", version = "0.6", features = ["rlp", "byteorder", "rustc-hex"], default-features = false } -impl-serde = { path = "../primitive-types/impls/serde", version = "0.2", default-features = false, optional = true } +impl-serde = { path = "../primitive-types/impls/serde", version = "0.3.0", default-features = false, optional = true } impl-rlp = { path = "../primitive-types/impls/rlp", version = "0.2", default-features = false } [dev-dependencies] diff --git a/primitive-types/Cargo.toml b/primitive-types/Cargo.toml index b289a67c1..1ea39518c 100644 --- a/primitive-types/Cargo.toml +++ b/primitive-types/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies] fixed-hash = { version = "0.5", path = "../fixed-hash", default-features = false } uint = { version = "0.8.1", path = "../uint", default-features = false } -impl-serde = { version = "0.2.1", path = "impls/serde", default-features = false, optional = true } +impl-serde = { version = "0.3.0", path = "impls/serde", default-features = false, optional = true } impl-codec = { version = "0.4.1", path = "impls/codec", default-features = false, optional = true } impl-rlp = { version = "0.2", path = "impls/rlp", default-features = false, optional = true } diff --git a/primitive-types/impls/serde/Cargo.toml b/primitive-types/impls/serde/Cargo.toml index dc01bc6ab..a57ada2a2 100644 --- a/primitive-types/impls/serde/Cargo.toml +++ b/primitive-types/impls/serde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "impl-serde" -version = "0.2.3" +version = "0.3.0" authors = ["Parity Technologies "] edition = "2018" license = "Apache-2.0/MIT" From 24a392d6a4d285611bfd3a40dec2316836844fd5 Mon Sep 17 00:00:00 2001 From: Andronik Ordian Date: Fri, 3 Jan 2020 09:24:29 +0300 Subject: [PATCH 3/3] impl-serde: cargo fmt --- primitive-types/impls/serde/src/serialize.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/primitive-types/impls/serde/src/serialize.rs b/primitive-types/impls/serde/src/serialize.rs index a1fbe4cc1..d632652f1 100644 --- a/primitive-types/impls/serde/src/serialize.rs +++ b/primitive-types/impls/serde/src/serialize.rs @@ -22,19 +22,18 @@ pub fn to_hex(bytes: &[u8], skip_leading_zero: bool) -> String { let non_zero = bytes.iter().take_while(|b| **b == 0).count(); let bytes = &bytes[non_zero..]; if bytes.is_empty() { - return "0x0".into() + return "0x0".into(); } else { bytes } } else if bytes.is_empty() { - return "0x".into() + return "0x".into(); } else { bytes }; let mut slice = vec![0u8; (bytes.len() + 1) * 2]; - to_hex_raw(&mut slice, bytes, skip_leading_zero) - .into() + to_hex_raw(&mut slice, bytes, skip_leading_zero).into() } fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { @@ -104,7 +103,6 @@ pub fn from_hex(v: &str) -> Result, String> { Ok(bytes) } - /// Serializes a slice of bytes. pub fn serialize_raw(slice: &mut [u8], bytes: &[u8], serializer: S) -> Result where