From ea6a4c26357feda2fc4c43912c57d3ce2ad41be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 2 Aug 2020 01:23:11 +0200 Subject: [PATCH] refactor aquatic_udp_protocol, notably make converters trait fns --- TODO.md | 1 - aquatic_udp/src/lib/common.rs | 2 +- aquatic_udp/src/lib/handlers.rs | 2 +- aquatic_udp/src/lib/network.rs | 7 +- aquatic_udp_load_test/src/common.rs | 2 +- aquatic_udp_load_test/src/handler.rs | 2 +- aquatic_udp_load_test/src/network.rs | 7 +- aquatic_udp_load_test/src/utils.rs | 2 +- .../src/{types => }/common.rs | 1 + aquatic_udp_protocol/src/converters/common.rs | 23 -- aquatic_udp_protocol/src/converters/mod.rs | 6 - .../src/converters/requests.rs | 241 ----------- .../src/converters/responses.rs | 248 ------------ aquatic_udp_protocol/src/lib.rs | 16 +- aquatic_udp_protocol/src/request.rs | 381 ++++++++++++++++++ aquatic_udp_protocol/src/response.rs | 375 +++++++++++++++++ aquatic_udp_protocol/src/types/mod.rs | 7 - aquatic_udp_protocol/src/types/request.rs | 132 ------ aquatic_udp_protocol/src/types/response.rs | 128 ------ 19 files changed, 774 insertions(+), 809 deletions(-) rename aquatic_udp_protocol/src/{types => }/common.rs (99%) delete mode 100644 aquatic_udp_protocol/src/converters/common.rs delete mode 100644 aquatic_udp_protocol/src/converters/mod.rs delete mode 100644 aquatic_udp_protocol/src/converters/requests.rs delete mode 100644 aquatic_udp_protocol/src/converters/responses.rs create mode 100644 aquatic_udp_protocol/src/request.rs create mode 100644 aquatic_udp_protocol/src/response.rs delete mode 100644 aquatic_udp_protocol/src/types/mod.rs delete mode 100644 aquatic_udp_protocol/src/types/request.rs delete mode 100644 aquatic_udp_protocol/src/types/response.rs diff --git a/TODO.md b/TODO.md index 30425e9..85baef0 100644 --- a/TODO.md +++ b/TODO.md @@ -44,7 +44,6 @@ * handle errors similarily to aquatic_ws, including errors in socket workers and using log crate * More tests? -* Protocol crate: converter functions should be trait functions # Not important diff --git a/aquatic_udp/src/lib/common.rs b/aquatic_udp/src/lib/common.rs index 148852f..b23c6d6 100644 --- a/aquatic_udp/src/lib/common.rs +++ b/aquatic_udp/src/lib/common.rs @@ -7,7 +7,7 @@ use indexmap::IndexMap; use parking_lot::Mutex; pub use aquatic_common::ValidUntil; -pub use aquatic_udp_protocol::types::*; +pub use aquatic_udp_protocol::*; pub const MAX_PACKET_SIZE: usize = 4096; diff --git a/aquatic_udp/src/lib/handlers.rs b/aquatic_udp/src/lib/handlers.rs index a688972..6566d73 100644 --- a/aquatic_udp/src/lib/handlers.rs +++ b/aquatic_udp/src/lib/handlers.rs @@ -7,7 +7,7 @@ use parking_lot::MutexGuard; use rand::{SeedableRng, Rng, rngs::{SmallRng, StdRng}}; use aquatic_common::{convert_ipv4_mapped_ipv6, extract_response_peers}; -use aquatic_udp_protocol::types::*; +use aquatic_udp_protocol::*; use crate::common::*; use crate::config::Config; diff --git a/aquatic_udp/src/lib/network.rs b/aquatic_udp/src/lib/network.rs index 4ae642b..d5c0983 100644 --- a/aquatic_udp/src/lib/network.rs +++ b/aquatic_udp/src/lib/network.rs @@ -9,8 +9,7 @@ use mio::{Events, Poll, Interest, Token}; use mio::net::UdpSocket; use socket2::{Socket, Domain, Type, Protocol}; -use aquatic_udp_protocol::types::IpVersion; -use aquatic_udp_protocol::converters::{response_to_bytes, request_from_bytes}; +use aquatic_udp_protocol::{Request, Response, IpVersion}; use crate::common::*; use crate::config::Config; @@ -131,7 +130,7 @@ fn read_requests( loop { match socket.recv_from(&mut buffer[..]) { Ok((amt, src)) => { - let request = request_from_bytes( + let request = Request::from_bytes( &buffer[..amt], config.protocol.max_scrape_torrents ); @@ -212,7 +211,7 @@ fn send_responses( let ip_version = ip_version_from_ip(src.ip()); - response_to_bytes(&mut cursor, response, ip_version).unwrap(); + response.write(&mut cursor, ip_version).unwrap(); let amt = cursor.position() as usize; diff --git a/aquatic_udp_load_test/src/common.rs b/aquatic_udp_load_test/src/common.rs index 42866f4..443c58c 100644 --- a/aquatic_udp_load_test/src/common.rs +++ b/aquatic_udp_load_test/src/common.rs @@ -5,7 +5,7 @@ use hashbrown::HashMap; use parking_lot::Mutex; use serde::{Serialize, Deserialize}; -use aquatic_udp_protocol::types::*; +use aquatic_udp_protocol::*; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] diff --git a/aquatic_udp_load_test/src/handler.rs b/aquatic_udp_load_test/src/handler.rs index 1756a74..7148e2c 100644 --- a/aquatic_udp_load_test/src/handler.rs +++ b/aquatic_udp_load_test/src/handler.rs @@ -8,7 +8,7 @@ use rand::distributions::WeightedIndex; use rand::prelude::*; use rand_distr::Pareto; -use aquatic_udp_protocol::types::*; +use aquatic_udp_protocol::*; use crate::common::*; use crate::utils::*; diff --git a/aquatic_udp_load_test/src/network.rs b/aquatic_udp_load_test/src/network.rs index 6ff8087..9569702 100644 --- a/aquatic_udp_load_test/src/network.rs +++ b/aquatic_udp_load_test/src/network.rs @@ -7,8 +7,7 @@ use crossbeam_channel::{Receiver, Sender}; use mio::{net::UdpSocket, Events, Poll, Interest, Token}; use socket2::{Socket, Domain, Type, Protocol}; -use aquatic_udp_protocol::converters::*; -use aquatic_udp_protocol::types::*; +use aquatic_udp_protocol::*; use crate::common::*; @@ -131,7 +130,7 @@ fn read_responses( responses: &mut Vec<(ThreadId, Response)>, ){ while let Ok(amt) = socket.recv(buffer) { - match response_from_bytes(&buffer[0..amt]){ + match Response::from_bytes(&buffer[0..amt]){ Ok(response) => { match response { Response::Announce(ref r) => { @@ -171,7 +170,7 @@ fn send_requests( while let Ok(request) = receiver.try_recv() { cursor.set_position(0); - if let Err(err) = request_to_bytes(&mut cursor, request){ + if let Err(err) = request.write(&mut cursor){ eprintln!("request_to_bytes err: {}", err); } diff --git a/aquatic_udp_load_test/src/utils.rs b/aquatic_udp_load_test/src/utils.rs index 48e25f1..929b9b9 100644 --- a/aquatic_udp_load_test/src/utils.rs +++ b/aquatic_udp_load_test/src/utils.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use rand_distr::{Standard, Pareto}; use rand::prelude::*; -use aquatic_udp_protocol::types::*; +use aquatic_udp_protocol::*; use crate::common::*; diff --git a/aquatic_udp_protocol/src/types/common.rs b/aquatic_udp_protocol/src/common.rs similarity index 99% rename from aquatic_udp_protocol/src/types/common.rs rename to aquatic_udp_protocol/src/common.rs index 57e7233..24f1026 100644 --- a/aquatic_udp_protocol/src/types/common.rs +++ b/aquatic_udp_protocol/src/common.rs @@ -49,6 +49,7 @@ pub struct ResponsePeer { } + #[cfg(test)] impl quickcheck::Arbitrary for IpVersion { fn arbitrary(g: &mut G) -> Self { diff --git a/aquatic_udp_protocol/src/converters/common.rs b/aquatic_udp_protocol/src/converters/common.rs deleted file mode 100644 index 957e21d..0000000 --- a/aquatic_udp_protocol/src/converters/common.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::types::AnnounceEvent; - - -#[inline] -pub fn event_from_i32(i: i32) -> AnnounceEvent { - match i { - 1 => AnnounceEvent::Completed, - 2 => AnnounceEvent::Started, - 3 => AnnounceEvent::Stopped, - _ => AnnounceEvent::None - } -} - - -#[inline] -pub fn event_to_i32(event: AnnounceEvent) -> i32 { - match event { - AnnounceEvent::None => 0, - AnnounceEvent::Completed => 1, - AnnounceEvent::Started => 2, - AnnounceEvent::Stopped => 3 - } -} \ No newline at end of file diff --git a/aquatic_udp_protocol/src/converters/mod.rs b/aquatic_udp_protocol/src/converters/mod.rs deleted file mode 100644 index 52c9219..0000000 --- a/aquatic_udp_protocol/src/converters/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod common; -pub mod requests; -pub mod responses; - -pub use self::requests::*; -pub use self::responses::*; \ No newline at end of file diff --git a/aquatic_udp_protocol/src/converters/requests.rs b/aquatic_udp_protocol/src/converters/requests.rs deleted file mode 100644 index 2532825..0000000 --- a/aquatic_udp_protocol/src/converters/requests.rs +++ /dev/null @@ -1,241 +0,0 @@ -use byteorder::{ReadBytesExt, WriteBytesExt, NetworkEndian}; - -use std::convert::TryInto; -use std::io::{self, Cursor, Read, Write}; -use std::net::Ipv4Addr; - -use crate::types::*; - -use super::common::*; - - -const PROTOCOL_IDENTIFIER: i64 = 4_497_486_125_440; - - -#[derive(Debug)] -pub struct RequestParseError { - pub transaction_id: Option, - pub message: Option, - pub error: Option, -} - - -impl RequestParseError { - pub fn new(err: io::Error, transaction_id: i32) -> Self { - Self { - transaction_id: Some(TransactionId(transaction_id)), - message: None, - error: Some(err) - } - } - pub fn io(err: io::Error) -> Self { - Self { - transaction_id: None, - message: None, - error: Some(err) - } - } - pub fn text(transaction_id: i32, message: &str) -> Self { - Self { - transaction_id: Some(TransactionId(transaction_id)), - message: Some(message.to_string()), - error: None, - } - } -} - - -#[inline] -pub fn request_to_bytes( - bytes: &mut impl Write, - request: Request -) -> Result<(), io::Error> { - match request { - Request::Connect(r) => { - bytes.write_i64::(PROTOCOL_IDENTIFIER)?; - bytes.write_i32::(0)?; - bytes.write_i32::(r.transaction_id.0)?; - }, - - Request::Announce(r) => { - bytes.write_i64::(r.connection_id.0)?; - bytes.write_i32::(1)?; - bytes.write_i32::(r.transaction_id.0)?; - - bytes.write_all(&r.info_hash.0)?; - bytes.write_all(&r.peer_id.0)?; - - bytes.write_i64::(r.bytes_downloaded.0)?; - bytes.write_i64::(r.bytes_left.0)?; - bytes.write_i64::(r.bytes_uploaded.0)?; - - bytes.write_i32::(event_to_i32(r.event))?; - - bytes.write_all(&r.ip_address.map_or( - [0; 4], - |ip| ip.octets() - ))?; - - bytes.write_u32::(r.key.0)?; - bytes.write_i32::(r.peers_wanted.0)?; - bytes.write_u16::(r.port.0)?; - }, - - Request::Scrape(r) => { - bytes.write_i64::(r.connection_id.0)?; - bytes.write_i32::(2)?; - bytes.write_i32::(r.transaction_id.0)?; - - for info_hash in r.info_hashes { - bytes.write_all(&info_hash.0)?; - } - } - } - - Ok(()) -} - - -#[inline] -pub fn request_from_bytes( - bytes: &[u8], - max_scrape_torrents: u8, -) -> Result { - let mut cursor = Cursor::new(bytes); - - let connection_id = cursor.read_i64::() - .map_err(RequestParseError::io)?; - let action = cursor.read_i32::() - .map_err(RequestParseError::io)?; - let transaction_id = cursor.read_i32::() - .map_err(RequestParseError::io)?; - - match action { - // Connect - 0 => { - if connection_id == PROTOCOL_IDENTIFIER { - Ok((ConnectRequest { - transaction_id: TransactionId(transaction_id) - }).into()) - } else { - Err(RequestParseError::text( - transaction_id, - "Protocol identifier missing" - )) - } - }, - - // Announce - 1 => { - let mut info_hash = [0; 20]; - let mut peer_id = [0; 20]; - let mut ip = [0; 4]; - - cursor.read_exact(&mut info_hash) - .map_err(|err| RequestParseError::new(err, transaction_id))?; - cursor.read_exact(&mut peer_id) - .map_err(|err| RequestParseError::new(err, transaction_id))?; - - let bytes_downloaded = cursor.read_i64::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - let bytes_left = cursor.read_i64::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - let bytes_uploaded = cursor.read_i64::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - let event = cursor.read_i32::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - - cursor.read_exact(&mut ip) - .map_err(|err| RequestParseError::new(err, transaction_id))?; - - let key = cursor.read_u32::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - let peers_wanted = cursor.read_i32::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - let port = cursor.read_u16::() - .map_err(|err| RequestParseError::new(err, transaction_id))?; - - let opt_ip = if ip == [0; 4] { - None - } else { - Some(Ipv4Addr::from(ip)) - }; - - Ok((AnnounceRequest { - connection_id: ConnectionId(connection_id), - transaction_id: TransactionId(transaction_id), - info_hash: InfoHash(info_hash), - peer_id: PeerId(peer_id), - bytes_downloaded: NumberOfBytes(bytes_downloaded), - bytes_uploaded: NumberOfBytes(bytes_uploaded), - bytes_left: NumberOfBytes(bytes_left), - event: event_from_i32(event), - ip_address: opt_ip, - key: PeerKey(key), - peers_wanted: NumberOfPeers(peers_wanted), - port: Port(port) - }).into()) - }, - - // Scrape - 2 => { - let position = cursor.position() as usize; - let inner = cursor.into_inner(); - - let info_hashes = (&inner[position..]).chunks_exact(20) - .take(max_scrape_torrents as usize) - .map(|chunk| InfoHash(chunk.try_into().unwrap())) - .collect(); - - Ok((ScrapeRequest { - connection_id: ConnectionId(connection_id), - transaction_id: TransactionId(transaction_id), - info_hashes - }).into()) - } - - _ => Err(RequestParseError::text(transaction_id, "Invalid action")) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - fn same_after_conversion(request: Request) -> bool { - let mut buf = Vec::new(); - - request_to_bytes(&mut buf, request.clone()).unwrap(); - let r2 = request_from_bytes(&buf[..], ::std::u8::MAX).unwrap(); - - let success = request == r2; - - if !success { - println!("before: {:#?}\nafter: {:#?}", request, r2); - } - - success - } - - #[quickcheck] - fn test_connect_request_convert_identity( - request: ConnectRequest - ) -> bool { - same_after_conversion(request.into()) - } - - #[quickcheck] - fn test_announce_request_convert_identity( - request: AnnounceRequest - ) -> bool { - same_after_conversion(request.into()) - } - - #[quickcheck] - fn test_scrape_request_convert_identity( - request: ScrapeRequest - ) -> bool { - same_after_conversion(request.into()) - } -} \ No newline at end of file diff --git a/aquatic_udp_protocol/src/converters/responses.rs b/aquatic_udp_protocol/src/converters/responses.rs deleted file mode 100644 index f80caaa..0000000 --- a/aquatic_udp_protocol/src/converters/responses.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::convert::TryInto; -use std::io::{self, Cursor, Write}; -use std::net::{IpAddr, Ipv6Addr, Ipv4Addr}; - -use byteorder::{ReadBytesExt, WriteBytesExt, NetworkEndian}; - -use crate::types::*; - - -/// Returning IPv6 peers doesn't really work with UDP. It is not supported by -/// https://libtorrent.org/udp_tracker_protocol.html. There is a suggestion in -/// https://web.archive.org/web/20170503181830/http://opentracker.blog.h3q.com/2007/12/28/the-ipv6-situation/ -/// of using action number 4 and returning IPv6 octets just like for IPv4 -/// addresses. Clients seem not to support it very well, but due to a lack of -/// alternative solutions, it is implemented here. -#[inline] -pub fn response_to_bytes( - bytes: &mut impl Write, - response: Response, - ip_version: IpVersion -) -> Result<(), io::Error> { - match response { - Response::Connect(r) => { - bytes.write_i32::(0)?; - bytes.write_i32::(r.transaction_id.0)?; - bytes.write_i64::(r.connection_id.0)?; - }, - Response::Announce(r) => { - if ip_version == IpVersion::IPv4 { - bytes.write_i32::(1)?; - bytes.write_i32::(r.transaction_id.0)?; - bytes.write_i32::(r.announce_interval.0)?; - bytes.write_i32::(r.leechers.0)?; - bytes.write_i32::(r.seeders.0)?; - - // Silently ignore peers with wrong IP version - for peer in r.peers { - if let IpAddr::V4(ip) = peer.ip_address { - bytes.write_all(&ip.octets())?; - bytes.write_u16::(peer.port.0)?; - } - } - } else { - bytes.write_i32::(4)?; - bytes.write_i32::(r.transaction_id.0)?; - bytes.write_i32::(r.announce_interval.0)?; - bytes.write_i32::(r.leechers.0)?; - bytes.write_i32::(r.seeders.0)?; - - // Silently ignore peers with wrong IP version - for peer in r.peers { - if let IpAddr::V6(ip) = peer.ip_address { - bytes.write_all(&ip.octets())?; - bytes.write_u16::(peer.port.0)?; - } - } - } - }, - Response::Scrape(r) => { - bytes.write_i32::(2)?; - bytes.write_i32::(r.transaction_id.0)?; - - for torrent_stat in r.torrent_stats { - bytes.write_i32::(torrent_stat.seeders.0)?; - bytes.write_i32::(torrent_stat.completed.0)?; - bytes.write_i32::(torrent_stat.leechers.0)?; - } - }, - Response::Error(r) => { - bytes.write_i32::(3)?; - bytes.write_i32::(r.transaction_id.0)?; - - bytes.write_all(r.message.as_bytes())?; - }, - } - - Ok(()) -} - - -#[inline] -pub fn response_from_bytes(bytes: &[u8]) -> Result { - let mut cursor = Cursor::new(bytes); - - let action = cursor.read_i32::()?; - let transaction_id = cursor.read_i32::()?; - - match action { - // Connect - 0 => { - let connection_id = cursor.read_i64::()?; - - Ok((ConnectResponse { - connection_id: ConnectionId(connection_id), - transaction_id: TransactionId(transaction_id) - }).into()) - }, - // Announce - 1 => { - let announce_interval = cursor.read_i32::()?; - let leechers = cursor.read_i32::()?; - let seeders = cursor.read_i32::()?; - - let position = cursor.position() as usize; - let inner = cursor.into_inner(); - - let peers = inner[position..].chunks_exact(6).map(|chunk| { - let ip_bytes: [u8; 4] = (&chunk[..4]).try_into().unwrap(); - let ip_address = IpAddr::V4(Ipv4Addr::from(ip_bytes)); - let port = (&chunk[4..]).read_u16::().unwrap(); - - ResponsePeer { - ip_address, - port: Port(port), - } - }).collect(); - - Ok((AnnounceResponse { - transaction_id: TransactionId(transaction_id), - announce_interval: AnnounceInterval(announce_interval), - leechers: NumberOfPeers(leechers), - seeders: NumberOfPeers(seeders), - peers - }).into()) - }, - // Scrape - 2 => { - let position = cursor.position() as usize; - let inner = cursor.into_inner(); - - let stats = inner[position..].chunks_exact(12).map(|chunk| { - let mut cursor: Cursor<&[u8]> = Cursor::new(&chunk[..]); - - let seeders = cursor.read_i32::().unwrap(); - let downloads = cursor.read_i32::().unwrap(); - let leechers = cursor.read_i32::().unwrap(); - - TorrentScrapeStatistics { - seeders: NumberOfPeers(seeders), - completed: NumberOfDownloads(downloads), - leechers:NumberOfPeers(leechers) - } - }).collect(); - - Ok((ScrapeResponse { - transaction_id: TransactionId(transaction_id), - torrent_stats: stats - }).into()) - }, - // Error - 3 => { - let position = cursor.position() as usize; - let inner = cursor.into_inner(); - - Ok((ErrorResponse { - transaction_id: TransactionId(transaction_id), - message: String::from_utf8_lossy(&inner[position..]).into() - }).into()) - }, - // IPv6 announce - 4 => { - let announce_interval = cursor.read_i32::()?; - let leechers = cursor.read_i32::()?; - let seeders = cursor.read_i32::()?; - - let position = cursor.position() as usize; - let inner = cursor.into_inner(); - - let peers = inner[position..].chunks_exact(18).map(|chunk| { - let ip_bytes: [u8; 16] = (&chunk[..16]).try_into().unwrap(); - let ip_address = IpAddr::V6(Ipv6Addr::from(ip_bytes)); - let port = (&chunk[16..]).read_u16::().unwrap(); - - ResponsePeer { - ip_address, - port: Port(port), - } - }).collect(); - - Ok((AnnounceResponse { - transaction_id: TransactionId(transaction_id), - announce_interval: AnnounceInterval(announce_interval), - leechers: NumberOfPeers(leechers), - seeders: NumberOfPeers(seeders), - peers - }).into()) - }, - _ => { - Ok((ErrorResponse { - transaction_id: TransactionId(transaction_id), - message: "Invalid action".to_string() - }).into()) - } - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - fn same_after_conversion( - response: Response, - ip_version: IpVersion - ) -> bool { - let mut buf = Vec::new(); - - response_to_bytes(&mut buf, response.clone(), ip_version).unwrap(); - let r2 = response_from_bytes(&buf[..]).unwrap(); - - let success = response == r2; - - if !success { - println!("before: {:#?}\nafter: {:#?}", response, r2); - } - - success - } - - #[quickcheck] - fn test_connect_response_convert_identity( - response: ConnectResponse - ) -> bool { - same_after_conversion(response.into(), IpVersion::IPv4) - } - - #[quickcheck] - fn test_announce_response_convert_identity( - data: (AnnounceResponse, IpVersion) - ) -> bool { - let mut r = data.0; - - if data.1 == IpVersion::IPv4 { - r.peers.retain(|peer| peer.ip_address.is_ipv4()); - } else { - r.peers.retain(|peer| peer.ip_address.is_ipv6()); - } - - same_after_conversion(r.into(), data.1) - } - - #[quickcheck] - fn test_scrape_response_convert_identity( - response: ScrapeResponse - ) -> bool { - same_after_conversion(response.into(), IpVersion::IPv4) - } -} \ No newline at end of file diff --git a/aquatic_udp_protocol/src/lib.rs b/aquatic_udp_protocol/src/lib.rs index 782e516..295d4c1 100644 --- a/aquatic_udp_protocol/src/lib.rs +++ b/aquatic_udp_protocol/src/lib.rs @@ -1,11 +1,7 @@ -//! UDP BitTorrent tracker protocol structures +pub mod common; +pub mod request; +pub mod response; -#[cfg(test)] -extern crate quickcheck; -#[cfg(test)] -#[macro_use(quickcheck)] -extern crate quickcheck_macros; - - -pub mod converters; -pub mod types; \ No newline at end of file +pub use self::common::*; +pub use self::request::*; +pub use self::response::*; \ No newline at end of file diff --git a/aquatic_udp_protocol/src/request.rs b/aquatic_udp_protocol/src/request.rs new file mode 100644 index 0000000..f369d31 --- /dev/null +++ b/aquatic_udp_protocol/src/request.rs @@ -0,0 +1,381 @@ +use std::convert::TryInto; +use std::io::{self, Cursor, Read, Write}; +use std::net::Ipv4Addr; + +use byteorder::{ReadBytesExt, WriteBytesExt, NetworkEndian}; + +use super::common::*; + + +const PROTOCOL_IDENTIFIER: i64 = 4_497_486_125_440; + + +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub enum AnnounceEvent { + Started, + Stopped, + Completed, + None +} + + +impl AnnounceEvent { + #[inline] + pub fn from_i32(i: i32) -> Self { + match i { + 1 => Self::Completed, + 2 => Self::Started, + 3 => Self::Stopped, + _ => Self::None + } + } + + #[inline] + pub fn to_i32(&self) -> i32 { + match self { + AnnounceEvent::None => 0, + AnnounceEvent::Completed => 1, + AnnounceEvent::Started => 2, + AnnounceEvent::Stopped => 3 + } + } +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ConnectRequest { + pub transaction_id: TransactionId +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct AnnounceRequest { + pub connection_id: ConnectionId, + pub transaction_id: TransactionId, + pub info_hash: InfoHash, + pub peer_id: PeerId, + pub bytes_downloaded: NumberOfBytes, + pub bytes_uploaded: NumberOfBytes, + pub bytes_left: NumberOfBytes, + pub event: AnnounceEvent, + pub ip_address: Option, + pub key: PeerKey, + pub peers_wanted: NumberOfPeers, + pub port: Port +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ScrapeRequest { + pub connection_id: ConnectionId, + pub transaction_id: TransactionId, + pub info_hashes: Vec +} + + +#[derive(Debug)] +pub struct RequestParseError { + pub transaction_id: Option, + pub message: Option, + pub error: Option, +} + + +impl RequestParseError { + pub fn new(err: io::Error, transaction_id: i32) -> Self { + Self { + transaction_id: Some(TransactionId(transaction_id)), + message: None, + error: Some(err) + } + } + pub fn io(err: io::Error) -> Self { + Self { + transaction_id: None, + message: None, + error: Some(err) + } + } + pub fn text(transaction_id: i32, message: &str) -> Self { + Self { + transaction_id: Some(TransactionId(transaction_id)), + message: Some(message.to_string()), + error: None, + } + } +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum Request { + Connect(ConnectRequest), + Announce(AnnounceRequest), + Scrape(ScrapeRequest), +} + + +impl From for Request { + fn from(r: ConnectRequest) -> Self { + Self::Connect(r) + } +} + + +impl From for Request { + fn from(r: AnnounceRequest) -> Self { + Self::Announce(r) + } +} + + +impl From for Request { + fn from(r: ScrapeRequest) -> Self { + Self::Scrape(r) + } +} + + +impl Request { + pub fn write(self, bytes: &mut impl Write) -> Result<(), io::Error> { + match self { + Request::Connect(r) => { + bytes.write_i64::(PROTOCOL_IDENTIFIER)?; + bytes.write_i32::(0)?; + bytes.write_i32::(r.transaction_id.0)?; + }, + + Request::Announce(r) => { + bytes.write_i64::(r.connection_id.0)?; + bytes.write_i32::(1)?; + bytes.write_i32::(r.transaction_id.0)?; + + bytes.write_all(&r.info_hash.0)?; + bytes.write_all(&r.peer_id.0)?; + + bytes.write_i64::(r.bytes_downloaded.0)?; + bytes.write_i64::(r.bytes_left.0)?; + bytes.write_i64::(r.bytes_uploaded.0)?; + + bytes.write_i32::(r.event.to_i32())?; + + bytes.write_all(&r.ip_address.map_or( + [0; 4], + |ip| ip.octets() + ))?; + + bytes.write_u32::(r.key.0)?; + bytes.write_i32::(r.peers_wanted.0)?; + bytes.write_u16::(r.port.0)?; + }, + + Request::Scrape(r) => { + bytes.write_i64::(r.connection_id.0)?; + bytes.write_i32::(2)?; + bytes.write_i32::(r.transaction_id.0)?; + + for info_hash in r.info_hashes { + bytes.write_all(&info_hash.0)?; + } + } + } + + Ok(()) + } + + pub fn from_bytes( + bytes: &[u8], + max_scrape_torrents: u8, + ) -> Result { + let mut cursor = Cursor::new(bytes); + + let connection_id = cursor.read_i64::() + .map_err(RequestParseError::io)?; + let action = cursor.read_i32::() + .map_err(RequestParseError::io)?; + let transaction_id = cursor.read_i32::() + .map_err(RequestParseError::io)?; + + match action { + // Connect + 0 => { + if connection_id == PROTOCOL_IDENTIFIER { + Ok((ConnectRequest { + transaction_id: TransactionId(transaction_id) + }).into()) + } else { + Err(RequestParseError::text( + transaction_id, + "Protocol identifier missing" + )) + } + }, + + // Announce + 1 => { + let mut info_hash = [0; 20]; + let mut peer_id = [0; 20]; + let mut ip = [0; 4]; + + cursor.read_exact(&mut info_hash) + .map_err(|err| RequestParseError::new(err, transaction_id))?; + cursor.read_exact(&mut peer_id) + .map_err(|err| RequestParseError::new(err, transaction_id))?; + + let bytes_downloaded = cursor.read_i64::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + let bytes_left = cursor.read_i64::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + let bytes_uploaded = cursor.read_i64::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + let event = cursor.read_i32::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + + cursor.read_exact(&mut ip) + .map_err(|err| RequestParseError::new(err, transaction_id))?; + + let key = cursor.read_u32::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + let peers_wanted = cursor.read_i32::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + let port = cursor.read_u16::() + .map_err(|err| RequestParseError::new(err, transaction_id))?; + + let opt_ip = if ip == [0; 4] { + None + } else { + Some(Ipv4Addr::from(ip)) + }; + + Ok((AnnounceRequest { + connection_id: ConnectionId(connection_id), + transaction_id: TransactionId(transaction_id), + info_hash: InfoHash(info_hash), + peer_id: PeerId(peer_id), + bytes_downloaded: NumberOfBytes(bytes_downloaded), + bytes_uploaded: NumberOfBytes(bytes_uploaded), + bytes_left: NumberOfBytes(bytes_left), + event: AnnounceEvent::from_i32(event), + ip_address: opt_ip, + key: PeerKey(key), + peers_wanted: NumberOfPeers(peers_wanted), + port: Port(port) + }).into()) + }, + + // Scrape + 2 => { + let position = cursor.position() as usize; + let inner = cursor.into_inner(); + + let info_hashes = (&inner[position..]).chunks_exact(20) + .take(max_scrape_torrents as usize) + .map(|chunk| InfoHash(chunk.try_into().unwrap())) + .collect(); + + Ok((ScrapeRequest { + connection_id: ConnectionId(connection_id), + transaction_id: TransactionId(transaction_id), + info_hashes + }).into()) + } + + _ => Err(RequestParseError::text(transaction_id, "Invalid action")) + } + } +} + + +#[cfg(test)] +mod tests { + use quickcheck_macros::quickcheck; + + use super::*; + + impl quickcheck::Arbitrary for AnnounceEvent { + fn arbitrary(g: &mut G) -> Self { + match (bool::arbitrary(g), bool::arbitrary(g)){ + (false, false) => Self::Started, + (true, false) => Self::Started, + (false, true) => Self::Completed, + (true, true) => Self::None, + } + } + } + + impl quickcheck::Arbitrary for ConnectRequest { + fn arbitrary(g: &mut G) -> Self { + Self { + transaction_id: TransactionId(i32::arbitrary(g)), + } + } + } + + impl quickcheck::Arbitrary for AnnounceRequest { + fn arbitrary(g: &mut G) -> Self { + Self { + connection_id: ConnectionId(i64::arbitrary(g)), + transaction_id: TransactionId(i32::arbitrary(g)), + info_hash: InfoHash::arbitrary(g), + peer_id: PeerId::arbitrary(g), + bytes_downloaded: NumberOfBytes(i64::arbitrary(g)), + bytes_uploaded: NumberOfBytes(i64::arbitrary(g)), + bytes_left: NumberOfBytes(i64::arbitrary(g)), + event: AnnounceEvent::arbitrary(g), + ip_address: None, + key: PeerKey(u32::arbitrary(g)), + peers_wanted: NumberOfPeers(i32::arbitrary(g)), + port: Port(u16::arbitrary(g)) + } + } + } + + impl quickcheck::Arbitrary for ScrapeRequest { + fn arbitrary(g: &mut G) -> Self { + let info_hashes = (0..u8::arbitrary(g)).map(|_| { + InfoHash::arbitrary(g) + }).collect(); + + Self { + connection_id: ConnectionId(i64::arbitrary(g)), + transaction_id: TransactionId(i32::arbitrary(g)), + info_hashes, + } + } + } + + fn same_after_conversion(request: Request) -> bool { + let mut buf = Vec::new(); + + request.clone().write(&mut buf).unwrap(); + let r2 = Request::from_bytes(&buf[..], ::std::u8::MAX).unwrap(); + + let success = request == r2; + + if !success { + println!("before: {:#?}\nafter: {:#?}", request, r2); + } + + success + } + + #[quickcheck] + fn test_connect_request_convert_identity( + request: ConnectRequest + ) -> bool { + same_after_conversion(request.into()) + } + + #[quickcheck] + fn test_announce_request_convert_identity( + request: AnnounceRequest + ) -> bool { + same_after_conversion(request.into()) + } + + #[quickcheck] + fn test_scrape_request_convert_identity( + request: ScrapeRequest + ) -> bool { + same_after_conversion(request.into()) + } +} \ No newline at end of file diff --git a/aquatic_udp_protocol/src/response.rs b/aquatic_udp_protocol/src/response.rs new file mode 100644 index 0000000..b17c3b2 --- /dev/null +++ b/aquatic_udp_protocol/src/response.rs @@ -0,0 +1,375 @@ +use std::convert::TryInto; +use std::io::{self, Cursor, Write}; +use std::net::{IpAddr, Ipv6Addr, Ipv4Addr}; + +use byteorder::{ReadBytesExt, WriteBytesExt, NetworkEndian}; + +use super::common::*; + + +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +pub struct TorrentScrapeStatistics { + pub seeders: NumberOfPeers, + pub completed: NumberOfDownloads, + pub leechers: NumberOfPeers +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ConnectResponse { + pub connection_id: ConnectionId, + pub transaction_id: TransactionId +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct AnnounceResponse { + pub transaction_id: TransactionId, + pub announce_interval: AnnounceInterval, + pub leechers: NumberOfPeers, + pub seeders: NumberOfPeers, + pub peers: Vec +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ScrapeResponse { + pub transaction_id: TransactionId, + pub torrent_stats: Vec +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ErrorResponse { + pub transaction_id: TransactionId, + pub message: String +} + + +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum Response { + Connect(ConnectResponse), + Announce(AnnounceResponse), + Scrape(ScrapeResponse), + Error(ErrorResponse), +} + + +impl From for Response { + fn from(r: ConnectResponse) -> Self { + Self::Connect(r) + } +} + + +impl From for Response { + fn from(r: AnnounceResponse) -> Self { + Self::Announce(r) + } +} + + +impl From for Response { + fn from(r: ScrapeResponse) -> Self { + Self::Scrape(r) + } +} + + +impl From for Response { + fn from(r: ErrorResponse) -> Self { + Self::Error(r) + } +} + + +impl Response { + /// Returning IPv6 peers doesn't really work with UDP. It is not supported + /// by https://libtorrent.org/udp_tracker_protocol.html. There is a + /// suggestion in https://web.archive.org/web/20170503181830/http://opentracker.blog.h3q.com/2007/12/28/the-ipv6-situation/ + /// of using action number 4 and returning IPv6 octets just like for IPv4 + /// addresses. Clients seem not to support it very well, but due to a lack + /// of alternative solutions, it is implemented here. + #[inline] + pub fn write( + self, + bytes: &mut impl Write, + ip_version: IpVersion + ) -> Result<(), io::Error> { + match self { + Response::Connect(r) => { + bytes.write_i32::(0)?; + bytes.write_i32::(r.transaction_id.0)?; + bytes.write_i64::(r.connection_id.0)?; + }, + Response::Announce(r) => { + if ip_version == IpVersion::IPv4 { + bytes.write_i32::(1)?; + bytes.write_i32::(r.transaction_id.0)?; + bytes.write_i32::(r.announce_interval.0)?; + bytes.write_i32::(r.leechers.0)?; + bytes.write_i32::(r.seeders.0)?; + + // Silently ignore peers with wrong IP version + for peer in r.peers { + if let IpAddr::V4(ip) = peer.ip_address { + bytes.write_all(&ip.octets())?; + bytes.write_u16::(peer.port.0)?; + } + } + } else { + bytes.write_i32::(4)?; + bytes.write_i32::(r.transaction_id.0)?; + bytes.write_i32::(r.announce_interval.0)?; + bytes.write_i32::(r.leechers.0)?; + bytes.write_i32::(r.seeders.0)?; + + // Silently ignore peers with wrong IP version + for peer in r.peers { + if let IpAddr::V6(ip) = peer.ip_address { + bytes.write_all(&ip.octets())?; + bytes.write_u16::(peer.port.0)?; + } + } + } + }, + Response::Scrape(r) => { + bytes.write_i32::(2)?; + bytes.write_i32::(r.transaction_id.0)?; + + for torrent_stat in r.torrent_stats { + bytes.write_i32::(torrent_stat.seeders.0)?; + bytes.write_i32::(torrent_stat.completed.0)?; + bytes.write_i32::(torrent_stat.leechers.0)?; + } + }, + Response::Error(r) => { + bytes.write_i32::(3)?; + bytes.write_i32::(r.transaction_id.0)?; + + bytes.write_all(r.message.as_bytes())?; + }, + } + + Ok(()) + } + + #[inline] + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut cursor = Cursor::new(bytes); + + let action = cursor.read_i32::()?; + let transaction_id = cursor.read_i32::()?; + + match action { + // Connect + 0 => { + let connection_id = cursor.read_i64::()?; + + Ok((ConnectResponse { + connection_id: ConnectionId(connection_id), + transaction_id: TransactionId(transaction_id) + }).into()) + }, + // Announce + 1 => { + let announce_interval = cursor.read_i32::()?; + let leechers = cursor.read_i32::()?; + let seeders = cursor.read_i32::()?; + + let position = cursor.position() as usize; + let inner = cursor.into_inner(); + + let peers = inner[position..].chunks_exact(6).map(|chunk| { + let ip_bytes: [u8; 4] = (&chunk[..4]).try_into().unwrap(); + let ip_address = IpAddr::V4(Ipv4Addr::from(ip_bytes)); + let port = (&chunk[4..]).read_u16::().unwrap(); + + ResponsePeer { + ip_address, + port: Port(port), + } + }).collect(); + + Ok((AnnounceResponse { + transaction_id: TransactionId(transaction_id), + announce_interval: AnnounceInterval(announce_interval), + leechers: NumberOfPeers(leechers), + seeders: NumberOfPeers(seeders), + peers + }).into()) + }, + // Scrape + 2 => { + let position = cursor.position() as usize; + let inner = cursor.into_inner(); + + let stats = inner[position..].chunks_exact(12).map(|chunk| { + let mut cursor: Cursor<&[u8]> = Cursor::new(&chunk[..]); + + let seeders = cursor.read_i32::().unwrap(); + let downloads = cursor.read_i32::().unwrap(); + let leechers = cursor.read_i32::().unwrap(); + + TorrentScrapeStatistics { + seeders: NumberOfPeers(seeders), + completed: NumberOfDownloads(downloads), + leechers:NumberOfPeers(leechers) + } + }).collect(); + + Ok((ScrapeResponse { + transaction_id: TransactionId(transaction_id), + torrent_stats: stats + }).into()) + }, + // Error + 3 => { + let position = cursor.position() as usize; + let inner = cursor.into_inner(); + + Ok((ErrorResponse { + transaction_id: TransactionId(transaction_id), + message: String::from_utf8_lossy(&inner[position..]).into() + }).into()) + }, + // IPv6 announce + 4 => { + let announce_interval = cursor.read_i32::()?; + let leechers = cursor.read_i32::()?; + let seeders = cursor.read_i32::()?; + + let position = cursor.position() as usize; + let inner = cursor.into_inner(); + + let peers = inner[position..].chunks_exact(18).map(|chunk| { + let ip_bytes: [u8; 16] = (&chunk[..16]).try_into().unwrap(); + let ip_address = IpAddr::V6(Ipv6Addr::from(ip_bytes)); + let port = (&chunk[16..]).read_u16::().unwrap(); + + ResponsePeer { + ip_address, + port: Port(port), + } + }).collect(); + + Ok((AnnounceResponse { + transaction_id: TransactionId(transaction_id), + announce_interval: AnnounceInterval(announce_interval), + leechers: NumberOfPeers(leechers), + seeders: NumberOfPeers(seeders), + peers + }).into()) + }, + _ => { + Ok((ErrorResponse { + transaction_id: TransactionId(transaction_id), + message: "Invalid action".to_string() + }).into()) + } + } + } +} + + +#[cfg(test)] +mod tests { + use quickcheck_macros::quickcheck; + + use super::*; + + impl quickcheck::Arbitrary for TorrentScrapeStatistics { + fn arbitrary(g: &mut G) -> Self { + Self { + seeders: NumberOfPeers(i32::arbitrary(g)), + completed: NumberOfDownloads(i32::arbitrary(g)), + leechers: NumberOfPeers(i32::arbitrary(g)), + } + } + } + + impl quickcheck::Arbitrary for ConnectResponse { + fn arbitrary(g: &mut G) -> Self { + Self { + connection_id: ConnectionId(i64::arbitrary(g)), + transaction_id: TransactionId(i32::arbitrary(g)), + } + } + } + + impl quickcheck::Arbitrary for AnnounceResponse { + fn arbitrary(g: &mut G) -> Self { + let peers = (0..u8::arbitrary(g)).map(|_| { + ResponsePeer::arbitrary(g) + }).collect(); + + Self { + transaction_id: TransactionId(i32::arbitrary(g)), + announce_interval: AnnounceInterval(i32::arbitrary(g)), + leechers: NumberOfPeers(i32::arbitrary(g)), + seeders: NumberOfPeers(i32::arbitrary(g)), + peers, + } + } + } + + impl quickcheck::Arbitrary for ScrapeResponse { + fn arbitrary(g: &mut G) -> Self { + let torrent_stats = (0..u8::arbitrary(g)).map(|_| { + TorrentScrapeStatistics::arbitrary(g) + }).collect(); + + Self { + transaction_id: TransactionId(i32::arbitrary(g)), + torrent_stats, + } + } + } + + fn same_after_conversion( + response: Response, + ip_version: IpVersion + ) -> bool { + let mut buf = Vec::new(); + + response.clone().write(&mut buf, ip_version).unwrap(); + let r2 = Response::from_bytes(&buf[..]).unwrap(); + + let success = response == r2; + + if !success { + println!("before: {:#?}\nafter: {:#?}", response, r2); + } + + success + } + + #[quickcheck] + fn test_connect_response_convert_identity( + response: ConnectResponse + ) -> bool { + same_after_conversion(response.into(), IpVersion::IPv4) + } + + #[quickcheck] + fn test_announce_response_convert_identity( + data: (AnnounceResponse, IpVersion) + ) -> bool { + let mut r = data.0; + + if data.1 == IpVersion::IPv4 { + r.peers.retain(|peer| peer.ip_address.is_ipv4()); + } else { + r.peers.retain(|peer| peer.ip_address.is_ipv6()); + } + + same_after_conversion(r.into(), data.1) + } + + #[quickcheck] + fn test_scrape_response_convert_identity( + response: ScrapeResponse + ) -> bool { + same_after_conversion(response.into(), IpVersion::IPv4) + } +} diff --git a/aquatic_udp_protocol/src/types/mod.rs b/aquatic_udp_protocol/src/types/mod.rs deleted file mode 100644 index bf686fa..0000000 --- a/aquatic_udp_protocol/src/types/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod common; -pub mod request; -pub mod response; - -pub use self::common::*; -pub use self::request::*; -pub use self::response::*; diff --git a/aquatic_udp_protocol/src/types/request.rs b/aquatic_udp_protocol/src/types/request.rs deleted file mode 100644 index 3011738..0000000 --- a/aquatic_udp_protocol/src/types/request.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::net::Ipv4Addr; - -use super::common::*; - - -#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] -pub enum AnnounceEvent { - Started, - Stopped, - Completed, - None -} - - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ConnectRequest { - pub transaction_id: TransactionId -} - - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct AnnounceRequest { - pub connection_id: ConnectionId, - pub transaction_id: TransactionId, - pub info_hash: InfoHash, - pub peer_id: PeerId, - pub bytes_downloaded: NumberOfBytes, - pub bytes_uploaded: NumberOfBytes, - pub bytes_left: NumberOfBytes, - pub event: AnnounceEvent, - pub ip_address: Option, - pub key: PeerKey, - pub peers_wanted: NumberOfPeers, - pub port: Port -} - - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ScrapeRequest { - pub connection_id: ConnectionId, - pub transaction_id: TransactionId, - pub info_hashes: Vec -} - - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum Request { - Connect(ConnectRequest), - Announce(AnnounceRequest), - Scrape(ScrapeRequest), -} - - -impl From for Request { - fn from(r: ConnectRequest) -> Self { - Self::Connect(r) - } -} - - -impl From for Request { - fn from(r: AnnounceRequest) -> Self { - Self::Announce(r) - } -} - - -impl From for Request { - fn from(r: ScrapeRequest) -> Self { - Self::Scrape(r) - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for AnnounceEvent { - fn arbitrary(g: &mut G) -> Self { - match (bool::arbitrary(g), bool::arbitrary(g)){ - (false, false) => Self::Started, - (true, false) => Self::Started, - (false, true) => Self::Completed, - (true, true) => Self::None, - } - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for ConnectRequest { - fn arbitrary(g: &mut G) -> Self { - Self { - transaction_id: TransactionId(i32::arbitrary(g)), - } - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for AnnounceRequest { - fn arbitrary(g: &mut G) -> Self { - Self { - connection_id: ConnectionId(i64::arbitrary(g)), - transaction_id: TransactionId(i32::arbitrary(g)), - info_hash: InfoHash::arbitrary(g), - peer_id: PeerId::arbitrary(g), - bytes_downloaded: NumberOfBytes(i64::arbitrary(g)), - bytes_uploaded: NumberOfBytes(i64::arbitrary(g)), - bytes_left: NumberOfBytes(i64::arbitrary(g)), - event: AnnounceEvent::arbitrary(g), - ip_address: None, - key: PeerKey(u32::arbitrary(g)), - peers_wanted: NumberOfPeers(i32::arbitrary(g)), - port: Port(u16::arbitrary(g)) - } - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for ScrapeRequest { - fn arbitrary(g: &mut G) -> Self { - let info_hashes = (0..u8::arbitrary(g)).map(|_| { - InfoHash::arbitrary(g) - }).collect(); - - Self { - connection_id: ConnectionId(i64::arbitrary(g)), - transaction_id: TransactionId(i32::arbitrary(g)), - info_hashes, - } - } -} \ No newline at end of file diff --git a/aquatic_udp_protocol/src/types/response.rs b/aquatic_udp_protocol/src/types/response.rs deleted file mode 100644 index 59fb1f3..0000000 --- a/aquatic_udp_protocol/src/types/response.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::common::*; - - -#[derive(PartialEq, Eq, Debug, Copy, Clone)] -pub struct TorrentScrapeStatistics { - pub seeders: NumberOfPeers, - pub completed: NumberOfDownloads, - pub leechers: NumberOfPeers -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ConnectResponse { - pub connection_id: ConnectionId, - pub transaction_id: TransactionId -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct AnnounceResponse { - pub transaction_id: TransactionId, - pub announce_interval: AnnounceInterval, - pub leechers: NumberOfPeers, - pub seeders: NumberOfPeers, - pub peers: Vec -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ScrapeResponse { - pub transaction_id: TransactionId, - pub torrent_stats: Vec -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct ErrorResponse { - pub transaction_id: TransactionId, - pub message: String -} - -#[derive(PartialEq, Eq, Clone, Debug)] -pub enum Response { - Connect(ConnectResponse), - Announce(AnnounceResponse), - Scrape(ScrapeResponse), - Error(ErrorResponse), -} - - -impl From for Response { - fn from(r: ConnectResponse) -> Self { - Self::Connect(r) - } -} - - -impl From for Response { - fn from(r: AnnounceResponse) -> Self { - Self::Announce(r) - } -} - - -impl From for Response { - fn from(r: ScrapeResponse) -> Self { - Self::Scrape(r) - } -} - - -impl From for Response { - fn from(r: ErrorResponse) -> Self { - Self::Error(r) - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for TorrentScrapeStatistics { - fn arbitrary(g: &mut G) -> Self { - Self { - seeders: NumberOfPeers(i32::arbitrary(g)), - completed: NumberOfDownloads(i32::arbitrary(g)), - leechers: NumberOfPeers(i32::arbitrary(g)), - } - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for ConnectResponse { - fn arbitrary(g: &mut G) -> Self { - Self { - connection_id: ConnectionId(i64::arbitrary(g)), - transaction_id: TransactionId(i32::arbitrary(g)), - } - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for AnnounceResponse { - fn arbitrary(g: &mut G) -> Self { - let peers = (0..u8::arbitrary(g)).map(|_| { - ResponsePeer::arbitrary(g) - }).collect(); - - Self { - transaction_id: TransactionId(i32::arbitrary(g)), - announce_interval: AnnounceInterval(i32::arbitrary(g)), - leechers: NumberOfPeers(i32::arbitrary(g)), - seeders: NumberOfPeers(i32::arbitrary(g)), - peers, - } - } -} - - -#[cfg(test)] -impl quickcheck::Arbitrary for ScrapeResponse { - fn arbitrary(g: &mut G) -> Self { - let torrent_stats = (0..u8::arbitrary(g)).map(|_| { - TorrentScrapeStatistics::arbitrary(g) - }).collect(); - - Self { - transaction_id: TransactionId(i32::arbitrary(g)), - torrent_stats, - } - } -} \ No newline at end of file