Skip to content

Commit 181c27e

Browse files
committedFeb 12, 2025··
docs: [torrust#1261] review docs for tracker-core package
1 parent 74d0d28 commit 181c27e

32 files changed

+1467
-616
lines changed
 

‎packages/http-protocol/src/v1/query.rs

+7
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ mod tests {
249249
assert_eq!(query.get_param("param2"), Some("value2".to_string()));
250250
}
251251

252+
#[test]
253+
fn should_ignore_duplicate_param_values_when_asked_to_return_only_one_value() {
254+
let query = Query::from(vec![("param1", "value1"), ("param1", "value2")]);
255+
256+
assert_eq!(query.get_param("param1"), Some("value1".to_string()));
257+
}
258+
252259
#[test]
253260
fn should_fail_parsing_an_invalid_query_string() {
254261
let invalid_raw_query = "name=value=value";

‎packages/tracker-core/src/announce_handler.rs

+121-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,95 @@
1+
//! Announce handler.
2+
//!
3+
//! Handling `announce` requests is the most important task for a `BitTorrent`
4+
//! tracker.
5+
//!
6+
//! A `BitTorrent` swarm is a network of peers that are all trying to download
7+
//! the same torrent. When a peer wants to find other peers it announces itself
8+
//! to the swarm via the tracker. The peer sends its data to the tracker so that
9+
//! the tracker can add it to the swarm. The tracker responds to the peer with
10+
//! the list of other peers in the swarm so that the peer can contact them to
11+
//! start downloading pieces of the file from them.
12+
//!
13+
//! Once you have instantiated the `AnnounceHandler` you can `announce` a new [`peer::Peer`](torrust_tracker_primitives) with:
14+
//!
15+
//! ```rust,no_run
16+
//! use std::net::SocketAddr;
17+
//! use std::net::IpAddr;
18+
//! use std::net::Ipv4Addr;
19+
//! use std::str::FromStr;
20+
//!
21+
//! use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
22+
//! use torrust_tracker_primitives::DurationSinceUnixEpoch;
23+
//! use torrust_tracker_primitives::peer;
24+
//! use bittorrent_primitives::info_hash::InfoHash;
25+
//!
26+
//! let info_hash = InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap();
27+
//!
28+
//! let peer = peer::Peer {
29+
//! peer_id: PeerId(*b"-qB00000000000000001"),
30+
//! peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8081),
31+
//! updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
32+
//! uploaded: NumberOfBytes::new(0),
33+
//! downloaded: NumberOfBytes::new(0),
34+
//! left: NumberOfBytes::new(0),
35+
//! event: AnnounceEvent::Completed,
36+
//! };
37+
//!
38+
//! let peer_ip = IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap());
39+
//! ```
40+
//!
41+
//! ```text
42+
//! let announce_data = announce_handler.announce(&info_hash, &mut peer, &peer_ip).await;
43+
//! ```
44+
//!
45+
//! The handler returns the list of peers for the torrent with the infohash
46+
//! `3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0`, filtering out the peer that is
47+
//! making the `announce` request.
48+
//!
49+
//! > **NOTICE**: that the peer argument is mutable because the handler can
50+
//! > change the peer IP if the peer is using a loopback IP.
51+
//!
52+
//! The `peer_ip` argument is the resolved peer ip. It's a common practice that
53+
//! trackers ignore the peer ip in the `announce` request params, and resolve
54+
//! the peer ip using the IP of the client making the request. As the tracker is
55+
//! a domain service, the peer IP must be provided for the handler user, which
56+
//! is usually a higher component with access the the request metadata, for
57+
//! example, connection data, proxy headers, etcetera.
58+
//!
59+
//! The returned struct is:
60+
//!
61+
//! ```rust,no_run
62+
//! use torrust_tracker_primitives::peer;
63+
//! use torrust_tracker_configuration::AnnouncePolicy;
64+
//!
65+
//! pub struct AnnounceData {
66+
//! pub peers: Vec<peer::Peer>,
67+
//! pub swarm_stats: SwarmMetadata,
68+
//! pub policy: AnnouncePolicy, // the tracker announce policy.
69+
//! }
70+
//!
71+
//! pub struct SwarmMetadata {
72+
//! pub completed: u32, // The number of peers that have ever completed downloading
73+
//! pub seeders: u32, // The number of active peers that have completed downloading (seeders)
74+
//! pub leechers: u32, // The number of active peers that have not completed downloading (leechers)
75+
//! }
76+
//!
77+
//! // Core tracker configuration
78+
//! pub struct AnnounceInterval {
79+
//! // ...
80+
//! pub interval: u32, // Interval in seconds that the client should wait between sending regular announce requests to the tracker
81+
//! pub interval_min: u32, // Minimum announce interval. Clients must not reannounce more frequently than this
82+
//! // ...
83+
//! }
84+
//! ```
85+
//!
86+
//! ## Related BEPs:
87+
//!
88+
//! Refer to `BitTorrent` BEPs and other sites for more information about the `announce` request:
89+
//!
90+
//! - [BEP 3. The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html)
91+
//! - [BEP 23. Tracker Returns Compact Peer Lists](https://www.bittorrent.org/beps/bep_0023.html)
92+
//! - [Vuze docs](https://wiki.vuze.com/w/Announce)
193
use std::net::IpAddr;
294
use std::sync::Arc;
395

@@ -10,18 +102,20 @@ use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
10102
use super::torrent::repository::in_memory::InMemoryTorrentRepository;
11103
use super::torrent::repository::persisted::DatabasePersistentTorrentRepository;
12104

105+
/// Handles `announce` requests from `BitTorrent` clients.
13106
pub struct AnnounceHandler {
14107
/// The tracker configuration.
15108
config: Core,
16109

17-
/// The in-memory torrents repository.
110+
/// Repository for in-memory torrent data.
18111
in_memory_torrent_repository: Arc<InMemoryTorrentRepository>,
19112

20-
/// The persistent torrents repository.
113+
/// Repository for persistent torrent data (database).
21114
db_torrent_repository: Arc<DatabasePersistentTorrentRepository>,
22115
}
23116

24117
impl AnnounceHandler {
118+
/// Creates a new `AnnounceHandler`.
25119
#[must_use]
26120
pub fn new(
27121
config: &Core,
@@ -35,9 +129,20 @@ impl AnnounceHandler {
35129
}
36130
}
37131

38-
/// It handles an announce request.
132+
/// Processes an announce request from a peer.
39133
///
40134
/// BEP 03: [The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html).
135+
///
136+
/// # Parameters
137+
///
138+
/// - `info_hash`: The unique identifier of the torrent.
139+
/// - `peer`: The peer announcing itself (may be updated if IP is adjusted).
140+
/// - `remote_client_ip`: The IP address of the client making the request.
141+
/// - `peers_wanted`: Specifies how many peers the client wants in the response.
142+
///
143+
/// # Returns
144+
///
145+
/// An `AnnounceData` struct containing the list of peers, swarm statistics, and tracker policy.
41146
pub fn announce(
42147
&self,
43148
info_hash: &InfoHash,
@@ -77,9 +182,8 @@ impl AnnounceHandler {
77182
}
78183
}
79184

80-
/// It updates the torrent entry in memory, it also stores in the database
81-
/// the torrent info data which is persistent, and finally return the data
82-
/// needed for a `announce` request response.
185+
/// Updates the torrent data in memory, persists statistics if needed, and
186+
/// returns the updated swarm stats.
83187
#[must_use]
84188
fn upsert_peer_and_get_stats(&self, info_hash: &InfoHash, peer: &peer::Peer) -> SwarmMetadata {
85189
let swarm_metadata_before = self.in_memory_torrent_repository.get_swarm_metadata(info_hash);
@@ -95,7 +199,7 @@ impl AnnounceHandler {
95199
swarm_metadata_after
96200
}
97201

98-
/// It stores the torrents stats into the database (if persistency is enabled).
202+
/// Persists torrent statistics to the database if persistence is enabled.
99203
fn persist_stats(&self, info_hash: &InfoHash, swarm_metadata: &SwarmMetadata) {
100204
if self.config.tracker_policy.persistent_torrent_completed_stat {
101205
let completed = swarm_metadata.downloaded;
@@ -106,22 +210,25 @@ impl AnnounceHandler {
106210
}
107211
}
108212

109-
/// How many peers the peer announcing wants in the announce response.
213+
/// Specifies how many peers a client wants in the announce response.
110214
#[derive(Clone, Debug, PartialEq, Default)]
111215
pub enum PeersWanted {
112-
/// The peer wants as many peers as possible in the announce response.
216+
/// Request as many peers as possible (default behavior).
113217
#[default]
114218
AsManyAsPossible,
115-
/// The peer only wants a certain amount of peers in the announce response.
219+
220+
/// Request a specific number of peers.
116221
Only { amount: usize },
117222
}
118223

119224
impl PeersWanted {
225+
/// Request a specific number of peers.
120226
#[must_use]
121227
pub fn only(limit: u32) -> Self {
122228
limit.into()
123229
}
124230

231+
/// Returns the maximum number of peers allowed based on the request and tracker limit.
125232
fn limit(&self) -> usize {
126233
match self {
127234
PeersWanted::AsManyAsPossible => TORRENT_PEERS_LIMIT,
@@ -159,6 +266,10 @@ impl From<u32> for PeersWanted {
159266
}
160267
}
161268

269+
/// Assigns the correct IP address to a peer based on tracker settings.
270+
///
271+
/// If the client IP is a loopback address and the tracker has an external IP
272+
/// configured, the external IP will be assigned to the peer.
162273
#[must_use]
163274
fn assign_ip_address_to_peer(remote_client_ip: &IpAddr, tracker_external_ip: Option<IpAddr>) -> IpAddr {
164275
if let Some(host_ip) = tracker_external_ip.filter(|_| remote_client_ip.is_loopback()) {

‎packages/tracker-core/src/authentication/handler.rs

+93-48
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
//! This module implements the `KeysHandler` service
2+
//!
3+
//! It's responsible for managing authentication keys for the `BitTorrent` tracker.
4+
//!
5+
//! The service handles both persistent and in-memory storage of peer keys, and
6+
//! supports adding new keys (either pre-generated or randomly created),
7+
//! removing keys, and loading keys from the database into memory. Keys can be
8+
//! either permanent or expire after a configurable duration per key.
19
use std::sync::Arc;
210
use std::time::Duration;
311

@@ -11,29 +19,44 @@ use super::{key, CurrentClock, Key, PeerKey};
1119
use crate::databases;
1220
use crate::error::PeerKeyError;
1321

14-
/// This type contains the info needed to add a new tracker key.
22+
/// Contains the information needed to add a new tracker key.
1523
///
16-
/// You can upload a pre-generated key or let the app to generate a new one.
17-
/// You can also set an expiration date or leave it empty (`None`) if you want
18-
/// to create a permanent key that does not expire.
24+
/// A new key can either be a pre-generated key provided by the user or can be
25+
/// randomly generated by the application. Additionally, the key may be set to
26+
/// expire after a certain number of seconds, or be permanent (if no expiration
27+
/// is specified).
1928
#[derive(Debug)]
2029
pub struct AddKeyRequest {
21-
/// The pre-generated key. Use `None` to generate a random key.
30+
/// The pre-generated key as a string. If `None` the service will generate a
31+
/// random key.
2232
pub opt_key: Option<String>,
2333

24-
/// How long the key will be valid in seconds. Use `None` for permanent keys.
34+
/// The duration (in seconds) for which the key is valid. Use `None` for
35+
/// permanent keys.
2536
pub opt_seconds_valid: Option<u64>,
2637
}
2738

39+
/// The `KeysHandler` service manages the creation, addition, removal, and loading
40+
/// of authentication keys for the tracker.
41+
///
42+
/// It uses both a persistent (database) repository and an in-memory repository
43+
/// to manage keys.
2844
pub struct KeysHandler {
29-
/// The database repository for the authentication keys.
45+
/// The database repository for storing authentication keys persistently.
3046
db_key_repository: Arc<DatabaseKeyRepository>,
3147

32-
/// In-memory implementation of the authentication key repository.
48+
/// The in-memory repository for caching authentication keys.
3349
in_memory_key_repository: Arc<InMemoryKeyRepository>,
3450
}
3551

3652
impl KeysHandler {
53+
/// Creates a new instance of the `KeysHandler` service.
54+
///
55+
/// # Parameters
56+
///
57+
/// - `db_key_repository`: A shared reference to the database key repository.
58+
/// - `in_memory_key_repository`: A shared reference to the in-memory key
59+
/// repository.
3760
#[must_use]
3861
pub fn new(db_key_repository: &Arc<DatabaseKeyRepository>, in_memory_key_repository: &Arc<InMemoryKeyRepository>) -> Self {
3962
Self {
@@ -42,18 +65,24 @@ impl KeysHandler {
4265
}
4366
}
4467

45-
/// Adds new peer keys to the tracker.
68+
/// Adds a new peer key to the tracker.
69+
///
70+
/// The key may be pre-generated or generated on-the-fly.
71+
///
72+
/// Depending on whether an expiration duration is specified, the key will
73+
/// be either expiring or permanent.
4674
///
47-
/// Keys can be pre-generated or randomly created. They can also be
48-
/// permanent or expire.
75+
/// # Parameters
76+
///
77+
/// - `add_key_req`: The request containing options for key creation.
4978
///
5079
/// # Errors
5180
///
52-
/// Will return an error if:
81+
/// Returns an error if:
5382
///
54-
/// - The key duration overflows the duration type maximum value.
83+
/// - The provided key duration exceeds the maximum allowed value.
5584
/// - The provided pre-generated key is invalid.
56-
/// - The key could not been persisted due to database issues.
85+
/// - There is an error persisting the key in the database.
5786
pub async fn add_peer_key(&self, add_key_req: AddKeyRequest) -> Result<PeerKey, PeerKeyError> {
5887
if let Some(pre_existing_key) = add_key_req.opt_key {
5988
// Pre-generated key
@@ -125,29 +154,31 @@ impl KeysHandler {
125154
}
126155
}
127156

128-
/// It generates a new permanent authentication key.
157+
/// Generates a new permanent authentication key.
129158
///
130-
/// Authentication keys are used by HTTP trackers.
159+
/// Permanent keys do not expire.
131160
///
132161
/// # Errors
133162
///
134-
/// Will return a `database::Error` if unable to add the `auth_key` to the database.
163+
/// Returns a `databases::error::Error` if the key cannot be persisted in
164+
/// the database.
135165
pub(crate) async fn generate_permanent_peer_key(&self) -> Result<PeerKey, databases::error::Error> {
136166
self.generate_expiring_peer_key(None).await
137167
}
138168

139-
/// It generates a new expiring authentication key.
169+
/// Generates a new authentication key with an optional expiration lifetime.
140170
///
141-
/// Authentication keys are used by HTTP trackers.
171+
/// If a `lifetime` is provided, the generated key will expire after that
172+
/// duration. The new key is stored both in the database and in memory.
142173
///
143-
/// # Errors
174+
/// # Parameters
144175
///
145-
/// Will return a `database::Error` if unable to add the `auth_key` to the database.
176+
/// - `lifetime`: An optional duration specifying how long the key is valid.
146177
///
147-
/// # Arguments
178+
/// # Errors
148179
///
149-
/// * `lifetime` - The duration in seconds for the new key. The key will be
150-
/// no longer valid after `lifetime` seconds.
180+
/// Returns a `databases::error::Error` if there is an issue adding the key
181+
/// to the database.
151182
pub async fn generate_expiring_peer_key(&self, lifetime: Option<Duration>) -> Result<PeerKey, databases::error::Error> {
152183
let peer_key = key::generate_key(lifetime);
153184

@@ -158,36 +189,36 @@ impl KeysHandler {
158189
Ok(peer_key)
159190
}
160191

161-
/// It adds a pre-generated permanent authentication key.
192+
/// Adds a pre-generated permanent authentication key.
162193
///
163-
/// Authentication keys are used by HTTP trackers.
194+
/// Internally, this calls `add_expiring_peer_key` with no expiration.
164195
///
165-
/// # Errors
196+
/// # Parameters
166197
///
167-
/// Will return a `database::Error` if unable to add the `auth_key` to the
168-
/// database. For example, if the key already exist.
198+
/// - `key`: The pre-generated key.
169199
///
170-
/// # Arguments
200+
/// # Errors
171201
///
172-
/// * `key` - The pre-generated key.
202+
/// Returns a `databases::error::Error` if there is an issue persisting the
203+
/// key.
173204
pub(crate) async fn add_permanent_peer_key(&self, key: Key) -> Result<PeerKey, databases::error::Error> {
174205
self.add_expiring_peer_key(key, None).await
175206
}
176207

177-
/// It adds a pre-generated authentication key.
208+
/// Adds a pre-generated authentication key with an optional expiration.
178209
///
179-
/// Authentication keys are used by HTTP trackers.
210+
/// The key is stored in both the database and the in-memory repository.
180211
///
181-
/// # Errors
212+
/// # Parameters
182213
///
183-
/// Will return a `database::Error` if unable to add the `auth_key` to the
184-
/// database. For example, if the key already exist.
214+
/// - `key`: The pre-generated key.
215+
/// - `valid_until`: An optional timestamp (as a duration since the Unix
216+
/// epoch) after which the key expires.
185217
///
186-
/// # Arguments
218+
/// # Errors
187219
///
188-
/// * `key` - The pre-generated key.
189-
/// * `lifetime` - The duration in seconds for the new key. The key will be
190-
/// no longer valid after `lifetime` seconds.
220+
/// Returns a `databases::error::Error` if there is an issue adding the key
221+
/// to the database.
191222
pub(crate) async fn add_expiring_peer_key(
192223
&self,
193224
key: Key,
@@ -205,11 +236,18 @@ impl KeysHandler {
205236
Ok(peer_key)
206237
}
207238

208-
/// It removes an authentication key.
239+
/// Removes an authentication key.
240+
///
241+
/// The key is removed from both the database and the in-memory repository.
242+
///
243+
/// # Parameters
244+
///
245+
/// - `key`: A reference to the key to be removed.
209246
///
210247
/// # Errors
211248
///
212-
/// Will return a `database::Error` if unable to remove the `key` to the database.
249+
/// Returns a `databases::error::Error` if the key cannot be removed from
250+
/// the database.
213251
pub async fn remove_peer_key(&self, key: &Key) -> Result<(), databases::error::Error> {
214252
self.db_key_repository.remove(key)?;
215253

@@ -218,19 +256,26 @@ impl KeysHandler {
218256
Ok(())
219257
}
220258

221-
/// It removes an authentication key from memory.
259+
/// Removes an authentication key from the in-memory repository.
260+
///
261+
/// This function does not interact with the database.
262+
///
263+
/// # Parameters
264+
///
265+
/// - `key`: A reference to the key to be removed.
222266
pub(crate) async fn remove_in_memory_auth_key(&self, key: &Key) {
223267
self.in_memory_key_repository.remove(key).await;
224268
}
225269

226-
/// The `Tracker` stores the authentication keys in memory and in the
227-
/// database. In case you need to restart the `Tracker` you can load the
228-
/// keys from the database into memory with this function. Keys are
229-
/// automatically stored in the database when they are generated.
270+
/// Loads all authentication keys from the database into the in-memory
271+
/// repository.
272+
///
273+
/// This is useful during tracker startup to ensure that all persisted keys
274+
/// are available in memory.
230275
///
231276
/// # Errors
232277
///
233-
/// Will return a `database::Error` if unable to `load_keys` from the database.
278+
/// Returns a `databases::error::Error` if there is an issue loading the keys from the database.
234279
pub async fn load_peer_keys_from_database(&self) -> Result<(), databases::error::Error> {
235280
let keys_from_database = self.db_key_repository.load_keys()?;
236281

‎packages/tracker-core/src/authentication/key/mod.rs

+69-33
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,45 @@
1-
//! Tracker authentication services and structs.
1+
//! Tracker authentication services and types.
22
//!
3-
//! This module contains functions to handle tracker keys.
4-
//! Tracker keys are tokens used to authenticate the tracker clients when the tracker runs
5-
//! in `private` or `private_listed` modes.
3+
//! This module provides functions and data structures for handling tracker keys.
4+
//! Tracker keys are tokens used to authenticate tracker clients when the
5+
//! tracker is running in `private` mode.
66
//!
7-
//! There are services to [`generate_key`] and [`verify_key_expiration`] authentication keys.
7+
//! Authentication keys are used exclusively by HTTP trackers. Every key has an
8+
//! expiration time, meaning that it is only valid for a predetermined period.
9+
//! Once the expiration time is reached, an expiring key will be rejected.
810
//!
9-
//! Authentication keys are used only by HTTP trackers. All keys have an expiration time, that means
10-
//! they are only valid during a period of time. After that time the expiring key will no longer be valid.
11+
//! The primary key structure is [`PeerKey`], which couples a randomly generated
12+
//! [`Key`] (a 32-character alphanumeric string) with an optional expiration
13+
//! timestamp.
1114
//!
12-
//! Keys are stored in this struct:
15+
//! # Examples
1316
//!
14-
//! ```rust,no_run
17+
//! Generating a new key valid for `9999` seconds:
18+
//!
19+
//! ```rust
20+
//! use bittorrent_tracker_core::authentication;
21+
//! use std::time::Duration;
22+
//!
23+
//! let expiring_key = authentication::key::generate_key(Some(Duration::new(9999, 0)));
24+
//!
25+
//! // Later, verify that the key is still valid.
26+
//! assert!(authentication::key::verify_key_expiration(&expiring_key).is_ok());
27+
//! ```
28+
//!
29+
//! The core key types are defined as follows:
30+
//!
31+
//! ```rust
1532
//! use bittorrent_tracker_core::authentication::Key;
1633
//! use torrust_tracker_primitives::DurationSinceUnixEpoch;
1734
//!
1835
//! pub struct PeerKey {
19-
//! /// Random 32-char string. For example: `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ`
36+
//! /// A random 32-character authentication token (e.g., `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ`)
2037
//! pub key: Key,
2138
//!
22-
//! /// Timestamp, the key will be no longer valid after this timestamp.
23-
//! /// If `None` the keys will not expire (permanent key).
39+
//! /// The timestamp after which the key expires. If `None`, the key is permanent.
2440
//! pub valid_until: Option<DurationSinceUnixEpoch>,
2541
//! }
2642
//! ```
27-
//!
28-
//! You can generate a new key valid for `9999` seconds and `0` nanoseconds from the current time with the following:
29-
//!
30-
//! ```rust,no_run
31-
//! use bittorrent_tracker_core::authentication;
32-
//! use std::time::Duration;
33-
//!
34-
//! let expiring_key = authentication::key::generate_key(Some(Duration::new(9999, 0)));
35-
//!
36-
//! // And you can later verify it with:
37-
//!
38-
//! assert!(authentication::key::verify_key_expiration(&expiring_key).is_ok());
39-
//! ```
4043
pub mod peer_key;
4144
pub mod repository;
4245

@@ -75,17 +78,33 @@ pub(crate) fn generate_expiring_key(lifetime: Duration) -> PeerKey {
7578
generate_key(Some(lifetime))
7679
}
7780

78-
/// It generates a new random 32-char authentication [`PeerKey`].
81+
/// Generates a new random 32-character authentication key (`PeerKey`).
7982
///
80-
/// It can be an expiring or permanent key.
83+
/// If a lifetime is provided, the generated key will expire after the specified
84+
/// duration; otherwise, the key is permanent (i.e., it never expires).
8185
///
8286
/// # Panics
8387
///
84-
/// It would panic if the `lifetime: Duration` + Duration is more than `Duration::MAX`.
88+
/// Panics if the addition of the lifetime to the current time overflows
89+
/// (an extremely unlikely event).
8590
///
8691
/// # Arguments
8792
///
88-
/// * `lifetime`: if `None` the key will be permanent.
93+
/// * `lifetime`: An optional duration specifying how long the key is valid.
94+
/// If `None`, the key is permanent.
95+
///
96+
/// # Examples
97+
///
98+
/// ```rust
99+
/// use bittorrent_tracker_core::authentication::key;
100+
/// use std::time::Duration;
101+
///
102+
/// // Generate an expiring key valid for 3600 seconds.
103+
/// let expiring_key = key::generate_key(Some(Duration::from_secs(3600)));
104+
///
105+
/// // Generate a permanent key.
106+
/// let permanent_key = key::generate_key(None);
107+
/// ```
89108
#[must_use]
90109
pub fn generate_key(lifetime: Option<Duration>) -> PeerKey {
91110
let random_key = Key::random();
@@ -107,13 +126,27 @@ pub fn generate_key(lifetime: Option<Duration>) -> PeerKey {
107126
}
108127
}
109128

110-
/// It verifies an [`PeerKey`]. It checks if the expiration date has passed.
111-
/// Permanent keys without duration (`None`) do not expire.
129+
/// Verifies whether a given authentication key (`PeerKey`) is still valid.
130+
///
131+
/// For expiring keys, this function compares the key's expiration timestamp
132+
/// against the current time. Permanent keys (with `None` as their expiration)
133+
/// are always valid.
112134
///
113135
/// # Errors
114136
///
115-
/// Will return a verification error [`enum@crate::authentication::key::Error`] if
116-
/// it cannot verify the key.
137+
/// Returns a verification error of type [`enum@Error`] if the key has expired.
138+
///
139+
/// # Examples
140+
///
141+
/// ```rust
142+
/// use bittorrent_tracker_core::authentication::key;
143+
/// use std::time::Duration;
144+
///
145+
/// let expiring_key = key::generate_key(Some(Duration::from_secs(100)));
146+
///
147+
/// // If the key's expiration time has passed, the verification will fail.
148+
/// assert!(key::verify_key_expiration(&expiring_key).is_ok());
149+
/// ```
117150
pub fn verify_key_expiration(auth_key: &PeerKey) -> Result<(), Error> {
118151
let current_time: DurationSinceUnixEpoch = CurrentClock::now();
119152

@@ -136,17 +169,20 @@ pub fn verify_key_expiration(auth_key: &PeerKey) -> Result<(), Error> {
136169
#[derive(Debug, Error)]
137170
#[allow(dead_code)]
138171
pub enum Error {
172+
/// Wraps an underlying error encountered during key verification.
139173
#[error("Key could not be verified: {source}")]
140174
KeyVerificationError {
141175
source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
142176
},
143177

178+
/// Indicates that the key could not be read or found.
144179
#[error("Failed to read key: {key}, {location}")]
145180
UnableToReadKey {
146181
location: &'static Location<'static>,
147182
key: Box<Key>,
148183
},
149184

185+
/// Indicates that the key has expired.
150186
#[error("Key has expired, {location}")]
151187
KeyExpired { location: &'static Location<'static> },
152188
}

‎packages/tracker-core/src/authentication/key/peer_key.rs

+82-21
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
//! Authentication keys for private trackers.
2+
//!
3+
//! This module defines the types and functionality for managing authentication
4+
//! keys used by the tracker. These keys, represented by the `Key` and `PeerKey`
5+
//! types, are essential for authenticating peers in private tracker
6+
//! environments.
7+
//!
8+
//! A `Key` is a 32-character alphanumeric token, while a `PeerKey` couples a
9+
//! `Key` with an optional expiration timestamp. If the expiration is set (via
10+
//! `valid_until`), the key will become invalid after that time.
111
use std::str::FromStr;
212
use std::time::Duration;
313

@@ -11,22 +21,42 @@ use torrust_tracker_primitives::DurationSinceUnixEpoch;
1121

1222
use super::AUTH_KEY_LENGTH;
1323

14-
/// An authentication key which can potentially have an expiration time.
15-
/// After that time is will automatically become invalid.
24+
/// A peer authentication key with an optional expiration time.
25+
///
26+
/// A `PeerKey` associates a generated `Key` (a 32-character alphanumeric string)
27+
/// with an optional expiration timestamp (`valid_until`). If `valid_until` is
28+
/// `None`, the key is considered permanent.
29+
///
30+
/// # Example
31+
///
32+
/// ```rust
33+
/// use std::time::Duration;
34+
/// use bittorrent_tracker_core::authentication::key::peer_key::{Key, PeerKey};
35+
///
36+
/// let expiring_key = PeerKey {
37+
/// key: Key::random(),
38+
/// valid_until: Some(Duration::from_secs(3600)), // Expires in 1 hour
39+
/// };
40+
///
41+
/// let permanent_key = PeerKey {
42+
/// key: Key::random(),
43+
/// valid_until: None,
44+
/// };
45+
/// ```
1646
#[derive(Serialize, Deserialize, Debug, Clone)]
1747
pub struct PeerKey {
18-
/// Random 32-char string. For example: `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ`
48+
/// A 32-character authentication key. For example: `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ`
1949
pub key: Key,
2050

21-
/// Timestamp, the key will be no longer valid after this timestamp.
22-
/// If `None` the keys will not expire (permanent key).
51+
/// An optional expiration timestamp. If set, the key becomes invalid after
52+
/// this time. A value of `None` indicates a permanent key.
2353
pub valid_until: Option<DurationSinceUnixEpoch>,
2454
}
2555

2656
impl PartialEq for PeerKey {
2757
fn eq(&self, other: &Self) -> bool {
28-
// We ignore the fractions of seconds when comparing the timestamps
29-
// because we only store the seconds in the database.
58+
// When comparing two PeerKeys, ignore fractions of seconds since only
59+
// whole seconds are stored in the database.
3060
self.key == other.key
3161
&& match (&self.valid_until, &other.valid_until) {
3262
(Some(a), Some(b)) => a.as_secs() == b.as_secs(),
@@ -53,14 +83,17 @@ impl PeerKey {
5383
self.key.clone()
5484
}
5585

56-
/// It returns the expiry time. For example, for the starting time for Unix Epoch
57-
/// (timestamp 0) it will return a `DateTime` whose string representation is
58-
/// `1970-01-01 00:00:00 UTC`.
86+
/// Computes and returns the expiration time as a UTC `DateTime`, if one
87+
/// exists.
88+
///
89+
/// The returned time is derived from the stored seconds since the Unix
90+
/// epoch. Note that any fractional seconds are discarded since only whole
91+
/// seconds are stored in the database.
5992
///
6093
/// # Panics
6194
///
62-
/// Will panic when the key timestamp overflows the internal i64 type.
63-
/// (this will naturally happen in 292.5 billion years)
95+
/// Panics if the key's timestamp overflows the internal `i64` type (this is
96+
/// extremely unlikely, happening roughly 292.5 billion years from now).
6497
#[must_use]
6598
pub fn expiry_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
6699
// We remove the fractions of seconds because we only store the seconds
@@ -72,17 +105,37 @@ impl PeerKey {
72105

73106
/// A token used for authentication.
74107
///
75-
/// - It contains only ascii alphanumeric chars: lower and uppercase letters and
76-
/// numbers.
77-
/// - It's a 32-char string.
108+
/// The `Key` type encapsulates a 32-character string that must consist solely
109+
/// of ASCII alphanumeric characters (0-9, a-z, A-Z). This key is used by the
110+
/// tracker to authenticate peers.
111+
///
112+
/// # Examples
113+
///
114+
/// Creating a key from a valid string:
115+
///
116+
/// ```
117+
/// use bittorrent_tracker_core::authentication::key::peer_key::Key;
118+
/// let key = Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap();
119+
/// ```
120+
///
121+
/// Generating a random key:
122+
///
123+
/// ```
124+
/// use bittorrent_tracker_core::authentication::key::peer_key::Key;
125+
/// let random_key = Key::random();
126+
/// ```
78127
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Display, Hash)]
79128
pub struct Key(String);
80129

81130
impl Key {
131+
/// Constructs a new `Key` from the given string.
132+
///
82133
/// # Errors
83134
///
84-
/// Will return an error is the string represents an invalid key.
85-
/// Valid keys can only contain 32 chars including 0-9, a-z and A-Z.
135+
/// Returns a `ParseKeyError` if:
136+
///
137+
/// - The input string does not have exactly 32 characters.
138+
/// - The input string contains characters that are not ASCII alphanumeric.
86139
pub fn new(value: &str) -> Result<Self, ParseKeyError> {
87140
if value.len() != AUTH_KEY_LENGTH {
88141
return Err(ParseKeyError::InvalidKeyLength);
@@ -95,11 +148,14 @@ impl Key {
95148
Ok(Self(value.to_owned()))
96149
}
97150

98-
/// It generates a random key.
151+
/// Generates a new random authentication key.
152+
///
153+
/// The random key is generated by sampling 32 ASCII alphanumeric characters.
99154
///
100155
/// # Panics
101156
///
102-
/// Will panic if the random number generator fails to generate a valid key.
157+
/// Panics if the random number generator fails to produce a valid key
158+
/// (extremely unlikely).
103159
pub fn random() -> Self {
104160
let random_id: String = rng()
105161
.sample_iter(&Alphanumeric)
@@ -115,9 +171,11 @@ impl Key {
115171
}
116172
}
117173

118-
/// Error returned when a key cannot be parsed from a string.
174+
/// Errors that can occur when parsing a string into a `Key`.
175+
///
176+
/// # Examples
119177
///
120-
/// ```text
178+
/// ```rust
121179
/// use bittorrent_tracker_core::authentication::Key;
122180
/// use std::str::FromStr;
123181
///
@@ -132,9 +190,12 @@ impl Key {
132190
/// this error.
133191
#[derive(Debug, Error)]
134192
pub enum ParseKeyError {
193+
/// The provided key does not have exactly 32 characters.
135194
#[error("Invalid key length. Key must be have 32 chars")]
136195
InvalidKeyLength,
137196

197+
/// The provided key contains invalid characters. Only ASCII alphanumeric
198+
/// characters are allowed.
138199
#[error("Invalid chars for key. Key can only alphanumeric chars (0-9, a-z, A-Z)")]
139200
InvalidChars,
140201
}

‎packages/tracker-core/src/authentication/key/repository/in_memory.rs

+48-5
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,78 @@
1+
//! In-memory implementation of the authentication key repository.
12
use crate::authentication::key::{Key, PeerKey};
23

3-
/// In-memory implementation of the authentication key repository.
4+
/// An in-memory repository for storing authentication keys.
5+
///
6+
/// This repository maintains a mapping between a peer's [`Key`] and its
7+
/// corresponding [`PeerKey`]. It is designed for use in private tracker
8+
/// environments where keys are maintained in memory.
49
#[derive(Debug, Default)]
510
pub struct InMemoryKeyRepository {
611
/// Tracker users' keys. Only for private trackers.
712
keys: tokio::sync::RwLock<std::collections::HashMap<Key, PeerKey>>,
813
}
914

1015
impl InMemoryKeyRepository {
11-
/// It adds a new authentication key.
16+
/// Inserts a new authentication key into the repository.
17+
///
18+
/// This function acquires a write lock on the internal storage and inserts
19+
/// the provided [`PeerKey`], using its inner [`Key`] as the map key.
20+
///
21+
/// # Arguments
22+
///
23+
/// * `auth_key` - A reference to the [`PeerKey`] to be inserted.
1224
pub(crate) async fn insert(&self, auth_key: &PeerKey) {
1325
self.keys.write().await.insert(auth_key.key.clone(), auth_key.clone());
1426
}
1527

16-
/// It removes an authentication key.
28+
/// Removes an authentication key from the repository.
29+
///
30+
/// This function acquires a write lock on the internal storage and removes
31+
/// the key that matches the provided [`Key`].
32+
///
33+
/// # Arguments
34+
///
35+
/// * `key` - A reference to the [`Key`] corresponding to the key to be removed.
1736
pub(crate) async fn remove(&self, key: &Key) {
1837
self.keys.write().await.remove(key);
1938
}
2039

40+
/// Retrieves an authentication key from the repository.
41+
///
42+
/// This function acquires a read lock on the internal storage and returns a
43+
/// cloned [`PeerKey`] if the provided [`Key`] exists.
44+
///
45+
/// # Arguments
46+
///
47+
/// * `key` - A reference to the [`Key`] to look up.
48+
///
49+
/// # Returns
50+
///
51+
/// An `Option<PeerKey>` containing the matching key if found, or `None`
52+
/// otherwise.
2153
pub(crate) async fn get(&self, key: &Key) -> Option<PeerKey> {
2254
self.keys.read().await.get(key).cloned()
2355
}
2456

25-
/// It clears all the authentication keys.
57+
/// Clears all authentication keys from the repository.
58+
///
59+
/// This function acquires a write lock on the internal storage and removes
60+
/// all entries.
2661
#[allow(dead_code)]
2762
pub(crate) async fn clear(&self) {
2863
let mut keys = self.keys.write().await;
2964
keys.clear();
3065
}
3166

32-
/// It resets the authentication keys with a new list of keys.
67+
/// Resets the repository with a new list of authentication keys.
68+
///
69+
/// This function clears all existing keys and then inserts each key from
70+
/// the provided vector.
71+
///
72+
/// # Arguments
73+
///
74+
/// * `peer_keys` - A vector of [`PeerKey`] instances that will replace the
75+
/// current set of keys.
3376
pub async fn reset_with(&self, peer_keys: Vec<PeerKey>) {
3477
let mut keys_lock = self.keys.write().await;
3578

Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
//! Key repository implementations.
12
pub mod in_memory;
23
pub mod persisted;

‎packages/tracker-core/src/authentication/key/repository/persisted.rs

+33-7
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,72 @@
1+
//! The database repository for the authentication keys.
12
use std::sync::Arc;
23

34
use crate::authentication::key::{Key, PeerKey};
45
use crate::databases::{self, Database};
56

6-
/// The database repository for the authentication keys.
7+
/// A repository for storing authentication keys in a persistent database.
8+
///
9+
/// This repository provides methods to add, remove, and load authentication
10+
/// keys from the underlying database. It wraps an instance of a type
11+
/// implementing the [`Database`] trait.
712
pub struct DatabaseKeyRepository {
813
database: Arc<Box<dyn Database>>,
914
}
1015

1116
impl DatabaseKeyRepository {
17+
/// Creates a new `DatabaseKeyRepository` instance.
18+
///
19+
/// # Arguments
20+
///
21+
/// * `database` - A shared reference to a boxed database implementation.
22+
///
23+
/// # Returns
24+
///
25+
/// A new instance of `DatabaseKeyRepository`
1226
#[must_use]
1327
pub fn new(database: &Arc<Box<dyn Database>>) -> Self {
1428
Self {
1529
database: database.clone(),
1630
}
1731
}
1832

19-
/// It adds a new key to the database.
33+
/// Adds a new authentication key to the database.
34+
///
35+
/// # Arguments
36+
///
37+
/// * `peer_key` - A reference to the [`PeerKey`] to be persisted.
2038
///
2139
/// # Errors
2240
///
23-
/// Will return a `databases::error::Error` if unable to add the `auth_key` to the database.
41+
/// Returns a [`databases::error::Error`] if the key cannot be added.
2442
pub(crate) fn add(&self, peer_key: &PeerKey) -> Result<(), databases::error::Error> {
2543
self.database.add_key_to_keys(peer_key)?;
2644
Ok(())
2745
}
2846

29-
/// It removes an key from the database.
47+
/// Removes an authentication key from the database.
48+
///
49+
/// # Arguments
50+
///
51+
/// * `key` - A reference to the [`Key`] corresponding to the key to remove.
3052
///
3153
/// # Errors
3254
///
33-
/// Will return a `database::Error` if unable to remove the `key` from the database.
55+
/// Returns a [`databases::error::Error`] if the key cannot be removed.
3456
pub(crate) fn remove(&self, key: &Key) -> Result<(), databases::error::Error> {
3557
self.database.remove_key_from_keys(key)?;
3658
Ok(())
3759
}
3860

39-
/// It loads all keys from the database.
61+
/// Loads all authentication keys from the database.
4062
///
4163
/// # Errors
4264
///
43-
/// Will return a `database::Error` if unable to load the keys from the database.
65+
/// Returns a [`databases::error::Error`] if the keys cannot be loaded.
66+
///
67+
/// # Returns
68+
///
69+
/// A vector containing all persisted [`PeerKey`] entries.
4470
pub(crate) fn load_keys(&self) -> Result<Vec<PeerKey>, databases::error::Error> {
4571
let keys = self.database.load_keys()?;
4672
Ok(keys)

‎packages/tracker-core/src/authentication/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
//! Tracker authentication services and structs.
2+
//!
3+
//! One of the crate responsibilities is to create and keep authentication keys.
4+
//! Auth keys are used by HTTP trackers when the tracker is running in `private`
5+
//! mode.
6+
//!
7+
//! HTTP tracker's clients need to obtain an authentication key before starting
8+
//! requesting the tracker. Once they get one they have to include a `PATH`
9+
//! param with the key in all the HTTP requests. For example, when a peer wants
10+
//! to `announce` itself it has to use the HTTP tracker endpoint:
11+
//!
12+
//! `GET /announce/:key`
13+
//!
14+
//! The common way to obtain the keys is by using the tracker API directly or
15+
//! via other applications like the [Torrust Index](https://github.com/torrust/torrust-index).
116
use crate::CurrentClock;
217

318
pub mod handler;

‎packages/tracker-core/src/authentication/service.rs

+48-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! Authentication service.
12
use std::panic::Location;
23
use std::sync::Arc;
34

@@ -6,6 +7,11 @@ use torrust_tracker_configuration::Core;
67
use super::key::repository::in_memory::InMemoryKeyRepository;
78
use super::{key, Error, Key};
89

10+
/// The authentication service responsible for validating peer keys.
11+
///
12+
/// The service uses an in-memory key repository along with the tracker
13+
/// configuration to determine whether a given peer key is valid. In a private
14+
/// tracker, only registered keys (and optionally unexpired keys) are allowed.
915
#[derive(Debug)]
1016
pub struct AuthenticationService {
1117
/// The tracker configuration.
@@ -16,6 +22,18 @@ pub struct AuthenticationService {
1622
}
1723

1824
impl AuthenticationService {
25+
/// Creates a new instance of the `AuthenticationService`.
26+
///
27+
/// # Parameters
28+
///
29+
/// - `config`: A reference to the tracker core configuration.
30+
/// - `in_memory_key_repository`: A shared reference to an in-memory key
31+
/// repository.
32+
///
33+
/// # Returns
34+
///
35+
/// An `AuthenticationService` instance initialized with the given
36+
/// configuration and repository.
1937
#[must_use]
2038
pub fn new(config: &Core, in_memory_key_repository: &Arc<InMemoryKeyRepository>) -> Self {
2139
Self {
@@ -24,12 +42,23 @@ impl AuthenticationService {
2442
}
2543
}
2644

27-
/// It authenticates the peer `key` against the `Tracker` authentication
28-
/// key list.
45+
/// Authenticates a peer key against the tracker's authentication key list.
46+
///
47+
/// For private trackers, the key must be registered (and optionally not
48+
/// expired) to be considered valid. For public trackers, authentication
49+
/// always succeeds.
50+
///
51+
/// # Parameters
52+
///
53+
/// - `key`: A reference to the peer key that needs to be authenticated.
2954
///
3055
/// # Errors
3156
///
32-
/// Will return an error if the the authentication key cannot be verified.
57+
/// Returns an error if:
58+
///
59+
/// - The tracker is in private mode and the key cannot be found in the
60+
/// repository.
61+
/// - The key is found but fails the expiration check (if expiration is enforced).
3362
pub async fn authenticate(&self, key: &Key) -> Result<(), Error> {
3463
if self.tracker_is_private() {
3564
self.verify_auth_key(key).await
@@ -44,11 +73,25 @@ impl AuthenticationService {
4473
self.config.private
4574
}
4675

47-
/// It verifies an authentication key.
76+
/// Verifies the authentication key against the in-memory repository.
77+
///
78+
/// This function retrieves the key from the repository. If the key is not
79+
/// found, it returns an error with the caller's location. If the key is
80+
/// found, the function then checks the key's expiration based on the
81+
/// tracker configuration. The behavior differs depending on whether a
82+
/// `private` configuration is provided and whether key expiration checking
83+
/// is enabled.
84+
///
85+
/// # Parameters
86+
///
87+
/// - `key`: A reference to the peer key that needs to be verified.
4888
///
4989
/// # Errors
5090
///
51-
/// Will return a `key::Error` if unable to get any `auth_key`.
91+
/// Returns an error if:
92+
///
93+
/// - The key is not found in the repository.
94+
/// - The key fails the expiration check when such verification is required.
5295
async fn verify_auth_key(&self, key: &Key) -> Result<(), Error> {
5396
match self.in_memory_key_repository.get(key).await {
5497
None => Err(Error::UnableToReadKey {

‎packages/tracker-core/src/databases/driver/mysql.rs

+11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
//! The `MySQL` database driver.
2+
//!
3+
//! This module provides an implementation of the [`Database`] trait for `MySQL`
4+
//! using the `r2d2_mysql` connection pool. It configures the MySQL connection
5+
//! based on a URL, creates the necessary tables (for torrent metrics, torrent
6+
//! whitelist, and authentication keys), and implements all CRUD operations
7+
//! required by the persistence layer.
28
use std::str::FromStr;
39
use std::time::Duration;
410

@@ -15,6 +21,11 @@ use crate::authentication::{self, Key};
1521

1622
const DRIVER: Driver = Driver::MySQL;
1723

24+
/// `MySQL` driver implementation.
25+
///
26+
/// This struct encapsulates a connection pool for `MySQL`, built using the
27+
/// `r2d2_mysql` connection manager. It implements the [`Database`] trait to
28+
/// provide persistence operations.
1829
pub(crate) struct Mysql {
1930
pool: Pool<MySqlConnectionManager>,
2031
}

‎packages/tracker-core/src/databases/driver/sqlite.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
//! The `SQLite3` database driver.
2+
//!
3+
//! This module provides an implementation of the [`Database`] trait for
4+
//! `SQLite3` using the `r2d2_sqlite` connection pool. It defines the schema for
5+
//! whitelist, torrent metrics, and authentication keys, and provides methods
6+
//! to create and drop tables as well as perform CRUD operations on these
7+
//! persistent objects.
28
use std::panic::Location;
39
use std::str::FromStr;
410

@@ -14,18 +20,29 @@ use crate::authentication::{self, Key};
1420

1521
const DRIVER: Driver = Driver::Sqlite3;
1622

23+
/// `SQLite` driver implementation.
24+
///
25+
/// This struct encapsulates a connection pool for `SQLite` using the `r2d2_sqlite`
26+
/// connection manager.
1727
pub(crate) struct Sqlite {
1828
pool: Pool<SqliteConnectionManager>,
1929
}
2030

2131
impl Sqlite {
22-
/// It instantiates a new `SQLite3` database driver.
32+
/// Instantiates a new `SQLite3` database driver.
2333
///
24-
/// Refer to [`databases::Database::new`](crate::core::databases::Database::new).
34+
/// This function creates a connection manager for the `SQLite` database
35+
/// located at `db_path` and then builds a connection pool using `r2d2`. If
36+
/// the pool cannot be created, an error is returned (wrapped with the
37+
/// appropriate driver information).
38+
///
39+
/// # Arguments
40+
///
41+
/// * `db_path` - A string slice representing the file path to the `SQLite` database.
2542
///
2643
/// # Errors
2744
///
28-
/// Will return `r2d2::Error` if `db_path` is not able to create `SqLite` database.
45+
/// Returns an [`Error`] if the connection pool cannot be built.
2946
pub fn new(db_path: &str) -> Result<Self, Error> {
3047
let manager = SqliteConnectionManager::file(db_path);
3148
let pool = r2d2::Pool::builder().build(manager).map_err(|e| (e, DRIVER))?;

‎packages/tracker-core/src/databases/error.rs

+31-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
//! Database errors.
22
//!
3-
//! This module contains the [Database errors](crate::databases::error::Error).
3+
//! This module defines the [`Error`] enum used to represent errors that occur
4+
//! during database operations. These errors encapsulate issues such as missing
5+
//! query results, malformed queries, connection failures, and connection pool
6+
//! creation errors. Each error variant includes contextual information such as
7+
//! the associated database driver and, when applicable, the source error.
8+
//!
9+
//! External errors from database libraries (e.g., `rusqlite`, `mysql`) are
10+
//! converted into this error type using the provided `From` implementations.
411
use std::panic::Location;
512
use std::sync::Arc;
613

@@ -9,45 +16,62 @@ use torrust_tracker_located_error::{DynError, Located, LocatedError};
916

1017
use super::driver::Driver;
1118

19+
/// Database error type that encapsulates various failures encountered during
20+
/// database operations.
1221
#[derive(thiserror::Error, Debug, Clone)]
1322
pub enum Error {
14-
/// The query unexpectedly returned nothing.
23+
/// Indicates that a query unexpectedly returned no rows.
24+
///
25+
/// This error variant is used when a query that is expected to return a
26+
/// result does not.
1527
#[error("The {driver} query unexpectedly returned nothing: {source}")]
1628
QueryReturnedNoRows {
1729
source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
1830
driver: Driver,
1931
},
2032

21-
/// The query was malformed.
33+
/// Indicates that the query was malformed.
34+
///
35+
/// This error variant is used when the SQL query itself is invalid or
36+
/// improperly formatted.
2237
#[error("The {driver} query was malformed: {source}")]
2338
InvalidQuery {
2439
source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
2540
driver: Driver,
2641
},
2742

28-
/// Unable to insert a record into the database
43+
/// Indicates a failure to insert a record into the database.
44+
///
45+
/// This error is raised when an insertion operation fails.
2946
#[error("Unable to insert record into {driver} database, {location}")]
3047
InsertFailed {
3148
location: &'static Location<'static>,
3249
driver: Driver,
3350
},
3451

35-
/// Unable to delete a record into the database
52+
/// Indicates a failure to delete a record from the database.
53+
///
54+
/// This error includes an error code that may be returned by the database
55+
/// driver.
3656
#[error("Failed to remove record from {driver} database, error-code: {error_code}, {location}")]
3757
DeleteFailed {
3858
location: &'static Location<'static>,
3959
error_code: usize,
4060
driver: Driver,
4161
},
4262

43-
/// Unable to connect to the database
63+
/// Indicates a failure to connect to the database.
64+
///
65+
/// This error variant wraps connection-related errors, such as those caused by an invalid URL.
4466
#[error("Failed to connect to {driver} database: {source}")]
4567
ConnectionError {
4668
source: LocatedError<'static, UrlError>,
4769
driver: Driver,
4870
},
4971

50-
/// Unable to create a connection pool
72+
/// Indicates a failure to create a connection pool.
73+
///
74+
/// This error variant is used when the connection pool creation (using r2d2) fails.
5175
#[error("Failed to create r2d2 {driver} connection pool: {source}")]
5276
ConnectionPool {
5377
source: LocatedError<'static, r2d2::Error>,
+84-65
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,51 @@
11
//! The persistence module.
22
//!
3-
//! Persistence is currently implemented with one [`Database`] trait.
3+
//! Persistence is currently implemented using a single [`Database`] trait.
44
//!
55
//! There are two implementations of the trait (two drivers):
66
//!
7-
//! - `Mysql`
8-
//! - `Sqlite`
7+
//! - **`MySQL`**
8+
//! - **`Sqlite`**
99
//!
10-
//! > **NOTICE**: There are no database migrations. If there are any changes,
11-
//! > we will implemented them or provide a script to migrate to the new schema.
10+
//! > **NOTICE**: There are no database migrations at this time. If schema
11+
//! > changes occur, either migration functionality will be implemented or a
12+
//! > script will be provided to migrate to the new schema.
1213
//!
13-
//! The persistent objects are:
14+
//! The persistent objects handled by this module include:
1415
//!
15-
//! - [Torrent metrics](#torrent-metrics)
16-
//! - [Torrent whitelist](torrent-whitelist)
17-
//! - [Authentication keys](authentication-keys)
16+
//! - **Torrent metrics**: Metrics such as the number of completed downloads for
17+
//! each torrent.
18+
//! - **Torrent whitelist**: A list of torrents (by infohash) that are allowed.
19+
//! - **Authentication keys**: Expiring authentication keys used to secure
20+
//! access to private trackers.
1821
//!
19-
//! # Torrent metrics
22+
//! # Torrent Metrics
2023
//!
21-
//! Field | Sample data | Description
22-
//! ---|---|---
23-
//! `id` | 1 | Autoincrement id
24-
//! `info_hash` | `c1277613db1d28709b034a017ab2cae4be07ae10` | `BitTorrent` infohash V1
25-
//! `completed` | 20 | The number of peers that have ever completed downloading the torrent associated to this entry. See [`Entry`](torrust_tracker_torrent_repository::entry::Entry) for more information.
24+
//! | Field | Sample data | Description |
25+
//! |-------------|--------------------------------------------|-----------------------------------------------------------------------------|
26+
//! | `id` | 1 | Auto-increment id |
27+
//! | `info_hash` | `c1277613db1d28709b034a017ab2cae4be07ae10` | `BitTorrent` infohash V1 |
28+
//! | `completed` | 20 | The number of peers that have completed downloading the associated torrent. |
2629
//!
27-
//! > **NOTICE**: The peer list for a torrent is not persisted. Since peer have to re-announce themselves on intervals, the data is be
28-
//! > regenerated again after some minutes.
30+
//! > **NOTICE**: The peer list for a torrent is not persisted. Because peers re-announce at
31+
//! > intervals, the peer list is regenerated periodically.
2932
//!
30-
//! # Torrent whitelist
33+
//! # Torrent Whitelist
3134
//!
32-
//! Field | Sample data | Description
33-
//! ---|---|---
34-
//! `id` | 1 | Autoincrement id
35-
//! `info_hash` | `c1277613db1d28709b034a017ab2cae4be07ae10` | `BitTorrent` infohash V1
35+
//! | Field | Sample data | Description |
36+
//! |-------------|--------------------------------------------|--------------------------------|
37+
//! | `id` | 1 | Auto-increment id |
38+
//! | `info_hash` | `c1277613db1d28709b034a017ab2cae4be07ae10` | `BitTorrent` infohash V1 |
3639
//!
37-
//! # Authentication keys
40+
//! # Authentication Keys
3841
//!
39-
//! Field | Sample data | Description
40-
//! ---|---|---
41-
//! `id` | 1 | Autoincrement id
42-
//! `key` | `IrweYtVuQPGbG9Jzx1DihcPmJGGpVy82` | Token
43-
//! `valid_until` | 1672419840 | Timestamp for the expiring date
42+
//! | Field | Sample data | Description |
43+
//! |---------------|------------------------------------|--------------------------------------|
44+
//! | `id` | 1 | Auto-increment id |
45+
//! | `key` | `IrweYtVuQPGbG9Jzx1DihcPmJGGpVy82` | Authentication token (32 chars) |
46+
//! | `valid_until` | 1672419840 | Timestamp indicating expiration time |
4447
//!
45-
//! > **NOTICE**: All keys must have an expiration date.
48+
//! > **NOTICE**: All authentication keys must have an expiration date.
4649
pub mod driver;
4750
pub mod error;
4851
pub mod setup;
@@ -54,143 +57,159 @@ use torrust_tracker_primitives::PersistentTorrents;
5457
use self::error::Error;
5558
use crate::authentication::{self, Key};
5659

57-
/// The persistence trait. It contains all the methods to interact with the database.
60+
/// The persistence trait.
61+
///
62+
/// This trait defines all the methods required to interact with the database,
63+
/// including creating and dropping schema tables, and CRUD operations for
64+
/// torrent metrics, whitelists, and authentication keys. Implementations of
65+
/// this trait must ensure that operations are safe, consistent, and report
66+
/// errors using the [`Error`] type.
5867
#[automock]
5968
pub trait Database: Sync + Send {
60-
/// It generates the database tables. SQL queries are hardcoded in the trait
61-
/// implementation.
69+
/// Creates the necessary database tables.
70+
///
71+
/// The SQL queries for table creation are hardcoded in the trait implementation.
6272
///
6373
/// # Context: Schema
6474
///
6575
/// # Errors
6676
///
67-
/// Will return `Error` if unable to create own tables.
77+
/// Returns an [`Error`] if the tables cannot be created.
6878
fn create_database_tables(&self) -> Result<(), Error>;
6979

70-
/// It drops the database tables.
80+
/// Drops the database tables.
81+
///
82+
/// This operation removes the persistent schema.
7183
///
7284
/// # Context: Schema
7385
///
7486
/// # Errors
7587
///
76-
/// Will return `Err` if unable to drop tables.
88+
/// Returns an [`Error`] if the tables cannot be dropped.
7789
fn drop_database_tables(&self) -> Result<(), Error>;
7890

7991
// Torrent Metrics
8092

81-
/// It loads the torrent metrics data from the database.
93+
/// Loads torrent metrics data from the database.
8294
///
83-
/// It returns an array of tuples with the torrent
84-
/// [`InfoHash`] and the
85-
/// [`downloaded`](torrust_tracker_torrent_repository::entry::Torrent::downloaded) counter
86-
/// which is the number of times the torrent has been downloaded.
87-
/// See [`Entry::downloaded`](torrust_tracker_torrent_repository::entry::Torrent::downloaded).
95+
/// This function returns the persistent torrent metrics as a collection of
96+
/// tuples, where each tuple contains an [`InfoHash`] and the `downloaded`
97+
/// counter (i.e. the number of times the torrent has been downloaded).
8898
///
8999
/// # Context: Torrent Metrics
90100
///
91101
/// # Errors
92102
///
93-
/// Will return `Err` if unable to load.
103+
/// Returns an [`Error`] if the metrics cannot be loaded.
94104
fn load_persistent_torrents(&self) -> Result<PersistentTorrents, Error>;
95105

96-
/// It saves the torrent metrics data into the database.
106+
/// Saves torrent metrics data into the database.
107+
///
108+
/// # Arguments
109+
///
110+
/// * `info_hash` - A reference to the torrent's info hash.
111+
/// * `downloaded` - The number of times the torrent has been downloaded.
97112
///
98113
/// # Context: Torrent Metrics
99114
///
100115
/// # Errors
101116
///
102-
/// Will return `Err` if unable to save.
117+
/// Returns an [`Error`] if the metrics cannot be saved.
103118
fn save_persistent_torrent(&self, info_hash: &InfoHash, downloaded: u32) -> Result<(), Error>;
104119

105120
// Whitelist
106121

107-
/// It loads the whitelisted torrents from the database.
122+
/// Loads the whitelisted torrents from the database.
108123
///
109124
/// # Context: Whitelist
110125
///
111126
/// # Errors
112127
///
113-
/// Will return `Err` if unable to load.
128+
/// Returns an [`Error`] if the whitelist cannot be loaded.
114129
fn load_whitelist(&self) -> Result<Vec<InfoHash>, Error>;
115130

116-
/// It checks if the torrent is whitelisted.
131+
/// Retrieves a whitelisted torrent from the database.
117132
///
118-
/// It returns `Some(InfoHash)` if the torrent is whitelisted, `None` otherwise.
133+
/// Returns `Some(InfoHash)` if the torrent is in the whitelist, or `None`
134+
/// otherwise.
119135
///
120136
/// # Context: Whitelist
121137
///
122138
/// # Errors
123139
///
124-
/// Will return `Err` if unable to load.
140+
/// Returns an [`Error`] if the whitelist cannot be queried.
125141
fn get_info_hash_from_whitelist(&self, info_hash: InfoHash) -> Result<Option<InfoHash>, Error>;
126142

127-
/// It adds the torrent to the whitelist.
143+
/// Adds a torrent to the whitelist.
128144
///
129145
/// # Context: Whitelist
130146
///
131147
/// # Errors
132148
///
133-
/// Will return `Err` if unable to save.
149+
/// Returns an [`Error`] if the torrent cannot be added to the whitelist.
134150
fn add_info_hash_to_whitelist(&self, info_hash: InfoHash) -> Result<usize, Error>;
135151

136-
/// It checks if the torrent is whitelisted.
152+
/// Checks whether a torrent is whitelisted.
153+
///
154+
/// This default implementation returns `true` if the infohash is included
155+
/// in the whitelist, or `false` otherwise.
137156
///
138157
/// # Context: Whitelist
139158
///
140159
/// # Errors
141160
///
142-
/// Will return `Err` if unable to load.
161+
/// Returns an [`Error`] if the whitelist cannot be queried.
143162
fn is_info_hash_whitelisted(&self, info_hash: InfoHash) -> Result<bool, Error> {
144163
Ok(self.get_info_hash_from_whitelist(info_hash)?.is_some())
145164
}
146165

147-
/// It removes the torrent from the whitelist.
166+
/// Removes a torrent from the whitelist.
148167
///
149168
/// # Context: Whitelist
150169
///
151170
/// # Errors
152171
///
153-
/// Will return `Err` if unable to save.
172+
/// Returns an [`Error`] if the torrent cannot be removed from the whitelist.
154173
fn remove_info_hash_from_whitelist(&self, info_hash: InfoHash) -> Result<usize, Error>;
155174

156175
// Authentication keys
157176

158-
/// It loads the expiring authentication keys from the database.
177+
/// Loads all authentication keys from the database.
159178
///
160179
/// # Context: Authentication Keys
161180
///
162181
/// # Errors
163182
///
164-
/// Will return `Err` if unable to load.
183+
/// Returns an [`Error`] if the keys cannot be loaded.
165184
fn load_keys(&self) -> Result<Vec<authentication::PeerKey>, Error>;
166185

167-
/// It gets an expiring authentication key from the database.
186+
/// Retrieves a specific authentication key from the database.
168187
///
169-
/// It returns `Some(PeerKey)` if a [`PeerKey`](crate::authentication::PeerKey)
170-
/// with the input [`Key`] exists, `None` otherwise.
188+
/// Returns `Some(PeerKey)` if a key corresponding to the provided [`Key`]
189+
/// exists, or `None` otherwise.
171190
///
172191
/// # Context: Authentication Keys
173192
///
174193
/// # Errors
175194
///
176-
/// Will return `Err` if unable to load.
195+
/// Returns an [`Error`] if the key cannot be queried.
177196
fn get_key_from_keys(&self, key: &Key) -> Result<Option<authentication::PeerKey>, Error>;
178197

179-
/// It adds an expiring authentication key to the database.
198+
/// Adds an authentication key to the database.
180199
///
181200
/// # Context: Authentication Keys
182201
///
183202
/// # Errors
184203
///
185-
/// Will return `Err` if unable to save.
204+
/// Returns an [`Error`] if the key cannot be saved.
186205
fn add_key_to_keys(&self, auth_key: &authentication::PeerKey) -> Result<usize, Error>;
187206

188-
/// It removes an expiring authentication key from the database.
207+
/// Removes an authentication key from the database.
189208
///
190209
/// # Context: Authentication Keys
191210
///
192211
/// # Errors
193212
///
194-
/// Will return `Err` if unable to load.
213+
/// Returns an [`Error`] if the key cannot be removed.
195214
fn remove_key_from_keys(&self, key: &Key) -> Result<usize, Error>;
196215
}

‎packages/tracker-core/src/databases/setup.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,43 @@
1+
//! This module provides functionality for setting up databases.
12
use std::sync::Arc;
23

34
use torrust_tracker_configuration::Core;
45

56
use super::driver::{self, Driver};
67
use super::Database;
78

9+
/// Initializes and returns a database instance based on the provided configuration.
10+
///
11+
/// This function creates a new database instance according to the settings
12+
/// defined in the [`Core`] configuration. It selects the appropriate driver
13+
/// (either `Sqlite3` or `MySQL`) as specified in `config.database.driver` and
14+
/// attempts to build the database connection using the path defined in
15+
/// `config.database.path`.
16+
///
17+
/// The resulting database instance is wrapped in a shared pointer (`Arc`) to a
18+
/// boxed trait object, allowing safe sharing of the database connection across
19+
/// multiple threads.
20+
///
821
/// # Panics
922
///
10-
/// Will panic if database cannot be initialized.
23+
/// This function will panic if the database cannot be initialized (i.e., if the
24+
/// driver fails to build the connection). This is enforced by the use of
25+
/// [`expect`](std::result::Result::expect) in the implementation.
26+
///
27+
/// # Example
28+
///
29+
/// ```rust,no_run
30+
/// use torrust_tracker_configuration::Core;
31+
/// use bittorrent_tracker_core::databases::setup::initialize_database;
32+
///
33+
/// // Create a default configuration (ensure it is properly set up for your environment)
34+
/// let config = Core::default();
35+
///
36+
/// // Initialize the database; this will panic if initialization fails.
37+
/// let database = initialize_database(&config);
38+
///
39+
/// // The returned database instance can now be used for persistence operations.
40+
/// ```
1141
#[must_use]
1242
pub fn initialize_database(config: &Core) -> Arc<Box<dyn Database>> {
1343
let driver = match config.database.driver {
@@ -17,3 +47,15 @@ pub fn initialize_database(config: &Core) -> Arc<Box<dyn Database>> {
1747

1848
Arc::new(driver::build(&driver, &config.database.path).expect("Database driver build failed."))
1949
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use super::initialize_database;
54+
use crate::test_helpers::tests::ephemeral_configuration;
55+
56+
#[test]
57+
fn it_should_initialize_the_sqlite_database() {
58+
let config = ephemeral_configuration();
59+
let _database = initialize_database(&config);
60+
}
61+
}

‎packages/tracker-core/src/error.rs

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
//! Errors returned by the core tracker.
1+
//! Core tracker errors.
2+
//!
3+
//! This module defines the error types used internally by the `BitTorrent`
4+
//! tracker core.
5+
//!
6+
//! These errors encapsulate issues such as whitelisting violations, invalid
7+
//! peer key data, and database persistence failures. Each error variant
8+
//! includes contextual information (such as source code location) to facilitate
9+
//! debugging.
210
use std::panic::Location;
311

412
use bittorrent_primitives::info_hash::InfoHash;
@@ -7,29 +15,41 @@ use torrust_tracker_located_error::LocatedError;
715
use super::authentication::key::ParseKeyError;
816
use super::databases;
917

10-
/// Whitelist errors returned by the core tracker.
18+
/// Errors related to torrent whitelisting.
19+
///
20+
/// This error is returned when an operation involves a torrent that is not
21+
/// present in the whitelist.
1122
#[derive(thiserror::Error, Debug, Clone)]
1223
pub enum WhitelistError {
24+
/// Indicates that the torrent identified by `info_hash` is not whitelisted.
1325
#[error("The torrent: {info_hash}, is not whitelisted, {location}")]
1426
TorrentNotWhitelisted {
1527
info_hash: InfoHash,
1628
location: &'static Location<'static>,
1729
},
1830
}
1931

20-
/// Peers keys errors returned by the core tracker.
32+
/// Errors related to peer key operations.
33+
///
34+
/// This error type covers issues encountered during the handling of peer keys,
35+
/// including validation of key durations, parsing errors, and database
36+
/// persistence problems.
2137
#[allow(clippy::module_name_repetitions)]
2238
#[derive(thiserror::Error, Debug, Clone)]
2339
pub enum PeerKeyError {
40+
/// Returned when the duration specified for the peer key exceeds the
41+
/// maximum.
2442
#[error("Invalid peer key duration: {seconds_valid:?}, is not valid")]
2543
DurationOverflow { seconds_valid: u64 },
2644

45+
/// Returned when the provided peer key is invalid.
2746
#[error("Invalid key: {key}")]
2847
InvalidKey {
2948
key: String,
3049
source: LocatedError<'static, ParseKeyError>,
3150
},
3251

52+
/// Returned when persisting the peer key to the database fails.
3353
#[error("Can't persist key: {source}")]
3454
DatabaseError {
3555
source: LocatedError<'static, databases::error::Error>,

‎packages/tracker-core/src/lib.rs

+53-305
Large diffs are not rendered by default.

‎packages/tracker-core/src/scrape_handler.rs

+71-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,64 @@
1+
//! Scrape handler.
2+
//!
3+
//! The `scrape` request allows clients to query metadata about the swarm in bulk.
4+
//!
5+
//! An `scrape` request includes a list of infohashes whose swarm metadata you
6+
//! want to collect.
7+
//!
8+
//! ## Scrape Response Format
9+
//!
10+
//! The returned struct is:
11+
//!
12+
//! ```rust,no_run
13+
//! use bittorrent_primitives::info_hash::InfoHash;
14+
//! use std::collections::HashMap;
15+
//!
16+
//! pub struct ScrapeData {
17+
//! pub files: HashMap<InfoHash, SwarmMetadata>,
18+
//! }
19+
//!
20+
//! pub struct SwarmMetadata {
21+
//! pub complete: u32, // The number of active peers that have completed downloading (seeders)
22+
//! pub downloaded: u32, // The number of peers that have ever completed downloading
23+
//! pub incomplete: u32, // The number of active peers that have not completed downloading (leechers)
24+
//! }
25+
//! ```
26+
//!
27+
//! ## Example JSON Response
28+
//!
29+
//! The JSON representation of a sample `scrape` response would be like the following:
30+
//!
31+
//! ```json
32+
//! {
33+
//! 'files': {
34+
//! 'xxxxxxxxxxxxxxxxxxxx': {'complete': 11, 'downloaded': 13772, 'incomplete': 19},
35+
//! 'yyyyyyyyyyyyyyyyyyyy': {'complete': 21, 'downloaded': 206, 'incomplete': 20}
36+
//! }
37+
//! }
38+
//! ```
39+
//!
40+
//! `xxxxxxxxxxxxxxxxxxxx` and `yyyyyyyyyyyyyyyyyyyy` are 20-byte infohash arrays.
41+
//! There are two data structures for infohashes: byte arrays and hex strings:
42+
//!
43+
//! ```rust,no_run
44+
//! use bittorrent_primitives::info_hash::InfoHash;
45+
//! use std::str::FromStr;
46+
//!
47+
//! let info_hash: InfoHash = [255u8; 20].into();
48+
//!
49+
//! assert_eq!(
50+
//! info_hash,
51+
//! InfoHash::from_str("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap()
52+
//! );
53+
//! ```
54+
//!
55+
//! ## References:
56+
//!
57+
//! Refer to `BitTorrent` BEPs and other sites for more information about the `scrape` request:
58+
//!
59+
//! - [BEP 48. Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html)
60+
//! - [BEP 15. UDP Tracker Protocol for `BitTorrent`. Scrape section](https://www.bittorrent.org/beps/bep_0015.html)
61+
//! - [Vuze docs](https://wiki.vuze.com/w/Scrape)
162
use std::sync::Arc;
263

364
use bittorrent_primitives::info_hash::InfoHash;
@@ -7,15 +68,17 @@ use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
768
use super::torrent::repository::in_memory::InMemoryTorrentRepository;
869
use super::whitelist;
970

71+
/// Handles scrape requests, providing torrent swarm metadata.
1072
pub struct ScrapeHandler {
11-
/// The service to check is a torrent is whitelisted.
73+
/// Service for authorizing access to whitelisted torrents.
1274
whitelist_authorization: Arc<whitelist::authorization::WhitelistAuthorization>,
1375

1476
/// The in-memory torrents repository.
1577
in_memory_torrent_repository: Arc<InMemoryTorrentRepository>,
1678
}
1779

1880
impl ScrapeHandler {
81+
/// Creates a new `ScrapeHandler` instance.
1982
#[must_use]
2083
pub fn new(
2184
whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
@@ -27,9 +90,14 @@ impl ScrapeHandler {
2790
}
2891
}
2992

30-
/// It handles a scrape request.
93+
/// Handles a scrape request for multiple torrents.
3194
///
32-
/// BEP 48: [Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html).
95+
/// - Returns metadata for each requested torrent.
96+
/// - If a torrent isn't whitelisted or doesn't exist, returns zeroed stats.
97+
///
98+
/// # BEP Reference:
99+
///
100+
/// [BEP 48: Scrape Protocol](https://www.bittorrent.org/beps/bep_0048.html)
33101
pub async fn scrape(&self, info_hashes: &Vec<InfoHash>) -> ScrapeData {
34102
let mut scrape_data = ScrapeData::empty();
35103

‎packages/tracker-core/src/torrent/manager.rs

+45-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! Torrents manager.
12
use std::sync::Arc;
23
use std::time::Duration;
34

@@ -8,6 +9,18 @@ use super::repository::in_memory::InMemoryTorrentRepository;
89
use super::repository::persisted::DatabasePersistentTorrentRepository;
910
use crate::{databases, CurrentClock};
1011

12+
/// The `TorrentsManager` is responsible for managing torrent entries by
13+
/// integrating persistent storage and in-memory state. It provides methods to
14+
/// load torrent data from the database into memory, and to periodically clean
15+
/// up stale torrent entries by removing inactive peers or entire torrent
16+
/// entries that no longer have active peers.
17+
///
18+
/// This manager relies on two repositories:
19+
///
20+
/// - An **in-memory repository** to provide fast access to the current torrent
21+
/// state.
22+
/// - A **persistent repository** that stores aggregate torrent metrics (e.g.,
23+
/// seeders count) across tracker restarts.
1124
pub struct TorrentsManager {
1225
/// The tracker configuration.
1326
config: Core,
@@ -21,6 +34,19 @@ pub struct TorrentsManager {
2134
}
2235

2336
impl TorrentsManager {
37+
/// Creates a new instance of `TorrentsManager`.
38+
///
39+
/// # Arguments
40+
///
41+
/// * `config` - A reference to the tracker configuration.
42+
/// * `in_memory_torrent_repository` - A shared reference to the in-memory
43+
/// repository of torrents.
44+
/// * `db_torrent_repository` - A shared reference to the persistent
45+
/// repository for torrent metrics.
46+
///
47+
/// # Returns
48+
///
49+
/// A new `TorrentsManager` instance with cloned references of the provided dependencies.
2450
#[must_use]
2551
pub fn new(
2652
config: &Core,
@@ -34,13 +60,16 @@ impl TorrentsManager {
3460
}
3561
}
3662

37-
/// It loads the torrents from database into memory. It only loads the
38-
/// torrent entry list with the number of seeders for each torrent. Peers
39-
/// data is not persisted.
63+
/// Loads torrents from the persistent database into the in-memory repository.
64+
///
65+
/// This function retrieves the list of persistent torrent entries (which
66+
/// include only the aggregate metrics, not the detailed peer lists) from
67+
/// the database, and then imports that data into the in-memory repository.
4068
///
4169
/// # Errors
4270
///
43-
/// Will return a `database::Error` if unable to load the list of `persistent_torrents` from the database.
71+
/// Returns a `databases::error::Error` if unable to load the persistent
72+
/// torrent data.
4473
#[allow(dead_code)]
4574
pub(crate) fn load_torrents_from_database(&self) -> Result<(), databases::error::Error> {
4675
let persistent_torrents = self.db_torrent_repository.load_all()?;
@@ -50,7 +79,18 @@ impl TorrentsManager {
5079
Ok(())
5180
}
5281

53-
/// Remove inactive peers and (optionally) peerless torrents.
82+
/// Cleans up torrent entries by removing inactive peers and, optionally,
83+
/// torrents with no active peers.
84+
///
85+
/// This function performs two cleanup tasks:
86+
///
87+
/// 1. It removes peers from torrent entries that have not been updated
88+
/// within a cutoff time. The cutoff time is calculated as the current
89+
/// time minus the maximum allowed peer timeout, as specified in the
90+
/// tracker policy.
91+
/// 2. If the tracker is configured to remove peerless torrents
92+
/// (`remove_peerless_torrents` is set), it removes entire torrent
93+
/// entries that have no active peers.
5494
pub fn cleanup_torrents(&self) {
5595
let current_cutoff = CurrentClock::now_sub(&Duration::from_secs(u64::from(self.config.tracker_policy.max_peer_timeout)))
5696
.unwrap_or_default();
+161-19
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,168 @@
1-
//! Structs to store the swarm data.
1+
//! Swarm Data Structures.
22
//!
3-
//! There are to main data structures:
3+
//! This module defines the primary data structures used to store and manage
4+
//! swarm data within the tracker. In `BitTorrent` terminology, a "swarm" is
5+
//! the collection of peers that are sharing or downloading a given torrent.
46
//!
5-
//! - A torrent [`Entry`](torrust_tracker_torrent_repository::entry::Entry): it contains all the information stored by the tracker for one torrent.
6-
//! - The [`SwarmMetadata`](torrust_tracker_primitives::swarm_metadata::SwarmMetadata): it contains aggregate information that can me derived from the torrent entries.
7+
//! There are two main types of data stored:
78
//!
8-
//! A "swarm" is a network of peers that are trying to download the same torrent.
9+
//! - **Torrent Entry** (`Entry`): Contains all the information the tracker
10+
//! stores for a single torrent, including the list of peers currently in the
11+
//! swarm. This data is crucial for peers to locate each other and initiate
12+
//! downloads.
913
//!
10-
//! The torrent entry contains the "swarm" data, which is basically the list of peers in the swarm.
11-
//! That's the most valuable information the peer want to get from the tracker, because it allows them to
12-
//! start downloading torrent from those peers.
14+
//! - **Swarm Metadata** (`SwarmMetadata`): Contains aggregate data derived from
15+
//! all torrent entries. This metadata is split into:
16+
//! - **Active Peers Data:** Metrics related to the peers that are currently
17+
//! active in the swarm.
18+
//! - **Historical Data:** Metrics collected since the tracker started, such
19+
//! as the total number of completed downloads.
1320
//!
14-
//! The "swarm metadata" contains aggregate data derived from the torrent entries. There two types of data:
21+
//! ## Metrics Collected
1522
//!
16-
//! - For **active peers**: metrics related to the current active peers in the swarm.
17-
//! - **Historical data**: since the tracker started running.
23+
//! The tracker collects and aggregates the following metrics:
1824
//!
19-
//! The tracker collects metrics for:
25+
//! - The total number of peers that have completed downloading the torrent
26+
//! since the tracker began collecting metrics.
27+
//! - The number of completed downloads from peers that remain active (i.e., seeders).
28+
//! - The number of active peers that have not completed downloading the torrent (i.e., leechers).
2029
//!
21-
//! - The number of peers that have completed downloading the torrent since the tracker started collecting metrics.
22-
//! - The number of peers that have completed downloading the torrent and are still active, that means they are actively participating in the network,
23-
//! by announcing themselves periodically to the tracker. Since they have completed downloading they have a full copy of the torrent data. Peers with a
24-
//! full copy of the data are called "seeders".
25-
//! - The number of peers that have NOT completed downloading the torrent and are still active, that means they are actively participating in the network.
26-
//! Peer that don not have a full copy of the torrent data are called "leechers".
30+
//! This information is used both to inform peers about available connections
31+
//! and to provide overall swarm statistics.
2732
//!
33+
//! This module re-exports core types from the torrent repository crate to
34+
//! simplify integration.
35+
//!
36+
//! ## Internal Data Structures
37+
//!
38+
//! The [`torrent`](crate::torrent) module contains all the data structures
39+
//! stored by the tracker except for peers.
40+
//!
41+
//! We can represent the data stored in memory internally by the tracker with
42+
//! this JSON object:
43+
//!
44+
//! ```json
45+
//! {
46+
//! "c1277613db1d28709b034a017ab2cae4be07ae10": {
47+
//! "completed": 0,
48+
//! "peers": {
49+
//! "-qB00000000000000001": {
50+
//! "peer_id": "-qB00000000000000001",
51+
//! "peer_addr": "2.137.87.41:1754",
52+
//! "updated": 1672419840,
53+
//! "uploaded": 120,
54+
//! "downloaded": 60,
55+
//! "left": 60,
56+
//! "event": "started"
57+
//! },
58+
//! "-qB00000000000000002": {
59+
//! "peer_id": "-qB00000000000000002",
60+
//! "peer_addr": "23.17.287.141:2345",
61+
//! "updated": 1679415984,
62+
//! "uploaded": 80,
63+
//! "downloaded": 20,
64+
//! "left": 40,
65+
//! "event": "started"
66+
//! }
67+
//! }
68+
//! }
69+
//! }
70+
//! ```
71+
//!
72+
//! The tracker maintains an indexed-by-info-hash list of torrents. For each
73+
//! torrent, it stores a torrent `Entry`. The torrent entry has two attributes:
74+
//!
75+
//! - `completed`: which is hte number of peers that have completed downloading
76+
//! the torrent file/s. As they have completed downloading, they have a full
77+
//! version of the torrent data, and they can provide the full data to other
78+
//! peers. That's why they are also known as "seeders".
79+
//! - `peers`: an indexed and orderer list of peer for the torrent. Each peer
80+
//! contains the data received from the peer in the `announce` request.
81+
//!
82+
//! The [`crate::torrent`] module not only contains the original data obtained
83+
//! from peer via `announce` requests, it also contains aggregate data that can
84+
//! be derived from the original data. For example:
85+
//!
86+
//! ```rust,no_run
87+
//! pub struct SwarmMetadata {
88+
//! pub complete: u32, // The number of active peers that have completed downloading (seeders)
89+
//! pub downloaded: u32, // The number of peers that have ever completed downloading
90+
//! pub incomplete: u32, // The number of active peers that have not completed downloading (leechers)
91+
//! }
92+
//! ```
93+
//!
94+
//! > **NOTICE**: that `complete` or `completed` peers are the peers that have
95+
//! > completed downloading, but only the active ones are considered "seeders".
96+
//!
97+
//! `SwarmMetadata` struct follows name conventions for `scrape` responses. See
98+
//! [BEP 48](https://www.bittorrent.org/beps/bep_0048.html), while `SwarmMetadata`
99+
//! is used for the rest of cases.
100+
//!
101+
//! ## Peers
102+
//!
103+
//! A `Peer` is the struct used by the tracker to keep peers data:
104+
//!
105+
//! ```rust,no_run
106+
//! use std::net::SocketAddr;
107+
//! use aquatic_udp_protocol::PeerId;
108+
//! use torrust_tracker_primitives::DurationSinceUnixEpoch;
109+
//! use aquatic_udp_protocol::NumberOfBytes;
110+
//! use aquatic_udp_protocol::AnnounceEvent;
111+
//!
112+
//! pub struct Peer {
113+
//! pub peer_id: PeerId, // The peer ID
114+
//! pub peer_addr: SocketAddr, // Peer socket address
115+
//! pub updated: DurationSinceUnixEpoch, // Last time (timestamp) when the peer was updated
116+
//! pub uploaded: NumberOfBytes, // Number of bytes the peer has uploaded so far
117+
//! pub downloaded: NumberOfBytes, // Number of bytes the peer has downloaded so far
118+
//! pub left: NumberOfBytes, // The number of bytes this peer still has to download
119+
//! pub event: AnnounceEvent, // The event the peer has announced: `started`, `completed`, `stopped`
120+
//! }
121+
//! ```
122+
//!
123+
//! Notice that most of the attributes are obtained from the `announce` request.
124+
//! For example, an HTTP announce request would contain the following `GET` parameters:
125+
//!
126+
//! <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>
127+
//!
128+
//! 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.
129+
//!
130+
//! We can represent the data stored in memory with this JSON object:
131+
//!
132+
//! ```json
133+
//! {
134+
//! "c1277613db1d28709b034a017ab2cae4be07ae10": {
135+
//! "completed": 0,
136+
//! "peers": {
137+
//! "-qB00000000000000001": {
138+
//! "peer_id": "-qB00000000000000001",
139+
//! "peer_addr": "2.137.87.41:1754",
140+
//! "updated": 1672419840,
141+
//! "uploaded": 120,
142+
//! "downloaded": 60,
143+
//! "left": 60,
144+
//! "event": "started"
145+
//! },
146+
//! "-qB00000000000000002": {
147+
//! "peer_id": "-qB00000000000000002",
148+
//! "peer_addr": "23.17.287.141:2345",
149+
//! "updated": 1679415984,
150+
//! "uploaded": 80,
151+
//! "downloaded": 20,
152+
//! "left": 40,
153+
//! "event": "started"
154+
//! }
155+
//! }
156+
//! }
157+
//! }
158+
//! ```
159+
//!
160+
//! That JSON object does not exist, it's only a representation of the `Tracker` torrents data.
161+
//!
162+
//! `c1277613db1d28709b034a017ab2cae4be07ae10` is the torrent infohash and `completed` contains the number of peers
163+
//! that have a full version of the torrent data, also known as seeders.
164+
//!
165+
//! Refer to [`peer`](torrust_tracker_primitives::peer) for more information about peers.
28166
pub mod manager;
29167
pub mod repository;
30168
pub mod services;
@@ -33,7 +171,11 @@ pub mod services;
33171
use torrust_tracker_torrent_repository::EntryMutexStd;
34172
use torrust_tracker_torrent_repository::TorrentsSkipMapMutexStd;
35173

36-
// Currently used types from the torrent repository crate.
174+
/// Alias for the primary torrent collection type, implemented as a skip map
175+
/// wrapped in a mutex. This type is used internally by the tracker to manage
176+
/// and access torrent entries.
37177
pub(crate) type Torrents = TorrentsSkipMapMutexStd;
178+
179+
/// Alias for a single torrent entry.
38180
#[cfg(test)]
39181
pub(crate) type TorrentEntry = EntryMutexStd;

‎packages/tracker-core/src/torrent/repository/in_memory.rs

+132-14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! In-memory torrents repository.
12
use std::cmp::max;
23
use std::sync::Arc;
34

@@ -13,51 +14,126 @@ use torrust_tracker_torrent_repository::EntryMutexStd;
1314

1415
use crate::torrent::Torrents;
1516

16-
/// The in-memory torrents repository.
17+
/// In-memory repository for torrent entries.
1718
///
18-
/// There are many implementations of the repository trait. We tried with
19-
/// different types of data structures, but the best performance was with
20-
/// the one we use for production. We kept the other implementations for
21-
/// reference.
19+
/// This repository manages the torrent entries and their associated peer lists
20+
/// in memory. It is built on top of a high-performance data structure (the
21+
/// production implementation) and provides methods to update, query, and remove
22+
/// torrent entries as well as to import persisted data.
23+
///
24+
/// Multiple implementations were considered, and the chosen implementation is
25+
/// used in production. Other implementations are kept for reference.
2226
#[derive(Debug, Default)]
2327
pub struct InMemoryTorrentRepository {
24-
/// The in-memory torrents repository implementation.
28+
/// The underlying in-memory data structure that stores torrent entries.
2529
torrents: Arc<Torrents>,
2630
}
2731

2832
impl InMemoryTorrentRepository {
29-
/// It inserts (or updates if it's already in the list) the peer in the
30-
/// torrent entry.
33+
/// Inserts or updates a peer in the torrent entry corresponding to the
34+
/// given infohash.
35+
///
36+
/// If the torrent entry already exists, the peer is added to its peer list;
37+
/// otherwise, a new torrent entry is created.
38+
///
39+
/// # Arguments
40+
///
41+
/// * `info_hash` - The unique identifier of the torrent.
42+
/// * `peer` - The peer to insert or update in the torrent entry.
3143
pub fn upsert_peer(&self, info_hash: &InfoHash, peer: &peer::Peer) {
3244
self.torrents.upsert_peer(info_hash, peer);
3345
}
3446

47+
/// Removes a torrent entry from the repository.
48+
///
49+
/// This method is only available in tests. It removes the torrent entry
50+
/// associated with the given info hash and returns the removed entry if it
51+
/// existed.
52+
///
53+
/// # Arguments
54+
///
55+
/// * `key` - The info hash of the torrent to remove.
56+
///
57+
/// # Returns
58+
///
59+
/// An `Option` containing the removed torrent entry if it existed.
3560
#[cfg(test)]
3661
#[must_use]
3762
pub(crate) fn remove(&self, key: &InfoHash) -> Option<EntryMutexStd> {
3863
self.torrents.remove(key)
3964
}
4065

66+
/// Removes inactive peers from all torrent entries.
67+
///
68+
/// A peer is considered inactive if its last update timestamp is older than
69+
/// the provided cutoff time.
70+
///
71+
/// # Arguments
72+
///
73+
/// * `current_cutoff` - The cutoff timestamp; peers not updated since this
74+
/// time will be removed.
4175
pub(crate) fn remove_inactive_peers(&self, current_cutoff: DurationSinceUnixEpoch) {
4276
self.torrents.remove_inactive_peers(current_cutoff);
4377
}
4478

79+
/// Removes torrent entries that have no active peers.
80+
///
81+
/// Depending on the tracker policy, torrents without any peers may be
82+
/// removed to conserve memory.
83+
///
84+
/// # Arguments
85+
///
86+
/// * `policy` - The tracker policy containing the configuration for
87+
/// removing peerless torrents.
4588
pub(crate) fn remove_peerless_torrents(&self, policy: &TrackerPolicy) {
4689
self.torrents.remove_peerless_torrents(policy);
4790
}
4891

92+
/// Retrieves a torrent entry by its infohash.
93+
///
94+
/// # Arguments
95+
///
96+
/// * `key` - The info hash of the torrent.
97+
///
98+
/// # Returns
99+
///
100+
/// An `Option` containing the torrent entry if found.
49101
#[must_use]
50102
pub(crate) fn get(&self, key: &InfoHash) -> Option<EntryMutexStd> {
51103
self.torrents.get(key)
52104
}
53105

106+
/// Retrieves a paginated list of torrent entries.
107+
///
108+
/// This method returns a vector of tuples, each containing an infohash and
109+
/// its associated torrent entry. The pagination parameters (offset and limit)
110+
/// can be used to control the size of the result set.
111+
///
112+
/// # Arguments
113+
///
114+
/// * `pagination` - An optional reference to a `Pagination` object.
115+
///
116+
/// # Returns
117+
///
118+
/// A vector of `(InfoHash, EntryMutexStd)` tuples.
54119
#[must_use]
55120
pub(crate) fn get_paginated(&self, pagination: Option<&Pagination>) -> Vec<(InfoHash, EntryMutexStd)> {
56121
self.torrents.get_paginated(pagination)
57122
}
58123

59-
/// It returns the data for a `scrape` response or empty if the torrent is
60-
/// not found.
124+
/// Retrieves swarm metadata for a given torrent.
125+
///
126+
/// This method returns the swarm metadata (aggregate information such as
127+
/// peer counts) for the torrent specified by the infohash. If the torrent
128+
/// entry is not found, a zeroed metadata struct is returned.
129+
///
130+
/// # Arguments
131+
///
132+
/// * `info_hash` - The info hash of the torrent.
133+
///
134+
/// # Returns
135+
///
136+
/// A `SwarmMetadata` struct containing the aggregated torrent data.
61137
#[must_use]
62138
pub(crate) fn get_swarm_metadata(&self, info_hash: &InfoHash) -> SwarmMetadata {
63139
match self.torrents.get(info_hash) {
@@ -66,9 +142,23 @@ impl InMemoryTorrentRepository {
66142
}
67143
}
68144

69-
/// Get torrent peers for a given torrent and client.
145+
/// Retrieves torrent peers for a given torrent and client, excluding the
146+
/// requesting client.
147+
///
148+
/// This method filters out the client making the request (based on its
149+
/// network address) and returns up to a maximum number of peers, defined by
150+
/// the greater of the provided limit or the global `TORRENT_PEERS_LIMIT`.
151+
///
152+
/// # Arguments
153+
///
154+
/// * `info_hash` - The info hash of the torrent.
155+
/// * `peer` - The client peer that should be excluded from the returned list.
156+
/// * `limit` - The maximum number of peers to return.
157+
///
158+
/// # Returns
70159
///
71-
/// It filters out the client making the request.
160+
/// A vector of peers (wrapped in `Arc`) representing the active peers for
161+
/// the torrent, excluding the requesting client.
72162
#[must_use]
73163
pub(crate) fn get_peers_for(&self, info_hash: &InfoHash, peer: &peer::Peer, limit: usize) -> Vec<Arc<peer::Peer>> {
74164
match self.torrents.get(info_hash) {
@@ -77,7 +167,19 @@ impl InMemoryTorrentRepository {
77167
}
78168
}
79169

80-
/// Get torrent peers for a given torrent.
170+
/// Retrieves the list of peers for a given torrent.
171+
///
172+
/// This method returns up to `TORRENT_PEERS_LIMIT` peers for the torrent
173+
/// specified by the info-hash.
174+
///
175+
/// # Arguments
176+
///
177+
/// * `info_hash` - The info hash of the torrent.
178+
///
179+
/// # Returns
180+
///
181+
/// A vector of peers (wrapped in `Arc`) representing the active peers for
182+
/// the torrent.
81183
#[must_use]
82184
pub fn get_torrent_peers(&self, info_hash: &InfoHash) -> Vec<Arc<peer::Peer>> {
83185
match self.torrents.get(info_hash) {
@@ -86,12 +188,28 @@ impl InMemoryTorrentRepository {
86188
}
87189
}
88190

89-
/// It calculates and returns the general [`TorrentsMetrics`].
191+
/// Calculates and returns overall torrent metrics.
192+
///
193+
/// The returned [`TorrentsMetrics`] contains aggregate data such as the
194+
/// total number of torrents, total complete (seeders), incomplete (leechers),
195+
/// and downloaded counts.
196+
///
197+
/// # Returns
198+
///
199+
/// A [`TorrentsMetrics`] struct with the aggregated metrics.
90200
#[must_use]
91201
pub fn get_torrents_metrics(&self) -> TorrentsMetrics {
92202
self.torrents.get_metrics()
93203
}
94204

205+
/// Imports persistent torrent data into the in-memory repository.
206+
///
207+
/// This method takes a set of persisted torrent entries (e.g., from a database)
208+
/// and imports them into the in-memory repository for immediate access.
209+
///
210+
/// # Arguments
211+
///
212+
/// * `persistent_torrents` - A reference to the persisted torrent data.
95213
pub fn import_persistent(&self, persistent_torrents: &PersistentTorrents) {
96214
self.torrents.import_persistent(persistent_torrents);
97215
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
//! Torrent repository implementations.
12
pub mod in_memory;
23
pub mod persisted;

‎packages/tracker-core/src/torrent/repository/persisted.rs

+43-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! The repository that stored persistent torrents' data into the database.
12
use std::sync::Arc;
23

34
use bittorrent_primitives::info_hash::InfoHash;
@@ -6,38 +7,71 @@ use torrust_tracker_primitives::PersistentTorrents;
67
use crate::databases::error::Error;
78
use crate::databases::Database;
89

9-
/// Torrent repository implementation that persists the torrents in a database.
10+
/// Torrent repository implementation that persists torrent metrics in a database.
1011
///
11-
/// Not all the torrent in-memory data is persisted. For now only some of the
12-
/// torrent metrics are persisted.
12+
/// This repository persists only a subset of the torrent data: the torrent
13+
/// metrics, specifically the number of downloads (or completed counts) for each
14+
/// torrent. It relies on a database driver (either `SQLite3` or `MySQL`) that
15+
/// implements the [`Database`] trait to perform the actual persistence
16+
/// operations.
17+
///
18+
/// # Note
19+
///
20+
/// Not all in-memory torrent data is persisted; only the aggregate metrics are
21+
/// stored.
1322
pub struct DatabasePersistentTorrentRepository {
14-
/// A database driver implementation: [`Sqlite3`](crate::core::databases::sqlite)
15-
/// or [`MySQL`](crate::core::databases::mysql)
23+
/// A shared reference to the database driver implementation.
24+
///
25+
/// The driver must implement the [`Database`] trait. This allows for
26+
/// different underlying implementations (e.g., `SQLite3` or `MySQL`) to be
27+
/// used interchangeably.
1628
database: Arc<Box<dyn Database>>,
1729
}
1830

1931
impl DatabasePersistentTorrentRepository {
32+
/// Creates a new instance of `DatabasePersistentTorrentRepository`.
33+
///
34+
/// # Arguments
35+
///
36+
/// * `database` - A shared reference to a boxed database driver
37+
/// implementing the [`Database`] trait.
38+
///
39+
/// # Returns
40+
///
41+
/// A new `DatabasePersistentTorrentRepository` instance with a cloned
42+
/// reference to the provided database.
2043
#[must_use]
2144
pub fn new(database: &Arc<Box<dyn Database>>) -> DatabasePersistentTorrentRepository {
2245
Self {
2346
database: database.clone(),
2447
}
2548
}
2649

27-
/// It loads the persistent torrents from the database.
50+
/// Loads all persistent torrent metrics from the database.
51+
///
52+
/// This function retrieves the torrent metrics (e.g., download counts) from the persistent store
53+
/// and returns them as a [`PersistentTorrents`] map.
2854
///
2955
/// # Errors
3056
///
31-
/// Will return a database `Err` if unable to load.
57+
/// Returns an [`Error`] if the underlying database query fails.
3258
pub(crate) fn load_all(&self) -> Result<PersistentTorrents, Error> {
3359
self.database.load_persistent_torrents()
3460
}
3561

36-
/// It saves the persistent torrent into the database.
62+
/// Saves the persistent torrent metric into the database.
63+
///
64+
/// This function stores or updates the download count for the torrent
65+
/// identified by the provided infohash.
66+
///
67+
/// # Arguments
68+
///
69+
/// * `info_hash` - The info hash of the torrent.
70+
/// * `downloaded` - The number of times the torrent has been downloaded.
3771
///
3872
/// # Errors
3973
///
40-
/// Will return a database `Err` if unable to save.
74+
/// Returns an [`Error`] if the database operation fails.
4175
pub(crate) fn save(&self, info_hash: &InfoHash, downloaded: u32) -> Result<(), Error> {
4276
self.database.save_persistent_torrent(info_hash, downloaded)
4377
}

‎packages/tracker-core/src/torrent/services.rs

+93-16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
//! Core tracker domain services.
22
//!
3-
//! There are two services:
3+
//! This module defines the primary services for retrieving torrent-related data
4+
//! from the tracker. There are two main services:
45
//!
5-
//! - [`get_torrent_info`]: it returns all the data about one torrent.
6-
//! - [`get_torrents`]: it returns data about some torrent in bulk excluding the peer list.
6+
//! - [`get_torrent_info`]: Returns all available data (including the list of
7+
//! peers) about a single torrent.
8+
//! - [`get_torrents_page`] and [`get_torrents`]: Return summarized data about
9+
//! multiple torrents, excluding the peer list.
10+
//!
11+
//! The full torrent info is represented by the [`Info`] struct, which includes
12+
//! swarm data (peer list) and aggregate metrics. The [`BasicInfo`] struct
13+
//! provides similar data but without the list of peers, making it suitable for
14+
//! bulk queries.
715
use std::sync::Arc;
816

917
use bittorrent_primitives::info_hash::InfoHash;
@@ -13,37 +21,74 @@ use torrust_tracker_torrent_repository::entry::EntrySync;
1321

1422
use crate::torrent::repository::in_memory::InMemoryTorrentRepository;
1523

16-
/// It contains all the information the tracker has about a torrent
24+
/// Full torrent information, including swarm (peer) details.
25+
///
26+
/// This struct contains all the information that the tracker holds about a
27+
/// torrent, including the infohash, aggregate swarm metrics (seeders, leechers,
28+
/// completed downloads), and the complete list of peers in the swarm.
1729
#[derive(Debug, PartialEq)]
1830
pub struct Info {
1931
/// The infohash of the torrent this data is related to
2032
pub info_hash: InfoHash,
21-
/// The total number of seeders for this torrent. Peer that actively serving a full copy of the torrent data
33+
34+
/// The total number of seeders for this torrent. Peer that actively serving
35+
/// a full copy of the torrent data
2236
pub seeders: u64,
23-
/// The total number of peers that have ever complete downloading this torrent
37+
38+
/// The total number of peers that have ever complete downloading this
39+
/// torrent
2440
pub completed: u64,
25-
/// The total number of leechers for this torrent. Peers that actively downloading this torrent
41+
42+
/// The total number of leechers for this torrent. Peers that actively
43+
/// downloading this torrent
2644
pub leechers: u64,
27-
/// The swarm: the list of peers that are actively trying to download or serving this torrent
45+
46+
/// The swarm: the list of peers that are actively trying to download or
47+
/// serving this torrent
2848
pub peers: Option<Vec<peer::Peer>>,
2949
}
3050

31-
/// It contains only part of the information the tracker has about a torrent
51+
/// Basic torrent information, excluding the list of peers.
3252
///
33-
/// It contains the same data as [Info] but without the list of peers in the swarm.
53+
/// This struct contains the same aggregate metrics as [`Info`] (infohash,
54+
/// seeders, completed, leechers) but omits the peer list. It is used when only
55+
/// summary information is needed.
3456
#[derive(Debug, PartialEq, Clone)]
3557
pub struct BasicInfo {
3658
/// The infohash of the torrent this data is related to
3759
pub info_hash: InfoHash,
38-
/// The total number of seeders for this torrent. Peer that actively serving a full copy of the torrent data
60+
61+
/// The total number of seeders for this torrent. Peer that actively serving
62+
/// a full copy of the torrent data
3963
pub seeders: u64,
40-
/// The total number of peers that have ever complete downloading this torrent
64+
65+
/// The total number of peers that have ever complete downloading this
66+
/// torrent
4167
pub completed: u64,
42-
/// The total number of leechers for this torrent. Peers that actively downloading this torrent
68+
69+
/// The total number of leechers for this torrent. Peers that actively
70+
/// downloading this torrent
4371
pub leechers: u64,
4472
}
4573

46-
/// It returns all the information the tracker has about one torrent in a [Info] struct.
74+
/// Retrieves complete torrent information for a given torrent.
75+
///
76+
/// This function queries the in-memory torrent repository for a torrent entry
77+
/// matching the provided infohash. If found, it extracts the swarm metadata
78+
/// (aggregate metrics) and the current list of peers, and returns an [`Info`]
79+
/// struct.
80+
///
81+
/// # Arguments
82+
///
83+
/// * `in_memory_torrent_repository` - A shared reference to the in-memory
84+
/// torrent repository.
85+
/// * `info_hash` - A reference to the torrent's infohash.
86+
///
87+
/// # Returns
88+
///
89+
/// An [`Option<Info>`] which is:
90+
/// - `Some(Info)` if the torrent exists in the repository.
91+
/// - `None` if the torrent is not found.
4792
#[must_use]
4893
pub fn get_torrent_info(in_memory_torrent_repository: &Arc<InMemoryTorrentRepository>, info_hash: &InfoHash) -> Option<Info> {
4994
let torrent_entry_option = in_memory_torrent_repository.get(info_hash);
@@ -65,7 +110,23 @@ pub fn get_torrent_info(in_memory_torrent_repository: &Arc<InMemoryTorrentReposi
65110
})
66111
}
67112

68-
/// It returns all the information the tracker has about multiple torrents in a [`BasicInfo`] struct, excluding the peer list.
113+
/// Retrieves summarized torrent information for a paginated set of torrents.
114+
///
115+
/// This function returns a vector of [`BasicInfo`] structures for torrents in
116+
/// the repository, according to the provided pagination parameters. The
117+
/// returned data excludes the peer list, providing only aggregate metrics.
118+
///
119+
/// # Arguments
120+
///
121+
/// * `in_memory_torrent_repository` - A shared reference to the in-memory
122+
/// torrent repository.
123+
/// * `pagination` - An optional reference to a [`Pagination`] object specifying
124+
/// offset and limit.
125+
///
126+
/// # Returns
127+
///
128+
/// A vector of [`BasicInfo`] structs representing the summarized data of the
129+
/// torrents.
69130
#[must_use]
70131
pub fn get_torrents_page(
71132
in_memory_torrent_repository: &Arc<InMemoryTorrentRepository>,
@@ -87,7 +148,23 @@ pub fn get_torrents_page(
87148
basic_infos
88149
}
89150

90-
/// It returns all the information the tracker has about multiple torrents in a [`BasicInfo`] struct, excluding the peer list.
151+
/// Retrieves summarized torrent information for a specified list of torrents.
152+
///
153+
/// This function iterates over a slice of infohashes, fetches the corresponding
154+
/// swarm metadata from the in-memory repository (if available), and returns a
155+
/// vector of [`BasicInfo`] structs. This function is useful for bulk queries
156+
/// where detailed peer information is not required.
157+
///
158+
/// # Arguments
159+
///
160+
/// * `in_memory_torrent_repository` - A shared reference to the in-memory
161+
/// torrent repository.
162+
/// * `info_hashes` - A slice of infohashes for which to retrieve the torrent
163+
/// information.
164+
///
165+
/// # Returns
166+
///
167+
/// A vector of [`BasicInfo`] structs for the requested torrents.
91168
#[must_use]
92169
pub fn get_torrents(in_memory_torrent_repository: &Arc<InMemoryTorrentRepository>, info_hashes: &[InfoHash]) -> Vec<BasicInfo> {
93170
let mut basic_infos: Vec<BasicInfo> = vec![];

‎packages/tracker-core/src/whitelist/authorization.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! Whitelist authorization.
12
use std::panic::Location;
23
use std::sync::Arc;
34

@@ -8,6 +9,10 @@ use tracing::instrument;
89
use super::repository::in_memory::InMemoryWhitelist;
910
use crate::error::WhitelistError;
1011

12+
/// Manages the authorization of torrents based on the whitelist.
13+
///
14+
/// Used to determine whether a given torrent (`infohash`) is allowed
15+
/// to be announced or scraped from the tracker.
1116
pub struct WhitelistAuthorization {
1217
/// Core tracker configuration.
1318
config: Core,
@@ -17,20 +22,30 @@ pub struct WhitelistAuthorization {
1722
}
1823

1924
impl WhitelistAuthorization {
20-
/// Creates a new authorization instance.
25+
/// Creates a new `WhitelistAuthorization` instance.
26+
///
27+
/// # Arguments
28+
/// - `config`: Tracker configuration.
29+
/// - `in_memory_whitelist`: The in-memory whitelist instance.
30+
///
31+
/// # Returns
32+
/// A new `WhitelistAuthorization` instance.
2133
pub fn new(config: &Core, in_memory_whitelist: &Arc<InMemoryWhitelist>) -> Self {
2234
Self {
2335
config: config.clone(),
2436
in_memory_whitelist: in_memory_whitelist.clone(),
2537
}
2638
}
2739

28-
/// It returns true if the torrent is authorized.
40+
/// Checks whether a torrent is authorized.
2941
///
30-
/// # Errors
42+
/// - If the tracker is **public**, all torrents are authorized.
43+
/// - If the tracker is **private** (listed mode), only whitelisted torrents
44+
/// are authorized.
3145
///
32-
/// Will return an error if the tracker is running in `listed` mode
33-
/// and the infohash is not whitelisted.
46+
/// # Errors
47+
/// Returns `WhitelistError::TorrentNotWhitelisted` if the tracker is in `listed` mode
48+
/// and the `info_hash` is not in the whitelist.
3449
#[instrument(skip(self, info_hash), err)]
3550
pub async fn authorize(&self, info_hash: &InfoHash) -> Result<(), WhitelistError> {
3651
if !self.is_listed() {
@@ -47,12 +62,12 @@ impl WhitelistAuthorization {
4762
})
4863
}
4964

50-
/// Returns `true` is the tracker is in listed mode.
65+
/// Checks if the tracker is running in "listed" mode.
5166
fn is_listed(&self) -> bool {
5267
self.config.listed
5368
}
5469

55-
/// It checks if a torrent is whitelisted.
70+
/// Checks if a torrent is present in the whitelist.
5671
async fn is_info_hash_whitelisted(&self, info_hash: &InfoHash) -> bool {
5772
self.in_memory_whitelist.contains(info_hash).await
5873
}

‎packages/tracker-core/src/whitelist/manager.rs

+35-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1+
//! Whitelist manager.
2+
//!
3+
//! This module provides the `WhitelistManager` struct, which is responsible for
4+
//! managing the whitelist of torrents.
15
use std::sync::Arc;
26

37
use bittorrent_primitives::info_hash::InfoHash;
48

59
use super::repository::in_memory::InMemoryWhitelist;
610
use super::repository::persisted::DatabaseWhitelist;
711
use crate::databases;
8-
9-
/// It handles the list of allowed torrents. Only for listed trackers.
12+
/// Manages the whitelist of allowed torrents.
13+
///
14+
/// This structure handles both the in-memory and persistent representations of
15+
/// the whitelist. It is primarily relevant for private trackers that restrict
16+
/// access to specific torrents.
1017
pub struct WhitelistManager {
1118
/// The in-memory list of allowed torrents.
1219
in_memory_whitelist: Arc<InMemoryWhitelist>,
@@ -16,6 +23,17 @@ pub struct WhitelistManager {
1623
}
1724

1825
impl WhitelistManager {
26+
/// Creates a new `WhitelistManager` instance.
27+
///
28+
/// # Arguments
29+
///
30+
/// - `database_whitelist`: Persistent database-backed whitelist repository.
31+
/// - `in_memory_whitelist`: In-memory whitelist repository for fast runtime
32+
/// access.
33+
///
34+
/// # Returns
35+
///
36+
/// A new `WhitelistManager` instance.
1937
#[must_use]
2038
pub fn new(database_whitelist: Arc<DatabaseWhitelist>, in_memory_whitelist: Arc<InMemoryWhitelist>) -> Self {
2139
Self {
@@ -24,35 +42,39 @@ impl WhitelistManager {
2442
}
2543
}
2644

27-
/// It adds a torrent to the whitelist.
28-
/// Adding torrents is not relevant to public trackers.
45+
/// Adds a torrent to the whitelist.
2946
///
30-
/// # Errors
47+
/// This operation is relevant for private trackers to control which
48+
/// torrents are allowed.
3149
///
32-
/// Will return a `database::Error` if unable to add the `info_hash` into the whitelist database.
50+
/// # Errors
51+
/// Returns a `database::Error` if the operation fails in the database.
3352
pub async fn add_torrent_to_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
3453
self.database_whitelist.add(info_hash)?;
3554
self.in_memory_whitelist.add(info_hash).await;
3655
Ok(())
3756
}
3857

39-
/// It removes a torrent from the whitelist.
40-
/// Removing torrents is not relevant to public trackers.
58+
/// Removes a torrent from the whitelist.
4159
///
42-
/// # Errors
60+
/// This operation is relevant for private trackers to revoke access to
61+
/// specific torrents.
4362
///
44-
/// Will return a `database::Error` if unable to remove the `info_hash` from the whitelist database.
63+
/// # Errors
64+
/// Returns a `database::Error` if the operation fails in the database.
4565
pub async fn remove_torrent_from_whitelist(&self, info_hash: &InfoHash) -> Result<(), databases::error::Error> {
4666
self.database_whitelist.remove(info_hash)?;
4767
self.in_memory_whitelist.remove(info_hash).await;
4868
Ok(())
4969
}
5070

51-
/// It loads the whitelist from the database.
71+
/// Loads the whitelist from the database into memory.
5272
///
53-
/// # Errors
73+
/// This is useful when restarting the tracker to ensure the in-memory
74+
/// whitelist is synchronized with the database.
5475
///
55-
/// Will return a `database::Error` if unable to load the list whitelisted `info_hash`s from the database.
76+
/// # Errors
77+
/// Returns a `database::Error` if the operation fails to load from the database.
5678
pub async fn load_whitelist_from_database(&self) -> Result<(), databases::error::Error> {
5779
let whitelisted_torrents_from_database = self.database_whitelist.load_from_database()?;
5880

‎packages/tracker-core/src/whitelist/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
//! This module contains the logic to manage the torrent whitelist.
2+
//!
3+
//! In tracker configurations where the tracker operates in "listed" mode, only
4+
//! torrents that have been explicitly added to the whitelist are allowed to
5+
//! perform announce and scrape actions. This module provides all the
6+
//! functionality required to manage such a whitelist.
7+
//!
8+
//! The module is organized into the following submodules:
9+
//!
10+
//! - **`authorization`**: Contains the logic to authorize torrents based on their
11+
//! whitelist status.
12+
//! - **`manager`**: Provides high-level management functions for the whitelist,
13+
//! such as adding or removing torrents.
14+
//! - **`repository`**: Implements persistence for whitelist data.
15+
//! - **`setup`**: Provides initialization routines for setting up the whitelist
16+
//! system.
17+
//! - **`test_helpers`**: Contains helper functions and fixtures for testing
18+
//! whitelist functionality.
119
pub mod authorization;
220
pub mod manager;
321
pub mod repository;

‎packages/tracker-core/src/whitelist/repository/in_memory.rs

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,42 @@
1+
//! The in-memory list of allowed torrents.
12
use bittorrent_primitives::info_hash::InfoHash;
23

3-
/// The in-memory list of allowed torrents.
4+
/// In-memory whitelist to manage allowed torrents.
5+
///
6+
/// Stores `InfoHash` values for quick lookup and modification.
47
#[derive(Debug, Default)]
58
pub struct InMemoryWhitelist {
6-
/// The list of allowed torrents.
9+
/// A thread-safe set of whitelisted `InfoHash` values.
710
whitelist: tokio::sync::RwLock<std::collections::HashSet<InfoHash>>,
811
}
912

1013
impl InMemoryWhitelist {
11-
/// It adds a torrent from the whitelist in memory.
14+
/// Adds a torrent to the in-memory whitelist.
15+
///
16+
/// # Returns
17+
///
18+
/// - `true` if the torrent was newly added.
19+
/// - `false` if the torrent was already in the whitelist.
1220
pub async fn add(&self, info_hash: &InfoHash) -> bool {
1321
self.whitelist.write().await.insert(*info_hash)
1422
}
1523

16-
/// It removes a torrent from the whitelist in memory.
24+
/// Removes a torrent from the in-memory whitelist.
25+
///
26+
/// # Returns
27+
///
28+
/// - `true` if the torrent was present and removed.
29+
/// - `false` if the torrent was not found.
1730
pub(crate) async fn remove(&self, info_hash: &InfoHash) -> bool {
1831
self.whitelist.write().await.remove(info_hash)
1932
}
2033

21-
/// It checks if it contains an info-hash.
34+
/// Checks if a torrent is in the whitelist.
2235
pub async fn contains(&self, info_hash: &InfoHash) -> bool {
2336
self.whitelist.read().await.contains(info_hash)
2437
}
2538

26-
/// It clears the whitelist.
39+
/// Clears all torrents from the whitelist.
2740
pub(crate) async fn clear(&self) {
2841
let mut whitelist = self.whitelist.write().await;
2942
whitelist.clear();
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
//! Repository implementations for the whitelist.
12
pub mod in_memory;
23
pub mod persisted;

0 commit comments

Comments
 (0)
Please sign in to comment.