From fe887e1de5600291f6e983f42784fe068a4f46fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 2 Aug 2020 09:27:44 +0200 Subject: [PATCH] aquatic http protocol: add deserialize impl for responses --- aquatic_http_protocol/Cargo.toml | 2 +- aquatic_http_protocol/src/common.rs | 8 +- aquatic_http_protocol/src/response.rs | 33 +++-- aquatic_http_protocol/src/utils.rs | 177 +++++++++++++++++++++++++- 4 files changed, 203 insertions(+), 17 deletions(-) diff --git a/aquatic_http_protocol/Cargo.toml b/aquatic_http_protocol/Cargo.toml index e1f3921..daa5b2d 100644 --- a/aquatic_http_protocol/Cargo.toml +++ b/aquatic_http_protocol/Cargo.toml @@ -25,6 +25,7 @@ harness = false [dependencies] anyhow = "1" +bendy = { version = "0.3", features = ["std", "serde"] } hashbrown = "0.8" hex = { version = "0.4", default-features = false } httparse = "1" @@ -36,7 +37,6 @@ serde = { version = "1", features = ["derive"] } smartstring = "0.2" [dev-dependencies] -bendy = { version = "0.3", features = ["std", "serde"] } criterion = "0.3" quickcheck = "0.9" quickcheck_macros = "0.9" \ No newline at end of file diff --git a/aquatic_http_protocol/src/common.rs b/aquatic_http_protocol/src/common.rs index a44bc4e..125ee46 100644 --- a/aquatic_http_protocol/src/common.rs +++ b/aquatic_http_protocol/src/common.rs @@ -1,25 +1,27 @@ use std::str::FromStr; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use super::utils::*; -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct PeerId( #[serde( serialize_with = "serialize_20_bytes", + deserialize_with = "deserialize_20_bytes", )] pub [u8; 20] ); -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(transparent)] pub struct InfoHash( #[serde( serialize_with = "serialize_20_bytes", + deserialize_with = "deserialize_20_bytes", )] pub [u8; 20] ); diff --git a/aquatic_http_protocol/src/response.rs b/aquatic_http_protocol/src/response.rs index 7f1dd16..87a5d74 100644 --- a/aquatic_http_protocol/src/response.rs +++ b/aquatic_http_protocol/src/response.rs @@ -2,36 +2,42 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use std::io::Write; use std::collections::BTreeMap; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use super::common::*; use super::utils::*; -#[derive(Debug, Clone, Serialize)] -pub struct ResponsePeer{ +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ResponsePeer{ pub ip_address: I, pub port: u16 } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct ResponsePeerListV4( - #[serde(serialize_with = "serialize_response_peers_ipv4")] + #[serde( + serialize_with = "serialize_response_peers_ipv4", + deserialize_with = "deserialize_response_peers_ipv4", + )] pub Vec> ); -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct ResponsePeerListV6( - #[serde(serialize_with = "serialize_response_peers_ipv6")] + #[serde( + serialize_with = "serialize_response_peers_ipv6", + deserialize_with = "deserialize_response_peers_ipv6", + )] pub Vec> ); -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScrapeStatistics { pub complete: usize, pub incomplete: usize, @@ -39,7 +45,7 @@ pub struct ScrapeStatistics { } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnounceResponse { #[serde(rename = "interval")] pub announce_interval: usize, @@ -99,7 +105,7 @@ impl AnnounceResponse { } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScrapeResponse { /// BTreeMap instead of HashMap since keys need to be serialized in order pub files: BTreeMap, @@ -133,7 +139,7 @@ impl ScrapeResponse { } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct FailureResponse { pub failure_reason: String, } @@ -158,7 +164,7 @@ impl FailureResponse { } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Response { Announce(AnnounceResponse), @@ -175,6 +181,9 @@ impl Response { Response::Scrape(r) => r.write(output), } } + pub fn from_bytes(bytes: &[u8]) -> Result { + ::bendy::serde::de::from_bytes(bytes) + } } diff --git a/aquatic_http_protocol/src/utils.rs b/aquatic_http_protocol/src/utils.rs index 0b12d75..f2f8d76 100644 --- a/aquatic_http_protocol/src/utils.rs +++ b/aquatic_http_protocol/src/utils.rs @@ -2,7 +2,7 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use std::io::Write; use anyhow::Context; -use serde::Serializer; +use serde::{Serializer, Deserializer, de::Visitor}; use smartstring::{SmartString, LazyCompact}; use super::response::ResponsePeer; @@ -114,6 +114,42 @@ pub fn serialize_20_bytes( } +struct TwentyByteVisitor; + +impl<'de> Visitor<'de> for TwentyByteVisitor { + type Value = [u8; 20]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("20 bytes") + } + + #[inline] + fn visit_bytes(self, value: &[u8]) -> Result + where E: ::serde::de::Error, + { + if value.len() != 20 { + return Err(::serde::de::Error::custom("not 20 bytes")); + } + + let mut arr = [0u8; 20]; + + arr.copy_from_slice(value); + + Ok(arr) + } +} + + +#[inline] +pub fn deserialize_20_bytes<'de, D>( + deserializer: D +) -> Result<[u8; 20], D::Error> + where D: Deserializer<'de> +{ + deserializer.deserialize_any(TwentyByteVisitor) +} + + pub fn serialize_response_peers_ipv4( response_peers: &[ResponsePeer], serializer: S @@ -144,10 +180,116 @@ pub fn serialize_response_peers_ipv6( } +struct ResponsePeersIpv4Visitor; + + +impl<'de> Visitor<'de> for ResponsePeersIpv4Visitor { + type Value = Vec>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("byte-encoded ipv4 address-port pairs") + } + + #[inline] + fn visit_bytes(self, value: &[u8]) -> Result + where E: ::serde::de::Error, + { + let mut peers = Vec::new(); + + let mut ip_bytes = [0u8; 4]; + let mut port_bytes = [0u8; 2]; + + let chunks = value.chunks_exact(6); + + if !chunks.remainder().is_empty(){ + return Err(::serde::de::Error::custom("trailing bytes")); + } + + for chunk in chunks { + ip_bytes.copy_from_slice(&chunk[0..4]); + port_bytes.copy_from_slice(&chunk[4..6]); + + let peer = ResponsePeer { + ip_address: Ipv4Addr::from(u32::from_be_bytes(ip_bytes)), + port: u16::from_be_bytes(port_bytes), + }; + + peers.push(peer); + } + + Ok(peers) + } +} + + +#[inline] +pub fn deserialize_response_peers_ipv4<'de, D>( + deserializer: D +) -> Result>, D::Error> + where D: Deserializer<'de> +{ + deserializer.deserialize_any(ResponsePeersIpv4Visitor) +} + + +struct ResponsePeersIpv6Visitor; + + +impl<'de> Visitor<'de> for ResponsePeersIpv6Visitor { + type Value = Vec>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("byte-encoded ipv6 address-port pairs") + } + + #[inline] + fn visit_bytes(self, value: &[u8]) -> Result + where E: ::serde::de::Error, + { + let mut peers = Vec::new(); + + let mut ip_bytes = [0u8; 16]; + let mut port_bytes = [0u8; 2]; + + let chunks = value.chunks_exact(18); + + if !chunks.remainder().is_empty(){ + return Err(::serde::de::Error::custom("trailing bytes")); + } + + for chunk in chunks { + ip_bytes.copy_from_slice(&chunk[0..16]); + port_bytes.copy_from_slice(&chunk[16..18]); + + let peer = ResponsePeer { + ip_address: Ipv6Addr::from(u128::from_be_bytes(ip_bytes)), + port: u16::from_be_bytes(port_bytes), + }; + + peers.push(peer); + } + + Ok(peers) + } +} + + +#[inline] +pub fn deserialize_response_peers_ipv6<'de, D>( + deserializer: D +) -> Result>, D::Error> + where D: Deserializer<'de> +{ + deserializer.deserialize_any(ResponsePeersIpv6Visitor) +} + + #[cfg(test)] mod tests { use quickcheck_macros::*; + use crate::common::InfoHash; + use super::*; #[test] @@ -216,4 +358,37 @@ mod tests { assert!(urldecode("%").is_err()); assert!(urldecode("%å7").is_err()); } + + #[quickcheck] + fn test_serde_response_peers_ipv4( + peers: Vec>, + ) -> bool { + let serialized = bendy::serde::to_bytes(&peers).unwrap(); + let deserialized: Vec> = + ::bendy::serde::from_bytes(&serialized).unwrap(); + + peers == deserialized + } + + #[quickcheck] + fn test_serde_response_peers_ipv6( + peers: Vec>, + ) -> bool { + let serialized = bendy::serde::to_bytes(&peers).unwrap(); + let deserialized: Vec> = + ::bendy::serde::from_bytes(&serialized).unwrap(); + + peers == deserialized + } + + #[quickcheck] + fn test_serde_info_hash( + info_hash: InfoHash, + ) -> bool { + let serialized = bendy::serde::to_bytes(&info_hash).unwrap(); + let deserialized: InfoHash = + ::bendy::serde::from_bytes(&serialized).unwrap(); + + info_hash == deserialized + } } \ No newline at end of file