torrust_tracker/core/
mod.rs

1//! The core `tracker` module contains the generic `BitTorrent` tracker logic which is independent of the delivery layer.
2//!
3//! It contains the tracker services and their dependencies. It's a domain layer which does not
4//! specify how the end user should connect to the `Tracker`.
5//!
6//! Typically this module is intended to be used by higher modules like:
7//!
8//! - A UDP tracker
9//! - A HTTP tracker
10//! - A tracker REST API
11//!
12//! ```text
13//! Delivery layer     Domain layer
14//!
15//!     HTTP tracker |
16//!      UDP tracker |> Core tracker
17//! Tracker REST API |
18//! ```
19//!
20//! # Table of contents
21//!
22//! - [Tracker](#tracker)
23//!   - [Announce request](#announce-request)
24//!   - [Scrape request](#scrape-request)
25//!   - [Torrents](#torrents)
26//!   - [Peers](#peers)
27//! - [Configuration](#configuration)
28//! - [Services](#services)
29//! - [Authentication](#authentication)
30//! - [Statistics](#statistics)
31//! - [Persistence](#persistence)
32//!
33//! # Tracker
34//!
35//! The `Tracker` is the main struct in this module. `The` tracker has some groups of responsibilities:
36//!
37//! - **Core tracker**: it handles the information about torrents and peers.
38//! - **Authentication**: it handles authentication keys which are used by HTTP trackers.
39//! - **Authorization**: it handles the permission to perform requests.
40//! - **Whitelist**: when the tracker runs in `listed` or `private_listed` mode all operations are restricted to whitelisted torrents.
41//! - **Statistics**: it keeps and serves the tracker statistics.
42//!
43//! Refer to [torrust-tracker-configuration](https://docs.rs/torrust-tracker-configuration) crate docs to get more information about the tracker settings.
44//!
45//! ## Announce request
46//!
47//! Handling `announce` requests is the most important task for a `BitTorrent` tracker.
48//!
49//! A `BitTorrent` swarm is a network of peers that are all trying to download the same torrent.
50//! When a peer wants to find other peers it announces itself to the swarm via the tracker.
51//! The peer sends its data to the tracker so that the tracker can add it to the swarm.
52//! The tracker responds to the peer with the list of other peers in the swarm so that
53//! the peer can contact them to start downloading pieces of the file from them.
54//!
55//! Once you have instantiated the `Tracker` you can `announce` a new [`peer::Peer`] with:
56//!
57//! ```rust,no_run
58//! use std::net::SocketAddr;
59//! use std::net::IpAddr;
60//! use std::net::Ipv4Addr;
61//! use std::str::FromStr;
62//!
63//! use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
64//! use torrust_tracker_primitives::DurationSinceUnixEpoch;
65//! use torrust_tracker_primitives::peer;
66//! use torrust_tracker_primitives::info_hash::InfoHash;
67//!
68//! let info_hash = InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap();
69//!
70//! let peer = peer::Peer {
71//!     peer_id: PeerId(*b"-qB00000000000000001"),
72//!     peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8081),
73//!     updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
74//!     uploaded: NumberOfBytes::new(0),
75//!     downloaded: NumberOfBytes::new(0),
76//!     left: NumberOfBytes::new(0),
77//!     event: AnnounceEvent::Completed,
78//! };
79//!
80//! let peer_ip = IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap());
81//! ```
82//!
83//! ```text
84//! let announce_data = tracker.announce(&info_hash, &mut peer, &peer_ip).await;
85//! ```
86//!
87//! The `Tracker` returns the list of peers for the torrent with the infohash `3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0`,
88//! filtering out the peer that is making the `announce` request.
89//!
90//! > **NOTICE**: that the peer argument is mutable because the `Tracker` can change the peer IP if the peer is using a loopback IP.
91//!
92//! The `peer_ip` argument is the resolved peer ip. It's a common practice that trackers ignore the peer ip in the `announce` request params,
93//! and resolve the peer ip using the IP of the client making the request. As the tracker is a domain service, the peer IP must be provided
94//! for the `Tracker` user, which is usually a higher component with access the the request metadata, for example, connection data, proxy headers,
95//! etcetera.
96//!
97//! The returned struct is:
98//!
99//! ```rust,no_run
100//! use torrust_tracker_primitives::peer;
101//! use torrust_tracker_configuration::AnnouncePolicy;
102//!
103//! pub struct AnnounceData {
104//!     pub peers: Vec<peer::Peer>,
105//!     pub swarm_stats: SwarmMetadata,
106//!     pub policy: AnnouncePolicy, // the tracker announce policy.
107//! }
108//!
109//! pub struct SwarmMetadata {
110//!     pub completed: u32, // The number of peers that have ever completed downloading
111//!     pub seeders: u32,   // The number of active peers that have completed downloading (seeders)
112//!     pub leechers: u32,  // The number of active peers that have not completed downloading (leechers)
113//! }
114//!
115//! // Core tracker configuration
116//! pub struct AnnounceInterval {
117//!     // ...
118//!     pub interval: u32, // Interval in seconds that the client should wait between sending regular announce requests to the tracker
119//!     pub interval_min: u32, // Minimum announce interval. Clients must not reannounce more frequently than this
120//!     // ...
121//! }
122//! ```
123//!
124//! Refer to `BitTorrent` BEPs and other sites for more information about the `announce` request:
125//!
126//! - [BEP 3. The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html)
127//! - [BEP 23. Tracker Returns Compact Peer Lists](https://www.bittorrent.org/beps/bep_0023.html)
128//! - [Vuze docs](https://wiki.vuze.com/w/Announce)
129//!
130//! ## Scrape request
131//!
132//! The `scrape` request allows clients to query metadata about the swarm in bulk.
133//!
134//! An `scrape` request includes a list of infohashes whose swarm metadata you want to collect.
135//!
136//! The returned struct is:
137//!
138//! ```rust,no_run
139//! use torrust_tracker_primitives::info_hash::InfoHash;
140//! use std::collections::HashMap;
141//!
142//! pub struct ScrapeData {
143//!     pub files: HashMap<InfoHash, SwarmMetadata>,
144//! }
145//!
146//! pub struct SwarmMetadata {
147//!     pub complete: u32,   // The number of active peers that have completed downloading (seeders)
148//!     pub downloaded: u32, // The number of peers that have ever completed downloading
149//!     pub incomplete: u32, // The number of active peers that have not completed downloading (leechers)
150//! }
151//! ```
152//!
153//! The JSON representation of a sample `scrape` response would be like the following:
154//!
155//! ```json
156//! {
157//!     'files': {
158//!       'xxxxxxxxxxxxxxxxxxxx': {'complete': 11, 'downloaded': 13772, 'incomplete': 19},
159//!       'yyyyyyyyyyyyyyyyyyyy': {'complete': 21, 'downloaded': 206, 'incomplete': 20}
160//!     }
161//! }
162//! ```
163//!  
164//! `xxxxxxxxxxxxxxxxxxxx` and `yyyyyyyyyyyyyyyyyyyy` are 20-byte infohash arrays.
165//! There are two data structures for infohashes: byte arrays and hex strings:
166//!
167//! ```rust,no_run
168//! use torrust_tracker_primitives::info_hash::InfoHash;
169//! use std::str::FromStr;
170//!
171//! let info_hash: InfoHash = [255u8; 20].into();
172//!
173//! assert_eq!(
174//!     info_hash,
175//!     InfoHash::from_str("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap()
176//! );
177//! ```
178//! Refer to `BitTorrent` BEPs and other sites for more information about the `scrape` request:
179//!
180//! - [BEP 48. Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html)
181//! - [BEP 15. UDP Tracker Protocol for `BitTorrent`. Scrape section](https://www.bittorrent.org/beps/bep_0015.html)
182//! - [Vuze docs](https://wiki.vuze.com/w/Scrape)
183//!
184//! ## Torrents
185//!
186//! The [`torrent`] module contains all the data structures stored by the `Tracker` except for peers.
187//!
188//! We can represent the data stored in memory internally by the `Tracker` with this JSON object:
189//!
190//! ```json
191//! {
192//!     "c1277613db1d28709b034a017ab2cae4be07ae10": {
193//!         "completed": 0,
194//!         "peers": {
195//!             "-qB00000000000000001": {
196//!                 "peer_id": "-qB00000000000000001",
197//!                 "peer_addr": "2.137.87.41:1754",
198//!                 "updated": 1672419840,
199//!                 "uploaded": 120,
200//!                 "downloaded": 60,
201//!                 "left": 60,
202//!                 "event": "started"
203//!             },
204//!             "-qB00000000000000002": {
205//!                 "peer_id": "-qB00000000000000002",
206//!                 "peer_addr": "23.17.287.141:2345",
207//!                 "updated": 1679415984,
208//!                 "uploaded": 80,
209//!                 "downloaded": 20,
210//!                 "left": 40,
211//!                 "event": "started"
212//!             }
213//!         }
214//!     }
215//! }
216//! ```
217//!
218//! The `Tracker` maintains an indexed-by-info-hash list of torrents. For each torrent, it stores a torrent `Entry`.
219//! The torrent entry has two attributes:
220//!
221//! - `completed`: which is hte number of peers that have completed downloading the torrent file/s. As they have completed downloading,
222//!   they have a full version of the torrent data, and they can provide the full data to other peers. That's why they are also known as "seeders".
223//! - `peers`: an indexed and orderer list of peer for the torrent. Each peer contains the data received from the peer in the `announce` request.
224//!
225//! The [`torrent`] module not only contains the original data obtained from peer via `announce` requests, it also contains
226//! aggregate data that can be derived from the original data. For example:
227//!
228//! ```rust,no_run
229//! pub struct SwarmMetadata {
230//!     pub complete: u32,   // The number of active peers that have completed downloading (seeders)
231//!     pub downloaded: u32, // The number of peers that have ever completed downloading
232//!     pub incomplete: u32, // The number of active peers that have not completed downloading (leechers)
233//! }
234//!
235//! ```
236//!
237//! > **NOTICE**: that `complete` or `completed` peers are the peers that have completed downloading, but only the active ones are considered "seeders".
238//!
239//! `SwarmMetadata` struct follows name conventions for `scrape` responses. See [BEP 48](https://www.bittorrent.org/beps/bep_0048.html), while `SwarmMetadata`
240//! is used for the rest of cases.
241//!
242//! Refer to [`torrent`] module for more details about these data structures.
243//!
244//! ## Peers
245//!
246//! A `Peer` is the struct used by the `Tracker` to keep peers data:
247//!
248//! ```rust,no_run
249//! use std::net::SocketAddr;
250
251//! use aquatic_udp_protocol::PeerId;
252//! use torrust_tracker_primitives::DurationSinceUnixEpoch;
253//! use aquatic_udp_protocol::NumberOfBytes;
254//! use aquatic_udp_protocol::AnnounceEvent;
255//!
256//! pub struct Peer {
257//!     pub peer_id: PeerId,                     // The peer ID
258//!     pub peer_addr: SocketAddr,           // Peer socket address
259//!     pub updated: DurationSinceUnixEpoch, // Last time (timestamp) when the peer was updated
260//!     pub uploaded: NumberOfBytes,         // Number of bytes the peer has uploaded so far
261//!     pub downloaded: NumberOfBytes,       // Number of bytes the peer has downloaded so far   
262//!     pub left: NumberOfBytes,             // The number of bytes this peer still has to download
263//!     pub event: AnnounceEvent,            // The event the peer has announced: `started`, `completed`, `stopped`
264//! }
265//! ```
266//!
267//! Notice that most of the attributes are obtained from the `announce` request.
268//! For example, an HTTP announce request would contain the following `GET` parameters:
269//!
270//! <http://0.0.0.0:7070/announce?info_hash=%81%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00&peer_addr=2.137.87.41&downloaded=0&uploaded=0&peer_id=-qB00000000000000001&port=17548&left=0&event=completed&compact=0>
271//!
272//! The `Tracker` keeps an in-memory ordered data structure with all the torrents and a list of peers for each torrent, together with some swarm metrics.
273//!
274//! We can represent the data stored in memory with this JSON object:
275//!
276//! ```json
277//! {
278//!     "c1277613db1d28709b034a017ab2cae4be07ae10": {
279//!         "completed": 0,
280//!         "peers": {
281//!             "-qB00000000000000001": {
282//!                 "peer_id": "-qB00000000000000001",
283//!                 "peer_addr": "2.137.87.41:1754",
284//!                 "updated": 1672419840,
285//!                 "uploaded": 120,
286//!                 "downloaded": 60,
287//!                 "left": 60,
288//!                 "event": "started"
289//!             },
290//!             "-qB00000000000000002": {
291//!                 "peer_id": "-qB00000000000000002",
292//!                 "peer_addr": "23.17.287.141:2345",
293//!                 "updated": 1679415984,
294//!                 "uploaded": 80,
295//!                 "downloaded": 20,
296//!                 "left": 40,
297//!                 "event": "started"
298//!             }
299//!         }
300//!     }
301//! }
302//! ```
303//!
304//! That JSON object does not exist, it's only a representation of the `Tracker` torrents data.
305//!
306//! `c1277613db1d28709b034a017ab2cae4be07ae10` is the torrent infohash and `completed` contains the number of peers
307//! that have a full version of the torrent data, also known as seeders.
308//!
309//! Refer to [`peer`] module for more information about peers.
310//!
311//! # Configuration
312//!
313//! You can control the behavior of this module with the module settings:
314//!
315//! ```toml
316//! [logging]
317//! threshold = "debug"
318//!
319//! [core]
320//! inactive_peer_cleanup_interval = 600
321//! listed = false
322//! private = false
323//! tracker_usage_statistics = true
324//!
325//! [core.announce_policy]
326//! interval = 120
327//! interval_min = 120
328//!
329//! [core.database]
330//! driver = "sqlite3"
331//! path = "./storage/tracker/lib/database/sqlite3.db"
332//!
333//! [core.net]
334//! on_reverse_proxy = false
335//! external_ip = "2.137.87.41"
336//!
337//! [core.tracker_policy]
338//! max_peer_timeout = 900
339//! persistent_torrent_completed_stat = false
340//! remove_peerless_torrents = true
341//! ```
342//!
343//! Refer to the [`configuration` module documentation](https://docs.rs/torrust-tracker-configuration) to get more information about all options.
344//!
345//! # Services
346//!
347//! Services are domain services on top of the core tracker. Right now there are two types of service:
348//!
349//! - For statistics
350//! - For torrents
351//!
352//! Services usually format the data inside the tracker to make it easier to consume by other parts.
353//! They also decouple the internal data structure, used by the tracker, from the way we deliver that data to the consumers.
354//! The internal data structure is designed for performance or low memory consumption. And it should be changed
355//! without affecting the external consumers.
356//!
357//! Services can include extra features like pagination, for example.
358//!
359//! Refer to [`services`] module for more information about services.
360//!
361//! # Authentication
362//!
363//! One of the core `Tracker` responsibilities is to create and keep authentication keys. Auth keys are used by HTTP trackers
364//! when the tracker is running in `private` or `private_listed` mode.
365//!
366//! HTTP tracker's clients need to obtain an auth key before starting requesting the tracker. Once the get one they have to include
367//! a `PATH` param with the key in all the HTTP requests. For example, when a peer wants to `announce` itself it has to use the
368//! HTTP tracker endpoint `GET /announce/:key`.
369//!
370//! The common way to obtain the keys is by using the tracker API directly or via other applications like the [Torrust Index](https://github.com/torrust/torrust-index).
371//!
372//! To learn more about tracker authentication, refer to the following modules :
373//!
374//! - [`auth`] module.
375//! - [`core`](crate::core) module.
376//! - [`http`](crate::servers::http) module.
377//!
378//! # Statistics
379//!
380//! The `Tracker` keeps metrics for some events:
381//!
382//! ```rust,no_run
383//! pub struct Metrics {
384//!     // IP version 4
385//!
386//!     // HTTP tracker
387//!     pub tcp4_connections_handled: u64,
388//!     pub tcp4_announces_handled: u64,
389//!     pub tcp4_scrapes_handled: u64,
390//!
391//!     // UDP tracker
392//!     pub udp4_connections_handled: u64,
393//!     pub udp4_announces_handled: u64,
394//!     pub udp4_scrapes_handled: u64,
395//!
396//!     // IP version 6
397//!
398//!     // HTTP tracker
399//!     pub tcp6_connections_handled: u64,
400//!     pub tcp6_announces_handled: u64,
401//!     pub tcp6_scrapes_handled: u64,
402//!
403//!     // UDP tracker
404//!     pub udp6_connections_handled: u64,
405//!     pub udp6_announces_handled: u64,
406//!     pub udp6_scrapes_handled: u64,
407//! }
408//! ```
409//!
410//! The metrics maintained by the `Tracker` are:
411//!
412//! - `connections_handled`: number of connections handled by the tracker
413//! - `announces_handled`: number of `announce` requests handled by the tracker
414//! - `scrapes_handled`: number of `scrape` handled requests by the tracker
415//!
416//! > **NOTICE**: as the HTTP tracker does not have an specific `connection` request like the UDP tracker, `connections_handled` are
417//! > increased on every `announce` and `scrape` requests.
418//!
419//! The tracker exposes an event sender API that allows the tracker users to send events. When a higher application service handles a
420//! `connection` , `announce` or `scrape` requests, it notifies the `Tracker` by sending statistics events.
421//!
422//! For example, the HTTP tracker would send an event like the following when it handles an `announce` request received from a peer using IP version 4.
423//!
424//! ```text
425//! tracker.send_stats_event(statistics::Event::Tcp4Announce).await
426//! ```
427//!
428//! Refer to [`statistics`] module for more information about statistics.
429//!
430//! # Persistence
431//!
432//! Right now the `Tracker` is responsible for storing and load data into and
433//! from the database, when persistence is enabled.
434//!
435//! There are three types of persistent object:
436//!
437//! - Authentication keys (only expiring keys)
438//! - Torrent whitelist
439//! - Torrent metrics
440//!
441//! Refer to [`databases`] module for more information about persistence.
442pub mod auth;
443pub mod databases;
444pub mod error;
445pub mod services;
446pub mod statistics;
447pub mod torrent;
448
449pub mod peer_tests;
450
451use std::cmp::max;
452use std::collections::HashMap;
453use std::net::IpAddr;
454use std::panic::Location;
455use std::sync::Arc;
456use std::time::Duration;
457
458use auth::PeerKey;
459use databases::driver::Driver;
460use derive_more::Constructor;
461use error::PeerKeyError;
462use tokio::sync::mpsc::error::SendError;
463use torrust_tracker_clock::clock::Time;
464use torrust_tracker_configuration::v2_0_0::database;
465use torrust_tracker_configuration::{AnnouncePolicy, Core, TORRENT_PEERS_LIMIT};
466use torrust_tracker_located_error::Located;
467use torrust_tracker_primitives::info_hash::InfoHash;
468use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
469use torrust_tracker_primitives::torrent_metrics::TorrentsMetrics;
470use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch};
471use torrust_tracker_torrent_repository::entry::EntrySync;
472use torrust_tracker_torrent_repository::repository::Repository;
473
474use self::auth::Key;
475use self::error::Error;
476use self::torrent::Torrents;
477use crate::core::databases::Database;
478use crate::CurrentClock;
479
480/// The domain layer tracker service.
481///
482/// Its main responsibility is to handle the `announce` and `scrape` requests.
483/// But it's also a container for the `Tracker` configuration, persistence,
484/// authentication and other services.
485///
486/// > **NOTICE**: the `Tracker` is not responsible for handling the network layer.
487/// > Typically, the `Tracker` is used by a higher application service that handles
488/// > the network layer.
489pub struct Tracker {
490    /// The tracker configuration.
491    config: Core,
492
493    /// A database driver implementation: [`Sqlite3`](crate::core::databases::sqlite)
494    /// or [`MySQL`](crate::core::databases::mysql)
495    database: Arc<Box<dyn Database>>,
496
497    /// Tracker users' keys. Only for private trackers.
498    keys: tokio::sync::RwLock<std::collections::HashMap<Key, auth::PeerKey>>,
499
500    /// The list of allowed torrents. Only for listed trackers.
501    whitelist: tokio::sync::RwLock<std::collections::HashSet<InfoHash>>,
502
503    /// The in-memory torrents repository.
504    torrents: Arc<Torrents>,
505
506    /// Service to send stats events.
507    stats_event_sender: Option<Box<dyn statistics::EventSender>>,
508
509    /// The in-memory stats repo.
510    stats_repository: statistics::Repo,
511}
512
513/// Structure that holds the data returned by the `announce` request.
514#[derive(Clone, Debug, PartialEq, Constructor, Default)]
515pub struct AnnounceData {
516    /// The list of peers that are downloading the same torrent.
517    /// It excludes the peer that made the request.
518    pub peers: Vec<Arc<peer::Peer>>,
519    /// Swarm statistics
520    pub stats: SwarmMetadata,
521    pub policy: AnnouncePolicy,
522}
523
524/// How many peers the peer announcing wants in the announce response.
525#[derive(Clone, Debug, PartialEq, Default)]
526pub enum PeersWanted {
527    /// The peer wants as many peers as possible in the announce response.
528    #[default]
529    All,
530    /// The peer only wants a certain amount of peers in the announce response.
531    Only { amount: usize },
532}
533
534impl PeersWanted {
535    #[must_use]
536    pub fn only(limit: u32) -> Self {
537        let amount: usize = match limit.try_into() {
538            Ok(amount) => amount,
539            Err(_) => TORRENT_PEERS_LIMIT,
540        };
541
542        Self::Only { amount }
543    }
544
545    fn limit(&self) -> usize {
546        match self {
547            PeersWanted::All => TORRENT_PEERS_LIMIT,
548            PeersWanted::Only { amount } => *amount,
549        }
550    }
551}
552
553impl From<i32> for PeersWanted {
554    fn from(value: i32) -> Self {
555        if value > 0 {
556            match value.try_into() {
557                Ok(peers_wanted) => Self::Only { amount: peers_wanted },
558                Err(_) => Self::All,
559            }
560        } else {
561            Self::All
562        }
563    }
564}
565
566/// Structure that holds the data returned by the `scrape` request.
567#[derive(Debug, PartialEq, Default)]
568pub struct ScrapeData {
569    /// A map of infohashes and swarm metadata for each torrent.
570    pub files: HashMap<InfoHash, SwarmMetadata>,
571}
572
573impl ScrapeData {
574    /// Creates a new empty `ScrapeData` with no files (torrents).
575    #[must_use]
576    pub fn empty() -> Self {
577        let files: HashMap<InfoHash, SwarmMetadata> = HashMap::new();
578        Self { files }
579    }
580
581    /// Creates a new `ScrapeData` with zeroed metadata for each torrent.
582    #[must_use]
583    pub fn zeroed(info_hashes: &Vec<InfoHash>) -> Self {
584        let mut scrape_data = Self::empty();
585
586        for info_hash in info_hashes {
587            scrape_data.add_file(info_hash, SwarmMetadata::zeroed());
588        }
589
590        scrape_data
591    }
592
593    /// Adds a torrent to the `ScrapeData`.
594    pub fn add_file(&mut self, info_hash: &InfoHash, swarm_metadata: SwarmMetadata) {
595        self.files.insert(*info_hash, swarm_metadata);
596    }
597
598    /// Adds a torrent to the `ScrapeData` with zeroed metadata.
599    pub fn add_file_with_zeroed_metadata(&mut self, info_hash: &InfoHash) {
600        self.files.insert(*info_hash, SwarmMetadata::zeroed());
601    }
602}
603
604/// This type contains the info needed to add a new tracker key.
605///
606/// You can upload a pre-generated key or let the app to generate a new one.
607/// You can also set an expiration date or leave it empty (`None`) if you want
608/// to create a permanent key that does not expire.
609#[derive(Debug)]
610pub struct AddKeyRequest {
611    /// The pre-generated key. Use `None` to generate a random key.
612    pub opt_key: Option<String>,
613
614    /// How long the key will be valid in seconds. Use `None` for permanent keys.
615    pub opt_seconds_valid: Option<u64>,
616}
617
618impl Tracker {
619    /// `Tracker` constructor.
620    ///
621    /// # Errors
622    ///
623    /// Will return a `databases::error::Error` if unable to connect to database. The `Tracker` is responsible for the persistence.
624    pub fn new(
625        config: &Core,
626        stats_event_sender: Option<Box<dyn statistics::EventSender>>,
627        stats_repository: statistics::Repo,
628    ) -> Result<Tracker, databases::error::Error> {
629        let driver = match config.database.driver {
630            database::Driver::Sqlite3 => Driver::Sqlite3,
631            database::Driver::MySQL => Driver::MySQL,
632        };
633
634        let database = Arc::new(databases::driver::build(&driver, &config.database.path)?);
635
636        Ok(Tracker {
637            config: config.clone(),
638            keys: tokio::sync::RwLock::new(std::collections::HashMap::new()),
639            whitelist: tokio::sync::RwLock::new(std::collections::HashSet::new()),
640            torrents: Arc::default(),
641            stats_event_sender,
642            stats_repository,
643            database,
644        })
645    }
646
647    /// Returns `true` is the tracker is in public mode.
648    pub fn is_public(&self) -> bool {
649        !self.config.private
650    }
651
652    /// Returns `true` is the tracker is in private mode.
653    pub fn is_private(&self) -> bool {
654        self.config.private
655    }
656
657    /// Returns `true` is the tracker is in whitelisted mode.
658    pub fn is_listed(&self) -> bool {
659        self.config.listed
660    }
661
662    /// Returns `true` if the tracker requires authentication.
663    pub fn requires_authentication(&self) -> bool {
664        self.is_private()
665    }
666
667    /// Returns `true` is the tracker is in whitelisted mode.
668    pub fn is_behind_reverse_proxy(&self) -> bool {
669        self.config.net.on_reverse_proxy
670    }
671
672    pub fn get_announce_policy(&self) -> AnnouncePolicy {
673        self.config.announce_policy
674    }
675
676    pub fn get_maybe_external_ip(&self) -> Option<IpAddr> {
677        self.config.net.external_ip
678    }
679
680    /// It handles an announce request.
681    ///
682    /// # Context: Tracker
683    ///
684    /// BEP 03: [The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html).
685    pub fn announce(
686        &self,
687        info_hash: &InfoHash,
688        peer: &mut peer::Peer,
689        remote_client_ip: &IpAddr,
690        peers_wanted: &PeersWanted,
691    ) -> AnnounceData {
692        // code-review: maybe instead of mutating the peer we could just return
693        // a tuple with the new peer and the announce data: (Peer, AnnounceData).
694        // It could even be a different struct: `StoredPeer` or `PublicPeer`.
695
696        // code-review: in the `scrape` function we perform an authorization check.
697        // We check if the torrent is whitelisted. Should we also check authorization here?
698        // I think so because the `Tracker` has the responsibility for checking authentication and authorization.
699        // The `Tracker` has delegated that responsibility to the handlers
700        // (because we want to return a friendly error response) but that does not mean we should
701        // double-check authorization at this domain level too.
702        // I would propose to return a `Result<AnnounceData, Error>` here.
703        // Besides, regarding authentication the `Tracker` is also responsible for authentication but
704        // we are actually handling authentication at the handlers level. So I would extract that
705        // responsibility into another authentication service.
706
707        tracing::debug!("Before: {peer:?}");
708        peer.change_ip(&assign_ip_address_to_peer(remote_client_ip, self.config.net.external_ip));
709        tracing::debug!("After: {peer:?}");
710
711        let stats = self.upsert_peer_and_get_stats(info_hash, peer);
712
713        let peers = self.get_peers_for(info_hash, peer, peers_wanted.limit());
714
715        AnnounceData {
716            peers,
717            stats,
718            policy: self.get_announce_policy(),
719        }
720    }
721
722    /// It handles a scrape request.
723    ///
724    /// # Context: Tracker
725    ///
726    /// BEP 48: [Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html).
727    pub async fn scrape(&self, info_hashes: &Vec<InfoHash>) -> ScrapeData {
728        let mut scrape_data = ScrapeData::empty();
729
730        for info_hash in info_hashes {
731            let swarm_metadata = match self.authorize(info_hash).await {
732                Ok(()) => self.get_swarm_metadata(info_hash),
733                Err(_) => SwarmMetadata::zeroed(),
734            };
735            scrape_data.add_file(info_hash, swarm_metadata);
736        }
737
738        scrape_data
739    }
740
741    /// It returns the data for a `scrape` response.
742    fn get_swarm_metadata(&self, info_hash: &InfoHash) -> SwarmMetadata {
743        match self.torrents.get(info_hash) {
744            Some(torrent_entry) => torrent_entry.get_swarm_metadata(),
745            None => SwarmMetadata::default(),
746        }
747    }
748
749    /// It loads the torrents from database into memory. It only loads the torrent entry list with the number of seeders for each torrent.
750    /// Peers data is not persisted.
751    ///
752    /// # Context: Tracker
753    ///
754    /// # Errors
755    ///
756    /// Will return a `database::Error` if unable to load the list of `persistent_torrents` from the database.
757    pub fn load_torrents_from_database(&self) -> Result<(), databases::error::Error> {
758        let persistent_torrents = self.database.load_persistent_torrents()?;
759
760        self.torrents.import_persistent(&persistent_torrents);
761
762        Ok(())
763    }
764
765    /// # Context: Tracker
766    ///
767    /// Get torrent peers for a given torrent and client.
768    ///
769    /// It filters out the client making the request.
770    fn get_peers_for(&self, info_hash: &InfoHash, peer: &peer::Peer, limit: usize) -> Vec<Arc<peer::Peer>> {
771        match self.torrents.get(info_hash) {
772            None => vec![],
773            Some(entry) => entry.get_peers_for_client(&peer.peer_addr, Some(max(limit, TORRENT_PEERS_LIMIT))),
774        }
775    }
776
777    /// # Context: Tracker
778    ///
779    /// Get torrent peers for a given torrent.
780    pub fn get_torrent_peers(&self, info_hash: &InfoHash) -> Vec<Arc<peer::Peer>> {
781        match self.torrents.get(info_hash) {
782            None => vec![],
783            Some(entry) => entry.get_peers(Some(TORRENT_PEERS_LIMIT)),
784        }
785    }
786
787    /// It updates the torrent entry in memory, it also stores in the database
788    /// the torrent info data which is persistent, and finally return the data
789    /// needed for a `announce` request response.
790    ///
791    /// # Context: Tracker
792    pub fn upsert_peer_and_get_stats(&self, info_hash: &InfoHash, peer: &peer::Peer) -> SwarmMetadata {
793        let swarm_metadata_before = match self.torrents.get_swarm_metadata(info_hash) {
794            Some(swarm_metadata) => swarm_metadata,
795            None => SwarmMetadata::zeroed(),
796        };
797
798        self.torrents.upsert_peer(info_hash, peer);
799
800        let swarm_metadata_after = match self.torrents.get_swarm_metadata(info_hash) {
801            Some(swarm_metadata) => swarm_metadata,
802            None => SwarmMetadata::zeroed(),
803        };
804
805        if swarm_metadata_before != swarm_metadata_after {
806            self.persist_stats(info_hash, &swarm_metadata_after);
807        }
808
809        swarm_metadata_after
810    }
811
812    /// It stores the torrents stats into the database (if persistency is enabled).
813    ///
814    /// # Context: Tracker
815    fn persist_stats(&self, info_hash: &InfoHash, swarm_metadata: &SwarmMetadata) {
816        if self.config.tracker_policy.persistent_torrent_completed_stat {
817            let completed = swarm_metadata.downloaded;
818            let info_hash = *info_hash;
819
820            drop(self.database.save_persistent_torrent(&info_hash, completed));
821        }
822    }
823
824    /// It calculates and returns the general `Tracker`
825    /// [`TorrentsMetrics`]
826    ///
827    /// # Context: Tracker
828    ///
829    /// # Panics
830    /// Panics if unable to get the torrent metrics.
831    pub fn get_torrents_metrics(&self) -> TorrentsMetrics {
832        self.torrents.get_metrics()
833    }
834
835    /// Remove inactive peers and (optionally) peerless torrents.
836    ///
837    /// # Context: Tracker
838    pub fn cleanup_torrents(&self) {
839        let current_cutoff = CurrentClock::now_sub(&Duration::from_secs(u64::from(self.config.tracker_policy.max_peer_timeout)))
840            .unwrap_or_default();
841
842        self.torrents.remove_inactive_peers(current_cutoff);
843
844        if self.config.tracker_policy.remove_peerless_torrents {
845            self.torrents.remove_peerless_torrents(&self.config.tracker_policy);
846        }
847    }
848
849    /// It authenticates the peer `key` against the `Tracker` authentication
850    /// key list.
851    ///
852    /// # Errors
853    ///
854    /// Will return an error if the the authentication key cannot be verified.
855    ///
856    /// # Context: Authentication
857    pub async fn authenticate(&self, key: &Key) -> Result<(), auth::Error> {
858        if self.is_private() {
859            self.verify_auth_key(key).await
860        } else {
861            Ok(())
862        }
863    }
864
865    /// Adds new peer keys to the tracker.
866    ///
867    /// Keys can be pre-generated or randomly created. They can also be permanent or expire.
868    ///
869    /// # Errors
870    ///
871    /// Will return an error if:
872    ///
873    /// - The key duration overflows the duration type maximum value.
874    /// - The provided pre-generated key is invalid.
875    /// - The key could not been persisted due to database issues.
876    pub async fn add_peer_key(&self, add_key_req: AddKeyRequest) -> Result<auth::PeerKey, PeerKeyError> {
877        // code-review: all methods related to keys should be moved to a new independent "keys" service.
878
879        match add_key_req.opt_key {
880            // Upload pre-generated key
881            Some(pre_existing_key) => {
882                if let Some(seconds_valid) = add_key_req.opt_seconds_valid {
883                    // Expiring key
884                    let Some(valid_until) = CurrentClock::now_add(&Duration::from_secs(seconds_valid)) else {
885                        return Err(PeerKeyError::DurationOverflow { seconds_valid });
886                    };
887
888                    let key = pre_existing_key.parse::<Key>();
889
890                    match key {
891                        Ok(key) => match self.add_auth_key(key, Some(valid_until)).await {
892                            Ok(auth_key) => Ok(auth_key),
893                            Err(err) => Err(PeerKeyError::DatabaseError {
894                                source: Located(err).into(),
895                            }),
896                        },
897                        Err(err) => Err(PeerKeyError::InvalidKey {
898                            key: pre_existing_key,
899                            source: Located(err).into(),
900                        }),
901                    }
902                } else {
903                    // Permanent key
904                    let key = pre_existing_key.parse::<Key>();
905
906                    match key {
907                        Ok(key) => match self.add_permanent_auth_key(key).await {
908                            Ok(auth_key) => Ok(auth_key),
909                            Err(err) => Err(PeerKeyError::DatabaseError {
910                                source: Located(err).into(),
911                            }),
912                        },
913                        Err(err) => Err(PeerKeyError::InvalidKey {
914                            key: pre_existing_key,
915                            source: Located(err).into(),
916                        }),
917                    }
918                }
919            }
920            // Generate a new random key
921            None => match add_key_req.opt_seconds_valid {
922                // Expiring key
923                Some(seconds_valid) => match self.generate_auth_key(Some(Duration::from_secs(seconds_valid))).await {
924                    Ok(auth_key) => Ok(auth_key),
925                    Err(err) => Err(PeerKeyError::DatabaseError {
926                        source: Located(err).into(),
927                    }),
928                },
929                // Permanent key
930                None => match self.generate_permanent_auth_key().await {
931                    Ok(auth_key) => Ok(auth_key),
932                    Err(err) => Err(PeerKeyError::DatabaseError {
933                        source: Located(err).into(),
934                    }),
935                },
936            },
937        }
938    }
939
940    /// It generates a new permanent authentication key.
941    ///
942    /// Authentication keys are used by HTTP trackers.
943    ///
944    /// # Context: Authentication
945    ///
946    /// # Errors
947    ///
948    /// Will return a `database::Error` if unable to add the `auth_key` to the database.
949    pub async fn generate_permanent_auth_key(&self) -> Result<auth::PeerKey, databases::error::Error> {
950        self.generate_auth_key(None).await
951    }
952
953    /// It generates a new expiring authentication key.
954    ///
955    /// Authentication keys are used by HTTP trackers.
956    ///
957    /// # Context: Authentication
958    ///
959    /// # Errors
960    ///
961    /// Will return a `database::Error` if unable to add the `auth_key` to the database.
962    ///
963    /// # Arguments
964    ///
965    /// * `lifetime` - The duration in seconds for the new key. The key will be
966    ///   no longer valid after `lifetime` seconds.
967    pub async fn generate_auth_key(&self, lifetime: Option<Duration>) -> Result<auth::PeerKey, databases::error::Error> {
968        let auth_key = auth::generate_key(lifetime);
969
970        self.database.add_key_to_keys(&auth_key)?;
971        self.keys.write().await.insert(auth_key.key.clone(), auth_key.clone());
972        Ok(auth_key)
973    }
974
975    /// It adds a pre-generated permanent authentication key.
976    ///
977    /// Authentication keys are used by HTTP trackers.
978    ///
979    /// # Context: Authentication
980    ///
981    /// # Errors
982    ///
983    /// Will return a `database::Error` if unable to add the `auth_key` to the
984    /// database. For example, if the key already exist.
985    ///
986    /// # Arguments
987    ///
988    /// * `key` - The pre-generated key.
989    pub async fn add_permanent_auth_key(&self, key: Key) -> Result<auth::PeerKey, databases::error::Error> {
990        self.add_auth_key(key, None).await
991    }
992
993    /// It adds a pre-generated authentication key.
994    ///
995    /// Authentication keys are used by HTTP trackers.
996    ///
997    /// # Context: Authentication
998    ///
999    /// # Errors
1000    ///
1001    /// Will return a `database::Error` if unable to add the `auth_key` to the
1002    /// database. For example, if the key already exist.
1003    ///
1004    /// # Arguments
1005    ///
1006    /// * `key` - The pre-generated key.
1007    /// * `lifetime` - The duration in seconds for the new key. The key will be
1008    ///   no longer valid after `lifetime` seconds.
1009    pub async fn add_auth_key(
1010        &self,
1011        key: Key,
1012        valid_until: Option<DurationSinceUnixEpoch>,
1013    ) -> Result<auth::PeerKey, databases::error::Error> {
1014        let auth_key = PeerKey { key, valid_until };
1015
1016        // code-review: should we return a friendly error instead of the DB
1017        // constrain error when the key already exist? For now, it's returning
1018        // the specif error for each DB driver when a UNIQUE constrain fails.
1019        self.database.add_key_to_keys(&auth_key)?;
1020        self.keys.write().await.insert(auth_key.key.clone(), auth_key.clone());
1021        Ok(auth_key)
1022    }
1023
1024    /// It removes an authentication key.
1025    ///
1026    /// # Context: Authentication    
1027    ///
1028    /// # Errors
1029    ///
1030    /// Will return a `database::Error` if unable to remove the `key` to the database.
1031    pub async fn remove_auth_key(&self, key: &Key) -> Result<(), databases::error::Error> {
1032        self.database.remove_key_from_keys(key)?;
1033        self.keys.write().await.remove(key);
1034        Ok(())
1035    }
1036
1037    /// It verifies an authentication key.
1038    ///
1039    /// # Context: Authentication
1040    ///
1041    /// # Errors
1042    ///
1043    /// Will return a `key::Error` if unable to get any `auth_key`.
1044    async fn verify_auth_key(&self, key: &Key) -> Result<(), auth::Error> {
1045        match self.keys.read().await.get(key) {
1046            None => Err(auth::Error::UnableToReadKey {
1047                location: Location::caller(),
1048                key: Box::new(key.clone()),
1049            }),
1050            Some(key) => match self.config.private_mode {
1051                Some(private_mode) => {
1052                    if private_mode.check_keys_expiration {
1053                        return auth::verify_key_expiration(key);
1054                    }
1055
1056                    Ok(())
1057                }
1058                None => auth::verify_key_expiration(key),
1059            },
1060        }
1061    }
1062
1063    /// The `Tracker` stores the authentication keys in memory and in the database.
1064    /// In case you need to restart the `Tracker` you can load the keys from the database
1065    /// into memory with this function. Keys are automatically stored in the database when they
1066    /// are generated.
1067    ///
1068    /// # Context: Authentication
1069    ///
1070    /// # Errors
1071    ///
1072    /// Will return a `database::Error` if unable to `load_keys` from the database.
1073    pub async fn load_keys_from_database(&self) -> Result<(), databases::error::Error> {
1074        let keys_from_database = self.database.load_keys()?;
1075        let mut keys = self.keys.write().await;
1076
1077        keys.clear();
1078
1079        for key in keys_from_database {
1080            keys.insert(key.key.clone(), key);
1081        }
1082
1083        Ok(())
1084    }
1085
1086    /// Right now, there is only authorization when the `Tracker` runs in
1087    /// `listed` or `private_listed` modes.
1088    ///
1089    /// # Context: Authorization
1090    ///
1091    /// # Errors
1092    ///
1093    /// Will return an error if the tracker is running in `listed` mode
1094    /// and the infohash is not whitelisted.
1095    pub async fn authorize(&self, info_hash: &InfoHash) -> Result<(), Error> {
1096        if !self.is_listed() {
1097            return Ok(());
1098        }
1099
1100        if self.is_info_hash_whitelisted(info_hash).await {
1101            return Ok(());
1102        }
1103
1104        Err(Error::TorrentNotWhitelisted {
1105            info_hash: *info_hash,
1106            location: Location::caller(),
1107        })
1108    }
1109
1110    /// It adds a torrent to the whitelist.
1111    /// Adding torrents is not relevant to public trackers.
1112    ///
1113    /// # Context: Whitelist
1114    ///
1115    /// # Errors
1116    ///
1117    /// Will return a `database::Error` if unable to add the `info_hash` into the whitelist database.
1118    pub async fn add_torrent_to_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
1119        self.add_torrent_to_database_whitelist(info_hash)?;
1120        self.add_torrent_to_memory_whitelist(info_hash).await;
1121        Ok(())
1122    }
1123
1124    /// It adds a torrent to the whitelist if it has not been whitelisted previously
1125    fn add_torrent_to_database_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
1126        let is_whitelisted = self.database.is_info_hash_whitelisted(*info_hash)?;
1127
1128        if is_whitelisted {
1129            return Ok(());
1130        }
1131
1132        self.database.add_info_hash_to_whitelist(*info_hash)?;
1133
1134        Ok(())
1135    }
1136
1137    pub async fn add_torrent_to_memory_whitelist(&self, info_hash: &InfoHash) -> bool {
1138        self.whitelist.write().await.insert(*info_hash)
1139    }
1140
1141    /// It removes a torrent from the whitelist.
1142    /// Removing torrents is not relevant to public trackers.
1143    ///
1144    /// # Context: Whitelist
1145    ///
1146    /// # Errors
1147    ///
1148    /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database.
1149    pub async fn remove_torrent_from_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
1150        self.remove_torrent_from_database_whitelist(info_hash)?;
1151        self.remove_torrent_from_memory_whitelist(info_hash).await;
1152        Ok(())
1153    }
1154
1155    /// It removes a torrent from the whitelist in the database.
1156    ///
1157    /// # Context: Whitelist
1158    ///
1159    /// # Errors
1160    ///
1161    /// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database.
1162    pub fn remove_torrent_from_database_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
1163        let is_whitelisted = self.database.is_info_hash_whitelisted(*info_hash)?;
1164
1165        if !is_whitelisted {
1166            return Ok(());
1167        }
1168
1169        self.database.remove_info_hash_from_whitelist(*info_hash)?;
1170
1171        Ok(())
1172    }
1173
1174    /// It removes a torrent from the whitelist in memory.
1175    ///
1176    /// # Context: Whitelist
1177    pub async fn remove_torrent_from_memory_whitelist(&self, info_hash: &InfoHash) -> bool {
1178        self.whitelist.write().await.remove(info_hash)
1179    }
1180
1181    /// It checks if a torrent is whitelisted.
1182    ///
1183    /// # Context: Whitelist
1184    pub async fn is_info_hash_whitelisted(&self, info_hash: &InfoHash) -> bool {
1185        self.whitelist.read().await.contains(info_hash)
1186    }
1187
1188    /// It loads the whitelist from the database.
1189    ///
1190    /// # Context: Whitelist
1191    ///
1192    /// # Errors
1193    ///
1194    /// Will return a `database::Error` if unable to load the list whitelisted `info_hash`s from the database.
1195    pub async fn load_whitelist_from_database(&self) -> Result<(), databases::error::Error> {
1196        let whitelisted_torrents_from_database = self.database.load_whitelist()?;
1197        let mut whitelist = self.whitelist.write().await;
1198
1199        whitelist.clear();
1200
1201        for info_hash in whitelisted_torrents_from_database {
1202            let _: bool = whitelist.insert(info_hash);
1203        }
1204
1205        Ok(())
1206    }
1207
1208    /// It return the `Tracker` [`statistics::Metrics`].
1209    ///
1210    /// # Context: Statistics
1211    pub async fn get_stats(&self) -> tokio::sync::RwLockReadGuard<'_, statistics::Metrics> {
1212        self.stats_repository.get_stats().await
1213    }
1214
1215    /// It allows to send a statistic events which eventually will be used to update [`statistics::Metrics`].
1216    ///
1217    /// # Context: Statistics
1218    pub async fn send_stats_event(&self, event: statistics::Event) -> Option<Result<(), SendError<statistics::Event>>> {
1219        match &self.stats_event_sender {
1220            None => None,
1221            Some(stats_event_sender) => stats_event_sender.send_event(event).await,
1222        }
1223    }
1224
1225    /// It drops the database tables.
1226    ///
1227    /// # Errors
1228    ///
1229    /// Will return `Err` if unable to drop tables.
1230    pub fn drop_database_tables(&self) -> Result<(), databases::error::Error> {
1231        // todo: this is only used for testing. WE have to pass the database
1232        // reference directly to the tests instead of via the tracker.
1233        self.database.drop_database_tables()
1234    }
1235}
1236
1237#[must_use]
1238fn assign_ip_address_to_peer(remote_client_ip: &IpAddr, tracker_external_ip: Option<IpAddr>) -> IpAddr {
1239    if let Some(host_ip) = tracker_external_ip.filter(|_| remote_client_ip.is_loopback()) {
1240        host_ip
1241    } else {
1242        *remote_client_ip
1243    }
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248
1249    mod the_tracker {
1250
1251        use std::net::{IpAddr, Ipv4Addr, SocketAddr};
1252        use std::str::FromStr;
1253        use std::sync::Arc;
1254
1255        use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
1256        use torrust_tracker_configuration::TORRENT_PEERS_LIMIT;
1257        use torrust_tracker_primitives::info_hash::InfoHash;
1258        use torrust_tracker_primitives::DurationSinceUnixEpoch;
1259        use torrust_tracker_test_helpers::configuration;
1260
1261        use crate::core::peer::Peer;
1262        use crate::core::services::tracker_factory;
1263        use crate::core::{TorrentsMetrics, Tracker};
1264        use crate::shared::bit_torrent::info_hash::fixture::gen_seeded_infohash;
1265
1266        fn public_tracker() -> Tracker {
1267            tracker_factory(&configuration::ephemeral_public())
1268        }
1269
1270        fn private_tracker() -> Tracker {
1271            tracker_factory(&configuration::ephemeral_private())
1272        }
1273
1274        fn whitelisted_tracker() -> Tracker {
1275            tracker_factory(&configuration::ephemeral_listed())
1276        }
1277
1278        pub fn tracker_persisting_torrents_in_database() -> Tracker {
1279            let mut configuration = configuration::ephemeral();
1280            configuration.core.tracker_policy.persistent_torrent_completed_stat = true;
1281            tracker_factory(&configuration)
1282        }
1283
1284        fn sample_info_hash() -> InfoHash {
1285            "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap()
1286        }
1287
1288        // The client peer IP
1289        fn peer_ip() -> IpAddr {
1290            IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap())
1291        }
1292
1293        /// Sample peer whose state is not relevant for the tests
1294        fn sample_peer() -> Peer {
1295            complete_peer()
1296        }
1297
1298        /// Sample peer when for tests that need more than one peer
1299        fn sample_peer_1() -> Peer {
1300            Peer {
1301                peer_id: PeerId(*b"-qB00000000000000001"),
1302                peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8081),
1303                updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
1304                uploaded: NumberOfBytes::new(0),
1305                downloaded: NumberOfBytes::new(0),
1306                left: NumberOfBytes::new(0),
1307                event: AnnounceEvent::Completed,
1308            }
1309        }
1310
1311        /// Sample peer when for tests that need more than one peer
1312        fn sample_peer_2() -> Peer {
1313            Peer {
1314                peer_id: PeerId(*b"-qB00000000000000002"),
1315                peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 2)), 8082),
1316                updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
1317                uploaded: NumberOfBytes::new(0),
1318                downloaded: NumberOfBytes::new(0),
1319                left: NumberOfBytes::new(0),
1320                event: AnnounceEvent::Completed,
1321            }
1322        }
1323
1324        fn seeder() -> Peer {
1325            complete_peer()
1326        }
1327
1328        fn leecher() -> Peer {
1329            incomplete_peer()
1330        }
1331
1332        fn started_peer() -> Peer {
1333            incomplete_peer()
1334        }
1335
1336        fn completed_peer() -> Peer {
1337            complete_peer()
1338        }
1339
1340        /// A peer that counts as `complete` is swarm metadata
1341        /// IMPORTANT!: it only counts if the it has been announce at least once before
1342        /// announcing the `AnnounceEvent::Completed` event.
1343        fn complete_peer() -> Peer {
1344            Peer {
1345                peer_id: PeerId(*b"-qB00000000000000000"),
1346                peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080),
1347                updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
1348                uploaded: NumberOfBytes::new(0),
1349                downloaded: NumberOfBytes::new(0),
1350                left: NumberOfBytes::new(0), // No bytes left to download
1351                event: AnnounceEvent::Completed,
1352            }
1353        }
1354
1355        /// A peer that counts as `incomplete` is swarm metadata
1356        fn incomplete_peer() -> Peer {
1357            Peer {
1358                peer_id: PeerId(*b"-qB00000000000000000"),
1359                peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080),
1360                updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
1361                uploaded: NumberOfBytes::new(0),
1362                downloaded: NumberOfBytes::new(0),
1363                left: NumberOfBytes::new(1000), // Still bytes to download
1364                event: AnnounceEvent::Started,
1365            }
1366        }
1367
1368        #[tokio::test]
1369        async fn should_collect_torrent_metrics() {
1370            let tracker = public_tracker();
1371
1372            let torrents_metrics = tracker.get_torrents_metrics();
1373
1374            assert_eq!(
1375                torrents_metrics,
1376                TorrentsMetrics {
1377                    complete: 0,
1378                    downloaded: 0,
1379                    incomplete: 0,
1380                    torrents: 0
1381                }
1382            );
1383        }
1384
1385        #[tokio::test]
1386        async fn it_should_return_the_peers_for_a_given_torrent() {
1387            let tracker = public_tracker();
1388
1389            let info_hash = sample_info_hash();
1390            let peer = sample_peer();
1391
1392            tracker.upsert_peer_and_get_stats(&info_hash, &peer);
1393
1394            let peers = tracker.get_torrent_peers(&info_hash);
1395
1396            assert_eq!(peers, vec![Arc::new(peer)]);
1397        }
1398
1399        /// It generates a peer id from a number where the number is the last
1400        /// part of the peer ID. For example, for `12` it returns
1401        /// `-qB00000000000000012`.
1402        fn numeric_peer_id(two_digits_value: i32) -> PeerId {
1403            // Format idx as a string with leading zeros, ensuring it has exactly 2 digits
1404            let idx_str = format!("{two_digits_value:02}");
1405
1406            // Create the base part of the peer ID.
1407            let base = b"-qB00000000000000000";
1408
1409            // Concatenate the base with idx bytes, ensuring the total length is 20 bytes.
1410            let mut peer_id_bytes = [0u8; 20];
1411            peer_id_bytes[..base.len()].copy_from_slice(base);
1412            peer_id_bytes[base.len() - idx_str.len()..].copy_from_slice(idx_str.as_bytes());
1413
1414            PeerId(peer_id_bytes)
1415        }
1416
1417        #[tokio::test]
1418        async fn it_should_return_74_peers_at_the_most_for_a_given_torrent() {
1419            let tracker = public_tracker();
1420
1421            let info_hash = sample_info_hash();
1422
1423            for idx in 1..=75 {
1424                let peer = Peer {
1425                    peer_id: numeric_peer_id(idx),
1426                    peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, idx.try_into().unwrap())), 8080),
1427                    updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
1428                    uploaded: NumberOfBytes::new(0),
1429                    downloaded: NumberOfBytes::new(0),
1430                    left: NumberOfBytes::new(0), // No bytes left to download
1431                    event: AnnounceEvent::Completed,
1432                };
1433
1434                tracker.upsert_peer_and_get_stats(&info_hash, &peer);
1435            }
1436
1437            let peers = tracker.get_torrent_peers(&info_hash);
1438
1439            assert_eq!(peers.len(), 74);
1440        }
1441
1442        #[tokio::test]
1443        async fn it_should_return_the_peers_for_a_given_torrent_excluding_a_given_peer() {
1444            let tracker = public_tracker();
1445
1446            let info_hash = sample_info_hash();
1447            let peer = sample_peer();
1448
1449            tracker.upsert_peer_and_get_stats(&info_hash, &peer);
1450
1451            let peers = tracker.get_peers_for(&info_hash, &peer, TORRENT_PEERS_LIMIT);
1452
1453            assert_eq!(peers, vec![]);
1454        }
1455
1456        #[tokio::test]
1457        async fn it_should_return_74_peers_at_the_most_for_a_given_torrent_when_it_filters_out_a_given_peer() {
1458            let tracker = public_tracker();
1459
1460            let info_hash = sample_info_hash();
1461
1462            let excluded_peer = sample_peer();
1463
1464            tracker.upsert_peer_and_get_stats(&info_hash, &excluded_peer);
1465
1466            // Add 74 peers
1467            for idx in 2..=75 {
1468                let peer = Peer {
1469                    peer_id: numeric_peer_id(idx),
1470                    peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, idx.try_into().unwrap())), 8080),
1471                    updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
1472                    uploaded: NumberOfBytes::new(0),
1473                    downloaded: NumberOfBytes::new(0),
1474                    left: NumberOfBytes::new(0), // No bytes left to download
1475                    event: AnnounceEvent::Completed,
1476                };
1477
1478                tracker.upsert_peer_and_get_stats(&info_hash, &peer);
1479            }
1480
1481            let peers = tracker.get_peers_for(&info_hash, &excluded_peer, TORRENT_PEERS_LIMIT);
1482
1483            assert_eq!(peers.len(), 74);
1484        }
1485
1486        #[tokio::test]
1487        async fn it_should_return_the_torrent_metrics() {
1488            let tracker = public_tracker();
1489
1490            tracker.upsert_peer_and_get_stats(&sample_info_hash(), &leecher());
1491
1492            let torrent_metrics = tracker.get_torrents_metrics();
1493
1494            assert_eq!(
1495                torrent_metrics,
1496                TorrentsMetrics {
1497                    complete: 0,
1498                    downloaded: 0,
1499                    incomplete: 1,
1500                    torrents: 1,
1501                }
1502            );
1503        }
1504
1505        #[tokio::test]
1506        async fn it_should_get_many_the_torrent_metrics() {
1507            let tracker = public_tracker();
1508
1509            let start_time = std::time::Instant::now();
1510            for i in 0..1_000_000 {
1511                tracker.upsert_peer_and_get_stats(&gen_seeded_infohash(&i), &leecher());
1512            }
1513            let result_a = start_time.elapsed();
1514
1515            let start_time = std::time::Instant::now();
1516            let torrent_metrics = tracker.get_torrents_metrics();
1517            let result_b = start_time.elapsed();
1518
1519            assert_eq!(
1520                (torrent_metrics),
1521                (TorrentsMetrics {
1522                    complete: 0,
1523                    downloaded: 0,
1524                    incomplete: 1_000_000,
1525                    torrents: 1_000_000,
1526                }),
1527                "{result_a:?} {result_b:?}"
1528            );
1529        }
1530
1531        mod for_all_config_modes {
1532
1533            mod handling_an_announce_request {
1534
1535                use std::sync::Arc;
1536
1537                use crate::core::tests::the_tracker::{
1538                    peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
1539                };
1540                use crate::core::PeersWanted;
1541
1542                mod should_assign_the_ip_to_the_peer {
1543
1544                    use std::net::{IpAddr, Ipv4Addr};
1545
1546                    use crate::core::assign_ip_address_to_peer;
1547
1548                    #[test]
1549                    fn using_the_source_ip_instead_of_the_ip_in_the_announce_request() {
1550                        let remote_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 2));
1551
1552                        let peer_ip = assign_ip_address_to_peer(&remote_ip, None);
1553
1554                        assert_eq!(peer_ip, remote_ip);
1555                    }
1556
1557                    mod and_when_the_client_ip_is_a_ipv4_loopback_ip {
1558
1559                        use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
1560                        use std::str::FromStr;
1561
1562                        use crate::core::assign_ip_address_to_peer;
1563
1564                        #[test]
1565                        fn it_should_use_the_loopback_ip_if_the_tracker_does_not_have_the_external_ip_configuration() {
1566                            let remote_ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
1567
1568                            let peer_ip = assign_ip_address_to_peer(&remote_ip, None);
1569
1570                            assert_eq!(peer_ip, remote_ip);
1571                        }
1572
1573                        #[test]
1574                        fn it_should_use_the_external_tracker_ip_in_tracker_configuration_if_it_is_defined() {
1575                            let remote_ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
1576
1577                            let tracker_external_ip = IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap());
1578
1579                            let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
1580
1581                            assert_eq!(peer_ip, tracker_external_ip);
1582                        }
1583
1584                        #[test]
1585                        fn it_should_use_the_external_ip_in_the_tracker_configuration_if_it_is_defined_even_if_the_external_ip_is_an_ipv6_ip(
1586                        ) {
1587                            let remote_ip = IpAddr::V4(Ipv4Addr::LOCALHOST);
1588
1589                            let tracker_external_ip =
1590                                IpAddr::V6(Ipv6Addr::from_str("2345:0425:2CA1:0000:0000:0567:5673:23b5").unwrap());
1591
1592                            let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
1593
1594                            assert_eq!(peer_ip, tracker_external_ip);
1595                        }
1596                    }
1597
1598                    mod and_when_client_ip_is_a_ipv6_loopback_ip {
1599
1600                        use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
1601                        use std::str::FromStr;
1602
1603                        use crate::core::assign_ip_address_to_peer;
1604
1605                        #[test]
1606                        fn it_should_use_the_loopback_ip_if_the_tracker_does_not_have_the_external_ip_configuration() {
1607                            let remote_ip = IpAddr::V6(Ipv6Addr::LOCALHOST);
1608
1609                            let peer_ip = assign_ip_address_to_peer(&remote_ip, None);
1610
1611                            assert_eq!(peer_ip, remote_ip);
1612                        }
1613
1614                        #[test]
1615                        fn it_should_use_the_external_ip_in_tracker_configuration_if_it_is_defined() {
1616                            let remote_ip = IpAddr::V6(Ipv6Addr::LOCALHOST);
1617
1618                            let tracker_external_ip =
1619                                IpAddr::V6(Ipv6Addr::from_str("2345:0425:2CA1:0000:0000:0567:5673:23b5").unwrap());
1620
1621                            let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
1622
1623                            assert_eq!(peer_ip, tracker_external_ip);
1624                        }
1625
1626                        #[test]
1627                        fn it_should_use_the_external_ip_in_the_tracker_configuration_if_it_is_defined_even_if_the_external_ip_is_an_ipv4_ip(
1628                        ) {
1629                            let remote_ip = IpAddr::V6(Ipv6Addr::LOCALHOST);
1630
1631                            let tracker_external_ip = IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap());
1632
1633                            let peer_ip = assign_ip_address_to_peer(&remote_ip, Some(tracker_external_ip));
1634
1635                            assert_eq!(peer_ip, tracker_external_ip);
1636                        }
1637                    }
1638                }
1639
1640                #[tokio::test]
1641                async fn it_should_return_the_announce_data_with_an_empty_peer_list_when_it_is_the_first_announced_peer() {
1642                    let tracker = public_tracker();
1643
1644                    let mut peer = sample_peer();
1645
1646                    let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
1647
1648                    assert_eq!(announce_data.peers, vec![]);
1649                }
1650
1651                #[tokio::test]
1652                async fn it_should_return_the_announce_data_with_the_previously_announced_peers() {
1653                    let tracker = public_tracker();
1654
1655                    let mut previously_announced_peer = sample_peer_1();
1656                    tracker.announce(
1657                        &sample_info_hash(),
1658                        &mut previously_announced_peer,
1659                        &peer_ip(),
1660                        &PeersWanted::All,
1661                    );
1662
1663                    let mut peer = sample_peer_2();
1664                    let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
1665
1666                    assert_eq!(announce_data.peers, vec![Arc::new(previously_announced_peer)]);
1667                }
1668
1669                mod it_should_update_the_swarm_stats_for_the_torrent {
1670
1671                    use crate::core::tests::the_tracker::{
1672                        completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
1673                    };
1674                    use crate::core::PeersWanted;
1675
1676                    #[tokio::test]
1677                    async fn when_the_peer_is_a_seeder() {
1678                        let tracker = public_tracker();
1679
1680                        let mut peer = seeder();
1681
1682                        let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
1683
1684                        assert_eq!(announce_data.stats.complete, 1);
1685                    }
1686
1687                    #[tokio::test]
1688                    async fn when_the_peer_is_a_leecher() {
1689                        let tracker = public_tracker();
1690
1691                        let mut peer = leecher();
1692
1693                        let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
1694
1695                        assert_eq!(announce_data.stats.incomplete, 1);
1696                    }
1697
1698                    #[tokio::test]
1699                    async fn when_a_previously_announced_started_peer_has_completed_downloading() {
1700                        let tracker = public_tracker();
1701
1702                        // We have to announce with "started" event because peer does not count if peer was not previously known
1703                        let mut started_peer = started_peer();
1704                        tracker.announce(&sample_info_hash(), &mut started_peer, &peer_ip(), &PeersWanted::All);
1705
1706                        let mut completed_peer = completed_peer();
1707                        let announce_data =
1708                            tracker.announce(&sample_info_hash(), &mut completed_peer, &peer_ip(), &PeersWanted::All);
1709
1710                        assert_eq!(announce_data.stats.downloaded, 1);
1711                    }
1712                }
1713            }
1714
1715            mod handling_a_scrape_request {
1716
1717                use std::net::{IpAddr, Ipv4Addr};
1718
1719                use torrust_tracker_primitives::info_hash::InfoHash;
1720
1721                use crate::core::tests::the_tracker::{complete_peer, incomplete_peer, public_tracker};
1722                use crate::core::{PeersWanted, ScrapeData, SwarmMetadata};
1723
1724                #[tokio::test]
1725                async fn it_should_return_a_zeroed_swarm_metadata_for_the_requested_file_if_the_tracker_does_not_have_that_torrent(
1726                ) {
1727                    let tracker = public_tracker();
1728
1729                    let info_hashes = vec!["3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap()];
1730
1731                    let scrape_data = tracker.scrape(&info_hashes).await;
1732
1733                    let mut expected_scrape_data = ScrapeData::empty();
1734
1735                    expected_scrape_data.add_file_with_zeroed_metadata(&info_hashes[0]);
1736
1737                    assert_eq!(scrape_data, expected_scrape_data);
1738                }
1739
1740                #[tokio::test]
1741                async fn it_should_return_the_swarm_metadata_for_the_requested_file_if_the_tracker_has_that_torrent() {
1742                    let tracker = public_tracker();
1743
1744                    let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap();
1745
1746                    // Announce a "complete" peer for the torrent
1747                    let mut complete_peer = complete_peer();
1748                    tracker.announce(
1749                        &info_hash,
1750                        &mut complete_peer,
1751                        &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 10)),
1752                        &PeersWanted::All,
1753                    );
1754
1755                    // Announce an "incomplete" peer for the torrent
1756                    let mut incomplete_peer = incomplete_peer();
1757                    tracker.announce(
1758                        &info_hash,
1759                        &mut incomplete_peer,
1760                        &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 11)),
1761                        &PeersWanted::All,
1762                    );
1763
1764                    // Scrape
1765                    let scrape_data = tracker.scrape(&vec![info_hash]).await;
1766
1767                    // The expected swarm metadata for the file
1768                    let mut expected_scrape_data = ScrapeData::empty();
1769                    expected_scrape_data.add_file(
1770                        &info_hash,
1771                        SwarmMetadata {
1772                            complete: 0, // the "complete" peer does not count because it was not previously known
1773                            downloaded: 0,
1774                            incomplete: 1, // the "incomplete" peer we have just announced
1775                        },
1776                    );
1777
1778                    assert_eq!(scrape_data, expected_scrape_data);
1779                }
1780
1781                #[tokio::test]
1782                async fn it_should_allow_scraping_for_multiple_torrents() {
1783                    let tracker = public_tracker();
1784
1785                    let info_hashes = vec![
1786                        "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap(),
1787                        "99c82bb73505a3c0b453f9fa0e881d6e5a32a0c1".parse::<InfoHash>().unwrap(),
1788                    ];
1789
1790                    let scrape_data = tracker.scrape(&info_hashes).await;
1791
1792                    let mut expected_scrape_data = ScrapeData::empty();
1793                    expected_scrape_data.add_file_with_zeroed_metadata(&info_hashes[0]);
1794                    expected_scrape_data.add_file_with_zeroed_metadata(&info_hashes[1]);
1795
1796                    assert_eq!(scrape_data, expected_scrape_data);
1797                }
1798            }
1799        }
1800
1801        mod configured_as_whitelisted {
1802
1803            mod handling_authorization {
1804                use crate::core::tests::the_tracker::{sample_info_hash, whitelisted_tracker};
1805
1806                #[tokio::test]
1807                async fn it_should_authorize_the_announce_and_scrape_actions_on_whitelisted_torrents() {
1808                    let tracker = whitelisted_tracker();
1809
1810                    let info_hash = sample_info_hash();
1811
1812                    let result = tracker.add_torrent_to_whitelist(&info_hash).await;
1813                    assert!(result.is_ok());
1814
1815                    let result = tracker.authorize(&info_hash).await;
1816                    assert!(result.is_ok());
1817                }
1818
1819                #[tokio::test]
1820                async fn it_should_not_authorize_the_announce_and_scrape_actions_on_not_whitelisted_torrents() {
1821                    let tracker = whitelisted_tracker();
1822
1823                    let info_hash = sample_info_hash();
1824
1825                    let result = tracker.authorize(&info_hash).await;
1826                    assert!(result.is_err());
1827                }
1828            }
1829
1830            mod handling_the_torrent_whitelist {
1831                use crate::core::tests::the_tracker::{sample_info_hash, whitelisted_tracker};
1832
1833                #[tokio::test]
1834                async fn it_should_add_a_torrent_to_the_whitelist() {
1835                    let tracker = whitelisted_tracker();
1836
1837                    let info_hash = sample_info_hash();
1838
1839                    tracker.add_torrent_to_whitelist(&info_hash).await.unwrap();
1840
1841                    assert!(tracker.is_info_hash_whitelisted(&info_hash).await);
1842                }
1843
1844                #[tokio::test]
1845                async fn it_should_remove_a_torrent_from_the_whitelist() {
1846                    let tracker = whitelisted_tracker();
1847
1848                    let info_hash = sample_info_hash();
1849
1850                    tracker.add_torrent_to_whitelist(&info_hash).await.unwrap();
1851
1852                    tracker.remove_torrent_from_whitelist(&info_hash).await.unwrap();
1853
1854                    assert!(!tracker.is_info_hash_whitelisted(&info_hash).await);
1855                }
1856
1857                mod persistence {
1858                    use crate::core::tests::the_tracker::{sample_info_hash, whitelisted_tracker};
1859
1860                    #[tokio::test]
1861                    async fn it_should_load_the_whitelist_from_the_database() {
1862                        let tracker = whitelisted_tracker();
1863
1864                        let info_hash = sample_info_hash();
1865
1866                        tracker.add_torrent_to_whitelist(&info_hash).await.unwrap();
1867
1868                        // Remove torrent from the in-memory whitelist
1869                        tracker.whitelist.write().await.remove(&info_hash);
1870                        assert!(!tracker.is_info_hash_whitelisted(&info_hash).await);
1871
1872                        tracker.load_whitelist_from_database().await.unwrap();
1873
1874                        assert!(tracker.is_info_hash_whitelisted(&info_hash).await);
1875                    }
1876                }
1877            }
1878
1879            mod handling_an_announce_request {}
1880
1881            mod handling_an_scrape_request {
1882
1883                use torrust_tracker_primitives::info_hash::InfoHash;
1884                use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
1885
1886                use crate::core::tests::the_tracker::{
1887                    complete_peer, incomplete_peer, peer_ip, sample_info_hash, whitelisted_tracker,
1888                };
1889                use crate::core::{PeersWanted, ScrapeData};
1890
1891                #[test]
1892                fn it_should_be_able_to_build_a_zeroed_scrape_data_for_a_list_of_info_hashes() {
1893                    // Zeroed scrape data is used when the authentication for the scrape request fails.
1894
1895                    let sample_info_hash = sample_info_hash();
1896
1897                    let mut expected_scrape_data = ScrapeData::empty();
1898                    expected_scrape_data.add_file_with_zeroed_metadata(&sample_info_hash);
1899
1900                    assert_eq!(ScrapeData::zeroed(&vec![sample_info_hash]), expected_scrape_data);
1901                }
1902
1903                #[tokio::test]
1904                async fn it_should_return_the_zeroed_swarm_metadata_for_the_requested_file_if_it_is_not_whitelisted() {
1905                    let tracker = whitelisted_tracker();
1906
1907                    let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap();
1908
1909                    let mut peer = incomplete_peer();
1910                    tracker.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::All);
1911
1912                    // Announce twice to force non zeroed swarm metadata
1913                    let mut peer = complete_peer();
1914                    tracker.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::All);
1915
1916                    let scrape_data = tracker.scrape(&vec![info_hash]).await;
1917
1918                    // The expected zeroed swarm metadata for the file
1919                    let mut expected_scrape_data = ScrapeData::empty();
1920                    expected_scrape_data.add_file(&info_hash, SwarmMetadata::zeroed());
1921
1922                    assert_eq!(scrape_data, expected_scrape_data);
1923                }
1924            }
1925        }
1926
1927        mod configured_as_private {
1928
1929            mod handling_authentication {
1930                use std::str::FromStr;
1931                use std::time::Duration;
1932
1933                use crate::core::auth::{self};
1934                use crate::core::tests::the_tracker::private_tracker;
1935
1936                #[tokio::test]
1937                async fn it_should_fail_authenticating_a_peer_when_it_uses_an_unregistered_key() {
1938                    let tracker = private_tracker();
1939
1940                    let unregistered_key = auth::Key::from_str("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap();
1941
1942                    let result = tracker.authenticate(&unregistered_key).await;
1943
1944                    assert!(result.is_err());
1945                }
1946
1947                #[tokio::test]
1948                async fn it_should_fail_verifying_an_unregistered_authentication_key() {
1949                    let tracker = private_tracker();
1950
1951                    let unregistered_key = auth::Key::from_str("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap();
1952
1953                    assert!(tracker.verify_auth_key(&unregistered_key).await.is_err());
1954                }
1955
1956                #[tokio::test]
1957                async fn it_should_remove_an_authentication_key() {
1958                    let tracker = private_tracker();
1959
1960                    let expiring_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap();
1961
1962                    let result = tracker.remove_auth_key(&expiring_key.key()).await;
1963
1964                    assert!(result.is_ok());
1965                    assert!(tracker.verify_auth_key(&expiring_key.key()).await.is_err());
1966                }
1967
1968                #[tokio::test]
1969                async fn it_should_load_authentication_keys_from_the_database() {
1970                    let tracker = private_tracker();
1971
1972                    let expiring_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap();
1973
1974                    // Remove the newly generated key in memory
1975                    tracker.keys.write().await.remove(&expiring_key.key());
1976
1977                    let result = tracker.load_keys_from_database().await;
1978
1979                    assert!(result.is_ok());
1980                    assert!(tracker.verify_auth_key(&expiring_key.key()).await.is_ok());
1981                }
1982
1983                mod with_expiring_and {
1984
1985                    mod randomly_generated_keys {
1986                        use std::time::Duration;
1987
1988                        use torrust_tracker_clock::clock::Time;
1989                        use torrust_tracker_configuration::v2_0_0::core::PrivateMode;
1990
1991                        use crate::core::auth::Key;
1992                        use crate::core::tests::the_tracker::private_tracker;
1993                        use crate::CurrentClock;
1994
1995                        #[tokio::test]
1996                        async fn it_should_generate_the_key() {
1997                            let tracker = private_tracker();
1998
1999                            let peer_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap();
2000
2001                            assert_eq!(
2002                                peer_key.valid_until,
2003                                Some(CurrentClock::now_add(&Duration::from_secs(100)).unwrap())
2004                            );
2005                        }
2006
2007                        #[tokio::test]
2008                        async fn it_should_authenticate_a_peer_with_the_key() {
2009                            let tracker = private_tracker();
2010
2011                            let peer_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap();
2012
2013                            let result = tracker.authenticate(&peer_key.key()).await;
2014
2015                            assert!(result.is_ok());
2016                        }
2017
2018                        #[tokio::test]
2019                        async fn it_should_accept_an_expired_key_when_checking_expiration_is_disabled_in_configuration() {
2020                            let mut tracker = private_tracker();
2021
2022                            tracker.config.private_mode = Some(PrivateMode {
2023                                check_keys_expiration: false,
2024                            });
2025
2026                            let past_timestamp = Duration::ZERO;
2027
2028                            let peer_key = tracker
2029                                .add_auth_key(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap(), Some(past_timestamp))
2030                                .await
2031                                .unwrap();
2032
2033                            assert!(tracker.authenticate(&peer_key.key()).await.is_ok());
2034                        }
2035                    }
2036
2037                    mod pre_generated_keys {
2038                        use std::time::Duration;
2039
2040                        use torrust_tracker_clock::clock::Time;
2041                        use torrust_tracker_configuration::v2_0_0::core::PrivateMode;
2042
2043                        use crate::core::auth::Key;
2044                        use crate::core::tests::the_tracker::private_tracker;
2045                        use crate::core::AddKeyRequest;
2046                        use crate::CurrentClock;
2047
2048                        #[tokio::test]
2049                        async fn it_should_add_a_pre_generated_key() {
2050                            let tracker = private_tracker();
2051
2052                            let peer_key = tracker
2053                                .add_peer_key(AddKeyRequest {
2054                                    opt_key: Some(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap().to_string()),
2055                                    opt_seconds_valid: Some(100),
2056                                })
2057                                .await
2058                                .unwrap();
2059
2060                            assert_eq!(
2061                                peer_key.valid_until,
2062                                Some(CurrentClock::now_add(&Duration::from_secs(100)).unwrap())
2063                            );
2064                        }
2065
2066                        #[tokio::test]
2067                        async fn it_should_authenticate_a_peer_with_the_key() {
2068                            let tracker = private_tracker();
2069
2070                            let peer_key = tracker
2071                                .add_peer_key(AddKeyRequest {
2072                                    opt_key: Some(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap().to_string()),
2073                                    opt_seconds_valid: Some(100),
2074                                })
2075                                .await
2076                                .unwrap();
2077
2078                            let result = tracker.authenticate(&peer_key.key()).await;
2079
2080                            assert!(result.is_ok());
2081                        }
2082
2083                        #[tokio::test]
2084                        async fn it_should_accept_an_expired_key_when_checking_expiration_is_disabled_in_configuration() {
2085                            let mut tracker = private_tracker();
2086
2087                            tracker.config.private_mode = Some(PrivateMode {
2088                                check_keys_expiration: false,
2089                            });
2090
2091                            let peer_key = tracker
2092                                .add_peer_key(AddKeyRequest {
2093                                    opt_key: Some(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap().to_string()),
2094                                    opt_seconds_valid: Some(0),
2095                                })
2096                                .await
2097                                .unwrap();
2098
2099                            assert!(tracker.authenticate(&peer_key.key()).await.is_ok());
2100                        }
2101                    }
2102                }
2103
2104                mod with_permanent_and {
2105
2106                    mod randomly_generated_keys {
2107                        use crate::core::tests::the_tracker::private_tracker;
2108
2109                        #[tokio::test]
2110                        async fn it_should_generate_the_key() {
2111                            let tracker = private_tracker();
2112
2113                            let peer_key = tracker.generate_permanent_auth_key().await.unwrap();
2114
2115                            assert_eq!(peer_key.valid_until, None);
2116                        }
2117
2118                        #[tokio::test]
2119                        async fn it_should_authenticate_a_peer_with_the_key() {
2120                            let tracker = private_tracker();
2121
2122                            let peer_key = tracker.generate_permanent_auth_key().await.unwrap();
2123
2124                            let result = tracker.authenticate(&peer_key.key()).await;
2125
2126                            assert!(result.is_ok());
2127                        }
2128                    }
2129
2130                    mod pre_generated_keys {
2131                        use crate::core::auth::Key;
2132                        use crate::core::tests::the_tracker::private_tracker;
2133                        use crate::core::AddKeyRequest;
2134
2135                        #[tokio::test]
2136                        async fn it_should_add_a_pre_generated_key() {
2137                            let tracker = private_tracker();
2138
2139                            let peer_key = tracker
2140                                .add_peer_key(AddKeyRequest {
2141                                    opt_key: Some(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap().to_string()),
2142                                    opt_seconds_valid: None,
2143                                })
2144                                .await
2145                                .unwrap();
2146
2147                            assert_eq!(peer_key.valid_until, None);
2148                        }
2149
2150                        #[tokio::test]
2151                        async fn it_should_authenticate_a_peer_with_the_key() {
2152                            let tracker = private_tracker();
2153
2154                            let peer_key = tracker
2155                                .add_peer_key(AddKeyRequest {
2156                                    opt_key: Some(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap().to_string()),
2157                                    opt_seconds_valid: None,
2158                                })
2159                                .await
2160                                .unwrap();
2161
2162                            let result = tracker.authenticate(&peer_key.key()).await;
2163
2164                            assert!(result.is_ok());
2165                        }
2166                    }
2167                }
2168            }
2169
2170            mod handling_an_announce_request {}
2171
2172            mod handling_an_scrape_request {}
2173        }
2174
2175        mod configured_as_private_and_whitelisted {
2176
2177            mod handling_an_announce_request {}
2178
2179            mod handling_an_scrape_request {}
2180        }
2181
2182        mod handling_torrent_persistence {
2183
2184            use aquatic_udp_protocol::AnnounceEvent;
2185            use torrust_tracker_torrent_repository::entry::EntrySync;
2186            use torrust_tracker_torrent_repository::repository::Repository;
2187
2188            use crate::core::tests::the_tracker::{sample_info_hash, sample_peer, tracker_persisting_torrents_in_database};
2189
2190            #[tokio::test]
2191            async fn it_should_persist_the_number_of_completed_peers_for_all_torrents_into_the_database() {
2192                let tracker = tracker_persisting_torrents_in_database();
2193
2194                let info_hash = sample_info_hash();
2195
2196                let mut peer = sample_peer();
2197
2198                peer.event = AnnounceEvent::Started;
2199                let swarm_stats = tracker.upsert_peer_and_get_stats(&info_hash, &peer);
2200                assert_eq!(swarm_stats.downloaded, 0);
2201
2202                peer.event = AnnounceEvent::Completed;
2203                let swarm_stats = tracker.upsert_peer_and_get_stats(&info_hash, &peer);
2204                assert_eq!(swarm_stats.downloaded, 1);
2205
2206                // Remove the newly updated torrent from memory
2207                tracker.torrents.remove(&info_hash);
2208
2209                tracker.load_torrents_from_database().unwrap();
2210
2211                let torrent_entry = tracker.torrents.get(&info_hash).expect("it should be able to get entry");
2212
2213                // It persists the number of completed peers.
2214                assert_eq!(torrent_entry.get_swarm_metadata().downloaded, 1);
2215
2216                // It does not persist the peers
2217                assert!(torrent_entry.peers_is_empty());
2218            }
2219        }
2220    }
2221}