mirror of
https://github.com/YGGverse/aquatic.git
synced 2026-03-31 17:55:36 +00:00
refactor aquatic_udp_protocol, notably make converters trait fns
This commit is contained in:
parent
2cf161469f
commit
ea6a4c2635
19 changed files with 774 additions and 809 deletions
1
TODO.md
1
TODO.md
|
|
@ -44,7 +44,6 @@
|
||||||
* handle errors similarily to aquatic_ws, including errors in socket workers
|
* handle errors similarily to aquatic_ws, including errors in socket workers
|
||||||
and using log crate
|
and using log crate
|
||||||
* More tests?
|
* More tests?
|
||||||
* Protocol crate: converter functions should be trait functions
|
|
||||||
|
|
||||||
# Not important
|
# Not important
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use indexmap::IndexMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
pub use aquatic_common::ValidUntil;
|
pub use aquatic_common::ValidUntil;
|
||||||
pub use aquatic_udp_protocol::types::*;
|
pub use aquatic_udp_protocol::*;
|
||||||
|
|
||||||
|
|
||||||
pub const MAX_PACKET_SIZE: usize = 4096;
|
pub const MAX_PACKET_SIZE: usize = 4096;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use parking_lot::MutexGuard;
|
||||||
use rand::{SeedableRng, Rng, rngs::{SmallRng, StdRng}};
|
use rand::{SeedableRng, Rng, rngs::{SmallRng, StdRng}};
|
||||||
|
|
||||||
use aquatic_common::{convert_ipv4_mapped_ipv6, extract_response_peers};
|
use aquatic_common::{convert_ipv4_mapped_ipv6, extract_response_peers};
|
||||||
use aquatic_udp_protocol::types::*;
|
use aquatic_udp_protocol::*;
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,7 @@ use mio::{Events, Poll, Interest, Token};
|
||||||
use mio::net::UdpSocket;
|
use mio::net::UdpSocket;
|
||||||
use socket2::{Socket, Domain, Type, Protocol};
|
use socket2::{Socket, Domain, Type, Protocol};
|
||||||
|
|
||||||
use aquatic_udp_protocol::types::IpVersion;
|
use aquatic_udp_protocol::{Request, Response, IpVersion};
|
||||||
use aquatic_udp_protocol::converters::{response_to_bytes, request_from_bytes};
|
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
|
@ -131,7 +130,7 @@ fn read_requests(
|
||||||
loop {
|
loop {
|
||||||
match socket.recv_from(&mut buffer[..]) {
|
match socket.recv_from(&mut buffer[..]) {
|
||||||
Ok((amt, src)) => {
|
Ok((amt, src)) => {
|
||||||
let request = request_from_bytes(
|
let request = Request::from_bytes(
|
||||||
&buffer[..amt],
|
&buffer[..amt],
|
||||||
config.protocol.max_scrape_torrents
|
config.protocol.max_scrape_torrents
|
||||||
);
|
);
|
||||||
|
|
@ -212,7 +211,7 @@ fn send_responses(
|
||||||
|
|
||||||
let ip_version = ip_version_from_ip(src.ip());
|
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;
|
let amt = cursor.position() as usize;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use hashbrown::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use aquatic_udp_protocol::types::*;
|
use aquatic_udp_protocol::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use rand::distributions::WeightedIndex;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_distr::Pareto;
|
use rand_distr::Pareto;
|
||||||
|
|
||||||
use aquatic_udp_protocol::types::*;
|
use aquatic_udp_protocol::*;
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
use crate::utils::*;
|
use crate::utils::*;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ use crossbeam_channel::{Receiver, Sender};
|
||||||
use mio::{net::UdpSocket, Events, Poll, Interest, Token};
|
use mio::{net::UdpSocket, Events, Poll, Interest, Token};
|
||||||
use socket2::{Socket, Domain, Type, Protocol};
|
use socket2::{Socket, Domain, Type, Protocol};
|
||||||
|
|
||||||
use aquatic_udp_protocol::converters::*;
|
use aquatic_udp_protocol::*;
|
||||||
use aquatic_udp_protocol::types::*;
|
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
|
|
@ -131,7 +130,7 @@ fn read_responses(
|
||||||
responses: &mut Vec<(ThreadId, Response)>,
|
responses: &mut Vec<(ThreadId, Response)>,
|
||||||
){
|
){
|
||||||
while let Ok(amt) = socket.recv(buffer) {
|
while let Ok(amt) = socket.recv(buffer) {
|
||||||
match response_from_bytes(&buffer[0..amt]){
|
match Response::from_bytes(&buffer[0..amt]){
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
match response {
|
match response {
|
||||||
Response::Announce(ref r) => {
|
Response::Announce(ref r) => {
|
||||||
|
|
@ -171,7 +170,7 @@ fn send_requests(
|
||||||
while let Ok(request) = receiver.try_recv() {
|
while let Ok(request) = receiver.try_recv() {
|
||||||
cursor.set_position(0);
|
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);
|
eprintln!("request_to_bytes err: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
use rand_distr::{Standard, Pareto};
|
use rand_distr::{Standard, Pareto};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
||||||
use aquatic_udp_protocol::types::*;
|
use aquatic_udp_protocol::*;
|
||||||
|
|
||||||
use crate::common::*;
|
use crate::common::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ pub struct ResponsePeer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl quickcheck::Arbitrary for IpVersion {
|
impl quickcheck::Arbitrary for IpVersion {
|
||||||
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
|
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
pub mod common;
|
|
||||||
pub mod requests;
|
|
||||||
pub mod responses;
|
|
||||||
|
|
||||||
pub use self::requests::*;
|
|
||||||
pub use self::responses::*;
|
|
||||||
|
|
@ -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<TransactionId>,
|
|
||||||
pub message: Option<String>,
|
|
||||||
pub error: Option<io::Error>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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::<NetworkEndian>(PROTOCOL_IDENTIFIER)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
},
|
|
||||||
|
|
||||||
Request::Announce(r) => {
|
|
||||||
bytes.write_i64::<NetworkEndian>(r.connection_id.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(1)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
|
|
||||||
bytes.write_all(&r.info_hash.0)?;
|
|
||||||
bytes.write_all(&r.peer_id.0)?;
|
|
||||||
|
|
||||||
bytes.write_i64::<NetworkEndian>(r.bytes_downloaded.0)?;
|
|
||||||
bytes.write_i64::<NetworkEndian>(r.bytes_left.0)?;
|
|
||||||
bytes.write_i64::<NetworkEndian>(r.bytes_uploaded.0)?;
|
|
||||||
|
|
||||||
bytes.write_i32::<NetworkEndian>(event_to_i32(r.event))?;
|
|
||||||
|
|
||||||
bytes.write_all(&r.ip_address.map_or(
|
|
||||||
[0; 4],
|
|
||||||
|ip| ip.octets()
|
|
||||||
))?;
|
|
||||||
|
|
||||||
bytes.write_u32::<NetworkEndian>(r.key.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.peers_wanted.0)?;
|
|
||||||
bytes.write_u16::<NetworkEndian>(r.port.0)?;
|
|
||||||
},
|
|
||||||
|
|
||||||
Request::Scrape(r) => {
|
|
||||||
bytes.write_i64::<NetworkEndian>(r.connection_id.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(2)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(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<Request, RequestParseError> {
|
|
||||||
let mut cursor = Cursor::new(bytes);
|
|
||||||
|
|
||||||
let connection_id = cursor.read_i64::<NetworkEndian>()
|
|
||||||
.map_err(RequestParseError::io)?;
|
|
||||||
let action = cursor.read_i32::<NetworkEndian>()
|
|
||||||
.map_err(RequestParseError::io)?;
|
|
||||||
let transaction_id = cursor.read_i32::<NetworkEndian>()
|
|
||||||
.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::<NetworkEndian>()
|
|
||||||
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
|
||||||
let bytes_left = cursor.read_i64::<NetworkEndian>()
|
|
||||||
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
|
||||||
let bytes_uploaded = cursor.read_i64::<NetworkEndian>()
|
|
||||||
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
|
||||||
let event = cursor.read_i32::<NetworkEndian>()
|
|
||||||
.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::<NetworkEndian>()
|
|
||||||
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
|
||||||
let peers_wanted = cursor.read_i32::<NetworkEndian>()
|
|
||||||
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
|
||||||
let port = cursor.read_u16::<NetworkEndian>()
|
|
||||||
.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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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::<NetworkEndian>(0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
bytes.write_i64::<NetworkEndian>(r.connection_id.0)?;
|
|
||||||
},
|
|
||||||
Response::Announce(r) => {
|
|
||||||
if ip_version == IpVersion::IPv4 {
|
|
||||||
bytes.write_i32::<NetworkEndian>(1)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.announce_interval.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.leechers.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(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::<NetworkEndian>(peer.port.0)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bytes.write_i32::<NetworkEndian>(4)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.announce_interval.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.leechers.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(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::<NetworkEndian>(peer.port.0)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Response::Scrape(r) => {
|
|
||||||
bytes.write_i32::<NetworkEndian>(2)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
|
|
||||||
for torrent_stat in r.torrent_stats {
|
|
||||||
bytes.write_i32::<NetworkEndian>(torrent_stat.seeders.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(torrent_stat.completed.0)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(torrent_stat.leechers.0)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Response::Error(r) => {
|
|
||||||
bytes.write_i32::<NetworkEndian>(3)?;
|
|
||||||
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
|
||||||
|
|
||||||
bytes.write_all(r.message.as_bytes())?;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn response_from_bytes(bytes: &[u8]) -> Result<Response, io::Error> {
|
|
||||||
let mut cursor = Cursor::new(bytes);
|
|
||||||
|
|
||||||
let action = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
let transaction_id = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
|
|
||||||
match action {
|
|
||||||
// Connect
|
|
||||||
0 => {
|
|
||||||
let connection_id = cursor.read_i64::<NetworkEndian>()?;
|
|
||||||
|
|
||||||
Ok((ConnectResponse {
|
|
||||||
connection_id: ConnectionId(connection_id),
|
|
||||||
transaction_id: TransactionId(transaction_id)
|
|
||||||
}).into())
|
|
||||||
},
|
|
||||||
// Announce
|
|
||||||
1 => {
|
|
||||||
let announce_interval = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
let leechers = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
let seeders = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
|
|
||||||
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::<NetworkEndian>().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::<NetworkEndian>().unwrap();
|
|
||||||
let downloads = cursor.read_i32::<NetworkEndian>().unwrap();
|
|
||||||
let leechers = cursor.read_i32::<NetworkEndian>().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::<NetworkEndian>()?;
|
|
||||||
let leechers = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
let seeders = cursor.read_i32::<NetworkEndian>()?;
|
|
||||||
|
|
||||||
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::<NetworkEndian>().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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
//! UDP BitTorrent tracker protocol structures
|
pub mod common;
|
||||||
|
pub mod request;
|
||||||
|
pub mod response;
|
||||||
|
|
||||||
#[cfg(test)]
|
pub use self::common::*;
|
||||||
extern crate quickcheck;
|
pub use self::request::*;
|
||||||
#[cfg(test)]
|
pub use self::response::*;
|
||||||
#[macro_use(quickcheck)]
|
|
||||||
extern crate quickcheck_macros;
|
|
||||||
|
|
||||||
|
|
||||||
pub mod converters;
|
|
||||||
pub mod types;
|
|
||||||
381
aquatic_udp_protocol/src/request.rs
Normal file
381
aquatic_udp_protocol/src/request.rs
Normal file
|
|
@ -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<Ipv4Addr>,
|
||||||
|
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<InfoHash>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RequestParseError {
|
||||||
|
pub transaction_id: Option<TransactionId>,
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub error: Option<io::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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<ConnectRequest> for Request {
|
||||||
|
fn from(r: ConnectRequest) -> Self {
|
||||||
|
Self::Connect(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<AnnounceRequest> for Request {
|
||||||
|
fn from(r: AnnounceRequest) -> Self {
|
||||||
|
Self::Announce(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ScrapeRequest> 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::<NetworkEndian>(PROTOCOL_IDENTIFIER)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
},
|
||||||
|
|
||||||
|
Request::Announce(r) => {
|
||||||
|
bytes.write_i64::<NetworkEndian>(r.connection_id.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(1)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
|
||||||
|
bytes.write_all(&r.info_hash.0)?;
|
||||||
|
bytes.write_all(&r.peer_id.0)?;
|
||||||
|
|
||||||
|
bytes.write_i64::<NetworkEndian>(r.bytes_downloaded.0)?;
|
||||||
|
bytes.write_i64::<NetworkEndian>(r.bytes_left.0)?;
|
||||||
|
bytes.write_i64::<NetworkEndian>(r.bytes_uploaded.0)?;
|
||||||
|
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.event.to_i32())?;
|
||||||
|
|
||||||
|
bytes.write_all(&r.ip_address.map_or(
|
||||||
|
[0; 4],
|
||||||
|
|ip| ip.octets()
|
||||||
|
))?;
|
||||||
|
|
||||||
|
bytes.write_u32::<NetworkEndian>(r.key.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.peers_wanted.0)?;
|
||||||
|
bytes.write_u16::<NetworkEndian>(r.port.0)?;
|
||||||
|
},
|
||||||
|
|
||||||
|
Request::Scrape(r) => {
|
||||||
|
bytes.write_i64::<NetworkEndian>(r.connection_id.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(2)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(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<Self, RequestParseError> {
|
||||||
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
|
let connection_id = cursor.read_i64::<NetworkEndian>()
|
||||||
|
.map_err(RequestParseError::io)?;
|
||||||
|
let action = cursor.read_i32::<NetworkEndian>()
|
||||||
|
.map_err(RequestParseError::io)?;
|
||||||
|
let transaction_id = cursor.read_i32::<NetworkEndian>()
|
||||||
|
.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::<NetworkEndian>()
|
||||||
|
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
||||||
|
let bytes_left = cursor.read_i64::<NetworkEndian>()
|
||||||
|
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
||||||
|
let bytes_uploaded = cursor.read_i64::<NetworkEndian>()
|
||||||
|
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
||||||
|
let event = cursor.read_i32::<NetworkEndian>()
|
||||||
|
.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::<NetworkEndian>()
|
||||||
|
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
||||||
|
let peers_wanted = cursor.read_i32::<NetworkEndian>()
|
||||||
|
.map_err(|err| RequestParseError::new(err, transaction_id))?;
|
||||||
|
let port = cursor.read_u16::<NetworkEndian>()
|
||||||
|
.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: quickcheck::Gen>(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: quickcheck::Gen>(g: &mut G) -> Self {
|
||||||
|
Self {
|
||||||
|
transaction_id: TransactionId(i32::arbitrary(g)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl quickcheck::Arbitrary for AnnounceRequest {
|
||||||
|
fn arbitrary<G: quickcheck::Gen>(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: quickcheck::Gen>(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())
|
||||||
|
}
|
||||||
|
}
|
||||||
375
aquatic_udp_protocol/src/response.rs
Normal file
375
aquatic_udp_protocol/src/response.rs
Normal file
|
|
@ -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<ResponsePeer>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
|
pub struct ScrapeResponse {
|
||||||
|
pub transaction_id: TransactionId,
|
||||||
|
pub torrent_stats: Vec<TorrentScrapeStatistics>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[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<ConnectResponse> for Response {
|
||||||
|
fn from(r: ConnectResponse) -> Self {
|
||||||
|
Self::Connect(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<AnnounceResponse> for Response {
|
||||||
|
fn from(r: AnnounceResponse) -> Self {
|
||||||
|
Self::Announce(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ScrapeResponse> for Response {
|
||||||
|
fn from(r: ScrapeResponse) -> Self {
|
||||||
|
Self::Scrape(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ErrorResponse> 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::<NetworkEndian>(0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
bytes.write_i64::<NetworkEndian>(r.connection_id.0)?;
|
||||||
|
},
|
||||||
|
Response::Announce(r) => {
|
||||||
|
if ip_version == IpVersion::IPv4 {
|
||||||
|
bytes.write_i32::<NetworkEndian>(1)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.announce_interval.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.leechers.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(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::<NetworkEndian>(peer.port.0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bytes.write_i32::<NetworkEndian>(4)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.announce_interval.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.leechers.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(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::<NetworkEndian>(peer.port.0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Response::Scrape(r) => {
|
||||||
|
bytes.write_i32::<NetworkEndian>(2)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
|
||||||
|
for torrent_stat in r.torrent_stats {
|
||||||
|
bytes.write_i32::<NetworkEndian>(torrent_stat.seeders.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(torrent_stat.completed.0)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(torrent_stat.leechers.0)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Response::Error(r) => {
|
||||||
|
bytes.write_i32::<NetworkEndian>(3)?;
|
||||||
|
bytes.write_i32::<NetworkEndian>(r.transaction_id.0)?;
|
||||||
|
|
||||||
|
bytes.write_all(r.message.as_bytes())?;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, io::Error> {
|
||||||
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
|
let action = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
let transaction_id = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
|
||||||
|
match action {
|
||||||
|
// Connect
|
||||||
|
0 => {
|
||||||
|
let connection_id = cursor.read_i64::<NetworkEndian>()?;
|
||||||
|
|
||||||
|
Ok((ConnectResponse {
|
||||||
|
connection_id: ConnectionId(connection_id),
|
||||||
|
transaction_id: TransactionId(transaction_id)
|
||||||
|
}).into())
|
||||||
|
},
|
||||||
|
// Announce
|
||||||
|
1 => {
|
||||||
|
let announce_interval = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
let leechers = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
let seeders = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
|
||||||
|
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::<NetworkEndian>().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::<NetworkEndian>().unwrap();
|
||||||
|
let downloads = cursor.read_i32::<NetworkEndian>().unwrap();
|
||||||
|
let leechers = cursor.read_i32::<NetworkEndian>().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::<NetworkEndian>()?;
|
||||||
|
let leechers = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
let seeders = cursor.read_i32::<NetworkEndian>()?;
|
||||||
|
|
||||||
|
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::<NetworkEndian>().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: quickcheck::Gen>(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: quickcheck::Gen>(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: quickcheck::Gen>(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: quickcheck::Gen>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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::*;
|
|
||||||
|
|
@ -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<Ipv4Addr>,
|
|
||||||
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<InfoHash>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
|
||||||
pub enum Request {
|
|
||||||
Connect(ConnectRequest),
|
|
||||||
Announce(AnnounceRequest),
|
|
||||||
Scrape(ScrapeRequest),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<ConnectRequest> for Request {
|
|
||||||
fn from(r: ConnectRequest) -> Self {
|
|
||||||
Self::Connect(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<AnnounceRequest> for Request {
|
|
||||||
fn from(r: AnnounceRequest) -> Self {
|
|
||||||
Self::Announce(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<ScrapeRequest> for Request {
|
|
||||||
fn from(r: ScrapeRequest) -> Self {
|
|
||||||
Self::Scrape(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl quickcheck::Arbitrary for AnnounceEvent {
|
|
||||||
fn arbitrary<G: quickcheck::Gen>(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: quickcheck::Gen>(g: &mut G) -> Self {
|
|
||||||
Self {
|
|
||||||
transaction_id: TransactionId(i32::arbitrary(g)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl quickcheck::Arbitrary for AnnounceRequest {
|
|
||||||
fn arbitrary<G: quickcheck::Gen>(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: quickcheck::Gen>(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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<ResponsePeer>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
|
||||||
pub struct ScrapeResponse {
|
|
||||||
pub transaction_id: TransactionId,
|
|
||||||
pub torrent_stats: Vec<TorrentScrapeStatistics>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<ConnectResponse> for Response {
|
|
||||||
fn from(r: ConnectResponse) -> Self {
|
|
||||||
Self::Connect(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<AnnounceResponse> for Response {
|
|
||||||
fn from(r: AnnounceResponse) -> Self {
|
|
||||||
Self::Announce(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<ScrapeResponse> for Response {
|
|
||||||
fn from(r: ScrapeResponse) -> Self {
|
|
||||||
Self::Scrape(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<ErrorResponse> for Response {
|
|
||||||
fn from(r: ErrorResponse) -> Self {
|
|
||||||
Self::Error(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl quickcheck::Arbitrary for TorrentScrapeStatistics {
|
|
||||||
fn arbitrary<G: quickcheck::Gen>(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: quickcheck::Gen>(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: quickcheck::Gen>(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: quickcheck::Gen>(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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue