Skip to content

Commit

Permalink
Merge 95191f9 into 2074891
Browse files Browse the repository at this point in the history
  • Loading branch information
Benno committed Aug 5, 2022
2 parents 2074891 + 95191f9 commit 31a7b16
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 43 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -7,7 +7,7 @@ XorName is an array that is useful for calculations in DHT

## Serialization

`XorName` and `Prefix` can be serialized into a human-readable hex string, instead of as a `u8` array. To enable this, activate the `serialize-hex` feature. This also allows for these structures to be serialised when used as a key in a map like `HashMap`, because most formats only allow keys to be strings, instead of more complex types.
`XorName` and `Prefix` are serialized into a human-readable hex string, instead of as a `u8` array. This is enabled by default, with the `serialize-hex` feature. This also allows for these structures to be serialised when used as a key in a map like `HashMap`, because most formats only allow keys to be strings, instead of more complex types.

A struct like this:
```rust
Expand All @@ -21,8 +21,8 @@ struct MyStruct {
Will yield this JSON
```json
{
"prefix": "8a817b6d791f4b00000000000000000000000000000000000000000000000000/56",
"xor_name": "8a817b6d791f4bae4117ac7ae15a88cd2c62fba0b040972ce885f1a47625dea1"
"prefix": "10001101110001111100101000111001101101111101111010011001",
"xor_name": "8dc7ca39b7de990eb943fd64854776dd85aa82c33a4269693c57b36e0749ed8f"
}
```

Expand Down
52 changes: 42 additions & 10 deletions src/prefix.rs
Expand Up @@ -283,32 +283,51 @@ impl Debug for Prefix {
}
}

/// Format `Prefix` as bit string, e.g. `"010"` with a [`Prefix::bit_count`] of `3`.
impl Display for Prefix {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Use `Binary` impl from `XorName` with restricted width
write!(f, "{:width$b}", self.name, width = self.bit_count as usize)
}
}

#[derive(Debug)]
pub struct FromStrError {
pub invalid_char: char,
pub enum FromStrError {
InvalidChar(char),
TooLong(usize),
}

impl Display for FromStrError {
fn fmt(&self, formatter: &mut Formatter) -> FmtResult {
write!(
formatter,
"'{}' not allowed - the string must represent a binary number.",
self.invalid_char
)
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
FromStrError::InvalidChar(c) => {
write!(f, "expected `0` or `1`, but encountered `{}`", c)
}
FromStrError::TooLong(l) => {
write!(
f,
"max length exceeded {} with length of {l}",
XOR_NAME_LEN * 8
)
}
}
}
}

impl FromStr for Prefix {
type Err = FromStrError;

fn from_str(bits: &str) -> Result<Self, Self::Err> {
if bits.len() > XOR_NAME_LEN * 8 {
return Err(FromStrError::TooLong(bits.len()));
}
let mut name = [0; XOR_NAME_LEN];
for (i, bit) in bits.chars().enumerate() {
if bit == '1' {
let byte = i / 8;
name[byte] |= 1 << (7 - i);
name[byte] |= 1 << (7 - (i % 8));
} else if bit != '0' {
return Err(FromStrError { invalid_char: bit });
return Err(FromStrError::InvalidChar(bit));
}
}
Ok(Self::new(bits.len(), XorName(name)))
Expand Down Expand Up @@ -432,6 +451,19 @@ mod tests {
assert_eq!(&format!(2, "{:b}", parse("10")), "10");
assert_eq!(&format!(2, "{:b}", parse("11")), "11");
assert_eq!(&format!(7, "{:b}", parse("1100101")), "1100101");

// Bit string with 257 width
assert!(Prefix::from_str(&"1".repeat(XOR_NAME_LEN * 8 + 1)).is_err());
}

#[test]
fn format_parse_roundtrip() {
let format_parse_eq = |p| p == parse(&std::format!("{}", p));

assert!(format_parse_eq(Prefix::new(0, XorName([0xBB; 32]))));
assert!(format_parse_eq(Prefix::new(256, XorName([0x33; 32]))));
assert!(format_parse_eq(Prefix::new(5, XorName([0xAA; 32]))));
assert!(format_parse_eq(Prefix::new(76, XorName([0xAA; 32]))));
}

fn parse(input: &str) -> Prefix {
Expand Down
45 changes: 15 additions & 30 deletions src/serialize.rs
Expand Up @@ -4,7 +4,7 @@ use serde::{
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
use std::fmt;
use std::{fmt, str::FromStr};

impl Serialize for XorName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
Expand Down Expand Up @@ -61,11 +61,8 @@ impl Serialize for Prefix {
S: Serializer,
{
if serializer.is_human_readable() {
let hex_str = hex::encode(&self.name);
let bit_count = self.bit_count;
let s = std::format!("{hex_str}/{bit_count}");

return serializer.serialize_str(&s);
// Use `Display` impl from `Prefix`
return serializer.serialize_str(&std::format!("{}", self));
}

let mut s = serializer.serialize_struct("Prefix", 2)?;
Expand Down Expand Up @@ -95,22 +92,7 @@ impl<'de> Deserialize<'de> for Prefix {
where
E: de::Error,
{
let mut split = s.split('/');

let hex_str = split
.next()
.ok_or_else(|| de::Error::custom("`str::split` logic error"))?;
let bit_count = split
.next()
.ok_or_else(|| de::Error::custom("no `/` symbol encountered"))?;

let k: [u8; 32] = hex::FromHex::from_hex(hex_str)
.map_err(|_| de::Error::custom("invalid 32 byte hex string"))?;
let bit_count = bit_count
.parse::<usize>()
.map_err(|_e| de::Error::custom("bit_count is not a valid `usize`"))?;

Ok(Prefix::new(bit_count, XorName(k)))
Ok(Prefix::from_str(s).unwrap())
}
}
return deserializer.deserialize_str(PrefixVisitor);
Expand Down Expand Up @@ -161,23 +143,26 @@ mod test {

#[test]
fn prefix_ser_de() {
let bit_count = 15;
let prefix = Prefix {
bit_count: 14,
bit_count,
name: XorName([0xAA; 32]),
};
let prefix_derived = PrefixDerived {
bit_count: 14,
bit_count,
name: XorNameDerived([0xAA; 32]),
};

let xor_hex_str = static_str("aa".repeat(32) + "/14");
assert_tokens(&prefix.readable(), &[Token::Str(xor_hex_str)]);
assert_tokens(&prefix.readable(), &[Token::Str("101010101010101")]);

assert_tokens(&prefix.compact(), &prefix_tokens("Prefix", "XorName"));
assert_tokens(
&prefix.compact(),
&prefix_tokens(bit_count, "Prefix", "XorName"),
);
// Verify our `Serialize` impl is same as when it would be derived
assert_tokens(
&prefix_derived.compact(),
&prefix_tokens("PrefixDerived", "XorNameDerived"),
&prefix_tokens(bit_count, "PrefixDerived", "XorNameDerived"),
);
}

Expand All @@ -196,11 +181,11 @@ mod test {
}

// Compact/derived representation of `Prefix`
fn prefix_tokens(name: &'static str, name2: &'static str) -> Vec<Token> {
fn prefix_tokens(bit_count: u16, name: &'static str, name2: &'static str) -> Vec<Token> {
let mut v = vec![
Token::Struct { name, len: 2 },
Token::Str("bit_count"),
Token::U16(14),
Token::U16(bit_count),
Token::Str("name"),
];
v.extend_from_slice(&xor_tokens(name2));
Expand Down

0 comments on commit 31a7b16

Please sign in to comment.