From 44d6afcf2bc7f789ee6c028bef74ba44ba72fee7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 5 Apr 2023 13:48:20 +0100 Subject: [PATCH] docs: [#271] crate docs for servers::udp mod --- cSpell.json | 5 + src/servers/http/mod.rs | 4 +- src/servers/http/percent_encoding.rs | 2 +- src/servers/http/v1/requests/announce.rs | 2 +- src/servers/signals.rs | 2 +- src/servers/udp/handlers.rs | 44 +- src/servers/udp/mod.rs | 583 +++++++++++++++++++++++ tests/servers/api/v1/asserts.rs | 2 +- 8 files changed, 624 insertions(+), 20 deletions(-) diff --git a/cSpell.json b/cSpell.json index af0de7101..f07e2bfb4 100644 --- a/cSpell.json +++ b/cSpell.json @@ -1,6 +1,7 @@ { "words": [ "appuser", + "Arvid", "AUTOINCREMENT", "automock", "Avicora", @@ -21,6 +22,7 @@ "chrono", "clippy", "completei", + "connectionless", "dockerhub", "downloadedi", "filesd", @@ -47,6 +49,7 @@ "nanos", "nextest", "nocapture", + "Norberg", "numwant", "oneshot", "ostr", @@ -59,6 +62,7 @@ "reqwest", "rerequests", "rngs", + "routable", "rusqlite", "rustfmt", "Rustls", @@ -82,6 +86,7 @@ "Vagaa", "Vuze", "whitespaces", + "XBTT", "Xtorrent", "Xunlei", "xxxxxxxxxxxxxxxxxxxxd", diff --git a/src/servers/http/mod.rs b/src/servers/http/mod.rs index 78c086892..067e88fdd 100644 --- a/src/servers/http/mod.rs +++ b/src/servers/http/mod.rs @@ -43,7 +43,7 @@ //! //! Parameter | Type | Description | Required | Default | Example //! ---|---|---|---|---|--- -//! [`info_hash`](crate::servers::http::v1::requests::announce::Announce::info_hash) | percent encoded of 40-byte array | The `Info Hash` of the torrent. | Yes | No | `%81%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00` +//! [`info_hash`](crate::servers::http::v1::requests::announce::Announce::info_hash) | percent encoded of 20-byte array | The `Info Hash` of the torrent. | Yes | No | `%81%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00` //! `peer_addr` | string |The IP address of the peer. | No | No | `2.137.87.41` //! [`downloaded`](crate::servers::http::v1::requests::announce::Announce::downloaded) | positive integer |The number of bytes downloaded by the peer. | No | `0` | `0` //! [`uploaded`](crate::servers::http::v1::requests::announce::Announce::uploaded) | positive integer | The number of bytes uploaded by the peer. | No | `0` | `0` @@ -220,7 +220,7 @@ //! //! Parameter | Type | Description | Required | Default | Example //! ---|---|---|---|---|--- -//! [`info_hash`](crate::servers::http::v1::requests::scrape::Scrape::info_hashes) | percent encoded of 40-byte array | The `Info Hash` of the torrent. | Yes | No | `%81%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00` +//! [`info_hash`](crate::servers::http::v1::requests::scrape::Scrape::info_hashes) | percent encoded of 20-byte array | The `Info Hash` of the torrent. | Yes | No | `%81%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00` //! //! > **NOTICE**: you can scrape multiple torrents at the same time by passing //! multiple `info_hash` parameters. diff --git a/src/servers/http/percent_encoding.rs b/src/servers/http/percent_encoding.rs index b807e74c9..c8f0f7f12 100644 --- a/src/servers/http/percent_encoding.rs +++ b/src/servers/http/percent_encoding.rs @@ -19,7 +19,7 @@ use crate::shared::bit_torrent::info_hash::{ConversionError, InfoHash}; use crate::tracker::peer::{self, IdConversionError}; /// Percent decodes a percent encoded infohash. Internally an -/// [`InfoHash`](crate::shared::bit_torrent::info_hash::InfoHash) is a 40-byte array. +/// [`InfoHash`](crate::shared::bit_torrent::info_hash::InfoHash) is a 20-byte array. /// /// For example, given the infohash `3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0`, /// it's percent encoded representation is `%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0`. diff --git a/src/servers/http/v1/requests/announce.rs b/src/servers/http/v1/requests/announce.rs index 3725ee1df..1cf632eb5 100644 --- a/src/servers/http/v1/requests/announce.rs +++ b/src/servers/http/v1/requests/announce.rs @@ -88,7 +88,7 @@ pub struct Announce { /// Errors that can occur when parsing the `Announce` request. /// /// The `info_hash` and `peer_id` query params are special because they contain -/// binary data. The `info_hash` is a 40-byte SHA1 hash and the `peer_id` is a +/// binary data. The `info_hash` is a 20-byte SHA1 hash and the `peer_id` is a /// 20-byte array. #[derive(Error, Debug)] pub enum ParseAnnounceQueryError { diff --git a/src/servers/signals.rs b/src/servers/signals.rs index 879a82d5e..f0312b886 100644 --- a/src/servers/signals.rs +++ b/src/servers/signals.rs @@ -1,4 +1,4 @@ -/// This module contains functions to handle signals. +//! This module contains functions to handle signals. use log::info; /// Resolves on `ctrl_c` or the `terminate` signal. diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index e00203cfc..eaae8aa21 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -56,15 +56,19 @@ pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: /// # Errors /// -/// This function dose not ever return an error. +/// This function does not ever return an error. pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: &Tracker) -> Result { + debug!("udp connect request: {:#?}", request); + let connection_cookie = make(&remote_addr); let connection_id = into_connection_id(&connection_cookie); - let response = Response::from(ConnectResponse { + let response = ConnectResponse { transaction_id: request.transaction_id, connection_id, - }); + }; + + debug!("udp connect response: {:#?}", response); // send stats event match remote_addr { @@ -76,7 +80,7 @@ pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, t } } - Ok(response) + Ok(Response::from(response)) } /// # Errors @@ -124,8 +128,8 @@ pub async fn handle_announce( } #[allow(clippy::cast_possible_truncation)] - let announce_response = if remote_addr.is_ipv4() { - Response::from(AnnounceResponse { + if remote_addr.is_ipv4() { + let announce_response = AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, announce_interval: AnnounceInterval(i64::from(tracker.config.announce_interval) as i32), leechers: NumberOfPeers(i64::from(response.swarm_stats.leechers) as i32), @@ -144,9 +148,13 @@ pub async fn handle_announce( } }) .collect(), - }) + }; + + debug!("udp announce response: {:#?}", announce_response); + + Ok(Response::from(announce_response)) } else { - Response::from(AnnounceResponse { + let announce_response = AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, announce_interval: AnnounceInterval(i64::from(tracker.config.announce_interval) as i32), leechers: NumberOfPeers(i64::from(response.swarm_stats.leechers) as i32), @@ -165,16 +173,20 @@ pub async fn handle_announce( } }) .collect(), - }) - }; + }; - Ok(announce_response) + debug!("udp announce response: {:#?}", announce_response); + + Ok(Response::from(announce_response)) + } } /// # Errors /// -/// This function dose not ever return an error. +/// This function does not ever return an error. pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: &Tracker) -> Result { + debug!("udp scrape request: {:#?}", request); + // Convert from aquatic infohashes let mut info_hashes = vec![]; for info_hash in &request.info_hashes { @@ -217,10 +229,14 @@ pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tra } } - Ok(Response::from(ScrapeResponse { + let response = ScrapeResponse { transaction_id: request.transaction_id, torrent_stats, - })) + }; + + debug!("udp scrape response: {:#?}", response); + + Ok(Response::from(response)) } fn handle_error(e: &Error, transaction_id: TransactionId) -> Response { diff --git a/src/servers/udp/mod.rs b/src/servers/udp/mod.rs index 7b755a20b..c2dc04644 100644 --- a/src/servers/udp/mod.rs +++ b/src/servers/udp/mod.rs @@ -1,3 +1,586 @@ +//! UDP Tracker. +//! +//! This module contains the UDP tracker implementation. +//! +//! The UDP tracker is a simple UDP server that responds to these requests: +//! +//! - `Connect`: used to get a connection ID which must be provided on each +//! request in order to avoid spoofing the source address of the UDP packets. +//! - `Announce`: used to announce the presence of a peer to the tracker. +//! - `Scrape`: used to get information about a torrent. +//! +//! It was introduced in [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html) +//! as an alternative to the HTTP tracker. The UDP tracker is more efficient +//! than the HTTP tracker because it uses UDP instead of TCP. +//! +//! Refer to the [`bit_torrent`](crate::shared::bit_torrent) module for more +//! information about the `BitTorrent` protocol. +//! +//! Refer to [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html) +//! for more information about the UDP tracker protocol. And to [BEP 41. UDP Tracker Protocol Extensions]. +//! +//! > **NOTICE**: BEP-41 is not implemented yet. +//! +//! > **NOTICE**: we are using the [`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol) +//! crate so requests and responses are handled by it. +//! +//! > **NOTICE**: all values are send in network byte order ([big endian](https://en.wikipedia.org/wiki/Endianness)). +//! [`AnnounceRequest`](aquatic_udp_protocol::request::AnnounceRequest) there is +//! a wrapper struct [`AnnounceWrapper`](crate::servers::udp::request::AnnounceWrapper). +//! +//! ## Table of Contents +//! +//! - [Requests](#requests) +//! - [Connect](#connect) +//! - [Announce](#announce) +//! - [Scrape](#scrape) +//! - [Links](#links) +//! - [Credits](#credits) +//! +//! ## Requests +//! +//! Requests are sent to the tracker using UDP packets. The UDP tracker protocol +//! is designed to be as simple as possible. It uses a single UDP port and +//! supports only three types of requests: `Connect`, `Announce` and `Scrape`. +//! +//! Request are parsed from UDP packets using the [`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol) +//! crate and then handled by the [`Tracker`](crate::tracker::Tracker) struct. +//! And then the response is also build using the [`aquatic_udp_protocol`](https://crates.io/crates/aquatic_udp_protocol) +//! and converted to a UDP packet. +//! +//! ```text +//! UDP packet -> Aquatic Struct Request -> [Torrust Struct Request] -> Tracker -> Aquatic Struct Response -> UDP packet +//! ``` +//! +//! For the `Announce` there is a wrapper struct [`AnnounceWrapper`](crate::servers::udp::request::AnnounceWrapper). +//! +//! ### Connect +//! +//! `Connect` requests are used to get a connection ID which must be provided on +//! each request in order to avoid spoofing the source address of the UDP. +//! +//! The connection ID is a random 64-bit integer that is used to identify the +//! client. It is used to prevent spoofing of the source address of the UDP +//! packets. Before announcing or scraping, you have to obtain a connection ID. +//! +//! The connection ID is generated by the tracker and sent back to the client's +//! IP address. Only the client using that IP can receive the response, so the +//! tracker can be sure that the client is the one who sent the request. If the +//! client's IP was spoofed the tracker will send the response to the wrong +//! client and the client will not receive it. +//! +//! The reason why the UDP tracker protocol needs a connection ID to avoid IP +//! spoofing can be explained as follows: +//! +//! 1. No connection state: Unlike TCP, UDP is a connectionless protocol, +//! meaning that it does not establish a connection between two endpoints before +//! exchanging data. As a result, it is more susceptible to IP spoofing, where +//! an attacker sends packets with a forged source IP address, tricking the +//! receiver into believing that they are coming from a legitimate source. +//! +//! 2. Mitigating IP spoofing: To mitigate IP spoofing in the UDP tracker +//! protocol, a connection ID is used. When a client wants to interact with a +//! tracker, it sends a "connect" request to the tracker, which, in turn, +//! responds with a unique connection ID. This connection ID must be included in +//! all subsequent requests from the client to the tracker. +//! +//! 3. Validating requests: By requiring the connection ID, the tracker can +//! verify that the requests are coming from the same client that initially sent +//! the "connect" request. If an attacker attempts to spoof the client's IP +//! address, they would also need to know the valid connection ID to be accepted +//! by the tracker. This makes it significantly more challenging for an attacker +//! to spoof IP addresses and disrupt the P2P network. +//! +//! **Connect request (UDP packet)** +//! +//! Offset | Type/Size | Name | Description | Hex | Decimal +//! -------|-------------------|------------------|-------------------------------------------------|-----------------------------|----------------- +//! 0 | [`i64`](std::i64) | `protocol_id` | Magic constant that will identify the protocol. | `0x00_00_04_17_27_10_19_80` | `4497486125440` +//! 8 | [`i32`](std::i32) | `action` | Action identifying the connect request. | `0x00_00_00_00` | `0` +//! 12 | [`i32`](std::i32) | `transaction_id` | Randomly generated by the client. | `0x34_FA_A1_F9` | `-888840697` +//! +//! **Sample connect request (UDP packet)** +//! +//! UDP packet bytes: +//! +//! ```text +//! Offset: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +//! Decimal: [ 0, 0, 4, 23, 39, 16, 25, 128, 0, 0, 0, 0, 203, 5, 94, 7] +//! Hex: [0x00, 0x00, 0x04, 0x17, 0x27, 0x10, 0x19, 0x80, 0x00, 0x00, 0x00, 0x00, 0xCB, 0x05, 0x5E, 0x07] +//! Param: [<------------- protocol_id ------------------>,<------- action ------>,<--- transaction_id -->] +//! ``` +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes Dec (Big Endian) | Hex | Decimal +//! -------|-------------------|------------------|--------------------------------|-----------------------------|---------------- +//! 0 | [`i64`](std::i64) | `protocol_id` | [0, 0, 4, 23, 39, 16, 25, 128] | `0x00_00_04_17_27_10_19_80` | `4497486125440` +//! 4 | [`i32`](std::i32) | `action` | [0, 0, 0, 0] | `0x00_00_00_00` | `0` +//! 8 | [`i32`](std::i32) | `transaction_id` | [35, 63, 226, 1] | `0xCB_05_5E_07` | `-888840697` +//! +//! **Connect request (parsed struct)** +//! +//! After parsing the UDP packet, the [`ConnectRequest`](aquatic_udp_protocol::request::ConnectRequest) +//! request struct will look like this: +//! +//! Field | Type | Example +//! -----------------|----------------------------------------------------------------|------------- +//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `1950635409` +//! +//! **Connect response (UDP packet)** +//! +//! Offset | Type/Size | Name | Description | Hex | Decimal +//! -------|-------------------|------------------|-------------------------------------------------------|-----------------------------|----------------------- +//! 0 | [`i64`](std::i32) | `action` | Action identifying the connect request | `0x00_00_00_00` | `0` +//! 4 | [`i32`](std::i32) | `transaction_id` | Must match the `transaction_id` sent from the client. | `0xCB_05_5E_07` | `-888840697` +//! 8 | [`i32`](std::i64) | `connection_id` | Generated by the tracker to authenticate the client. | `0xC5_58_7C_09_08_48_D8_37` | `-4226491872051668937` +//! +//! > **NOTICE**: the `connection_id` is used when further information is +//! exchanged with the tracker, to identify the client. This `connection_id` can +//! be reused for multiple requests, but if it's cached for too long, it will +//! not be valid anymore. +//! +//! > **NOTICE**: `Hex` column is a signed 2's complement. +//! +//! **Sample connect response (UDP packet)** +//! +//! UDP packet bytes: +//! +//! ```text +//! Offset: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] +//! Decimal: [ 0, 0, 0, 0, 203, 5, 94, 7, 197, 88, 124, 9, 8, 72, 216, 55] +//! Hex: [0x00, 0x00, 0x00, 0x00, 0xCB, 0x05, 0x5E, 0x07, 0xC5, 0x58, 0x7C, 0x09, 0x08, 0x48, 0xD8, 0x37] +//! Param: [<------ action ------>,<-- transaction_id --->,<--------------- connection_id --------------->] +//! ``` +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes (Big Endian) | Hex | Decimal +//! -------|-------------------|------------------|-----------------------------------|------------------------------|----------------------- +//! 0 | [`i64`](std::i32) | `action` | [0, 0, 0, 0] | `0x00_00_00_00` | `0` +//! 4 | [`i64`](std::i32) | `transaction_id` | [203, 5, 94, 7] | `0xCB_05_5E_07` | `-888840697` +//! 8 | [`i64`](std::i64) | `connection_id` | [197, 88, 124, 9, 8, 72, 216, 55] | `0xC5_58_7C_09_08_48_D8_37` | `-4226491872051668937` +//! +//! > **NOTICE**: `Hex` column is a signed 2's complement. +//! +//! **Connect response (struct)** +//! +//! Before building the UDP packet, the [`ConnectResponse`](aquatic_udp_protocol::response::ConnectResponse) +//! struct will look like this: +//! +//! Field | Type | Example +//! -----------------|----------------------------------------------------------------|------------------------- +//! `connection_id` | [`ConnectionId`](aquatic_udp_protocol::common::ConnectionId) | `-4226491872051668937` +//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-888840697` +//! +//! **Connect specification** +//! +//! Original specification in [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html). +//! +//! ### Announce +//! +//! `Announce` requests are used to announce the presence of a peer to the +//! tracker. The tracker responds with a list of peers that are also downloading +//! the same torrent. A "swarm" is a group of peers that are downloading the +//! same torrent. +//! +//! **Announce request (UDP packet)** +//! +//! Offset | Type/Size | Name | Description | Hex | Decimal +//! -------|-------------------|------------------|--------------------------------------------------------------|-----------------------------------------------------------------|---------------------------------------------------------- +//! 0 | [`i64`](std::i64) | `connection_id` | The connection id acquired from establishing the connection. | `0xC5_58_7C_09_08_48_D8_37` | `-4226491872051668937` +//! 8 | [`i32`](std::i32) | `action` | Action for announce request. | `0x00_00_00_01` | `1` +//! 12 | [`i32`](std::i32) | `transaction_id` | Randomly generated by the client. | `0xA2_F9_54_48` | `-1560718264` +//! 16 | 20-byte | `info_hash` | The infohash of the torrent being announced. | `0x03_84_05_48_64_3A_F2_A7_B6_3A_9F_5C_BC_A3_48_BC_71_50_CA_3A` | `20071130873666512363095721859061691407221705274` +//! 36 | 20-byte | `peer_id` | The ID of the peer announcing the torrent. | `0x2D_71_42_34_34_31_30_2D_29_53_64_7E_64_65_34_78_4D_70_36_44` | `259430336069436570531165609119312093997849130564` +//! 56 | [`i64`](std::i64) | `downloaded` | The number of bytes the peer has downloaded so far. | `0x00_00_00_00_00_00_00_00` | `0` +//! 64 | [`i64`](std::i64) | `left` | The number of bytes left to download by the peer. | `0x00_00_00_00_00_00_00_00` | `0` +//! 72 | [`i64`](std::i64) | `uploaded` | The number of bytes the peer has uploaded so far. | `0x00_00_00_00_00_00_00_00` | `0` +//! 80 | [`i32`](std::i32) | `event` | The event the peer is reporting to the tracker. | `0x0`, `0x1`, `0x2`, `0x3` | `0`: none; `1`: completed; `2`: started; `3`: stopped +//! 84 | [`i32`](std::i32) | `IP address` | The peer IP. Ignored by the tracker. It uses the Sender's IP.| `0x00_00_00_00` | `0` +//! 88 | [`i32`](std::i32) | `key` | A unique key that is randomized by the client. | `0xEF_34_95_D6` | `-281766442` +//! 92 | [`i32`](std::i32) | `num_want` | The maximum number of peers the peer wants in the response. | `0x00_00_00_C8` | `200` +//! 96 | [`i16`](std::i16) | `port` | The port the peer is listening on. | `0x44_8C` | `17548` +//! +//! **Peer IP address** +//! +//! The peer IP address is always ignored by the tracker. It uses the Sender's +//! IP address. +//! +//! _"Do note that most trackers will only honor the IP address field under +//! limited circumstances."_ ([BEP 15](https://www.bittorrent.org/beps/bep_0015.html)). +//! +//! Although not supporter by this tracker a UDP tracker can use the IP address +//! provided by the peer in the announce request under specific circumstances +//! when it cannot rely on the source IP address of the incoming request. These +//! circumstances might include: +//! +//! 1. Network Address Translation (NAT): In cases where a peer is behind a NAT, +//! the private IP address of the peer is not directly routable over the +//! internet. The NAT device translates the private IP address to a public one +//! when sending packets to the tracker. The public IP address is what the +//! tracker sees as the source IP of the incoming request. However, if the peer +//! provides its private IP address in the announce request, the tracker can use +//! this information to facilitate communication between peers in the same +//! private network. +//! +//! 2. Proxy or VPN usage: If a peer uses a proxy or VPN service to connect to +//! the tracker, the source IP address seen by the tracker will be the one +//! assigned by the proxy or VPN server. In this case, if the peer provides its +//! actual IP address in the announce request, the tracker can use it to +//! establish a direct connection with other peers, bypassing the proxy or VPN +//! server. This might improve performance or help in cases where some peers +//! cannot connect to the proxy or VPN server. +//! +//! 3. Tracker is behind a NAT, firewall, proxy, VPN, or load balancer: In cases +//! where the tracker is behind a NAT, firewall, proxy, VPN, or load balancer, +//! the source IP address of the incoming request will be the public IP address +//! of the NAT, firewall, proxy, VPN, or load balancer. If the peer provides its +//! private IP address in the announce request, the tracker can use this +//! information to establish a direct connection with the peer. +//! +//! It's important to note that using the provided IP address can pose security +//! risks, as malicious peers might spoof their IP addresses in the announce +//! request to perform various types of attacks. +//! +//! > **NOTICE**: current tracker behavior is to ignore the IP address provided +//! by the peer, and use the source IP address of the incoming request, when the +//! tracker is not running behind a proxy, and to use the right-most IP address +//! in the `X-Forwarded-For` header when the tracker is running behind a proxy. +//! +//! > **NOTICE**: the tracker also changes the peer IP address to the tracker +//! external IP when the peer is using a loopback IP address. +//! +//! **Sample announce request (UDP packet)** +//! +//! - Infohash: `0x03840548643AF2A7B63A9F5CBCA348BC7150CA3A` +//! - Peer ID: `0x2D7142343431302D2953647E646534784D703644` +//! +//! UDP packet bytes: +//! +//! ```text +//! Offset: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100] +//! Decimal: [ 197, 88, 124, 9, 8, 72, 216, 55, 0, 0, 0, 1, 162, 249, 84, 72, 3, 132, 5, 72, 100, 58, 242, 167, 182, 58, 159, 92, 188, 163, 72, 188, 113, 80, 202, 58, 45, 113, 66, 52, 52, 49, 48, 45, 41, 83, 100, 126, 100, 101, 52, 120, 77, 112, 54, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 239, 52, 149, 214, 0, 0, 0, 200, 68, 140, 2, 1, 47] +//! Hex: [ 0xC5, 0x58, 0x7C, 0x09, 0x08, 0x48, 0xD8, 0x37, 0x00, 0x00, 0x00, 0x01, 0xA2, 0xF9, 0x54, 0x48, 0x03, 0x84, 0x05, 0x48, 0x64, 0x3A, 0xF2, 0xA7, 0xB6, 0x3A, 0x9F, 0x5C, 0xBC, 0xA3, 0x48, 0xBC, 0x71, 0x50, 0xCA, 0x3A, 0x2D, 0x71, 0x42, 0x34, 0x34, 0x31, 0x30, 0x2D, 0x29, 0x53, 0x64, 0x7E, 0x64, 0x65, 0x34, 0x78, 0x4D, 0x70, 0x36, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xEF, 0x34, 0x95, 0xD6, 0x00, 0x00, 0x00, 0xC8, 0x44, 0x8C, 0x02, 0x01, 0x2F] +//! Param: [<--------------- connection_id --------------->,<--------- action ---->,<-- transaction_id --->,<--------------------------------------------------------- info_hash ------------------------------------------------->,<---------------------------------------------- peer_id -------------------------------------------------------------->,<------------------- downloaded -------------->,<-------------------- left ------------------->,<---------------- uploaded ------------------->,<-------- event ------>,<----- IP address ---->,<--------- key ------->,<------ num_want ----->,<-- port --><---- BEP 41 --->] +//! ``` +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes Dec (Big Endian) | Hex | Decimal +//! -------|-------------------|-------------------|--------------------------------------------------------------------------|-----------------------------------------------------------------|---------------------------------------------------- +//! 0 | [`i64`](std::i64) | `connection_id` | `[197,88,124,9,8,72,216,55]` | `0xC5_58_7C_09_08_48_D8_37` | `-4226491872051668937` +//! 8 | [`i32`](std::i32) | `action` | `[0,0,0,1]` | `0x00_00_00_01` | `1` +//! 12 | [`i32`](std::i32) | `transaction_id` | `[162,249,84,72]` | `0xA2_F9_54_48` | `-1560718264` +//! 16 | 20 bytes | `info_hash` | `[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]` | `0x03_84_05_48_64_3A_F2_A7_B6_3A_9F_5C_BC_A3_48_BC_71_50_CA_3A` | `20071130873666512363095721859061691407221705274` +//! 36 | 20 bytes | `peer_id` | `[45,113,66,52,52,49,48,45,41,83,100,126,100,101,52,120,77,112,54,68]` | `0x2D_71_42_34_34_31_30_2D_29_53_64_7E_64_65_34_78_4D_70_36_44` | `259430336069436570531165609119312093997849130564` +//! 56 | [`i64`](std::i64) | `downloaded` | `[0,0,0,0,0,0,0,0]` | `0x00_00_00_00_00_00_00_00` | `0` +//! 64 | [`i64`](std::i64) | `left` | `[0,0,0,0,0,0,0,0]` | `0x00_00_00_00_00_00_00_00` | `0` +//! 72 | [`i64`](std::i64) | `uploaded` | `[0,0,0,0,0,0,0,0]` | `0x00_00_00_00_00_00_00_00` | `0` +//! 80 | [`i32`](std::i32) | `event` | `[0,0,0,2]` | `0x00_00_00_02` | `2` (`Started`) +//! 84 | [`i32`](std::i32) | `IP address` | `[0,0,0,0]` | `0x00_00_00_00` | `0` +//! 88 | [`i32`](std::i32) | `key` | `[239,52,149,214]` | `0xEF_34_95_D6` | `-281766442` +//! 92 | [`i32`](std::i32) | `num_want` | `[0,0,0,200]` | `0x00_00_00_C8` | `200` +//! 96 | [`i16`](std::i16) | `port` | `[8,140]` | `0x44_8C` | `17548` +//! 98 | 1 byte | `Option-Type` | `[2]` | `0x02` | `2` +//! 99 | 2 byte | `Length Byte` | `[1,47]` | `0x01_2F` | `303` +//! 101 | N bytes | | | | +//! +//! > **NOTICE**: bytes after offset 98 are part of the [BEP-41. UDP Tracker Protocol Extensions](https://www.bittorrent.org/beps/bep_0041.html). +//! There are three options defined for byte 98: `0x0` (`EndOfOptions`), `0x1` (`NOP`) and `0x2` (`URLData`). +//! +//! **Announce request (parsed struct)** +//! +//! After parsing the UDP packet, the [`AnnounceRequest`](aquatic_udp_protocol::request::AnnounceRequest) +//! struct will contain the following fields: +//! +//! Field | Type | Example +//! -------------------|---------------------------------------------------------------- |-------------- +//! `connection_id` | [`ConnectionId`](aquatic_udp_protocol::common::ConnectionId) | `-4226491872051668937` +//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` +//! `info_hash` | [`InfoHash`](aquatic_udp_protocol::common::InfoHash) | `[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]` +//! `peer_id` | [`PeerId`](aquatic_udp_protocol::common::PeerId) | `[45,113,66,52,52,49,48,45,41,83,100,126,100,101,52,120,77,112,54,68]` +//! `bytes_downloaded` | [`NumberOfBytes`](aquatic_udp_protocol::common::NumberOfBytes) | `0` +//! `bytes_uploaded` | [`TransactionId`](aquatic_udp_protocol::common::NumberOfBytes) | `0` +//! `event` | [`AnnounceEvent`](aquatic_udp_protocol::request::AnnounceEvent) | `Started` +//! `ip_address` | [`Ipv4Addr`](aquatic_udp_protocol::common::ConnectionId) | `None` +//! `peers_wanted` | [`NumberOfPeers`](aquatic_udp_protocol::common::NumberOfPeers) | `200` +//! `port` | [`Port`](aquatic_udp_protocol::common::Port) | `17548` +//! +//! > **NOTICE**: the `peers_wanted` field is the `num_want` field in the UDP +//! packet. +//! +//! We are using a wrapper struct for the aquatic [`AnnounceRequest`](aquatic_udp_protocol::request::AnnounceRequest) +//! struct, because we have our internal [`InfoHash`](crate::shared::bit_torrent::info_hash::InfoHash) +//! struct. +//! +//! ```text +//! pub struct AnnounceWrapper { +//! pub announce_request: AnnounceRequest, // aquatic +//! pub info_hash: InfoHash, // our own +//! } +//! ``` +//! +//! **Announce response (UDP packet)** +//! +//! Offset | Type/Size | Name | Description | Hex | Decimal +//! -----------|-------------------|------------------|---------------------------------------------------------------------------------|-----------------|---------------------------- +//! 0 | [`i32`](std::i32) | `action` | The action this is a reply to. | `0x00_00_00_01` | `1`: announce; `3`: error +//! 4 | [`i32`](std::i32) | `transaction_id` | Must match the `transaction_id` sent in the announce request. | `0x00_00_00_00` | `0` +//! 8 | [`i32`](std::i32) | `interval` | The number of seconds the peer should wait until re-announcing itself. | `0x00_00_00_00` | `0` +//! 12 | [`i32`](std::i32) | `leechers` | The number of peers in the swarm that has not finished downloading. | `0x00_00_00_00` | `0` +//! 16 | [`i32`](std::i32) | `seeders` | The number of peers in the swarm that has finished downloading and are seeding. | `0x00_00_00_00` | `0` +//! | | | | | +//! 20 + 6 * n | [`i32`](std::i32) | `IP address` | The IP of a peer in the swarm. | `0x69_69_69_69` | `1768515945` +//! 24 + 6 * n | [`i16`](std::i16) | `TCP port` | The peer's listen port. | `0x44_8C` | `17548` +//! 20 + 6 * N | | | | | +//! +//! > **NOTICE**: `Hex` column is a signed 2's complement. +//! +//! > **NOTICE**: `IP address` should always be set to 0 when the peer is using +//! `IPv6`. +//! +//! **Sample announce response (UDP packet)** +//! +//! UDP packet bytes (fixed part): +//! +//! ```text +//! Offset: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] +//! Decimal: [ 0, 0, 0, 1, 162, 249, 84, 72, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 1] +//! Hex: [ 0x00, 0x00, 0x00, 0x01, 0xA2, 0xF9, 0x54, 0x48, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01] +//! Param: [<------- action ------>,<-- transaction_id --->,<----- interval ------>,<----- leechers ------>,<------ seeders ------>] +//! ``` +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes (Big Endian) | Hex | Decimal +//! -----------|-------------------|------------------|---------------------|-----------------|---------------------------- +//! 0 | [`i32`](std::i32) | `action` | `[0, 0, 0, 0]` | `0x00_00_00_01` | `1`: announce; `3`: error +//! 4 | [`i32`](std::i32) | `transaction_id` | `[162,249,84,72]` | `0xA2_F9_54_48` | `-1560718264` +//! 8 | [`i32`](std::i32) | `interval` | `[0,0,0,120]` | `0x00_00_00_78` | `120` +//! 12 | [`i32`](std::i32) | `leechers` | `[0, 0, 0, 0]` | `0x00_00_00_00` | `0` +//! 16 | [`i32`](std::i32) | `seeders` | `[0, 0, 0, 1]` | `0x00_00_00_01` | `1` +//! +//! This is the fixed part of the packet. After the fixed part there is +//! dynamically generated data with the list of peers in the swarm. The list may +//! include `IPv4` or `IPv6` peers, depending on the address family of the +//! underlying UDP packet. I.e. packets from a v4 address use the v4 format, +//! those from a v6 address use the v6 format. +//! +//! UDP packet bytes (`IPv4` peer list): +//! +//! ```text +//! Offset: [ 20, 21, 22, 23, 24, 25] +//! Decimal: [ 105, 105, 105, 105, 08, 140] +//! Hex: [ 0x69, 0x69, 0x69, 0x69, 0x44, 0x8C] +//! Param: [<----- IP address ---->,<-TCP port>] +//! ``` +//! +//! > **NOTICE**: there are 6 bytes per peer (4 bytes for the `IPv4` address and +//! 2 bytes for the TCP port). +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes (Big Endian) | Hex | Decimal +//! ---------|-------------------|--------------|---------------------|-----------------|---------------------------- +//! 20 + 6*n | [`i32`](std::i32) | `IP address` | `[105,105,105,105]` | `0x69_69_69_69` | `1768515945` +//! 24 + 6*n | [`i16`](std::i16) | `TCP port` | `[8,140]` | `0x44_8C` | `17548` +//! 20 + 6*N | | | | | +//! +//! UDP packet bytes (`IPv6` peer list): +//! +//! ```text +//! Offset: [ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37] +//! Decimal: [ 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 08, 140] +//! Hex: [ 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x44, 0x8C] +//! Param: [<-------------------------------------------- IP address ------------------------------------->,<-TCP port>] +//! ``` +//! +//! > **NOTICE**: there are 18 bytes per peer (16 bytes for the `IPv6` address and +//! 2 bytes for the TCP port). +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes (Big Endian) | Hex | Decimal +//! ----------|---------------------|--------------|---------------------------------------------------------------------|-----------------------------------------------------|------------------------------------------- +//! 20 + 18*n | [`i128`](std::i128) | `IP address` | `[105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105]` | `0x69_69_69_69_69_69_69_69_69_69_69_69_69_69_69_69` | `140116268732151132014330720707198675305` +//! 24 + 18*n | [`i16`](std::i16) | `TCP port` | `[8,140]` | `0x44_8C` | `17548` +//! 20 + 18*N | | | | | +//! +//! > **NOTICE**: `Hex` column is a signed 2's complement. +//! +//! > **NOTICE**: the peer list does not include the peer that sent the announce +//! request. +//! +//! **Announce response (struct)** +//! +//! The [`AnnounceResponse`](aquatic_udp_protocol::response::AnnounceResponse) +//! struct will have the following fields: +//! +//! Field | Type | Example +//! --------------------|------------------------------------------------------------------------|-------------- +//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` +//! `announce_interval` | [`AnnounceInterval`](aquatic_udp_protocol::common::AnnounceInterval) | `120` +//! `leechers` | [`NumberOfPeers`](aquatic_udp_protocol::common::NumberOfPeers) | `0` +//! `seeders` | [`NumberOfPeers`](aquatic_udp_protocol::common::NumberOfPeers) | `1` +//! `peers` | Vector of [`ResponsePeer`](aquatic_udp_protocol::common::ResponsePeer) | `[]` +//! +//! **Announce specification** +//! +//! Original specification in [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html). +//! +//! ### Scrape +//! +//! The `scrape` request allows a peer to get [swarm metadata](crate::tracker::torrent::SwarmMetadata) +//! for multiple torrents at the same time. +//! +//! The response contains the [swarm metadata](crate::tracker::torrent::SwarmMetadata) +//! for that torrent: +//! +//! - [complete](crate::tracker::torrent::SwarmMetadata::complete) +//! - [downloaded](crate::tracker::torrent::SwarmMetadata::downloaded) +//! - [incomplete](crate::tracker::torrent::SwarmMetadata::incomplete) +//! +//! > **NOTICE**: up to about 74 torrents can be scraped at once. A full scrape +//! can't be done with this protocol. This is a limitation of the UDP protocol. +//! Defined with a hardcoded const [`MAX_SCRAPE_TORRENTS`](crate::shared::bit_torrent::common::MAX_SCRAPE_TORRENTS). +//! Refer to [issue 262](https://github.com/torrust/torrust-tracker/issues/262) +//! for more information about this limitation. +//! +//! **Scrape request (UDP packet)** +//! +//! Offset | Type/Size | Name | Description | Hex | Decimal +//! ----------|-------------------|------------------|------------------------------------------------------------------------|-----------------------------------------------------------------|-------------------------------------------------- +//! 0 | [`i64`](std::i64) | `connection_id` | The `connection_id` retrieved from the establishing of the connection. | `0xC5_58_7C_09_08_48_D8_37` | `-4226491872051668937` +//! 8 | [`i32`](std::i32) | `action` | Action identifying the scrape request | `0x00_00_00_02` | `2` (`Scrape`) +//! 12 | [`i32`](std::i32) | `transaction_id` | Randomly generated by the client. | `0xA2_F9_54_48` | `-1560718264` +//! 16 + 20*n | 20 bytes | `info_hash` | The infohash of the torrent being scraped. | `0x03_84_05_48_64_3A_F2_A7_B6_3A_9F_5C_BC_A3_48_BC_71_50_CA_3A` | `20071130873666512363095721859061691407221705274` +//! 16 + 20*N | | | | +//! +//! Dynamic part of the UDP packet: +//! +//! Offset | Type/Size | Name | Description | Hex | Decimal +//! ----------|-------------------|-------------|--------------------------------------------|-----------------------------------------------------------------|--------------------------------------------------- +//! 16 + 20*n | 20 bytes | `info_hash` | The infohash of the torrent being scraped. | `0x03_84_05_48_64_3A_F2_A7_B6_3A_9F_5C_BC_A3_48_BC_71_50_CA_3A` | `20071130873666512363095721859061691407221705274` +//! +//! The last field (`info_hash`) is repeated for each torrent being scraped. +//! +//! **Sample scrape request (UDP packet)** +//! +//! UDP packet bytes (fixed part): +//! +//! ```text +//! Offset: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35] +//! Decimal: [ 197, 88, 124, 9, 8, 72, 216, 55, 0, 0, 0, 2, 162, 249, 84, 72, 3, 132, 5, 72, 100, 58, 242, 167, 182, 58, 159, 92, 188, 163, 72, 188, 113, 80, 202, 58] +//! Hex: [ 0xC5, 0x58, 0x7C, 0x09, 0x08, 0x48, 0xD8, 0x37, 0x00, 0x00, 0x00, 0x02, 0xA2, 0xF9, 0x54, 0x48, 0x03, 0x84, 0x05, 0x48, 0x64, 0x3A, 0xF2, 0xA7, 0xB6, 0x3A, 0x9F, 0x5C, 0xBC, 0xA3, 0x48, 0xBC, 0x71, 0x50, 0xCA, 0x3A] +//! Param: [<--------------- connection_id --------------->,<--------- action ---->,<-- transaction_id --->,<--------------------------------------------------------- info_hash ------------------------------------------------->] +//! ``` +//! +//! UDP packet bytes (infohash list): +//! +//! ```text +//! Offset: [ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35] +//! Decimal: [ 3, 132, 5, 72, 100, 58, 242, 167, 182, 58, 159, 92, 188, 163, 72, 188, 113, 80, 202, 58] +//! Hex: [ 0x03, 0x84, 0x05, 0x48, 0x64, 0x3A, 0xF2, 0xA7, 0xB6, 0x3A, 0x9F, 0x5C, 0xBC, 0xA3, 0x48, 0xBC, 0x71, 0x50, 0xCA, 0x3A] +//! Param: [<--------------------------------------------------------- info_hash ------------------------------------------------->] +//! ``` +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes Dec (Big Endian) | Hex | Decimal +//! -------|-------------------|------------------|--------------------------------------------------------------------------|-----------------------------------------------------------------|-------------------------------------------------- +//! 0 | [`i64`](std::i64) | `connection_id` | `[197,88,124,9,8,72,216,55]` | `0xC5_58_7C_09_08_48_D8_37` | `-4226491872051668937` +//! 4 | [`i32`](std::i32) | `action` | `[0, 0, 0, 2]` | `0x00_00_00_02` | `2` (`Scrape`) +//! 8 | [`i32`](std::i32) | `transaction_id` | `[162,249,84,72]` | `0xA2_F9_54_48` | `-1560718264` +//! 8 | 20 bytes | `info_hash` | `[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]` | `0x03_84_05_48_64_3A_F2_A7_B6_3A_9F_5C_BC_A3_48_BC_71_50_CA_3A` | `20071130873666512363095721859061691407221705274` +//! +//! **Scrape request (parsed struct)** +//! +//! After parsing the UDP packet, the [`ScrapeRequest`](aquatic_udp_protocol::request::ScrapeRequest) +//! struct will look like this: +//! +//! Field | Type | Example +//! -----------------|----------------------------------------------------------------|---------------------------------------------------------------------------- +//! `connection_id` | [`ConnectionId`](aquatic_udp_protocol::common::ConnectionId) | `-4226491872051668937` +//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` +//! `info_hashes` | Vector of [`InfoHash`](aquatic_udp_protocol::common::InfoHash) | `[[3,132,5,72,100,58,242,167,182,58,159,92,188,163,72,188,113,80,202,58]]` +//! +//! **Scrape response (UDP packet)** +//! +//! Offset | Type/Size | Name (BEP15 or libtorrent) | Description | Hex | Decimal +//! ----------|-------------------|-----------------------------|-------------------------------------------------------|-----------------|----------------- +//! 0 | [`i32`](std::i32) | `action` | Action identifying the connect request | `0x00_00_00_00` | `2` (`Scrape`) +//! 4 | [`i32`](std::i32) | `transaction_id` | Must match the `transaction_id` sent from the client. | `0xA2_F9_54_48` | `-1560718264` +//! 8 + 12*n | [`i32`](std::i32) | `seeders` or `complete` | The current number of connected seeds. | `0x00_00_00_00` | `0` +//! 12 + 12*n | [`i32`](std::i32) | `completed` or `downloaded` | The number of times this torrent has been downloaded. | `0x00_00_00_00` | `0` +//! 16 + 12*n | [`i32`](std::i32) | `leechers` or `incomplete` | The current number of connected leechers. | `0x00_00_00_00` | `0` +//! 8 + 12*N | | | | | +//! +//! > **NOTICE**: `Hex` column is a signed 2's complement. +//! +//! Dynamic part of the UDP packet: +//! +//! Offset | Type/Size | Name (BEP15 or libtorrent) | Description | Hex | Decimal +//! ----------|-------------------|-----------------------------|-------------------------------------------------------|-----------------|----------------- +//! 8 + 12*n | [`i32`](std::i32) | `seeders` or `complete` | The current number of connected seeds. | `0x00_00_00_00` | `0` +//! 12 + 12*n | [`i32`](std::i32) | `completed` or `downloaded` | The number of times this torrent has been downloaded. | `0x00_00_00_00` | `0` +//! 16 + 12*n | [`i32`](std::i32) | `leechers` or `incomplete` | The current number of connected leechers. | `0x00_00_00_00` | `0` +//! 8 + 12*N | | | | | +//! +//! For each info hash in the request there will be 3 32-bit integers (12 bytes) +//! in the response with the number of seeders, leechers and downloads. +//! +//! **Sample scrape response (UDP packet)** +//! +//! UDP packet bytes: +//! +//! ```text +//! Offset: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] +//! Decimal: [ 0, 0, 0, 0, 203, 5, 94, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +//! Hex: [0x00, 0x00, 0x00, 0x00, 0xCB, 0x05, 0x5E, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +//! Param: [<------ action ------>,<-- transaction_id --->,<------ seeders ------>,<----- completed ----->,<------ leechers ----->] +//! ``` +//! +//! UDP packet fields: +//! +//! Offset | Type/Size | Name | Bytes (Big Endian) | Hex | Decimal +//! -------|-------------------|------------------|--------------------|------------------|---------------- +//! 0 | [`i32`](std::i32) | `action` | [0, 0, 0, 2] | `0x00_00_00_02` | `2` (`Scrape`) +//! 4 | [`i32`](std::i32) | `transaction_id` | [203, 5, 94, 7] | `0xA2_F9_54_48` | `-1560718264` +//! 8 | [`i32`](std::i32) | `seeders` | [0, 0, 0, 0] | `0x00_00_00_00` | `0` +//! 12 | [`i32`](std::i32) | `completed` | [0, 0, 0, 0] | `0x00_00_00_00` | `0` +//! 16 | [`i32`](std::i32) | `leechers` | [0, 0, 0, 0] | `0x00_00_00_00` | `0` +//! +//! > **NOTICE**: `Hex` column is a signed 2's complement. +//! +//! **Scrape response (struct)** +//! +//! Before building the UDP packet, the [`ScrapeResponse`](aquatic_udp_protocol::response::ScrapeResponse) +//! struct will look like this: +//! +//! Field | Type | Example +//! -----------------|-------------------------------------------------------------------------------------------------|--------------- +//! `transaction_id` | [`TransactionId`](aquatic_udp_protocol::common::TransactionId) | `-1560718264` +//! `torrent_stats` | Vector of [`TorrentScrapeStatistics`](aquatic_udp_protocol::response::TorrentScrapeStatistics) | `[]` +//! +//! **Scrape specification** +//! +//! Original specification in [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html). +//! +//! ## Links +//! +//! - [BEP 15. UDP Tracker Protocol for `BitTorrent`](https://www.bittorrent.org/beps/bep_0015.html). +//! - [BEP 41. UDP Tracker Protocol Extensions](https://www.bittorrent.org/beps/bep_0041.html). +//! - [libtorrent - Bittorrent UDP-tracker protocol extension](https://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html). +//! - [XBTT Tracker. UDP tracker protocol](https://xbtt.sourceforge.net/udp_tracker_protocol.html). +//! - [Wikipedia: UDP tracker](https://en.wikipedia.org/wiki/UDP_tracker). +//! +//! ## Credits +//! +//! [Bittorrent UDP-tracker protocol extension](https://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html) +//! documentation by [Arvid Norberg](https://github.com/arvidn) was very +//! supportive in the development of this documentation. Some descriptions were +//! taken from the [libtorrent](https://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html). pub mod connection_cookie; pub mod error; pub mod handlers; diff --git a/tests/servers/api/v1/asserts.rs b/tests/servers/api/v1/asserts.rs index 1b1f204a2..955293db1 100644 --- a/tests/servers/api/v1/asserts.rs +++ b/tests/servers/api/v1/asserts.rs @@ -134,6 +134,6 @@ async fn assert_unhandled_rejection(response: Response, reason: &str) { let response_text = response.text().await.unwrap(); assert!( response_text.contains(&reason_text), - ":\n response: `\"{response_text}\"`\n dose not contain: `\"{reason_text}\"`." + ":\n response: `\"{response_text}\"`\n does not contain: `\"{reason_text}\"`." ); }