mirror of
https://github.com/YGGverse/aquatic.git
synced 2026-04-02 18:55:32 +00:00
aquatic http protocol: add deserialize impl for responses
This commit is contained in:
parent
d487bf3ebb
commit
fe887e1de5
4 changed files with 203 additions and 17 deletions
|
|
@ -25,6 +25,7 @@ harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
bendy = { version = "0.3", features = ["std", "serde"] }
|
||||||
hashbrown = "0.8"
|
hashbrown = "0.8"
|
||||||
hex = { version = "0.4", default-features = false }
|
hex = { version = "0.4", default-features = false }
|
||||||
httparse = "1"
|
httparse = "1"
|
||||||
|
|
@ -36,7 +37,6 @@ serde = { version = "1", features = ["derive"] }
|
||||||
smartstring = "0.2"
|
smartstring = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bendy = { version = "0.3", features = ["std", "serde"] }
|
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
quickcheck = "0.9"
|
quickcheck = "0.9"
|
||||||
quickcheck_macros = "0.9"
|
quickcheck_macros = "0.9"
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use super::utils::*;
|
use super::utils::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct PeerId(
|
pub struct PeerId(
|
||||||
#[serde(
|
#[serde(
|
||||||
serialize_with = "serialize_20_bytes",
|
serialize_with = "serialize_20_bytes",
|
||||||
|
deserialize_with = "deserialize_20_bytes",
|
||||||
)]
|
)]
|
||||||
pub [u8; 20]
|
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)]
|
#[serde(transparent)]
|
||||||
pub struct InfoHash(
|
pub struct InfoHash(
|
||||||
#[serde(
|
#[serde(
|
||||||
serialize_with = "serialize_20_bytes",
|
serialize_with = "serialize_20_bytes",
|
||||||
|
deserialize_with = "deserialize_20_bytes",
|
||||||
)]
|
)]
|
||||||
pub [u8; 20]
|
pub [u8; 20]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,42 @@ use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use serde::Serialize;
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use super::common::*;
|
use super::common::*;
|
||||||
use super::utils::*;
|
use super::utils::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ResponsePeer<I>{
|
pub struct ResponsePeer<I: Eq>{
|
||||||
pub ip_address: I,
|
pub ip_address: I,
|
||||||
pub port: u16
|
pub port: u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ResponsePeerListV4(
|
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<ResponsePeer<Ipv4Addr>>
|
pub Vec<ResponsePeer<Ipv4Addr>>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ResponsePeerListV6(
|
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<ResponsePeer<Ipv6Addr>>
|
pub Vec<ResponsePeer<Ipv6Addr>>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ScrapeStatistics {
|
pub struct ScrapeStatistics {
|
||||||
pub complete: usize,
|
pub complete: usize,
|
||||||
pub incomplete: usize,
|
pub incomplete: usize,
|
||||||
|
|
@ -39,7 +45,7 @@ pub struct ScrapeStatistics {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AnnounceResponse {
|
pub struct AnnounceResponse {
|
||||||
#[serde(rename = "interval")]
|
#[serde(rename = "interval")]
|
||||||
pub announce_interval: usize,
|
pub announce_interval: usize,
|
||||||
|
|
@ -99,7 +105,7 @@ impl AnnounceResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ScrapeResponse {
|
pub struct ScrapeResponse {
|
||||||
/// BTreeMap instead of HashMap since keys need to be serialized in order
|
/// BTreeMap instead of HashMap since keys need to be serialized in order
|
||||||
pub files: BTreeMap<InfoHash, ScrapeStatistics>,
|
pub files: BTreeMap<InfoHash, ScrapeStatistics>,
|
||||||
|
|
@ -133,7 +139,7 @@ impl ScrapeResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct FailureResponse {
|
pub struct FailureResponse {
|
||||||
pub failure_reason: String,
|
pub failure_reason: String,
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +164,7 @@ impl FailureResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Response {
|
pub enum Response {
|
||||||
Announce(AnnounceResponse),
|
Announce(AnnounceResponse),
|
||||||
|
|
@ -175,6 +181,9 @@ impl Response {
|
||||||
Response::Scrape(r) => r.write(output),
|
Response::Scrape(r) => r.write(output),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ::bendy::serde::Error> {
|
||||||
|
::bendy::serde::de::from_bytes(bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::Serializer;
|
use serde::{Serializer, Deserializer, de::Visitor};
|
||||||
use smartstring::{SmartString, LazyCompact};
|
use smartstring::{SmartString, LazyCompact};
|
||||||
|
|
||||||
use super::response::ResponsePeer;
|
use super::response::ResponsePeer;
|
||||||
|
|
@ -114,6 +114,42 @@ pub fn serialize_20_bytes<S>(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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<E>(self, value: &[u8]) -> Result<Self::Value, E>
|
||||||
|
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<S>(
|
pub fn serialize_response_peers_ipv4<S>(
|
||||||
response_peers: &[ResponsePeer<Ipv4Addr>],
|
response_peers: &[ResponsePeer<Ipv4Addr>],
|
||||||
serializer: S
|
serializer: S
|
||||||
|
|
@ -144,10 +180,116 @@ pub fn serialize_response_peers_ipv6<S>(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ResponsePeersIpv4Visitor;
|
||||||
|
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for ResponsePeersIpv4Visitor {
|
||||||
|
type Value = Vec<ResponsePeer<Ipv4Addr>>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("byte-encoded ipv4 address-port pairs")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
|
||||||
|
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<Vec<ResponsePeer<Ipv4Addr>>, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(ResponsePeersIpv4Visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct ResponsePeersIpv6Visitor;
|
||||||
|
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for ResponsePeersIpv6Visitor {
|
||||||
|
type Value = Vec<ResponsePeer<Ipv6Addr>>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("byte-encoded ipv6 address-port pairs")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
|
||||||
|
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<Vec<ResponsePeer<Ipv6Addr>>, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
deserializer.deserialize_any(ResponsePeersIpv6Visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use quickcheck_macros::*;
|
use quickcheck_macros::*;
|
||||||
|
|
||||||
|
use crate::common::InfoHash;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -216,4 +358,37 @@ mod tests {
|
||||||
assert!(urldecode("%").is_err());
|
assert!(urldecode("%").is_err());
|
||||||
assert!(urldecode("%å7").is_err());
|
assert!(urldecode("%å7").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn test_serde_response_peers_ipv4(
|
||||||
|
peers: Vec<ResponsePeer<Ipv4Addr>>,
|
||||||
|
) -> bool {
|
||||||
|
let serialized = bendy::serde::to_bytes(&peers).unwrap();
|
||||||
|
let deserialized: Vec<ResponsePeer<Ipv4Addr>> =
|
||||||
|
::bendy::serde::from_bytes(&serialized).unwrap();
|
||||||
|
|
||||||
|
peers == deserialized
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn test_serde_response_peers_ipv6(
|
||||||
|
peers: Vec<ResponsePeer<Ipv6Addr>>,
|
||||||
|
) -> bool {
|
||||||
|
let serialized = bendy::serde::to_bytes(&peers).unwrap();
|
||||||
|
let deserialized: Vec<ResponsePeer<Ipv6Addr>> =
|
||||||
|
::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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue