diff --git a/TODO.md b/TODO.md index 4191d6f..a2ac0ff 100644 --- a/TODO.md +++ b/TODO.md @@ -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) diff --git a/aquatic_http/src/lib/handler.rs b/aquatic_http/src/lib/handler.rs index be18a72..e0e14a4 100644 --- a/aquatic_http/src/lib/handler.rs +++ b/aquatic_http/src/lib/handler.rs @@ -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( diff --git a/aquatic_http/src/lib/protocol/common.rs b/aquatic_http/src/lib/protocol/common.rs index 782bc22..a4ef3de 100644 --- a/aquatic_http/src/lib/protocol/common.rs +++ b/aquatic_http/src/lib/protocol/common.rs @@ -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( @@ -53,4 +53,19 @@ impl FromStr for AnnounceEvent { value => Err(format!("Unknown value: {}", value)) } } +} + + +#[cfg(test)] +impl quickcheck::Arbitrary for InfoHash { + fn arbitrary(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) + } } \ No newline at end of file diff --git a/aquatic_http/src/lib/protocol/response.rs b/aquatic_http/src/lib/protocol/response.rs index 5f780fa..46c8a24 100644 --- a/aquatic_http/src/lib/protocol/response.rs +++ b/aquatic_http/src/lib/protocol/response.rs @@ -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, + /// BTreeMap instead of HashMap since keys need to be serialized in order + pub files: BTreeMap, } impl ScrapeResponse { fn to_bytes(&self) -> Vec { - 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 { 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: &mut G) -> Self { + Self { + complete: usize::arbitrary(g), + incomplete: usize::arbitrary(g), + downloaded: 0, + } + } +} + + #[cfg(test)] impl quickcheck::Arbitrary for AnnounceResponse { fn arbitrary(g: &mut G) -> Self { @@ -226,6 +254,16 @@ impl quickcheck::Arbitrary for AnnounceResponse { } +#[cfg(test)] +impl quickcheck::Arbitrary for ScrapeResponse { + fn arbitrary(g: &mut G) -> Self { + Self { + files: BTreeMap::arbitrary(g), + } + } +} + + #[cfg(test)] impl quickcheck::Arbitrary for FailureResponse { fn arbitrary(g: &mut G) -> Self { @@ -249,7 +287,24 @@ mod tests { ).unwrap(); 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 {