diff --git a/Cargo.lock b/Cargo.lock index 9b7c10f39..de630b497 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3462,6 +3462,7 @@ name = "torrust-tracker-configuration" version = "3.0.0-alpha.12-develop" dependencies = [ "config", + "derive_more", "log", "serde", "serde_with", diff --git a/Cargo.toml b/Cargo.toml index 64f913e4f..daf3c0259 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,14 @@ serde_urlencoded = "0" torrust-tracker-test-helpers = { version = "3.0.0-alpha.12-develop", path = "packages/test-helpers" } [workspace] -members = ["contrib/bencode", "packages/configuration", "packages/located-error", "packages/primitives", "packages/test-helpers", "packages/torrent-repository-benchmarks"] +members = [ + "contrib/bencode", + "packages/configuration", + "packages/located-error", + "packages/primitives", + "packages/test-helpers", + "packages/torrent-repository-benchmarks", +] [profile.dev] debug = 1 diff --git a/cSpell.json b/cSpell.json index 9602ba39b..7b3ce4de9 100644 --- a/cSpell.json +++ b/cSpell.json @@ -50,6 +50,7 @@ "Hydranode", "Icelake", "imdl", + "impls", "incompletei", "infohash", "infohashes", diff --git a/packages/configuration/Cargo.toml b/packages/configuration/Cargo.toml index e373b4269..ecc8c976e 100644 --- a/packages/configuration/Cargo.toml +++ b/packages/configuration/Cargo.toml @@ -16,6 +16,7 @@ version.workspace = true [dependencies] config = "0" +derive_more = "0" log = { version = "0", features = ["release_max_level_info"] } serde = { version = "1", features = ["derive"] } serde_with = "3" diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 1c0979524..a8f605289 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -236,6 +236,7 @@ use std::sync::Arc; use std::{env, fs}; use config::{Config, ConfigError, File, FileFormat}; +use derive_more::Constructor; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, NoneAsEmptyString}; use thiserror::Error; @@ -387,26 +388,9 @@ pub struct HealthCheckApi { pub bind_address: String, } -/// Core configuration for the tracker. -#[allow(clippy::struct_excessive_bools)] -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] -pub struct Configuration { - /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, - /// `Debug` and `Trace`. Default is `Info`. - pub log_level: Option, - /// Tracker mode. See [`TrackerMode`] for more information. - pub mode: TrackerMode, - - // Database configuration - /// Database driver. Possible values are: `Sqlite3`, and `MySQL`. - pub db_driver: DatabaseDriver, - /// Database connection string. The format depends on the database driver. - /// For `Sqlite3`, the format is `path/to/database.db`, for example: - /// `./storage/tracker/lib/database/sqlite3.db`. - /// For `Mysql`, the format is `mysql://db_user:db_user_password:port/db_name`, for - /// example: `root:password@localhost:3306/torrust`. - pub db_path: String, - +/// Announce policy +#[derive(PartialEq, Eq, Debug, Clone, Copy, Constructor)] +pub struct AnnouncePolicy { /// Interval in seconds that the client should wait between sending regular /// announce requests to the tracker. /// @@ -418,7 +402,8 @@ pub struct Configuration { /// 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 announce_interval: u32, + pub interval: u32, + /// Minimum announce interval. Clients must not reannounce more frequently /// than this. /// @@ -430,6 +415,42 @@ pub struct Configuration { /// 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. + pub interval_min: u32, +} + +impl Default for AnnouncePolicy { + fn default() -> Self { + Self { + interval: 120, + interval_min: 120, + } + } +} + +/// Core configuration for the tracker. +#[allow(clippy::struct_excessive_bools)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +pub struct Configuration { + /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, + /// `Debug` and `Trace`. Default is `Info`. + pub log_level: Option, + /// Tracker mode. See [`TrackerMode`] for more information. + pub mode: TrackerMode, + + // Database configuration + /// Database driver. Possible values are: `Sqlite3`, and `MySQL`. + pub db_driver: DatabaseDriver, + /// Database connection string. The format depends on the database driver. + /// For `Sqlite3`, the format is `path/to/database.db`, for example: + /// `./storage/tracker/lib/database/sqlite3.db`. + /// For `Mysql`, the format is `mysql://db_user:db_user_password:port/db_name`, for + /// example: `root:password@localhost:3306/torrust`. + pub db_path: String, + + /// See [`AnnouncePolicy::interval`] + pub announce_interval: u32, + + /// See [`AnnouncePolicy::interval_min`] pub min_announce_interval: u32, /// Weather the tracker is behind a reverse proxy or not. /// If the tracker is behind a reverse proxy, the `X-Forwarded-For` header @@ -516,13 +537,15 @@ impl From for Error { impl Default for Configuration { fn default() -> Self { + let announce_policy = AnnouncePolicy::default(); + let mut configuration = Configuration { log_level: Option::from(String::from("info")), mode: TrackerMode::Public, db_driver: DatabaseDriver::Sqlite3, db_path: String::from("./storage/tracker/lib/database/sqlite3.db"), - announce_interval: 120, - min_announce_interval: 120, + announce_interval: announce_policy.interval, + min_announce_interval: announce_policy.interval_min, max_peer_timeout: 900, on_reverse_proxy: false, external_ip: Some(String::from("0.0.0.0")), diff --git a/src/core/databases/mod.rs b/src/core/databases/mod.rs index 14fcb6b5b..b80b11987 100644 --- a/src/core/databases/mod.rs +++ b/src/core/databases/mod.rs @@ -134,7 +134,7 @@ pub trait Database: Sync + Send { /// # Errors /// /// Will return `Err` if unable to save. - async fn save_persistent_torrent(&self, info_hash: &InfoHash, completed: u32) -> Result<(), Error>; + async fn save_persistent_torrent(&self, info_hash: &InfoHash, downloaded: u32) -> Result<(), Error>; // Whitelist diff --git a/src/core/mod.rs b/src/core/mod.rs index beb4b133d..fc44877c8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -98,12 +98,12 @@ //! //! ```rust,no_run //! use torrust_tracker::core::peer::Peer; +//! use torrust_tracker_configuration::AnnouncePolicy; //! //! pub struct AnnounceData { //! pub peers: Vec, //! pub swarm_stats: SwarmStats, -//! pub interval: u32, // Option `announce_interval` from core tracker configuration -//! pub interval_min: u32, // Option `min_announce_interval` from core tracker configuration +//! pub policy: AnnouncePolicy, // the tracker announce policy. //! } //! //! pub struct SwarmStats { @@ -445,9 +445,10 @@ use std::panic::Location; use std::sync::Arc; use std::time::Duration; +use derive_more::Constructor; use futures::future::join_all; use tokio::sync::mpsc::error::SendError; -use torrust_tracker_configuration::Configuration; +use torrust_tracker_configuration::{AnnouncePolicy, Configuration}; use torrust_tracker_primitives::TrackerMode; use self::auth::Key; @@ -487,7 +488,7 @@ pub struct Tracker { /// Structure that holds general `Tracker` torrents metrics. /// /// Metrics are aggregate values for all torrents. -#[derive(Debug, PartialEq, Default)] +#[derive(Copy, Clone, Debug, PartialEq, Default)] pub struct TorrentsMetrics { /// Total number of seeders for all torrents pub seeders: u64, @@ -500,20 +501,14 @@ pub struct TorrentsMetrics { } /// Structure that holds the data returned by the `announce` request. -#[derive(Debug, PartialEq, Default)] +#[derive(Clone, Debug, PartialEq, Constructor, Default)] pub struct AnnounceData { /// The list of peers that are downloading the same torrent. /// It excludes the peer that made the request. pub peers: Vec, /// Swarm statistics - pub swarm_stats: SwarmStats, - /// The interval in seconds that the client should wait between sending - /// regular requests to the tracker. - /// Refer to [`announce_interval`](torrust_tracker_configuration::Configuration::announce_interval). - pub interval: u32, - /// The minimum announce interval in seconds that the client should wait. - /// Refer to [`min_announce_interval`](torrust_tracker_configuration::Configuration::min_announce_interval). - pub interval_min: u32, + pub stats: SwarmStats, + pub policy: AnnouncePolicy, } /// Structure that holds the data returned by the `scrape` request. @@ -628,11 +623,12 @@ impl Tracker { let peers = self.get_torrent_peers_for_peer(info_hash, peer).await; + let policy = AnnouncePolicy::new(self.config.announce_interval, self.config.min_announce_interval); + AnnounceData { peers, - swarm_stats, - interval: self.config.announce_interval, - interval_min: self.config.min_announce_interval, + stats: swarm_stats, + policy, } } @@ -732,7 +728,7 @@ impl Tracker { let (stats, stats_updated) = self.torrents.update_torrent_with_peer_and_get_stats(info_hash, peer).await; if self.config.persistent_torrent_completed_stat && stats_updated { - let completed = stats.completed; + let completed = stats.downloaded; let info_hash = *info_hash; drop(self.database.save_persistent_torrent(&info_hash, completed).await); @@ -1390,7 +1386,7 @@ mod tests { let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip()).await; - assert_eq!(announce_data.swarm_stats.seeders, 1); + assert_eq!(announce_data.stats.complete, 1); } #[tokio::test] @@ -1401,7 +1397,7 @@ mod tests { let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip()).await; - assert_eq!(announce_data.swarm_stats.leechers, 1); + assert_eq!(announce_data.stats.incomplete, 1); } #[tokio::test] @@ -1415,7 +1411,7 @@ mod tests { let mut completed_peer = completed_peer(); let announce_data = tracker.announce(&sample_info_hash(), &mut completed_peer, &peer_ip()).await; - assert_eq!(announce_data.swarm_stats.completed, 1); + assert_eq!(announce_data.stats.downloaded, 1); } } } @@ -1739,11 +1735,11 @@ mod tests { peer.event = AnnounceEvent::Started; let swarm_stats = tracker.update_torrent_with_peer_and_get_stats(&info_hash, &peer).await; - assert_eq!(swarm_stats.completed, 0); + assert_eq!(swarm_stats.downloaded, 0); peer.event = AnnounceEvent::Completed; let swarm_stats = tracker.update_torrent_with_peer_and_get_stats(&info_hash, &peer).await; - assert_eq!(swarm_stats.completed, 1); + assert_eq!(swarm_stats.downloaded, 1); // Remove the newly updated torrent from memory tracker.torrents.get_torrents_mut().await.remove(&info_hash); diff --git a/src/core/peer.rs b/src/core/peer.rs index a64f87b66..03489ce30 100644 --- a/src/core/peer.rs +++ b/src/core/peer.rs @@ -277,9 +277,85 @@ impl Serialize for Id { } } -#[cfg(test)] -mod test { +pub mod fixture { + use std::net::SocketAddr; + + use aquatic_udp_protocol::NumberOfBytes; + + use super::{Id, Peer}; + + #[derive(PartialEq, Debug)] + + pub struct PeerBuilder { + peer: Peer, + } + + #[allow(clippy::derivable_impls)] + impl Default for PeerBuilder { + fn default() -> Self { + Self { peer: Peer::default() } + } + } + + impl PeerBuilder { + #[allow(dead_code)] + #[must_use] + pub fn with_peer_id(mut self, peer_id: &Id) -> Self { + self.peer.peer_id = *peer_id; + self + } + + #[allow(dead_code)] + #[must_use] + pub fn with_peer_addr(mut self, peer_addr: &SocketAddr) -> Self { + self.peer.peer_addr = *peer_addr; + self + } + + #[allow(dead_code)] + #[must_use] + pub fn with_bytes_pending_to_download(mut self, left: i64) -> Self { + self.peer.left = NumberOfBytes(left); + self + } + #[allow(dead_code)] + #[must_use] + pub fn with_no_bytes_pending_to_download(mut self) -> Self { + self.peer.left = NumberOfBytes(0); + self + } + + #[allow(dead_code)] + #[must_use] + pub fn build(self) -> Peer { + self.into() + } + + #[allow(dead_code)] + #[must_use] + pub fn into(self) -> Peer { + self.peer + } + } + + impl Default for Peer { + fn default() -> Self { + Self { + peer_id: Id(*b"-qB00000000000000000"), + peer_addr: std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(126, 0, 0, 1)), 8080), + updated: crate::shared::clock::DurationSinceUnixEpoch::new(1_669_397_478_934, 0), + uploaded: NumberOfBytes(0), + downloaded: NumberOfBytes(0), + left: NumberOfBytes(0), + event: aquatic_udp_protocol::AnnounceEvent::Started, + } + } + } +} + +#[cfg(test)] +pub mod test { mod torrent_peer_id { use crate::core::peer; diff --git a/src/core/torrent/mod.rs b/src/core/torrent/mod.rs index 79828d368..d19a97be1 100644 --- a/src/core/torrent/mod.rs +++ b/src/core/torrent/mod.rs @@ -33,6 +33,7 @@ pub mod repository; use std::time::Duration; use aquatic_udp_protocol::AnnounceEvent; +use derive_more::Constructor; use serde::{Deserialize, Serialize}; use super::peer::{self, Peer}; @@ -56,13 +57,13 @@ pub struct Entry { /// Swarm metadata dictionary in the scrape response. /// /// See [BEP 48: Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html) -#[derive(Debug, PartialEq, Default)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Constructor)] pub struct SwarmMetadata { - /// The number of peers that have ever completed downloading - pub downloaded: u32, - /// The number of active peers that have completed downloading (seeders) - pub complete: u32, - /// The number of active peers that have not completed downloading (leechers) + /// (i.e `completed`): The number of peers that have ever completed downloading + pub downloaded: u32, // + /// (i.e `seeders`): The number of active peers that have completed downloading (seeders) + pub complete: u32, //seeders + /// (i.e `leechers`): The number of active peers that have not completed downloading (leechers) pub incomplete: u32, } @@ -73,18 +74,8 @@ impl SwarmMetadata { } } -/// Swarm statistics for one torrent. -/// -/// See [BEP 48: Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html) -#[derive(Debug, PartialEq, Default)] -pub struct SwarmStats { - /// The number of peers that have ever completed downloading - pub completed: u32, - /// The number of active peers that have completed downloading (seeders) - pub seeders: u32, - /// The number of active peers that have not completed downloading (leechers) - pub leechers: u32, -} +/// [`SwarmStats`] has the same form as [`SwarmMetadata`] +pub type SwarmStats = SwarmMetadata; impl Entry { #[must_use] diff --git a/src/core/torrent/repository.rs b/src/core/torrent/repository.rs index ac3d03054..d4f8ee5e3 100644 --- a/src/core/torrent/repository.rs +++ b/src/core/torrent/repository.rs @@ -77,9 +77,9 @@ impl Repository for Sync { ( SwarmStats { - completed: stats.1, - seeders: stats.0, - leechers: stats.2, + downloaded: stats.1, + complete: stats.0, + incomplete: stats.2, }, stats_updated, ) @@ -131,9 +131,9 @@ impl Repository for SyncSingle { ( SwarmStats { - completed: stats.1, - seeders: stats.0, - leechers: stats.2, + downloaded: stats.1, + complete: stats.0, + incomplete: stats.2, }, stats_updated, ) @@ -176,9 +176,9 @@ impl TRepositoryAsync for RepositoryAsync { ( SwarmStats { - completed: stats.1, - seeders: stats.0, - leechers: stats.2, + downloaded: stats.1, + complete: stats.0, + incomplete: stats.2, }, stats_updated, ) @@ -234,9 +234,9 @@ impl TRepositoryAsync for AsyncSync { ( SwarmStats { - completed: stats.1, - seeders: stats.0, - leechers: stats.2, + downloaded: stats.1, + complete: stats.0, + incomplete: stats.2, }, stats_updated, ) @@ -281,9 +281,9 @@ impl TRepositoryAsync for RepositoryAsyncSingle { ( SwarmStats { - completed: stats.1, - seeders: stats.0, - leechers: stats.2, + downloaded: stats.1, + complete: stats.0, + incomplete: stats.2, }, stats_updated, ) diff --git a/src/servers/http/mod.rs b/src/servers/http/mod.rs index b2d232fc6..08a59ef90 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 [`Normal`](crate::servers::http::v1::responses::announce::Normal), i.e. `Non-Compact` //! 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..cfe422e7f 100644 --- a/src/servers/http/v1/handlers/announce.rs +++ b/src/servers/http/v1/handlers/announce.rs @@ -22,7 +22,7 @@ use crate::servers::http::v1::extractors::authentication_key::Extract as Extract use crate::servers::http::v1::extractors::client_ip_sources::Extract as ExtractClientIpSources; use crate::servers::http::v1::handlers::common::auth; use crate::servers::http::v1::requests::announce::{Announce, Compact, Event}; -use crate::servers::http::v1::responses::{self, announce}; +use crate::servers::http::v1::responses::{self}; use crate::servers::http::v1::services::peer_ip_resolver::ClientIpSources; use crate::servers::http::v1::services::{self, peer_ip_resolver}; use crate::shared::clock::{Current, Time}; @@ -117,13 +117,12 @@ async fn handle_announce( } fn build_response(announce_request: &Announce, announce_data: AnnounceData) -> Response { - 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(), - }, - // Default response format non compact - None => announce::NonCompact::from(announce_data).into_response(), + if announce_request.compact.as_ref().is_some_and(|f| *f == Compact::Accepted) { + let response: responses::Announce = announce_data.into(); + response.into_response() + } else { + let response: responses::Announce = announce_data.into(); + response.into_response() } } diff --git a/src/servers/http/v1/requests/announce.rs b/src/servers/http/v1/requests/announce.rs index 7f77f727d..08dd9da29 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. +/// - [`Normal`](crate::servers::http::v1::responses::announce::Normal), i.e. a `non-compact` 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..b1b474ea9 100644 --- a/src/servers/http/v1/responses/announce.rs +++ b/src/servers/http/v1/responses/announce.rs @@ -2,269 +2,217 @@ //! //! Data structures and logic to build the `announce` response. use std::io::Write; -use std::net::IpAddr; -use std::panic::Location; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; -use serde::{self, Deserialize, Serialize}; -use thiserror::Error; +use derive_more::{AsRef, Constructor, From}; use torrust_tracker_contrib_bencode::{ben_bytes, ben_int, ben_list, ben_map, BMutAccess, BencodeMut}; +use super::Response; +use crate::core::peer::Peer; use crate::core::{self, AnnounceData}; use crate::servers::http::v1::responses; -/// Normal (non compact) `announce` response. +/// An [`Announce`] response, that can be anything that is convertible from [`AnnounceData`]. /// -/// It's a bencoded dictionary. +/// The [`Announce`] can built from any data that implements: [`From`] and [`Into>`]. /// -/// ```rust -/// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::{NonCompact, Peer}; -/// -/// 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 -/// }, -/// ], -/// }; +/// The two standard forms of an announce response are: [`Normal`] and [`Compact`]. /// -/// let bytes = response.body(); /// -/// // The expected bencoded response. -/// let expected_bytes = b"d8:completei333e10:incompletei444e8:intervali111e12:min intervali222e5:peersld2:ip15:105.105.105.1057:peer id20:-qB000000000000000014:porti28784eed2:ip39:6969:6969:6969:6969:6969:6969:6969:69697:peer id20:-qB000000000000000024:porti28784eeee"; +/// _"To reduce the size of tracker responses and to reduce memory and +/// computational requirements in trackers, trackers may return peers as a +/// packed string rather than as a bencoded list."_ /// -/// assert_eq!( -/// String::from_utf8(bytes).unwrap(), -/// String::from_utf8(expected_bytes.to_vec()).unwrap() -/// ); -/// ``` +/// Refer to the official BEPs for more information: /// -/// 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, +/// - [BEP 03: The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html) +/// - [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(Debug, AsRef, PartialEq, Constructor)] +pub struct Announce +where + E: From + Into>, +{ + data: E, } -/// Peer information in the [`NonCompact`] -/// response. -/// -/// ```rust -/// use std::net::{IpAddr, Ipv4Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::{NonCompact, Peer}; -/// -/// let peer = Peer { -/// 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 { - /// The peer's ID. - pub peer_id: [u8; 20], - /// The peer's IP address. - pub ip: IpAddr, - /// The peer's port number. - pub port: u16, +/// Build any [`Announce`] from an [`AnnounceData`]. +impl + Into>> From for Announce { + fn from(data: AnnounceData) -> Self { + Self::new(data.into()) + } } -impl Peer { - #[must_use] - pub fn ben_map(&self) -> BencodeMut<'_> { - ben_map! { - "peer id" => ben_bytes!(self.peer_id.clone().to_vec()), - "ip" => ben_bytes!(self.ip.to_string()), - "port" => ben_int!(i64::from(self.port)) - } +/// Convert any Announce [`Announce`] into a [`axum::response::Response`] +impl + Into>> axum::response::IntoResponse for Announce +where + Announce: Response, +{ + fn into_response(self) -> axum::response::Response { + axum::response::IntoResponse::into_response(self.body().map(|bytes| (StatusCode::OK, bytes))) } } -impl From for Peer { - fn from(peer: core::peer::Peer) -> Self { - Peer { - peer_id: peer.peer_id.to_bytes(), - ip: peer.peer_addr.ip(), - port: peer.peer_addr.port(), +/// Implement the [`Response`] for the [`Announce`]. +/// +impl + Into>> Response for Announce { + fn body(self) -> Result, responses::error::Error> { + Ok(self.data.into()) + } +} + +/// Format of the [`Normal`] (Non-Compact) Encoding +pub struct Normal { + complete: i64, + incomplete: i64, + interval: i64, + min_interval: i64, + peers: Vec, +} + +impl From for Normal { + fn from(data: AnnounceData) -> Self { + Self { + complete: data.stats.complete.into(), + incomplete: data.stats.incomplete.into(), + interval: data.policy.interval.into(), + min_interval: data.policy.interval_min.into(), + peers: data.peers.into_iter().collect(), } } } -impl NonCompact { - /// Returns the bencoded body of the non-compact response. - /// - /// # Panics - /// - /// Will return an error if it can't access the bencode as a mutable `BListAccess`. - #[must_use] - pub fn body(&self) -> Vec { +#[allow(clippy::from_over_into)] +impl Into> for Normal { + fn into(self) -> Vec { let mut peers_list = ben_list!(); let peers_list_mut = peers_list.list_mut().unwrap(); for peer in &self.peers { - peers_list_mut.push(peer.ben_map()); + peers_list_mut.push(peer.into()); } (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!(self.complete), + "incomplete" => ben_int!(self.incomplete), + "interval" => ben_int!(self.interval), + "min interval" => ben_int!(self.min_interval), "peers" => peers_list.clone() }) .encode() } } -impl IntoResponse for NonCompact { - fn into_response(self) -> Response { - (StatusCode::OK, self.body()).into_response() - } +/// Format of the [`Compact`] Encoding +pub struct Compact { + complete: i64, + incomplete: i64, + interval: i64, + min_interval: i64, + peers: Vec, + peers6: Vec, } -impl From for NonCompact { - fn from(domain_announce_response: AnnounceData) -> Self { - let peers: Vec = domain_announce_response.peers.iter().map(|peer| Peer::from(*peer)).collect(); +impl From for Compact { + fn from(data: AnnounceData) -> Self { + let compact_peers: Vec = data.peers.into_iter().collect(); + + let (peers, peers6): (Vec>, Vec>) = + compact_peers.into_iter().collect(); + + let peers_encoded: CompactPeersEncoded = peers.into_iter().collect(); + let peers_encoded_6: CompactPeersEncoded = peers6.into_iter().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, + complete: data.stats.complete.into(), + incomplete: data.stats.incomplete.into(), + interval: data.policy.interval.into(), + min_interval: data.policy.interval_min.into(), + peers: peers_encoded.0, + peers6: peers_encoded_6.0, } } } -/// Compact `announce` response. -/// -/// _"To reduce the size of tracker responses and to reduce memory and -/// computational requirements in trackers, trackers may return peers as a -/// packed string rather than as a bencoded list."_ +#[allow(clippy::from_over_into)] +impl Into> for Compact { + fn into(self) -> Vec { + (ben_map! { + "complete" => ben_int!(self.complete), + "incomplete" => ben_int!(self.incomplete), + "interval" => ben_int!(self.interval), + "min interval" => ben_int!(self.min_interval), + "peers" => ben_bytes!(self.peers), + "peers6" => ben_bytes!(self.peers6) + }) + .encode() + } +} + +/// Marker Trait for Peer Vectors +pub trait PeerEncoding: From + PartialEq {} + +impl FromIterator for Vec

