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}