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
## aquatic_http
* scape response to bytes: does bendy encode info hashes here? I think it
should
* faster Request creation (splitn functions) using memchr, possibly
iterate over several bytes (& and =)
* test torrent transfer with real clients
* test tls
* 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
* tests of request parsing
* 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::vec::Drain;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use either::Either;
use hashbrown::HashMap;
use parking_lot::MutexGuard;
use rand::{Rng, SeedableRng, rngs::SmallRng};
@ -258,7 +258,7 @@ pub fn handle_scrape_requests(
);
let mut response = ScrapeResponse {
files: HashMap::with_capacity(num_to_take),
files: BTreeMap::new(),
};
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)]
pub struct InfoHash(
#[serde(
@ -54,3 +54,18 @@ impl FromStr for AnnounceEvent {
}
}
}
#[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 hashbrown::HashMap;
use std::collections::BTreeMap;
use serde::Serialize;
use super::common::*;
@ -101,13 +101,42 @@ impl AnnounceResponse {
#[derive(Debug, Clone, Serialize)]
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 {
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 {
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Response::Announce(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()
},
}
}
Response::Announce(r) => r.to_bytes(),
Response::Failure(r) => r.to_bytes(),
Response::Scrape(r) => r.to_bytes(),
}
}
}
@ -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)]
impl quickcheck::Arbitrary for AnnounceResponse {
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)]
impl quickcheck::Arbitrary for FailureResponse {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
@ -251,6 +289,23 @@ mod tests {
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]
fn test_failure_response_to_bytes(response: FailureResponse) -> bool {
let reference = bendy::serde::to_bytes(