Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ethbloom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion ethereum-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion primitive-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
2 changes: 1 addition & 1 deletion primitive-types/impls/serde/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "impl-serde"
version = "0.2.3"
version = "0.3.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0/MIT"
Expand Down
123 changes: 85 additions & 38 deletions primitive-types/impls/serde/src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,32 @@ 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';
Expand All @@ -36,6 +61,48 @@ 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<Vec<u8>, String> {
Comment thread
NikVolf marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you were against String error types :P

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. I just simply extracted the method, but haven't thought of proper error type here. Let me do a follow up PR on this :)

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<S>(slice: &mut [u8], bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
Expand All @@ -44,7 +111,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))
}
}

Expand All @@ -69,7 +136,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))
}
}

Expand Down Expand Up @@ -107,41 +174,7 @@ where
}

fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
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<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
Expand Down Expand Up @@ -229,6 +262,7 @@ where

#[cfg(test)]
mod tests {
use super::*;
extern crate serde_derive;

use self::serde_derive::{Deserialize, Serialize};
Expand Down Expand Up @@ -278,4 +312,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]));
}
}