{ + fn from_iter>(iter: T) -> Self { + let mut peers: Vec

= vec![]; + + for peer in iter { + peers.push(peer.into()); + } + + peers + } +} + +/// A [`NormalPeer`], for the [`Normal`] form. /// /// ```rust -/// use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::{Compact, CompactPeer}; +/// use std::net::{IpAddr, Ipv4Addr}; +/// use torrust_tracker::servers::http::v1::responses::announce::{Normal, NormalPeer}; /// -/// 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 -/// }, -/// ], +/// let peer = NormalPeer { +/// peer_id: *b"-qB00000000000000001", +/// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 +/// port: 0x7070, // 28784 /// }; /// -/// let bytes = response.body().unwrap(); -/// -/// // The expected bencoded response. -/// let expected_bytes = -/// // cspell:disable-next-line -/// b"d8:completei333e10:incompletei444e8:intervali111e12:min intervali222e5:peers6:iiiipp6:peers618:iiiiiiiiiiiiiiiippe"; -/// -/// assert_eq!( -/// String::from_utf8(bytes).unwrap(), -/// String::from_utf8(expected_bytes.to_vec()).unwrap() -/// ); -/// ``` -/// -/// Refer to the official BEPs for more information: -/// -/// - [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, +/// ``` +#[derive(Debug, PartialEq)] +pub struct NormalPeer { + /// The peer's ID. + pub peer_id: [u8; 20], + /// The peer's IP address. + pub ip: IpAddr, + /// The peer's port number. + pub port: u16, +} + +impl PeerEncoding for NormalPeer {} + +impl From for NormalPeer { + fn from(peer: core::peer::Peer) -> Self { + NormalPeer { + peer_id: peer.peer_id.to_bytes(), + ip: peer.peer_addr.ip(), + port: peer.peer_addr.port(), + } + } } -/// Compact peer. It's used in the [`Compact`] -/// response. +impl From<&NormalPeer> for BencodeMut<'_> { + fn from(value: &NormalPeer) -> Self { + ben_map! { + "peer id" => ben_bytes!(value.peer_id.clone().to_vec()), + "ip" => ben_bytes!(value.ip.to_string()), + "port" => ben_int!(i64::from(value.port)) + } + } +} + +/// A [`CompactPeer`], for the [`Compact`] form. /// -/// _"To reduce the size of tracker responses and to reduce memory and +/// _"To reduce the size of tracker responses and to reduce memory and /// computational requirements in trackers, trackers may return peers as a /// packed string rather than as a bencoded list."_ /// @@ -272,160 +220,107 @@ pub struct Compact { /// the peer's ID. /// /// ```rust -/// use std::net::{IpAddr, Ipv4Addr}; -/// use torrust_tracker::servers::http::v1::responses::announce::CompactPeer; +/// use std::net::{IpAddr, Ipv4Addr}; +/// use torrust_tracker::servers::http::v1::responses::announce::{Compact, CompactPeer, CompactPeerData}; /// -/// let compact_peer = CompactPeer { -/// ip: IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), // 105.105.105.105 -/// port: 0x7070 // 28784 -/// }; -/// ``` +/// let peer = CompactPeer::V4(CompactPeerData { +/// ip: Ipv4Addr::new(0x69, 0x69, 0x69, 0x69), // 105.105.105.105 +/// port: 0x7070, // 28784 +/// }); +/// +/// ``` /// /// Refer to [BEP 23: Tracker Returns Compact Peer Lists](https://www.bittorrent.org/beps/bep_0023.html) /// for more information. -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct CompactPeer { +#[derive(Clone, Debug, PartialEq)] +pub enum CompactPeer { /// The peer's IP address. - pub ip: IpAddr, + V4(CompactPeerData), /// The peer's port number. - pub port: u16, + V6(CompactPeerData), } -impl CompactPeer { - /// Returns the compact peer as a byte vector. - /// - /// # Errors - /// - /// Will return `Err` if internally interrupted. - pub fn bytes(&self) -> Result, Box> { - let mut bytes: Vec = Vec::new(); - match self.ip { - IpAddr::V4(ip) => { - bytes.write_all(&u32::from(ip).to_be_bytes())?; - } - IpAddr::V6(ip) => { - bytes.write_all(&u128::from(ip).to_be_bytes())?; - } - } - bytes.write_all(&self.port.to_be_bytes())?; - Ok(bytes) - } -} +impl PeerEncoding for CompactPeer {} impl From for CompactPeer { fn from(peer: core::peer::Peer) -> Self { - CompactPeer { - ip: peer.peer_addr.ip(), - port: peer.peer_addr.port(), + match (peer.peer_addr.ip(), peer.peer_addr.port()) { + (IpAddr::V4(ip), port) => Self::V4(CompactPeerData { ip, port }), + (IpAddr::V6(ip), port) => Self::V6(CompactPeerData { ip, port }), } } } -impl Compact { - /// Returns the bencoded compact response as a byte vector. - /// - /// # Errors - /// - /// 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)), - "peers" => ben_bytes!(self.peers_v4_bytes()?), - "peers6" => ben_bytes!(self.peers_v6_bytes()?) - }) - .encode(); +/// The [`CompactPeerData`], that made with either a [`Ipv4Addr`], or [`Ipv6Addr`] along with a `port`. +/// +#[derive(Clone, Debug, PartialEq)] +pub struct CompactPeerData { + /// The peer's IP address. + pub ip: V, + /// The peer's port number. + pub port: u16, +} - Ok(bytes) - } +impl FromIterator for (Vec>, Vec>) { + fn from_iter>(iter: T) -> Self { + let mut peers_v4: Vec> = vec![]; + let mut peers_v6: Vec> = vec![]; - fn peers_v4_bytes(&self) -> Result, Box> { - let mut bytes: Vec = Vec::new(); - for compact_peer in &self.peers { - match compact_peer.ip { - IpAddr::V4(_ip) => { - let peer_bytes = compact_peer.bytes()?; - bytes.write_all(&peer_bytes)?; - } - IpAddr::V6(_) => {} + for peer in iter { + match peer { + CompactPeer::V4(peer) => peers_v4.push(peer), + CompactPeer::V6(peer6) => peers_v6.push(peer6), } } - Ok(bytes) - } - fn peers_v6_bytes(&self) -> Result, Box> { - let mut bytes: Vec = Vec::new(); - for compact_peer in &self.peers { - match compact_peer.ip { - IpAddr::V6(_ip) => { - let peer_bytes = compact_peer.bytes()?; - bytes.write_all(&peer_bytes)?; - } - IpAddr::V4(_) => {} - } - } - Ok(bytes) + (peers_v4, peers_v6) } } -/// `Compact` response serialization error. -#[derive(Error, Debug)] -pub enum CompactSerializationError { - #[error("cannot write bytes: {inner_error} in {location}")] - CannotWriteBytes { - location: &'static Location<'static>, - inner_error: String, - }, -} +#[derive(From, PartialEq)] +struct CompactPeersEncoded(Vec); -impl From for responses::error::Error { - fn from(err: CompactSerializationError) -> Self { - responses::error::Error { - failure_reason: format!("{err}"), - } - } -} +impl FromIterator> for CompactPeersEncoded { + fn from_iter>>(iter: T) -> Self { + let mut bytes: Vec = vec![]; -impl IntoResponse for Compact { - fn into_response(self) -> Response { - match self.body() { - Ok(bytes) => (StatusCode::OK, bytes).into_response(), - Err(err) => responses::error::Error::from(CompactSerializationError::CannotWriteBytes { - location: Location::caller(), - inner_error: format!("{err}"), - }) - .into_response(), + for peer in iter { + bytes + .write_all(&u32::from(peer.ip).to_be_bytes()) + .expect("it should write peer ip"); + bytes.write_all(&peer.port.to_be_bytes()).expect("it should write peer port"); } + + bytes.into() } } -impl From for Compact { - fn from(domain_announce_response: AnnounceData) -> Self { - let peers: Vec = domain_announce_response - .peers - .iter() - .map(|peer| CompactPeer::from(*peer)) - .collect(); +impl FromIterator> for CompactPeersEncoded { + fn from_iter>>(iter: T) -> Self { + let mut bytes: Vec = Vec::new(); - 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, + for peer in iter { + bytes + .write_all(&u128::from(peer.ip).to_be_bytes()) + .expect("it should write peer ip"); + bytes.write_all(&peer.port.to_be_bytes()).expect("it should write peer port"); } + bytes.into() } } #[cfg(test)] mod tests { - use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + + use torrust_tracker_configuration::AnnouncePolicy; - use super::{NonCompact, Peer}; - use crate::servers::http::v1::responses::announce::{Compact, CompactPeer}; + use crate::core::peer::fixture::PeerBuilder; + use crate::core::peer::Id; + use crate::core::torrent::SwarmStats; + use crate::core::AnnounceData; + use crate::servers::http::v1::responses::announce::{Announce, Compact, Normal, Response}; // Some ascii values used in tests: // @@ -439,30 +334,32 @@ mod tests { // IP addresses and port numbers used in tests are chosen so that their bencoded representation // is also a valid string which makes asserts more readable. + fn setup_announce_data() -> AnnounceData { + let policy = AnnouncePolicy::new(111, 222); + + let peer_ipv4 = PeerBuilder::default() + .with_peer_id(&Id(*b"-qB00000000000000001")) + .with_peer_addr(&SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0x69, 0x69, 0x69, 0x69)), 0x7070)) + .build(); + + let peer_ipv6 = PeerBuilder::default() + .with_peer_id(&Id(*b"-qB00000000000000002")) + .with_peer_addr(&SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)), + 0x7070, + )) + .build(); + + let peers = vec![peer_ipv4, peer_ipv6]; + let stats = SwarmStats::new(333, 333, 444); + + AnnounceData::new(peers, stats, policy) + } + #[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 bytes = response.body(); + let response: Announce = setup_announce_data().into(); + let bytes = response.body().expect("it should encode the response"); // cspell:disable-next-line let expected_bytes = b"d8:completei333e10:incompletei444e8:intervali111e12:min intervali222e5:peersld2:ip15:105.105.105.1057:peer id20:-qB000000000000000014:porti28784eed2:ip39:6969:6969:6969:6969:6969:6969:6969:69697:peer id20:-qB000000000000000024:porti28784eeee"; @@ -475,26 +372,8 @@ 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 - }, - ], - }; - - let bytes = response.body().unwrap(); + let response: Announce = setup_announce_data().into(); + let bytes = response.body().expect("it should encode the response"); let expected_bytes = // cspell:disable-next-line diff --git a/src/servers/http/v1/responses/mod.rs b/src/servers/http/v1/responses/mod.rs index 3c6632fed..e22879c6d 100644 --- a/src/servers/http/v1/responses/mod.rs +++ b/src/servers/http/v1/responses/mod.rs @@ -5,3 +5,15 @@ pub mod announce; pub mod error; pub mod scrape; + +pub use announce::{Announce, Compact, Normal}; + +/// Trait that defines the Announce Response Format +pub trait Response: axum::response::IntoResponse { + /// Returns the Body of the Announce Response + /// + /// # Errors + /// + /// If unable to generate the response, it will return an error. + fn body(self) -> Result, error::Error>; +} diff --git a/src/servers/http/v1/services/announce.rs b/src/servers/http/v1/services/announce.rs index bdf8afc87..80dc1ca5b 100644 --- a/src/servers/http/v1/services/announce.rs +++ b/src/servers/http/v1/services/announce.rs @@ -94,6 +94,7 @@ mod tests { use std::sync::Arc; use mockall::predicate::eq; + use torrust_tracker_configuration::AnnouncePolicy; use torrust_tracker_test_helpers::configuration; use super::{sample_peer_using_ipv4, sample_peer_using_ipv6}; @@ -113,13 +114,12 @@ mod tests { let expected_announce_data = AnnounceData { peers: vec![], - swarm_stats: SwarmStats { - completed: 0, - seeders: 1, - leechers: 0, + stats: SwarmStats { + downloaded: 0, + complete: 1, + incomplete: 0, }, - interval: tracker.config.announce_interval, - interval_min: tracker.config.min_announce_interval, + policy: AnnouncePolicy::default(), }; assert_eq!(announce_data, expected_announce_data); diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index 18a341418..34ebaec89 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -152,8 +152,8 @@ pub async fn handle_announce( let announce_response = AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, announce_interval: AnnounceInterval(i64::from(tracker.config.announce_interval) as i32), - leechers: NumberOfPeers(i64::from(response.swarm_stats.leechers) as i32), - seeders: NumberOfPeers(i64::from(response.swarm_stats.seeders) as i32), + leechers: NumberOfPeers(i64::from(response.stats.incomplete) as i32), + seeders: NumberOfPeers(i64::from(response.stats.complete) as i32), peers: response .peers .iter() @@ -177,8 +177,8 @@ pub async fn handle_announce( let announce_response = AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, announce_interval: AnnounceInterval(i64::from(tracker.config.announce_interval) as i32), - leechers: NumberOfPeers(i64::from(response.swarm_stats.leechers) as i32), - seeders: NumberOfPeers(i64::from(response.swarm_stats.seeders) as i32), + leechers: NumberOfPeers(i64::from(response.stats.incomplete) as i32), + seeders: NumberOfPeers(i64::from(response.stats.complete) as i32), peers: response .peers .iter() diff --git a/tests/common/fixtures.rs b/tests/common/fixtures.rs index 9fd328d5d..bbdebff76 100644 --- a/tests/common/fixtures.rs +++ b/tests/common/fixtures.rs @@ -1,69 +1,3 @@ -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; - -use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; -use torrust_tracker::core::peer::{self, Id, Peer}; -use torrust_tracker::shared::clock::DurationSinceUnixEpoch; - -pub struct PeerBuilder { - peer: Peer, -} - -impl PeerBuilder { - #[allow(dead_code)] - pub fn default() -> PeerBuilder { - Self { - peer: default_peer_for_testing(), - } - } - - #[allow(dead_code)] - pub fn with_peer_id(mut self, peer_id: &Id) -> Self { - self.peer.peer_id = *peer_id; - self - } - - #[allow(dead_code)] - pub fn with_peer_addr(mut self, peer_addr: &SocketAddr) -> Self { - self.peer.peer_addr = *peer_addr; - self - } - - #[allow(dead_code)] - pub fn with_bytes_pending_to_download(mut self, left: i64) -> Self { - self.peer.left = NumberOfBytes(left); - self - } - - #[allow(dead_code)] - pub fn with_no_bytes_pending_to_download(mut self) -> Self { - self.peer.left = NumberOfBytes(0); - self - } - - #[allow(dead_code)] - pub fn build(self) -> Peer { - self.into() - } - - #[allow(dead_code)] - pub fn into(self) -> Peer { - self.peer - } -} - -#[allow(dead_code)] -fn default_peer_for_testing() -> Peer { - Peer { - peer_id: peer::Id(*b"-qB00000000000000000"), - peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080), - updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0), - uploaded: NumberOfBytes(0), - downloaded: NumberOfBytes(0), - left: NumberOfBytes(0), - event: AnnounceEvent::Started, - } -} - #[allow(dead_code)] pub fn invalid_info_hashes() -> Vec { [ diff --git a/tests/servers/api/v1/contract/context/stats.rs b/tests/servers/api/v1/contract/context/stats.rs index 45f7e604a..71738f8e5 100644 --- a/tests/servers/api/v1/contract/context/stats.rs +++ b/tests/servers/api/v1/contract/context/stats.rs @@ -1,10 +1,10 @@ use std::str::FromStr; +use torrust_tracker::core::peer::fixture::PeerBuilder; use torrust_tracker::servers::apis::v1::context::stats::resources::Stats; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; -use crate::common::fixtures::PeerBuilder; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::test_environment::running_test_environment; use crate::servers::api::v1::asserts::{assert_stats, assert_token_not_valid, assert_unauthorized}; diff --git a/tests/servers/api/v1/contract/context/torrent.rs b/tests/servers/api/v1/contract/context/torrent.rs index 3cac55e6a..dc91e8fc5 100644 --- a/tests/servers/api/v1/contract/context/torrent.rs +++ b/tests/servers/api/v1/contract/context/torrent.rs @@ -1,11 +1,11 @@ use std::str::FromStr; +use torrust_tracker::core::peer::fixture::PeerBuilder; use torrust_tracker::servers::apis::v1::context::torrent::resources::peer::Peer; use torrust_tracker::servers::apis::v1::context::torrent::resources::torrent::{self, Torrent}; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; -use crate::common::fixtures::PeerBuilder; use crate::common::http::{Query, QueryParam}; use crate::servers::api::connection_info::{connection_with_invalid_token, connection_with_no_token}; use crate::servers::api::test_environment::running_test_environment; diff --git a/tests/servers/http/v1/contract.rs b/tests/servers/http/v1/contract.rs index 3034847db..f3d1fcef0 100644 --- a/tests/servers/http/v1/contract.rs +++ b/tests/servers/http/v1/contract.rs @@ -90,10 +90,11 @@ mod for_all_config_modes { use reqwest::{Response, StatusCode}; use tokio::net::TcpListener; use torrust_tracker::core::peer; + use torrust_tracker::core::peer::fixture::PeerBuilder; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; - use crate::common::fixtures::{invalid_info_hashes, PeerBuilder}; + use crate::common::fixtures::invalid_info_hashes; use crate::servers::http::asserts::{ assert_announce_response, assert_bad_announce_request_error_response, assert_cannot_parse_query_param_error_response, assert_cannot_parse_query_params_error_response, assert_compact_announce_response, assert_empty_announce_response, @@ -884,10 +885,11 @@ mod for_all_config_modes { use tokio::net::TcpListener; use torrust_tracker::core::peer; + use torrust_tracker::core::peer::fixture::PeerBuilder; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; - use crate::common::fixtures::{invalid_info_hashes, PeerBuilder}; + use crate::common::fixtures::invalid_info_hashes; use crate::servers::http::asserts::{ assert_cannot_parse_query_params_error_response, assert_missing_query_params_for_scrape_request_error_response, assert_scrape_response, @@ -1160,10 +1162,10 @@ mod configured_as_whitelisted { use std::str::FromStr; use torrust_tracker::core::peer; + use torrust_tracker::core::peer::fixture::PeerBuilder; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; - use crate::common::fixtures::PeerBuilder; use crate::servers::http::asserts::assert_scrape_response; use crate::servers::http::client::Client; use crate::servers::http::requests; @@ -1333,10 +1335,10 @@ mod configured_as_private { use torrust_tracker::core::auth::Key; use torrust_tracker::core::peer; + use torrust_tracker::core::peer::fixture::PeerBuilder; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; - use crate::common::fixtures::PeerBuilder; use crate::servers::http::asserts::{assert_authentication_error_response, assert_scrape_response}; use crate::servers::http::client::Client; use crate::servers::http::requests;