diff --git a/src/servers/http/mod.rs b/src/servers/http/mod.rs index b2d232fc6..ba2af3b73 100644 --- a/src/servers/http/mod.rs +++ b/src/servers/http/mod.rs @@ -152,7 +152,7 @@ //! 000000f0: 65 e //! ``` //! -//! Refer to the [`NonCompact`](crate::servers::http::v1::responses::announce::NonCompact) +//! Refer to the [`NonCompact`](crate::servers::http::v1::responses::announce::Normal) //! response for more information about the response. //! //! **Sample compact response** diff --git a/src/servers/http/v1/handlers/announce.rs b/src/servers/http/v1/handlers/announce.rs index 0522042b1..23d8b2d6e 100644 --- a/src/servers/http/v1/handlers/announce.rs +++ b/src/servers/http/v1/handlers/announce.rs @@ -120,10 +120,10 @@ fn build_response(announce_request: &Announce, announce_data: AnnounceData) -> R match &announce_request.compact { Some(compact) => match compact { Compact::Accepted => announce::Compact::from(announce_data).into_response(), - Compact::NotAccepted => announce::NonCompact::from(announce_data).into_response(), + Compact::NotAccepted => announce::Normal::from(announce_data).into_response(), }, // Default response format non compact - None => announce::NonCompact::from(announce_data).into_response(), + None => announce::Normal::from(announce_data).into_response(), } } diff --git a/src/servers/http/v1/requests/announce.rs b/src/servers/http/v1/requests/announce.rs index 7f77f727d..a9add9e37 100644 --- a/src/servers/http/v1/requests/announce.rs +++ b/src/servers/http/v1/requests/announce.rs @@ -180,7 +180,7 @@ impl fmt::Display for Event { /// Depending on the value of this param, the tracker will return a different /// response: /// -/// - [`NonCompact`](crate::servers::http::v1::responses::announce::NonCompact) response. +/// - [`NonCompact`](crate::servers::http::v1::responses::announce::Normal) response. /// - [`Compact`](crate::servers::http::v1::responses::announce::Compact) response. /// /// Refer to [BEP 23. Tracker Returns Compact Peer Lists](https://www.bittorrent.org/beps/bep_0023.html) diff --git a/src/servers/http/v1/responses/announce.rs b/src/servers/http/v1/responses/announce.rs index 8a245476b..8dd71f1ac 100644 --- a/src/servers/http/v1/responses/announce.rs +++ b/src/servers/http/v1/responses/announce.rs @@ -14,33 +14,54 @@ use torrust_tracker_contrib_bencode::{ben_bytes, ben_int, ben_list, ben_map, BMu use crate::core::{self, AnnounceData}; use crate::servers::http::v1::responses; +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Announce { + pub policy: Policy, + pub stats: SwarmStats, + pub peer_list: PeerList, +} + +pub type NormalPeerList = PeerList; +pub type CompactPeerList = PeerList; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct PeerList { + pub peers: Vec, +} + /// Normal (non compact) `announce` response. /// /// It's a bencoded dictionary. /// /// ```rust /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::{NonCompact, Peer}; +/// use torrust_tracker::servers::http::v1::responses::announce::{Normal, NormalPeer, Policy, SwarmStats, NormalPeerList}; /// -/// let response = NonCompact { -/// interval: 111, -/// interval_min: 222, -/// complete: 333, -/// incomplete: 444, -/// peers: vec![ -/// // IPV4 -/// Peer { -/// peer_id: *b"-qB00000000000000001", -/// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 -/// port: 0x7070, // 28784 -/// }, -/// // IPV6 -/// Peer { -/// peer_id: *b"-qB00000000000000002", -/// ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), -/// port: 0x7070, // 28784 -/// }, -/// ], +/// let response = Normal { +/// policy: Policy { +/// interval: 111, +/// interval_min: 222, +/// }, +/// stats: SwarmStats { +/// complete: 333, +/// incomplete: 444, +/// }, +/// peer_list: NormalPeerList { +/// peers: vec![ +/// // IPV4 +/// NormalPeer { +/// peer_id: *b"-qB00000000000000001", +/// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 +/// port: 0x7070, // 28784 +/// }, +/// // IPV6 +/// NormalPeer { +/// peer_id: *b"-qB00000000000000002", +/// ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), +/// port: 0x7070, // 28784 +/// }, +/// ], +/// }, /// }; /// /// let bytes = response.body(); @@ -56,56 +77,23 @@ use crate::servers::http::v1::responses; /// /// Refer to [BEP 03: The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html) /// for more information. -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct NonCompact { - /// Interval in seconds that the client should wait between sending regular - /// announce requests to the tracker. - /// - /// It's a **recommended** wait time between announcements. - /// - /// This is the standard amount of time that clients should wait between - /// sending consecutive announcements to the tracker. This value is set by - /// the tracker and is typically provided in the tracker's response to a - /// client's initial request. It serves as a guideline for clients to know - /// how often they should contact the tracker for updates on the peer list, - /// while ensuring that the tracker is not overwhelmed with requests. - pub interval: u32, - /// Minimum announce interval. Clients must not reannounce more frequently - /// than this. - /// - /// It establishes the shortest allowed wait time. - /// - /// This is an optional parameter in the protocol that the tracker may - /// provide in its response. It sets a lower limit on the frequency at which - /// clients are allowed to send announcements. Clients should respect this - /// value to prevent sending too many requests in a short period, which - /// could lead to excessive load on the tracker or even getting banned by - /// the tracker for not adhering to the rules. - #[serde(rename = "min interval")] - pub interval_min: u32, - /// Number of peers with the entire file, i.e. seeders. - pub complete: u32, - /// Number of non-seeder peers, aka "leechers". - pub incomplete: u32, - /// A list of peers. The value is a list of dictionaries. - pub peers: Vec, -} +pub type Normal = Announce; -/// Peer information in the [`NonCompact`] +/// Peer information in the [`Normal`] /// response. /// /// ```rust /// use std::net::{IpAddr, Ipv4Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::{NonCompact, Peer}; +/// use torrust_tracker::servers::http::v1::responses::announce::{Normal, NormalPeer}; /// -/// let peer = Peer { +/// let peer = NormalPeer { /// peer_id: *b"-qB00000000000000001", /// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 /// port: 0x7070, // 28784 /// }; /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct Peer { +pub struct NormalPeer { /// The peer's ID. pub peer_id: [u8; 20], /// The peer's IP address. @@ -114,7 +102,7 @@ pub struct Peer { pub port: u16, } -impl Peer { +impl NormalPeer { #[must_use] pub fn ben_map(&self) -> BencodeMut<'_> { ben_map! { @@ -125,9 +113,9 @@ impl Peer { } } -impl From for Peer { +impl From for NormalPeer { fn from(peer: core::peer::Peer) -> Self { - Peer { + NormalPeer { peer_id: peer.peer_id.to_bytes(), ip: peer.peer_addr.ip(), port: peer.peer_addr.port(), @@ -135,7 +123,7 @@ impl From for Peer { } } -impl NonCompact { +impl Normal { /// Returns the bencoded body of the non-compact response. /// /// # Panics @@ -145,37 +133,45 @@ impl NonCompact { pub fn body(&self) -> Vec { let mut peers_list = ben_list!(); let peers_list_mut = peers_list.list_mut().unwrap(); - for peer in &self.peers { + for peer in &self.peer_list.peers { peers_list_mut.push(peer.ben_map()); } (ben_map! { - "complete" => ben_int!(i64::from(self.complete)), - "incomplete" => ben_int!(i64::from(self.incomplete)), - "interval" => ben_int!(i64::from(self.interval)), - "min interval" => ben_int!(i64::from(self.interval_min)), + "complete" => ben_int!(i64::from(self.stats.complete)), + "incomplete" => ben_int!(i64::from(self.stats.incomplete)), + "interval" => ben_int!(i64::from(self.policy.interval)), + "min interval" => ben_int!(i64::from(self.policy.interval_min)), "peers" => peers_list.clone() }) .encode() } } -impl IntoResponse for NonCompact { +impl IntoResponse for Normal { fn into_response(self) -> Response { (StatusCode::OK, self.body()).into_response() } } -impl From for NonCompact { +impl From for Normal { fn from(domain_announce_response: AnnounceData) -> Self { - let peers: Vec = domain_announce_response.peers.iter().map(|peer| Peer::from(*peer)).collect(); + let peers: Vec = domain_announce_response + .peers + .iter() + .map(|peer| NormalPeer::from(*peer)) + .collect(); Self { - interval: domain_announce_response.interval, - interval_min: domain_announce_response.interval_min, - complete: domain_announce_response.swarm_stats.seeders, - incomplete: domain_announce_response.swarm_stats.leechers, - peers, + policy: Policy { + interval: domain_announce_response.interval, + interval_min: domain_announce_response.interval_min, + }, + stats: SwarmStats { + complete: domain_announce_response.swarm_stats.seeders, + incomplete: domain_announce_response.swarm_stats.leechers, + }, + peer_list: PeerList { peers }, } } } @@ -188,25 +184,31 @@ impl From for NonCompact { /// /// ```rust /// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::{Compact, CompactPeer}; +/// use torrust_tracker::servers::http::v1::responses::announce::{Compact, CompactPeer, Policy, SwarmStats, CompactPeerList}; /// /// let response = Compact { -/// interval: 111, -/// interval_min: 222, -/// complete: 333, -/// incomplete: 444, -/// peers: vec![ -/// // IPV4 -/// CompactPeer { -/// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 -/// port: 0x7070, // 28784 -/// }, -/// // IPV6 -/// CompactPeer { -/// ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), -/// port: 0x7070, // 28784 -/// }, -/// ], +/// policy: Policy { +/// interval: 111, +/// interval_min: 222, +/// }, +/// stats: SwarmStats { +/// complete: 333, +/// incomplete: 444, +/// }, +/// peer_list: CompactPeerList { +/// peers: vec![ +/// // IPV4 +/// CompactPeer { +/// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 +/// port: 0x7070, // 28784 +/// }, +/// // IPV6 +/// CompactPeer { +/// ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), +/// port: 0x7070, // 28784 +/// }, +/// ], +/// }, /// }; /// /// let bytes = response.body().unwrap(); @@ -226,40 +228,7 @@ impl From for NonCompact { /// /// - [BEP 23: Tracker Returns Compact Peer Lists](https://www.bittorrent.org/beps/bep_0023.html) /// - [BEP 07: IPv6 Tracker Extension](https://www.bittorrent.org/beps/bep_0007.html) -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct Compact { - /// Interval in seconds that the client should wait between sending regular - /// announce requests to the tracker. - /// - /// It's a **recommended** wait time between announcements. - /// - /// This is the standard amount of time that clients should wait between - /// sending consecutive announcements to the tracker. This value is set by - /// the tracker and is typically provided in the tracker's response to a - /// client's initial request. It serves as a guideline for clients to know - /// how often they should contact the tracker for updates on the peer list, - /// while ensuring that the tracker is not overwhelmed with requests. - pub interval: u32, - /// Minimum announce interval. Clients must not reannounce more frequently - /// than this. - /// - /// It establishes the shortest allowed wait time. - /// - /// This is an optional parameter in the protocol that the tracker may - /// provide in its response. It sets a lower limit on the frequency at which - /// clients are allowed to send announcements. Clients should respect this - /// value to prevent sending too many requests in a short period, which - /// could lead to excessive load on the tracker or even getting banned by - /// the tracker for not adhering to the rules. - #[serde(rename = "min interval")] - pub interval_min: u32, - /// Number of seeders, aka "completed". - pub complete: u32, - /// Number of non-seeder peers, aka "incomplete". - pub incomplete: u32, - /// Compact peer list. - pub peers: Vec, -} +pub type Compact = Announce; /// Compact peer. It's used in the [`Compact`] /// response. @@ -329,10 +298,10 @@ impl Compact { /// Will return `Err` if internally interrupted. pub fn body(&self) -> Result, Box> { let bytes = (ben_map! { - "complete" => ben_int!(i64::from(self.complete)), - "incomplete" => ben_int!(i64::from(self.incomplete)), - "interval" => ben_int!(i64::from(self.interval)), - "min interval" => ben_int!(i64::from(self.interval_min)), + "complete" => ben_int!(i64::from(self.stats.complete)), + "incomplete" => ben_int!(i64::from(self.stats.incomplete)), + "interval" => ben_int!(i64::from(self.policy.interval)), + "min interval" => ben_int!(i64::from(self.policy.interval_min)), "peers" => ben_bytes!(self.peers_v4_bytes()?), "peers6" => ben_bytes!(self.peers_v6_bytes()?) }) @@ -343,7 +312,7 @@ impl Compact { fn peers_v4_bytes(&self) -> Result, Box> { let mut bytes: Vec = Vec::new(); - for compact_peer in &self.peers { + for compact_peer in &self.peer_list.peers { match compact_peer.ip { IpAddr::V4(_ip) => { let peer_bytes = compact_peer.bytes()?; @@ -357,7 +326,7 @@ impl Compact { fn peers_v6_bytes(&self) -> Result, Box> { let mut bytes: Vec = Vec::new(); - for compact_peer in &self.peers { + for compact_peer in &self.peer_list.peers { match compact_peer.ip { IpAddr::V6(_ip) => { let peer_bytes = compact_peer.bytes()?; @@ -410,22 +379,68 @@ impl From for Compact { .collect(); Self { - interval: domain_announce_response.interval, - interval_min: domain_announce_response.interval_min, - complete: domain_announce_response.swarm_stats.seeders, - incomplete: domain_announce_response.swarm_stats.leechers, - peers, + policy: Policy { + interval: domain_announce_response.interval, + interval_min: domain_announce_response.interval_min, + }, + stats: SwarmStats { + complete: domain_announce_response.swarm_stats.seeders, + incomplete: domain_announce_response.swarm_stats.leechers, + }, + peer_list: CompactPeerList { peers }, } } } +/// Announce policy +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Policy { + /// Interval in seconds that the client should wait between sending regular + /// announce requests to the tracker. + /// + /// It's a **recommended** wait time between announcements. + /// + /// This is the standard amount of time that clients should wait between + /// sending consecutive announcements to the tracker. This value is set by + /// the tracker and is typically provided in the tracker's response to a + /// client's initial request. It serves as a guideline for clients to know + /// how often they should contact the tracker for updates on the peer list, + /// while ensuring that the tracker is not overwhelmed with requests. + pub interval: u32, + + /// Minimum announce interval. Clients must not reannounce more frequently + /// than this. + /// + /// It establishes the shortest allowed wait time. + /// + /// This is an optional parameter in the protocol that the tracker may + /// provide in its response. It sets a lower limit on the frequency at which + /// clients are allowed to send announcements. Clients should respect this + /// value to prevent sending too many requests in a short period, which + /// could lead to excessive load on the tracker or even getting banned by + /// the tracker for not adhering to the rules. + #[serde(rename = "min interval")] + pub interval_min: u32, +} + +/// Announce policy +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct SwarmStats { + /// Number of seeders, aka "completed". + pub complete: u32, + /// Number of non-seeder peers, aka "incomplete". + pub incomplete: u32, +} + #[cfg(test)] mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - use super::{NonCompact, Peer}; - use crate::servers::http::v1::responses::announce::{Compact, CompactPeer}; + use super::{Normal, NormalPeer}; + use crate::servers::http::v1::responses::announce::{ + Compact, CompactPeer, CompactPeerList, NormalPeerList, Policy, SwarmStats, + }; // Some ascii values used in tests: // @@ -441,25 +456,31 @@ mod tests { #[test] fn non_compact_announce_response_can_be_bencoded() { - let response = NonCompact { - interval: 111, - interval_min: 222, - complete: 333, - incomplete: 444, - peers: vec![ - // IPV4 - Peer { - peer_id: *b"-qB00000000000000001", - ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 - port: 0x7070, // 28784 - }, - // IPV6 - Peer { - peer_id: *b"-qB00000000000000002", - ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), - port: 0x7070, // 28784 - }, - ], + let response = Normal { + policy: Policy { + interval: 111, + interval_min: 222, + }, + stats: SwarmStats { + complete: 333, + incomplete: 444, + }, + peer_list: NormalPeerList { + peers: vec![ + // IPV4 + NormalPeer { + peer_id: *b"-qB00000000000000001", + ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 + port: 0x7070, // 28784 + }, + // IPV6 + NormalPeer { + peer_id: *b"-qB00000000000000002", + ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), + port: 0x7070, // 28784 + }, + ], + }, }; let bytes = response.body(); @@ -476,22 +497,28 @@ mod tests { #[test] fn compact_announce_response_can_be_bencoded() { let response = Compact { - interval: 111, - interval_min: 222, - complete: 333, - incomplete: 444, - peers: vec![ - // IPV4 - CompactPeer { - ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 - port: 0x7070, // 28784 - }, - // IPV6 - CompactPeer { - ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), - port: 0x7070, // 28784 - }, - ], + policy: Policy { + interval: 111, + interval_min: 222, + }, + stats: SwarmStats { + complete: 333, + incomplete: 444, + }, + peer_list: CompactPeerList { + peers: vec![ + // IPV4 + CompactPeer { + ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 + port: 0x7070, // 28784 + }, + // IPV6 + CompactPeer { + ip: IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), + port: 0x7070, // 28784 + }, + ], + }, }; let bytes = response.body().unwrap();