aquatic_http: add hand-written ScrapeResponse serialization

This commit is contained in:
Joakim Frostegård 2020-07-18 17:46:16 +02:00
parent 17385c92ad
commit 84facea0ca
4 changed files with 94 additions and 26 deletions

View file

@ -9,14 +9,12 @@
and maybe run scripts should be adjusted and maybe run scripts should be adjusted
## aquatic_http ## aquatic_http
* scape response to bytes: does bendy encode info hashes here? I think it
should
* faster Request creation (splitn functions) using memchr, possibly * faster Request creation (splitn functions) using memchr, possibly
iterate over several bytes (& and =) iterate over several bytes (& and =)
* test torrent transfer with real clients * test torrent transfer with real clients
* test tls * test tls
* current serialized byte strings valid * current serialized byte strings valid
* scrape: does it work with multiple hashes? * scrape: does it work (serialization etc), and with multiple hashes?
* compact=0 should result in error response * compact=0 should result in error response
* tests of request parsing * tests of request parsing
* tests of response serialization (against data known to be good would be nice) * tests of response serialization (against data known to be good would be nice)

View file

@ -1,9 +1,9 @@
use std::collections::BTreeMap;
use std::time::Duration; use std::time::Duration;
use std::vec::Drain; use std::vec::Drain;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use either::Either; use either::Either;
use hashbrown::HashMap;
use parking_lot::MutexGuard; use parking_lot::MutexGuard;
use rand::{Rng, SeedableRng, rngs::SmallRng}; use rand::{Rng, SeedableRng, rngs::SmallRng};
@ -258,7 +258,7 @@ pub fn handle_scrape_requests(
); );
let mut response = ScrapeResponse { let mut response = ScrapeResponse {
files: HashMap::with_capacity(num_to_take), files: BTreeMap::new(),
}; };
let peer_ip = convert_ipv4_mapped_ipv4( let peer_ip = convert_ipv4_mapped_ipv4(

View file

@ -15,7 +15,7 @@ pub struct PeerId(
); );
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct InfoHash( pub struct InfoHash(
#[serde( #[serde(
@ -53,4 +53,19 @@ impl FromStr for AnnounceEvent {
value => Err(format!("Unknown value: {}", value)) value => Err(format!("Unknown value: {}", value))
} }
} }
}
#[cfg(test)]
impl quickcheck::Arbitrary for InfoHash {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
let mut arr = [b'x'; 20];
arr[0] = u8::arbitrary(g);
arr[1] = u8::arbitrary(g);
arr[18] = u8::arbitrary(g);
arr[19] = u8::arbitrary(g);
Self(arr)
}
} }

View file

@ -1,6 +1,6 @@
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use hashbrown::HashMap; use std::collections::BTreeMap;
use serde::Serialize; use serde::Serialize;
use super::common::*; use super::common::*;
@ -101,13 +101,42 @@ impl AnnounceResponse {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct ScrapeResponse { pub struct ScrapeResponse {
pub files: HashMap<InfoHash, ScrapeStatistics>, /// BTreeMap instead of HashMap since keys need to be serialized in order
pub files: BTreeMap<InfoHash, ScrapeStatistics>,
} }
impl ScrapeResponse { impl ScrapeResponse {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
unimplemented!() let mut bytes = Vec::with_capacity(
9 +
self.files.len() * (
3 +
20 +
12 +
5 + // Upper estimate
31 +
5 + // Upper estimate
2
) +
2
);
bytes.extend_from_slice(b"d5:filesd");
for (info_hash, statistics) in self.files.iter(){
bytes.extend_from_slice(b"20:");
bytes.extend_from_slice(&info_hash.0);
bytes.extend_from_slice(b"d8:completei");
let _ = itoa::write(&mut bytes, statistics.complete);
bytes.extend_from_slice(b"e10:downloadedi0e10:incompletei");
let _ = itoa::write(&mut bytes, statistics.incomplete);
bytes.extend_from_slice(b"ee");
}
bytes.extend_from_slice(b"ee");
bytes
} }
} }
@ -153,22 +182,9 @@ pub enum Response {
impl Response { impl Response {
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
match self { match self {
Response::Announce(r) => { Response::Announce(r) => r.to_bytes(),
r.to_bytes() Response::Failure(r) => r.to_bytes(),
}, Response::Scrape(r) => r.to_bytes(),
Response::Failure(r) => {
r.to_bytes()
},
Response::Scrape(r) => {
match bendy::serde::to_bytes(r){
Ok(bytes) => bytes,
Err(err) => {
::log::error!("Response::to_bytes: {}", err);
Vec::new()
},
}
}
} }
} }
} }
@ -212,6 +228,18 @@ impl quickcheck::Arbitrary for ResponsePeerListV6 {
} }
#[cfg(test)]
impl quickcheck::Arbitrary for ScrapeStatistics {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
Self {
complete: usize::arbitrary(g),
incomplete: usize::arbitrary(g),
downloaded: 0,
}
}
}
#[cfg(test)] #[cfg(test)]
impl quickcheck::Arbitrary for AnnounceResponse { impl quickcheck::Arbitrary for AnnounceResponse {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self { fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
@ -226,6 +254,16 @@ impl quickcheck::Arbitrary for AnnounceResponse {
} }
#[cfg(test)]
impl quickcheck::Arbitrary for ScrapeResponse {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
Self {
files: BTreeMap::arbitrary(g),
}
}
}
#[cfg(test)] #[cfg(test)]
impl quickcheck::Arbitrary for FailureResponse { impl quickcheck::Arbitrary for FailureResponse {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self { fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
@ -249,7 +287,24 @@ mod tests {
).unwrap(); ).unwrap();
response.to_bytes() == reference response.to_bytes() == reference
} }
#[quickcheck]
fn test_scrape_response_to_bytes(response: ScrapeResponse) -> bool {
let reference = bendy::serde::to_bytes(
&Response::Scrape(response.clone())
).unwrap();
let hand_written = response.to_bytes();
let success = hand_written == reference;
if !success {
println!("reference: {}", String::from_utf8_lossy(&reference));
println!("hand_written: {}", String::from_utf8_lossy(&hand_written));
}
success
}
#[quickcheck] #[quickcheck]
fn test_failure_response_to_bytes(response: FailureResponse) -> bool { fn test_failure_response_to_bytes(response: FailureResponse) -> bool {