diff --git a/cSpell.json b/cSpell.json
index 88794b2ad..b0ad4caf7 100644
--- a/cSpell.json
+++ b/cSpell.json
@@ -37,6 +37,7 @@
"leechers",
"libtorrent",
"Lphant",
+ "metainfo",
"middlewares",
"mockall",
"multimap",
diff --git a/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent
new file mode 100644
index 000000000..1a08a811b
Binary files /dev/null and b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent differ
diff --git a/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent.json b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent.json
new file mode 100644
index 000000000..caaa1a417
--- /dev/null
+++ b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent.json
@@ -0,0 +1,10 @@
+{
+ "created by": "qBittorrent v4.4.1",
+ "creation date": 1679674628,
+ "info": {
+ "length": 172204,
+ "name": "mandelbrot_2048x2048.png",
+ "piece length": 16384,
+ "pieces": "7D 91 71 0D 9D 4D BA 88 9B 54 20 54 D5 26 72 8D 5A 86 3F E1 21 DF 77 C7 F7 BB 6C 77 96 21 66 25 38 C5 D9 CD AB 8B 08 EF 8C 24 9B B2 F5 C4 CD 2A DF 0B C0 0C F0 AD DF 72 90 E5 B6 41 4C 23 6C 47 9B 8E 9F 46 AA 0C 0D 8E D1 97 FF EE 68 8B 5F 34 A3 87 D7 71 C5 A6 F9 8E 2E A6 31 7C BD F0 F9 E2 23 F9 CC 80 AF 54 00 04 F9 85 69 1C 77 89 C1 76 4E D6 AA BF 61 A6 C2 80 99 AB B6 5F 60 2F 40 A8 25 BE 32 A3 3D 9D 07 0C 79 68 98 D4 9D 63 49 AF 20 58 66 26 6F 98 6B 6D 32 34 CD 7D 08 15 5E 1A D0 00 09 57 AB 30 3B 20 60 C1 DC 12 87 D6 F3 E7 45 4F 70 67 09 36 31 55 F2 20 F6 6C A5 15 6F 2C 89 95 69 16 53 81 7D 31 F1 B6 BD 37 42 CC 11 0B B2 FC 2B 49 A5 85 B6 FC 76 74 44 93"
+ }
+}
\ No newline at end of file
diff --git a/src/shared/bit_torrent/common.rs b/src/shared/bit_torrent/common.rs
index 527ae9ebc..fd52e098c 100644
--- a/src/shared/bit_torrent/common.rs
+++ b/src/shared/bit_torrent/common.rs
@@ -1,27 +1,56 @@
+//! `BitTorrent` protocol primitive types
+//!
+//! [BEP 3. The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html)
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
use serde::{Deserialize, Serialize};
+/// The maximum number of torrents that can be returned in an `scrape` response.
+/// It's also the maximum number of peers returned in an `announce` response.
+///
+/// The [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html)
+/// defines this limit:
+///
+/// "Up to about 74 torrents can be scraped at once. A full scrape can't be done
+/// with this protocol."
+///
+/// The [BEP 48. Tracker Protocol Extension: Scrape](https://www.bittorrent.org/beps/bep_0048.html)
+/// does not specifically mention this limit, but the limit is being used for
+/// both the UDP and HTTP trackers since it's applied at the domain level.
pub const MAX_SCRAPE_TORRENTS: u8 = 74;
+
+/// HTTP tracker authentication key length.
+///
+/// See function to [`generate`](crate::tracker::auth::generate) the
+/// [`ExpiringKeys`](crate::tracker::auth::ExpiringKey) for more information.
pub const AUTH_KEY_LENGTH: usize = 32;
#[repr(u32)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
-pub enum Actions {
+enum Actions {
+ // todo: it seems this enum is not used anywhere. Values match the ones in
+ // aquatic_udp_protocol::request::Request::from_bytes.
Connect = 0,
Announce = 1,
Scrape = 2,
Error = 3,
}
+/// Announce events. Described on the
+/// [BEP 3. The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html)
#[derive(Serialize, Deserialize)]
#[serde(remote = "AnnounceEvent")]
pub enum AnnounceEventDef {
+ /// The peer has started downloading the torrent.
Started,
+ /// The peer has ceased downloading the torrent.
Stopped,
+ /// The peer has completed downloading the torrent.
Completed,
+ /// This is one of the announcements done at regular intervals.
None,
}
+/// Number of bytes downloaded, uploaded or pending to download (left) by the peer.
#[derive(Serialize, Deserialize)]
#[serde(remote = "NumberOfBytes")]
pub struct NumberOfBytesDef(pub i64);
diff --git a/src/shared/bit_torrent/info_hash.rs b/src/shared/bit_torrent/info_hash.rs
index fd7602cdd..7392c791d 100644
--- a/src/shared/bit_torrent/info_hash.rs
+++ b/src/shared/bit_torrent/info_hash.rs
@@ -1,13 +1,147 @@
+//! A `BitTorrent` `InfoHash`. It's a unique identifier for a `BitTorrent` torrent.
+//!
+//! "The 20-byte sha1 hash of the bencoded form of the info value
+//! from the metainfo file."
+//!
+//! See [BEP 3. The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html)
+//! for the official specification.
+//!
+//! This modules provides a type that can be used to represent infohashes.
+//!
+//! > **NOTICE**: It only supports Info Hash v1.
+//!
+//! Typically infohashes are represented as hex strings, but internally they are
+//! a 20-byte array.
+//!
+//! # Calculating the info-hash of a torrent file
+//!
+//! A sample torrent:
+//!
+//! - Torrent file: `mandelbrot_2048x2048_infohash_v1.png.torrent`
+//! - File: `mandelbrot_2048x2048.png`
+//! - Info Hash v1: `5452869be36f9f3350ccee6b4544e7e76caaadab`
+//! - Sha1 hash of the info dictionary: `5452869BE36F9F3350CCEE6B4544E7E76CAAADAB`
+//!
+//! A torrent file is a binary file encoded with [Bencode encoding](https://en.wikipedia.org/wiki/Bencode):
+//!
+//! ```text
+//! 0000000: 6431 303a 6372 6561 7465 6420 6279 3138 d10:created by18
+//! 0000010: 3a71 4269 7474 6f72 7265 6e74 2076 342e :qBittorrent v4.
+//! 0000020: 342e 3131 333a 6372 6561 7469 6f6e 2064 4.113:creation d
+//! 0000030: 6174 6569 3136 3739 3637 3436 3238 6534 atei1679674628e4
+//! 0000040: 3a69 6e66 6f64 363a 6c65 6e67 7468 6931 :infod6:lengthi1
+//! 0000050: 3732 3230 3465 343a 6e61 6d65 3234 3a6d 72204e4:name24:m
+//! 0000060: 616e 6465 6c62 726f 745f 3230 3438 7832 andelbrot_2048x2
+//! 0000070: 3034 382e 706e 6731 323a 7069 6563 6520 048.png12:piece
+//! 0000080: 6c65 6e67 7468 6931 3633 3834 6536 3a70 lengthi16384e6:p
+//! 0000090: 6965 6365 7332 3230 3a7d 9171 0d9d 4dba ieces220:}.q..M.
+//! 00000a0: 889b 5420 54d5 2672 8d5a 863f e121 df77 ..T T.&r.Z.?.!.w
+//! 00000b0: c7f7 bb6c 7796 2166 2538 c5d9 cdab 8b08 ...lw.!f%8......
+//! 00000c0: ef8c 249b b2f5 c4cd 2adf 0bc0 0cf0 addf ..$.....*.......
+//! 00000d0: 7290 e5b6 414c 236c 479b 8e9f 46aa 0c0d r...AL#lG...F...
+//! 00000e0: 8ed1 97ff ee68 8b5f 34a3 87d7 71c5 a6f9 .....h._4...q...
+//! 00000f0: 8e2e a631 7cbd f0f9 e223 f9cc 80af 5400 ...1|....#....T.
+//! 0000100: 04f9 8569 1c77 89c1 764e d6aa bf61 a6c2 ...i.w..vN...a..
+//! 0000110: 8099 abb6 5f60 2f40 a825 be32 a33d 9d07 ...._`/@.%.2.=..
+//! 0000120: 0c79 6898 d49d 6349 af20 5866 266f 986b .yh...cI. Xf&o.k
+//! 0000130: 6d32 34cd 7d08 155e 1ad0 0009 57ab 303b m24.}..^....W.0;
+//! 0000140: 2060 c1dc 1287 d6f3 e745 4f70 6709 3631 `.......EOpg.61
+//! 0000150: 55f2 20f6 6ca5 156f 2c89 9569 1653 817d U. .l..o,..i.S.}
+//! 0000160: 31f1 b6bd 3742 cc11 0bb2 fc2b 49a5 85b6 1...7B.....+I...
+//! 0000170: fc76 7444 9365 65 .vtD.ee
+//! ```
+//!
+//! You can generate that output with the command:
+//!
+//! ```text
+//! xxd mandelbrot_2048x2048_infohash_v1.png.torrent
+//! ```
+//!
+//! And you can show only the bytes (hexadecimal):
+//!
+//! ```text
+//! 6431303a6372656174656420627931383a71426974746f7272656e742076
+//! 342e342e3131333a6372656174696f6e2064617465693136373936373436
+//! 323865343a696e666f64363a6c656e6774686931373232303465343a6e61
+//! 6d6532343a6d616e64656c62726f745f3230343878323034382e706e6731
+//! 323a7069656365206c656e67746869313633383465363a70696563657332
+//! 32303a7d91710d9d4dba889b542054d526728d5a863fe121df77c7f7bb6c
+//! 779621662538c5d9cdab8b08ef8c249bb2f5c4cd2adf0bc00cf0addf7290
+//! e5b6414c236c479b8e9f46aa0c0d8ed197ffee688b5f34a387d771c5a6f9
+//! 8e2ea6317cbdf0f9e223f9cc80af540004f985691c7789c1764ed6aabf61
+//! a6c28099abb65f602f40a825be32a33d9d070c796898d49d6349af205866
+//! 266f986b6d3234cd7d08155e1ad0000957ab303b2060c1dc1287d6f3e745
+//! 4f706709363155f220f66ca5156f2c8995691653817d31f1b6bd3742cc11
+//! 0bb2fc2b49a585b6fc767444936565
+//! ```
+//!
+//! You can generate that output with the command:
+//!
+//! ```text
+//! `xxd -ps mandelbrot_2048x2048_infohash_v1.png.torrent`.
+//! ```
+//!
+//! The same data can be represented in a JSON format:
+//!
+//! ```json
+//! {
+//! "created by": "qBittorrent v4.4.1",
+//! "creation date": 1679674628,
+//! "info": {
+//! "length": 172204,
+//! "name": "mandelbrot_2048x2048.png",
+//! "piece length": 16384,
+//! "pieces": "7D 91 71 0D 9D 4D BA 88 9B 54 20 54 D5 26 72 8D 5A 86 3F E1 21 DF 77 C7 F7 BB 6C 77 96 21 66 25 38 C5 D9 CD AB 8B 08 EF 8C 24 9B B2 F5 C4 CD 2A DF 0B C0 0C F0 AD DF 72 90 E5 B6 41 4C 23 6C 47 9B 8E 9F 46 AA 0C 0D 8E D1 97 FF EE 68 8B 5F 34 A3 87 D7 71 C5 A6 F9 8E 2E A6 31 7C BD F0 F9 E2 23 F9 CC 80 AF 54 00 04 F9 85 69 1C 77 89 C1 76 4E D6 AA BF 61 A6 C2 80 99 AB B6 5F 60 2F 40 A8 25 BE 32 A3 3D 9D 07 0C 79 68 98 D4 9D 63 49 AF 20 58 66 26 6F 98 6B 6D 32 34 CD 7D 08 15 5E 1A D0 00 09 57 AB 30 3B 20 60 C1 DC 12 87 D6 F3 E7 45 4F 70 67 09 36 31 55 F2 20 F6 6C A5 15 6F 2C 89 95 69 16 53 81 7D 31 F1 B6 BD 37 42 CC 11 0B B2 FC 2B 49 A5 85 B6 FC 76 74 44 93"
+//! }
+//! }
+//! ```
+//!
+//! The JSON object was generated with:
+//!
+//! As you can see, there is a `info` attribute:
+//!
+//! ```json
+//! {
+//! "length": 172204,
+//! "name": "mandelbrot_2048x2048.png",
+//! "piece length": 16384,
+//! "pieces": "7D 91 71 0D 9D 4D BA 88 9B 54 20 54 D5 26 72 8D 5A 86 3F E1 21 DF 77 C7 F7 BB 6C 77 96 21 66 25 38 C5 D9 CD AB 8B 08 EF 8C 24 9B B2 F5 C4 CD 2A DF 0B C0 0C F0 AD DF 72 90 E5 B6 41 4C 23 6C 47 9B 8E 9F 46 AA 0C 0D 8E D1 97 FF EE 68 8B 5F 34 A3 87 D7 71 C5 A6 F9 8E 2E A6 31 7C BD F0 F9 E2 23 F9 CC 80 AF 54 00 04 F9 85 69 1C 77 89 C1 76 4E D6 AA BF 61 A6 C2 80 99 AB B6 5F 60 2F 40 A8 25 BE 32 A3 3D 9D 07 0C 79 68 98 D4 9D 63 49 AF 20 58 66 26 6F 98 6B 6D 32 34 CD 7D 08 15 5E 1A D0 00 09 57 AB 30 3B 20 60 C1 DC 12 87 D6 F3 E7 45 4F 70 67 09 36 31 55 F2 20 F6 6C A5 15 6F 2C 89 95 69 16 53 81 7D 31 F1 B6 BD 37 42 CC 11 0B B2 FC 2B 49 A5 85 B6 FC 76 74 44 93"
+//! }
+//! ```
+//!
+//! The infohash is the [SHA1](https://en.wikipedia.org/wiki/SHA-1) hash
+//! of the `info` attribute. That is, the SHA1 hash of:
+//!
+//! ```text
+//! 64363a6c656e6774686931373232303465343a6e61
+//! d6532343a6d616e64656c62726f745f3230343878323034382e706e6731
+//! 23a7069656365206c656e67746869313633383465363a70696563657332
+//! 2303a7d91710d9d4dba889b542054d526728d5a863fe121df77c7f7bb6c
+//! 79621662538c5d9cdab8b08ef8c249bb2f5c4cd2adf0bc00cf0addf7290
+//! 5b6414c236c479b8e9f46aa0c0d8ed197ffee688b5f34a387d771c5a6f9
+//! e2ea6317cbdf0f9e223f9cc80af540004f985691c7789c1764ed6aabf61
+//! 6c28099abb65f602f40a825be32a33d9d070c796898d49d6349af205866
+//! 66f986b6d3234cd7d08155e1ad0000957ab303b2060c1dc1287d6f3e745
+//! f706709363155f220f66ca5156f2c8995691653817d31f1b6bd3742cc11
+//! bb2fc2b49a585b6fc7674449365
+//! ```
+//!
+//! You can hash that byte string with
+//!
+//! The result is a 20-char string: `5452869BE36F9F3350CCEE6B4544E7E76CAAADAB`
use std::panic::Location;
use thiserror::Error;
+/// `BitTorrent` Info Hash v1
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub struct InfoHash(pub [u8; 20]);
const INFO_HASH_BYTES_LEN: usize = 20;
impl InfoHash {
+ /// Create a new `InfoHash` from a byte slice.
+ ///
/// # Panics
///
/// Will panic if byte slice does not contains the exact amount of bytes need for the `InfoHash`.
@@ -19,12 +153,13 @@ impl InfoHash {
ret
}
- /// For readability, when accessing the bytes array
+ /// Returns the `InfoHash` internal byte array.
#[must_use]
pub fn bytes(&self) -> [u8; 20] {
self.0
}
+ /// Returns the `InfoHash` as a hex string.
#[must_use]
pub fn to_hex_string(&self) -> String {
self.to_string()
@@ -79,13 +214,16 @@ impl std::convert::From<[u8; 20]> for InfoHash {
}
}
+/// Errors that can occur when converting from a `Vec` to an `InfoHash`.
#[derive(Error, Debug)]
pub enum ConversionError {
+ /// Not enough bytes for infohash. An infohash is 20 bytes.
#[error("not enough bytes for infohash: {message} {location}")]
NotEnoughBytes {
location: &'static Location<'static>,
message: String,
},
+ /// Too many bytes for infohash. An infohash is 20 bytes.
#[error("too many bytes for infohash: {message} {location}")]
TooManyBytes {
location: &'static Location<'static>,
diff --git a/src/shared/bit_torrent/mod.rs b/src/shared/bit_torrent/mod.rs
index 7579a0780..0e5d7e7f2 100644
--- a/src/shared/bit_torrent/mod.rs
+++ b/src/shared/bit_torrent/mod.rs
@@ -1,2 +1,3 @@
+//! Common code for the `BitTorrent` protocol.
pub mod common;
pub mod info_hash;
diff --git a/src/shared/clock/mod.rs b/src/shared/clock/mod.rs
index b5001e10e..7a5290f49 100644
--- a/src/shared/clock/mod.rs
+++ b/src/shared/clock/mod.rs
@@ -1,3 +1,27 @@
+//! Time related functions and types.
+//!
+//! It's usually a good idea to control where the time comes from
+//! in an application so that it can be mocked for testing and it can be
+//! controlled in production so we get the intended behavior without
+//! relying on the specific time zone for the underlying system.
+//!
+//! Clocks use the type `DurationSinceUnixEpoch` which is a
+//! `std::time::Duration` since the Unix Epoch (timestamp).
+//!
+//! ```text
+//! Local time: lun 2023-03-27 16:12:00 WEST
+//! Universal time: lun 2023-03-27 15:12:00 UTC
+//! Time zone: Atlantic/Canary (WEST, +0100)
+//! Timestamp: 1679929914
+//! Duration: 1679929914.10167426
+//! ```
+//!
+//! > **NOTICE**: internally the `Duration` is stores it's main unit as seconds in a `u64` and it will
+//! overflow in 584.9 billion years.
+//!
+//! > **NOTICE**: the timestamp does not depend on the time zone. That gives you
+//! the ability to use the clock regardless of the underlying system time zone
+//! configuration. See [Unix time Wikipedia entry](https://en.wikipedia.org/wiki/Unix_time).
pub mod static_time;
pub mod time_extent;
pub mod utils;
@@ -8,30 +32,47 @@ use std::time::Duration;
use chrono::{DateTime, NaiveDateTime, Utc};
+/// Duration since the Unix Epoch.
pub type DurationSinceUnixEpoch = Duration;
+/// Clock types.
#[derive(Debug)]
pub enum Type {
+ /// Clock that returns the current time.
WorkingClock,
+ /// Clock that returns always the same fixed time.
StoppedClock,
}
+/// A generic structure that represents a clock.
+///
+/// It can be either the working clock (production) or the stopped clock
+/// (testing). It implements the `Time` trait, which gives you the current time.
#[derive(Debug)]
pub struct Clock;
+/// The working clock. It returns the current time.
pub type Working = Clock<{ Type::WorkingClock as usize }>;
+/// The stopped clock. It returns always the same fixed time.
pub type Stopped = Clock<{ Type::StoppedClock as usize }>;
+/// The current clock. Defined at compilation time.
+/// It can be either the working clock (production) or the stopped clock (testing).
#[cfg(not(test))]
pub type Current = Working;
+/// The current clock. Defined at compilation time.
+/// It can be either the working clock (production) or the stopped clock (testing).
#[cfg(test)]
pub type Current = Stopped;
+/// Trait for types that can be used as a timestamp clock.
pub trait Time: Sized {
fn now() -> DurationSinceUnixEpoch;
}
+/// Trait for types that can be manipulate the current time in order to
+/// get time in the future or in the past after or before a duration of time.
pub trait TimeNow: Time {
#[must_use]
fn add(add_time: &Duration) -> Option {
@@ -43,28 +84,40 @@ pub trait TimeNow: Time {
}
}
+/// It converts a string in ISO 8601 format to a timestamp.
+/// For example, the string `1970-01-01T00:00:00.000Z` which is the Unix Epoch
+/// will be converted to a timestamp of 0: `DurationSinceUnixEpoch::ZERO`.
+///
/// # Panics
///
-/// Will panic if the input time cannot be converted to `DateTime::`.
-///
+/// Will panic if the input time cannot be converted to `DateTime::`, internally using the `i64` type.
+/// (this will naturally happen in 292.5 billion years)
#[must_use]
pub fn convert_from_iso_8601_to_timestamp(iso_8601: &str) -> DurationSinceUnixEpoch {
convert_from_datetime_utc_to_timestamp(&DateTime::::from_str(iso_8601).unwrap())
}
+/// It converts a `DateTime::` to a timestamp.
+/// For example, the `DateTime::` of the Unix Epoch will be converted to a
+/// timestamp of 0: `DurationSinceUnixEpoch::ZERO`.
+///
/// # Panics
///
-/// Will panic if the input time overflows the u64 type.
-///
+/// Will panic if the input time overflows the `u64` type.
+/// (this will naturally happen in 584.9 billion years)
#[must_use]
pub fn convert_from_datetime_utc_to_timestamp(datetime_utc: &DateTime) -> DurationSinceUnixEpoch {
DurationSinceUnixEpoch::from_secs(u64::try_from(datetime_utc.timestamp()).expect("Overflow of u64 seconds, very future!"))
}
+/// It converts a timestamp to a `DateTime::`.
+/// For example, the timestamp of 0: `DurationSinceUnixEpoch::ZERO` will be
+/// converted to the `DateTime::` of the Unix Epoch.
+///
/// # Panics
///
-/// Will panic if the input time overflows the i64 type.
-///
+/// Will panic if the input time overflows the `u64` seconds overflows the `i64` type.
+/// (this will naturally happen in 292.5 billion years)
#[must_use]
pub fn convert_from_timestamp_to_datetime_utc(duration: DurationSinceUnixEpoch) -> DateTime {
DateTime::::from_utc(
@@ -144,23 +197,37 @@ mod working_clock {
impl TimeNow for Working {}
}
+/// Trait for types that can be used as a timestamp clock stopped
+/// at a given time.
pub trait StoppedTime: TimeNow {
+ /// It sets the clock to a given time.
fn local_set(unix_time: &DurationSinceUnixEpoch);
+
+ /// It sets the clock to the Unix Epoch.
fn local_set_to_unix_epoch() {
Self::local_set(&DurationSinceUnixEpoch::ZERO);
}
+
+ /// It sets the clock to the time the application started.
fn local_set_to_app_start_time();
+
+ /// It sets the clock to the current system time.
fn local_set_to_system_time_now();
+ /// It adds a `Duration` to the clock.
+ ///
/// # Errors
///
/// Will return `IntErrorKind` if `duration` would overflow the internal `Duration`.
fn local_add(duration: &Duration) -> Result<(), IntErrorKind>;
+ /// It subtracts a `Duration` from the clock.
/// # Errors
///
/// Will return `IntErrorKind` if `duration` would underflow the internal `Duration`.
fn local_sub(duration: &Duration) -> Result<(), IntErrorKind>;
+
+ /// It resets the clock to default fixed time that is application start time (or the unix epoch when testing).
fn local_reset();
}
diff --git a/src/shared/clock/static_time.rs b/src/shared/clock/static_time.rs
index f916cec9c..79557b3c4 100644
--- a/src/shared/clock/static_time.rs
+++ b/src/shared/clock/static_time.rs
@@ -1,5 +1,8 @@
+//! It contains a static variable that is set to the time at which
+//! the application started.
use std::time::SystemTime;
lazy_static! {
+ /// The time at which the application started.
pub static ref TIME_AT_APP_START: SystemTime = SystemTime::now();
}
diff --git a/src/shared/clock/time_extent.rs b/src/shared/clock/time_extent.rs
index 64142c404..2f9e003be 100644
--- a/src/shared/clock/time_extent.rs
+++ b/src/shared/clock/time_extent.rs
@@ -1,43 +1,124 @@
+//! It includes functionality to handle time extents.
+//!
+//! Time extents are used to represent a duration of time which contains
+//! N times intervals of the same duration.
+//!
+//! Given a duration of: 60 seconds.
+//!
+//! ```text
+//! |------------------------------------------------------------|
+//! ```
+//!
+//! If we define a **base** duration of `10` seconds, we would have `6` intervals.
+//!
+//! ```text
+//! |----------|----------|----------|----------|----------|----------|
+//! ^--- 10 seconds
+//! ```
+//!
+//! Then, You can represent half of the duration (`30` seconds) as:
+//!
+//! ```text
+//! |----------|----------|----------|----------|----------|----------|
+//! ^--- 30 seconds
+//! ```
+//!
+//! `3` times (**multiplier**) the **base** interval (3*10 = 30 seconds):
+//!
+//! ```text
+//! |----------|----------|----------|----------|----------|----------|
+//! ^--- 30 seconds (3 units of 10 seconds)
+//! ```
+//!
+//! Time extents are a way to measure time duration using only one unit of time
+//! (**base** duration) repeated `N` times (**multiplier**).
+//!
+//! Time extents are not clocks in a sense that they do not have a start time.
+//! They are not synchronized with the real time. In order to measure time,
+//! you need to define a start time for the intervals.
+//!
+//! For example, we could measure time is "lustrums" (5 years) since the start
+//! of the 21st century. The time extent would contains a base 5-year duration
+//! and the multiplier. The current "lustrum" (2023) would be 5th one if we
+//! start counting "lustrums" at 1.
+//!
+//! ```text
+//! Lustrum 1: 2000-2004
+//! Lustrum 2: 2005-2009
+//! Lustrum 3: 2010-2014
+//! Lustrum 4: 2015-2019
+//! Lustrum 5: 2020-2024
+//! ```
+//!
+//! More practically time extents are used to represent number of time intervals
+//! since the Unix Epoch. Each interval is typically an amount of seconds.
+//! It's specially useful to check expiring dates. For example, you can have an
+//! authentication token that expires after 120 seconds. If you divide the
+//! current timestamp by 120 you get the number of 2-minute intervals since the
+//! Unix Epoch, you can hash that value with a secret key and send it to a
+//! client. The client can authenticate by sending the hashed value back to the
+//! server. The server can build the same hash and compare it with the one sent
+//! by the client. The hash would be the same during the 2-minute interval, but
+//! it would change after that. This method is one of the methods used by UDP
+//! trackers to generate and verify a connection ID, which a a token sent to
+//! the client to identify the connection.
use std::num::{IntErrorKind, TryFromIntError};
use std::time::Duration;
use super::{Stopped, TimeNow, Type, Working};
+/// This trait defines the operations that can be performed on a `TimeExtent`.
pub trait Extent: Sized + Default {
type Base;
type Multiplier;
type Product;
+ /// It creates a new `TimeExtent`.
fn new(unit: &Self::Base, count: &Self::Multiplier) -> Self;
+ /// It increases the `TimeExtent` by a multiplier.
+ ///
/// # Errors
///
/// Will return `IntErrorKind` if `add` would overflow the internal `Duration`.
fn increase(&self, add: Self::Multiplier) -> Result;
+ /// It decreases the `TimeExtent` by a multiplier.
+ ///
/// # Errors
///
/// Will return `IntErrorKind` if `sub` would underflow the internal `Duration`.
fn decrease(&self, sub: Self::Multiplier) -> Result;
+ /// It returns the total `Duration` of the `TimeExtent`.
fn total(&self) -> Option>;
+
+ /// It returns the total `Duration` of the `TimeExtent` plus one increment.
fn total_next(&self) -> Option>;
}
+/// The `TimeExtent` base `Duration`, which is the duration of a single interval.
pub type Base = Duration;
+/// The `TimeExtent` `Multiplier`, which is the number of `Base` duration intervals.
pub type Multiplier = u64;
+/// The `TimeExtent` product, which is the total duration of the `TimeExtent`.
pub type Product = Base;
+/// A `TimeExtent` is a duration of time which contains N times intervals
+/// of the same duration.
#[derive(Debug, Default, Hash, PartialEq, Eq)]
pub struct TimeExtent {
pub increment: Base,
pub amount: Multiplier,
}
+/// A zero time extent. It's the additive identity for a `TimeExtent`.
pub const ZERO: TimeExtent = TimeExtent {
increment: Base::ZERO,
amount: Multiplier::MIN,
};
+
+/// The maximum value for a `TimeExtent`.
pub const MAX: TimeExtent = TimeExtent {
increment: Base::MAX,
amount: Multiplier::MAX,
@@ -114,10 +195,23 @@ impl Extent for TimeExtent {
}
}
+/// A `TimeExtent` maker. It's a clock base on time extents.
+/// It gives you the time in time extents.
pub trait Make: Sized
where
Clock: TimeNow,
{
+ /// It gives you the current time extent (with a certain increment) for
+ /// the current time. It gets the current timestamp front he `Clock`.
+ ///
+ /// For example:
+ ///
+ /// - If the base increment is `1` second, it will return a time extent
+ /// whose duration is `1 second` and whose multiplier is the the number
+ /// of seconds since the Unix Epoch (time extent).
+ /// - If the base increment is `1` minute, it will return a time extent
+ /// whose duration is `60 seconds` and whose multiplier is the number of
+ /// minutes since the Unix Epoch (time extent).
#[must_use]
fn now(increment: &Base) -> Option> {
Clock::now()
@@ -129,6 +223,9 @@ where
})
}
+ /// Same as [`now`](crate::shared::clock::time_extent::Make::now), but it
+ /// will add an extra duration to the current time before calculating the
+ /// time extent. It gives you a time extent for a time in the future.
#[must_use]
fn now_after(increment: &Base, add_time: &Duration) -> Option> {
match Clock::add(add_time) {
@@ -143,6 +240,9 @@ where
}
}
+ /// Same as [`now`](crate::shared::clock::time_extent::Make::now), but it
+ /// will subtract a duration to the current time before calculating the
+ /// time extent. It gives you a time extent for a time in the past.
#[must_use]
fn now_before(increment: &Base, sub_time: &Duration) -> Option> {
match Clock::sub(sub_time) {
@@ -158,18 +258,28 @@ where
}
}
+/// A `TimeExtent` maker which makes `TimeExtents`.
+///
+/// It's a clock which measures time in `TimeExtents`.
#[derive(Debug)]
pub struct Maker {}
+/// A `TimeExtent` maker which makes `TimeExtents` from the `Working` clock.
pub type WorkingTimeExtentMaker = Maker<{ Type::WorkingClock as usize }>;
+
+/// A `TimeExtent` maker which makes `TimeExtents` from the `Stopped` clock.
pub type StoppedTimeExtentMaker = Maker<{ Type::StoppedClock as usize }>;
impl Make for WorkingTimeExtentMaker {}
impl Make for StoppedTimeExtentMaker {}
+/// The default `TimeExtent` maker. It is `WorkingTimeExtentMaker` in production
+/// and `StoppedTimeExtentMaker` in tests.
#[cfg(not(test))]
pub type DefaultTimeExtentMaker = WorkingTimeExtentMaker;
+/// The default `TimeExtent` maker. It is `WorkingTimeExtentMaker` in production
+/// and `StoppedTimeExtentMaker` in tests.
#[cfg(test)]
pub type DefaultTimeExtentMaker = StoppedTimeExtentMaker;
diff --git a/src/shared/clock/utils.rs b/src/shared/clock/utils.rs
index 9127f97b1..94d88d288 100644
--- a/src/shared/clock/utils.rs
+++ b/src/shared/clock/utils.rs
@@ -1,5 +1,7 @@
+//! It contains helper functions related to time.
use super::DurationSinceUnixEpoch;
+/// Serializes a `DurationSinceUnixEpoch` as a Unix timestamp in milliseconds.
/// # Errors
///
/// Will return `serde::Serializer::Error` if unable to serialize the `unix_time_value`.
diff --git a/src/shared/crypto/ephemeral_instance_keys.rs b/src/shared/crypto/ephemeral_instance_keys.rs
index 635d10fbd..44283365a 100644
--- a/src/shared/crypto/ephemeral_instance_keys.rs
+++ b/src/shared/crypto/ephemeral_instance_keys.rs
@@ -1,8 +1,13 @@
+//! This module contains the ephemeral instance keys used by the application.
+//!
+//! They are ephemeral because they are generated at runtime when the
+//! application starts and are not persisted anywhere.
use rand::rngs::ThreadRng;
use rand::Rng;
pub type Seed = [u8; 32];
lazy_static! {
+ /// The random static seed.
pub static ref RANDOM_SEED: Seed = Rng::gen(&mut ThreadRng::default());
}
diff --git a/src/shared/crypto/keys.rs b/src/shared/crypto/keys.rs
index 5e04eb551..92e180996 100644
--- a/src/shared/crypto/keys.rs
+++ b/src/shared/crypto/keys.rs
@@ -1,13 +1,30 @@
+//! This module contains logic related to cryptographic keys.
pub mod seeds {
+ //! This module contains logic related to cryptographic seeds.
+ //!
+ //! Specifically, it contains the logic for storing the seed and providing
+ //! it to other modules.
+ //!
+ //! A **seed** is a pseudo-random number that is used as a secret key for
+ //! cryptographic operations.
use self::detail::CURRENT_SEED;
use crate::shared::crypto::ephemeral_instance_keys::{Seed, RANDOM_SEED};
+ /// This trait is for structures that can keep and provide a seed.
pub trait Keeper {
type Seed: Sized + Default + AsMut<[u8]>;
+
+ /// It returns a reference to the seed that is keeping.
fn get_seed() -> &'static Self::Seed;
}
+ /// The seed keeper for the instance. When the application is running
+ /// in production, this will be the seed keeper that is used.
pub struct Instance;
+
+ /// The seed keeper for the current execution. It's a facade at compilation
+ /// time that will either be the instance seed keeper (with a randomly
+ /// generated key for production) or the zeroed seed keeper.
pub struct Current;
impl Keeper for Instance {
diff --git a/src/shared/crypto/mod.rs b/src/shared/crypto/mod.rs
index 066eb0f46..3c7c287b5 100644
--- a/src/shared/crypto/mod.rs
+++ b/src/shared/crypto/mod.rs
@@ -1,2 +1,3 @@
+//! Cryptographic primitives.
pub mod ephemeral_instance_keys;
pub mod keys;
diff --git a/src/shared/mod.rs b/src/shared/mod.rs
index 4b0d9138e..f016ba913 100644
--- a/src/shared/mod.rs
+++ b/src/shared/mod.rs
@@ -1,3 +1,8 @@
+//! Modules with generic logic used by several modules.
+//!
+//! - [`bit_torrent`]: `BitTorrent` protocol related logic.
+//! - [`clock`]: Times services.
+//! - [`crypto`]: Encryption related logic.
pub mod bit_torrent;
pub mod clock;
pub mod crypto;
diff --git a/src/tracker/auth.rs b/src/tracker/auth.rs
index 9fe111e5e..466187af5 100644
--- a/src/tracker/auth.rs
+++ b/src/tracker/auth.rs
@@ -120,8 +120,8 @@ impl ExpiringKey {
///
/// # Panics
///
- /// Will panic when the key timestamp overflows the ui64 type.
- ///
+ /// Will panic when the key timestamp overflows the internal i64 type.
+ /// (this will naturally happen in 292.5 billion years)
#[must_use]
pub fn expiry_time(&self) -> chrono::DateTime {
convert_from_timestamp_to_datetime_utc(self.valid_until)
diff --git a/src/tracker/torrent.rs b/src/tracker/torrent.rs
index 8eb557f1e..1e78cd909 100644
--- a/src/tracker/torrent.rs
+++ b/src/tracker/torrent.rs
@@ -123,15 +123,18 @@ impl Entry {
did_torrent_stats_change
}
- /// Get all swarm peers, limiting the result to the maximum number of scrape torrents.
+ /// Get all swarm peers, limiting the result to the maximum number of scrape
+ /// torrents.
#[must_use]
pub fn get_all_peers(&self) -> Vec<&peer::Peer> {
self.peers.values().take(MAX_SCRAPE_TORRENTS as usize).collect()
}
- /// It returns the list of peers for a given peer client.
+ /// It returns the list of peers for a given peer client, limiting the
+ /// result to the maximum number of scrape torrents.
///
- /// It filters out the input peer, typically because we want to return this list of peers to that client peer.
+ /// It filters out the input peer, typically because we want to return this
+ /// list of peers to that client peer.
#[must_use]
pub fn get_peers_for_peer(&self, client: &Peer) -> Vec<&peer::Peer> {
self.peers
@@ -143,7 +146,9 @@ impl Entry {
.collect()
}
- /// It returns the swarm metadata (statistics) as a tuple `(seeders, completed, leechers)`
+ /// It returns the swarm metadata (statistics) as a tuple:
+ ///
+ /// `(seeders, completed, leechers)`
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn get_stats(&self) -> (u32, u32, u32) {