From 18c4a51b744f46ab8a369b84a36ff6d1b1948595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Mon, 11 May 2020 23:20:58 +0200 Subject: [PATCH] aquatic_ws: use [u8; 20] for info hash etc, fix deserialization --- aquatic_ws/src/lib/protocol/deserialize.rs | 43 ++++++++++++++++++---- aquatic_ws/src/lib/protocol/mod.rs | 6 +-- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/aquatic_ws/src/lib/protocol/deserialize.rs b/aquatic_ws/src/lib/protocol/deserialize.rs index df312be..1ccbba2 100644 --- a/aquatic_ws/src/lib/protocol/deserialize.rs +++ b/aquatic_ws/src/lib/protocol/deserialize.rs @@ -6,7 +6,7 @@ use super::InfoHash; struct TwentyByteVisitor; impl<'de> Visitor<'de> for TwentyByteVisitor { - type Value = String; + type Value = [u8; 20]; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("string consisting of 20 bytes") @@ -15,19 +15,40 @@ impl<'de> Visitor<'de> for TwentyByteVisitor { fn visit_str(self, value: &str) -> Result where E: ::serde::de::Error, { - if value.chars().count() == 20 { // FIXME - Ok(value.to_string()) + // Value is encoded in nodejs reference client something as follows: + // ``` + // var infoHash = 'abcd..'; // 40 hexadecimals + // Buffer.from(infoHash, 'hex').toString('binary'); + // ``` + // which produces a UTF16 string with each char having only the low + // byte set. Here, we extract it by casting to u8. + + let mut arr = [0u8; 20]; + let mut max_i = 0; + + for (i, (a, c)) in arr.iter_mut().zip(value.chars()).enumerate(){ + if c as u32 > 255 { + return Err(E::custom(format!( + "character not in single byte range: {}", + c + ))) + } + *a = c as u8; + max_i = i; + } + + if max_i == 19 { + Ok(arr) } else { Err(E::custom(format!("not 20 bytes: {}", value))) } - } } pub fn deserialize_20_bytes<'de, D>( deserializer: D -) -> Result +) -> Result<[u8; 20], D::Error> where D: Deserializer<'de> { deserializer.deserialize_any(TwentyByteVisitor) @@ -95,7 +116,13 @@ mod tests { use super::*; fn info_hash_from_bytes(bytes: &[u8]) -> InfoHash { - InfoHash(String::from_utf8_lossy(bytes).to_string()) + let mut arr = [0u8; 20]; + + assert!(bytes.len() == 20); + + arr.copy_from_slice(&bytes[..]); + + InfoHash(arr) } #[test] @@ -107,12 +134,12 @@ mod tests { assert_eq!(observed, expected); - let input = r#""1aaaabbbbccccddddeeee""#; + let input = r#""aaaabbbbccccddddeee""#; let res_info_hash: Result = serde_json::from_str(input); assert!(res_info_hash.is_err()); - let input = r#""aaaabbbbccccddddeeeö""#; + let input = r#""aaaabbbbccccddddeee𝕊""#; let res_info_hash: Result = serde_json::from_str(input); assert!(res_info_hash.is_err()); diff --git a/aquatic_ws/src/lib/protocol/mod.rs b/aquatic_ws/src/lib/protocol/mod.rs index b444a05..fce47a1 100644 --- a/aquatic_ws/src/lib/protocol/mod.rs +++ b/aquatic_ws/src/lib/protocol/mod.rs @@ -10,7 +10,7 @@ use deserialize::*; #[serde(transparent)] pub struct PeerId( #[serde(deserialize_with = "deserialize_20_bytes")] - pub String + pub [u8; 20] ); @@ -18,7 +18,7 @@ pub struct PeerId( #[serde(transparent)] pub struct InfoHash( #[serde(deserialize_with = "deserialize_20_bytes")] - pub String + pub [u8; 20] ); @@ -26,7 +26,7 @@ pub struct InfoHash( #[serde(transparent)] pub struct OfferId( #[serde(deserialize_with = "deserialize_20_bytes")] - pub String + pub [u8; 20] );