Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 49bfdce

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

File tree

18 files changed

+927
-503
lines changed

18 files changed

+927
-503
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

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

‎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/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

+192
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,195 @@
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 want to collect.
6+
//!
7+
//! The returned struct is:
8+
//!
9+
//! ```rust,no_run
10+
//! use bittorrent_primitives::info_hash::InfoHash;
11+
//! use std::collections::HashMap;
12+
//!
13+
//! pub struct ScrapeData {
14+
//! pub files: HashMap<InfoHash, SwarmMetadata>,
15+
//! }
16+
//!
17+
//! pub struct SwarmMetadata {
18+
//! pub complete: u32, // The number of active peers that have completed downloading (seeders)
19+
//! pub downloaded: u32, // The number of peers that have ever completed downloading
20+
//! pub incomplete: u32, // The number of active peers that have not completed downloading (leechers)
21+
//! }
22+
//! ```
23+
//!
24+
//! The JSON representation of a sample `scrape` response would be like the following:
25+
//!
26+
//! ```json
27+
//! {
28+
//! 'files': {
29+
//! 'xxxxxxxxxxxxxxxxxxxx': {'complete': 11, 'downloaded': 13772, 'incomplete': 19},
30+
//! 'yyyyyyyyyyyyyyyyyyyy': {'complete': 21, 'downloaded': 206, 'incomplete': 20}
31+
//! }
32+
//! }
33+
//! ```
34+
//!
35+
//! `xxxxxxxxxxxxxxxxxxxx` and `yyyyyyyyyyyyyyyyyyyy` are 20-byte infohash arrays.
36+
//! There are two data structures for infohashes: byte arrays and hex strings:
37+
//!
38+
//! ```rust,no_run
39+
//! use bittorrent_primitives::info_hash::InfoHash;
40+
//! use std::str::FromStr;
41+
//!
42+
//! let info_hash: InfoHash = [255u8; 20].into();
43+
//!
44+
//! assert_eq!(
45+
//! info_hash,
46+
//! InfoHash::from_str("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap()
47+
//! );
48+
//! ```
49+
//! Refer to `BitTorrent` BEPs and other sites for more information about the `scrape` request:
50+
//!
51+
//! - [BEP 48. Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html)
52+
//! - [BEP 15. UDP Tracker Protocol for `BitTorrent`. Scrape section](https://www.bittorrent.org/beps/bep_0015.html)
53+
//! - [Vuze docs](https://wiki.vuze.com/w/Scrape)
54+
//!
55+
//! ## Torrents
56+
//!
57+
//! The [`torrent`](crate::torrent) module contains all the data structures stored by the `Tracker` except for peers.
58+
//!
59+
//! We can represent the data stored in memory internally by the `Tracker` with this JSON object:
60+
//!
61+
//! ```json
62+
//! {
63+
//! "c1277613db1d28709b034a017ab2cae4be07ae10": {
64+
//! "completed": 0,
65+
//! "peers": {
66+
//! "-qB00000000000000001": {
67+
//! "peer_id": "-qB00000000000000001",
68+
//! "peer_addr": "2.137.87.41:1754",
69+
//! "updated": 1672419840,
70+
//! "uploaded": 120,
71+
//! "downloaded": 60,
72+
//! "left": 60,
73+
//! "event": "started"
74+
//! },
75+
//! "-qB00000000000000002": {
76+
//! "peer_id": "-qB00000000000000002",
77+
//! "peer_addr": "23.17.287.141:2345",
78+
//! "updated": 1679415984,
79+
//! "uploaded": 80,
80+
//! "downloaded": 20,
81+
//! "left": 40,
82+
//! "event": "started"
83+
//! }
84+
//! }
85+
//! }
86+
//! }
87+
//! ```
88+
//!
89+
//! The `Tracker` maintains an indexed-by-info-hash list of torrents. For each torrent, it stores a torrent `Entry`.
90+
//! The torrent entry has two attributes:
91+
//!
92+
//! - `completed`: which is hte number of peers that have completed downloading the torrent file/s. As they have completed downloading,
93+
//! they have a full version of the torrent data, and they can provide the full data to other peers. That's why they are also known as "seeders".
94+
//! - `peers`: an indexed and orderer list of peer for the torrent. Each peer contains the data received from the peer in the `announce` request.
95+
//!
96+
//! The [`crate::torrent`] module not only contains the original data obtained from peer via `announce` requests, it also contains
97+
//! aggregate data that can be derived from the original data. For example:
98+
//!
99+
//! ```rust,no_run
100+
//! pub struct SwarmMetadata {
101+
//! pub complete: u32, // The number of active peers that have completed downloading (seeders)
102+
//! pub downloaded: u32, // The number of peers that have ever completed downloading
103+
//! pub incomplete: u32, // The number of active peers that have not completed downloading (leechers)
104+
//! }
105+
//!
106+
//! ```
107+
//!
108+
//! > **NOTICE**: that `complete` or `completed` peers are the peers that have completed downloading, but only the active ones are considered "seeders".
109+
//!
110+
//! `SwarmMetadata` struct follows name conventions for `scrape` responses. See [BEP 48](https://www.bittorrent.org/beps/bep_0048.html), while `SwarmMetadata`
111+
//! is used for the rest of cases.
112+
//!
113+
//! Refer to [`crate::torrent`] module for more details about these data structures.
114+
//!
115+
//! ## Peers
116+
//!
117+
//! A `Peer` is the struct used by the `Tracker` to keep peers data:
118+
//!
119+
//! ```rust,no_run
120+
//! use std::net::SocketAddr;
121+
//! use aquatic_udp_protocol::PeerId;
122+
//! use torrust_tracker_primitives::DurationSinceUnixEpoch;
123+
//! use aquatic_udp_protocol::NumberOfBytes;
124+
//! use aquatic_udp_protocol::AnnounceEvent;
125+
//!
126+
//! pub struct Peer {
127+
//! pub peer_id: PeerId, // The peer ID
128+
//! pub peer_addr: SocketAddr, // Peer socket address
129+
//! pub updated: DurationSinceUnixEpoch, // Last time (timestamp) when the peer was updated
130+
//! pub uploaded: NumberOfBytes, // Number of bytes the peer has uploaded so far
131+
//! pub downloaded: NumberOfBytes, // Number of bytes the peer has downloaded so far
132+
//! pub left: NumberOfBytes, // The number of bytes this peer still has to download
133+
//! pub event: AnnounceEvent, // The event the peer has announced: `started`, `completed`, `stopped`
134+
//! }
135+
//! ```
136+
//!
137+
//! Notice that most of the attributes are obtained from the `announce` request.
138+
//! For example, an HTTP announce request would contain the following `GET` parameters:
139+
//!
140+
//! <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>
141+
//!
142+
//! 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.
143+
//!
144+
//! We can represent the data stored in memory with this JSON object:
145+
//!
146+
//! ```json
147+
//! {
148+
//! "c1277613db1d28709b034a017ab2cae4be07ae10": {
149+
//! "completed": 0,
150+
//! "peers": {
151+
//! "-qB00000000000000001": {
152+
//! "peer_id": "-qB00000000000000001",
153+
//! "peer_addr": "2.137.87.41:1754",
154+
//! "updated": 1672419840,
155+
//! "uploaded": 120,
156+
//! "downloaded": 60,
157+
//! "left": 60,
158+
//! "event": "started"
159+
//! },
160+
//! "-qB00000000000000002": {
161+
//! "peer_id": "-qB00000000000000002",
162+
//! "peer_addr": "23.17.287.141:2345",
163+
//! "updated": 1679415984,
164+
//! "uploaded": 80,
165+
//! "downloaded": 20,
166+
//! "left": 40,
167+
//! "event": "started"
168+
//! }
169+
//! }
170+
//! }
171+
//! }
172+
//! ```
173+
//!
174+
//! That JSON object does not exist, it's only a representation of the `Tracker` torrents data.
175+
//!
176+
//! `c1277613db1d28709b034a017ab2cae4be07ae10` is the torrent infohash and `completed` contains the number of peers
177+
//! that have a full version of the torrent data, also known as seeders.
178+
//!
179+
//! Refer to [`peer`](torrust_tracker_primitives::peer) for more information about peers.
180+
//!
181+
//! # Persistence
182+
//!
183+
//! Right now the `Tracker` is responsible for storing and load data into and
184+
//! from the database, when persistence is enabled.
185+
//!
186+
//! There are three types of persistent object:
187+
//!
188+
//! - Authentication keys (only expiring keys)
189+
//! - Torrent whitelist
190+
//! - Torrent metrics
191+
//!
192+
//! Refer to [`crate::databases`] module for more information about persistence.
1193
use std::sync::Arc;
2194

3195
use bittorrent_primitives::info_hash::InfoHash;

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

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//! This module contains the logic to manage the whitelist.
12
pub mod authorization;
23
pub mod manager;
34
pub mod repository;

0 commit comments

Comments
 (0)
Please sign in to comment.