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) {