Skip to content

Commit

Permalink
feat(http): [torrust#184] normal (non-compact) announce response in a…
Browse files Browse the repository at this point in the history
…xum tracker

Implemeneted the normal (non-compact) announce response in the new Axum implementation for
the HTTP tracker. Only for the tracker public mode and with only the
mandatory announce request params.
  • Loading branch information
josecelano committed Feb 14, 2023
1 parent 42bd313 commit e85d115
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 52 deletions.
67 changes: 41 additions & 26 deletions src/http/axum_implementation/handlers/announce.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,56 @@
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;

use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
use axum::extract::State;
use axum::response::Json;
use axum_client_ip::{InsecureClientIp, SecureClientIp};
use log::debug;
use axum::response::{IntoResponse, Response};
use axum_client_ip::SecureClientIp;

use crate::http::axum_implementation::requests::announce::ExtractAnnounceRequest;
use crate::http::axum_implementation::resources::ok::Ok;
use crate::http::axum_implementation::responses::ok;
use crate::tracker::Tracker;
use crate::http::axum_implementation::requests::announce::{Announce, ExtractAnnounceRequest};
use crate::http::axum_implementation::responses;
use crate::protocol::clock::{Current, Time};
use crate::tracker::peer::Peer;
use crate::tracker::{statistics, Tracker};

/// WIP
#[allow(clippy::unused_async)]
pub async fn handle(
State(_tracker): State<Arc<Tracker>>,
State(tracker): State<Arc<Tracker>>,
ExtractAnnounceRequest(announce_request): ExtractAnnounceRequest,
insecure_ip: InsecureClientIp,
secure_ip: SecureClientIp,
) -> Json<Ok> {
/* todo:
- Extract remote client ip from request
- Build the `Peer`
- Call the `tracker.announce` method
- Send event for stats
- Move response from Warp to shared mod
- Send response
*/

// Sample announce URL used for debugging:
// http://0.0.0.0:7070/announce?info_hash=%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0&peer_id=-qB00000000000000001&port=17548
) -> Response {
// todo: compact response and optional params

let info_hash = announce_request.info_hash;
let remote_client_ip = secure_ip.0;

debug!("http announce request: {:#?}", announce_request);
debug!("info_hash: {:#?}", &info_hash);
debug!("remote client ip, insecure_ip: {:#?}", &insecure_ip);
debug!("remote client ip, secure_ip: {:#?}", &secure_ip);
let mut peer = peer_from_request(&announce_request, &remote_client_ip);

ok::response(&insecure_ip.0, &secure_ip.0)
let response = tracker.announce(&info_hash, &mut peer, &remote_client_ip).await;

match remote_client_ip {
IpAddr::V4(_) => {
tracker.send_stats_event(statistics::Event::Tcp4Announce).await;
}
IpAddr::V6(_) => {
tracker.send_stats_event(statistics::Event::Tcp6Announce).await;
}
}

responses::announce::Announce::from(response).into_response()
}

#[must_use]
fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> Peer {
#[allow(clippy::cast_possible_truncation)]
Peer {
peer_id: announce_request.peer_id,
peer_addr: SocketAddr::new(*peer_ip, announce_request.port),
updated: Current::now(),
// todo: optional parameters not included in the announce request yet
uploaded: NumberOfBytes(i128::from(0) as i64),
downloaded: NumberOfBytes(i128::from(0) as i64),
left: NumberOfBytes(i128::from(0) as i64),
event: AnnounceEvent::None,
}
}
91 changes: 91 additions & 0 deletions src/http/axum_implementation/responses/announce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::net::IpAddr;

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::{self, Deserialize, Serialize};

use crate::tracker::{self, AnnounceResponse};

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Announce {
pub interval: u32,
#[serde(rename = "min interval")]
pub interval_min: u32,
pub complete: u32,
pub incomplete: u32,
pub peers: Vec<Peer>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Peer {
pub peer_id: String,
pub ip: IpAddr,
pub port: u16,
}

impl From<tracker::peer::Peer> for Peer {
fn from(peer: tracker::peer::Peer) -> Self {
Peer {
peer_id: peer.peer_id.to_string(),
ip: peer.peer_addr.ip(),
port: peer.peer_addr.port(),
}
}
}

impl Announce {
/// # Panics
///
/// It would panic if the `Announce` struct contained an inappropriate type.
#[must_use]
pub fn write(&self) -> String {
serde_bencode::to_string(&self).unwrap()
}
}

impl IntoResponse for Announce {
fn into_response(self) -> Response {
(StatusCode::OK, self.write()).into_response()
}
}

impl From<AnnounceResponse> for Announce {
fn from(domain_announce_response: AnnounceResponse) -> Self {
let peers: Vec<Peer> = domain_announce_response.peers.iter().map(|peer| Peer::from(*peer)).collect();

Self {
interval: domain_announce_response.interval,
interval_min: domain_announce_response.interval_min,
complete: domain_announce_response.swam_stats.seeders,
incomplete: domain_announce_response.swam_stats.leechers,
peers,
}
}
}

#[cfg(test)]
mod tests {

use std::net::IpAddr;
use std::str::FromStr;

use super::{Announce, Peer};

#[test]
fn announce_response_can_be_bencoded() {
let response = Announce {
interval: 1,
interval_min: 2,
complete: 3,
incomplete: 4,
peers: vec![Peer {
peer_id: "-qB00000000000000001".to_string(),
ip: IpAddr::from_str("127.0.0.1").unwrap(),
port: 8080,
}],
};

// cspell:disable-next-line
assert_eq!(response.write(), "d8:completei3e10:incompletei4e8:intervali1e12:min intervali2e5:peersld2:ip9:127.0.0.17:peer_id20:-qB000000000000000014:porti8080eeee");
}
}
1 change: 1 addition & 0 deletions src/http/axum_implementation/responses/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod error;
pub mod ok;
pub mod announce;
9 changes: 8 additions & 1 deletion src/tracker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub struct TorrentsMetrics {
pub struct AnnounceResponse {
pub peers: Vec<Peer>,
pub swam_stats: SwamStats,
pub interval: u32,
pub interval_min: u32,
}

impl Tracker {
Expand Down Expand Up @@ -92,7 +94,12 @@ impl Tracker {
// todo: remove peer by using its `Id` instead of its socket address: `get_peers_excluding_peer(peer_id: peer::Id)`
let peers = self.get_peers_excluding_peers_with_address(info_hash, &peer.peer_addr).await;

AnnounceResponse { peers, swam_stats }
AnnounceResponse {
peers,
swam_stats,
interval: self.config.announce_interval,
interval_min: self.config.min_announce_interval,
}
}

/// # Errors
Expand Down
38 changes: 13 additions & 25 deletions tests/http_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1470,8 +1470,7 @@ mod axum_http_tracker_server {
start_ipv6_http_tracker, start_public_http_tracker,
};

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_respond_if_only_the_mandatory_fields_are_provided() {
let http_tracker_server = start_default_http_tracker(Version::Axum).await;

Expand Down Expand Up @@ -1742,8 +1741,7 @@ mod axum_http_tracker_server {
}
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_return_no_peers_if_the_announced_peer_is_the_first_one() {
let http_tracker_server = start_public_http_tracker(Version::Axum).await;

Expand All @@ -1768,8 +1766,7 @@ mod axum_http_tracker_server {
.await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_return_the_list_of_previously_announced_peers() {
let http_tracker_server = start_public_http_tracker(Version::Axum).await;

Expand All @@ -1793,7 +1790,7 @@ mod axum_http_tracker_server {
)
.await;

// It should only contain teh previously announced peer
// It should only contain the previously announced peer
assert_announce_response(
response,
&Announce {
Expand All @@ -1807,8 +1804,7 @@ mod axum_http_tracker_server {
.await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_consider_two_peers_to_be_the_same_when_they_have_the_same_peer_id_even_if_the_ip_is_different() {
let http_tracker_server = start_public_http_tracker(Version::Axum).await;

Expand Down Expand Up @@ -1872,8 +1868,7 @@ mod axum_http_tracker_server {
assert_compact_announce_response(response, &expected_response).await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_not_return_the_compact_response_by_default() {
// code-review: the HTTP tracker does not return the compact response by default if the "compact"
// param is not provided in the announce URL. The BEP 23 suggest to do so.
Expand Down Expand Up @@ -1912,8 +1907,7 @@ mod axum_http_tracker_server {
compact_announce.is_ok()
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_increase_the_number_of_tcp4_connections_handled_in_statistics() {
let http_tracker_server = start_public_http_tracker(Version::Axum).await;

Expand All @@ -1926,8 +1920,7 @@ mod axum_http_tracker_server {
assert_eq!(stats.tcp4_connections_handled, 1);
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_increase_the_number_of_tcp6_connections_handled_in_statistics() {
let http_tracker_server = start_ipv6_http_tracker(Version::Axum).await;

Expand Down Expand Up @@ -1960,8 +1953,7 @@ mod axum_http_tracker_server {
assert_eq!(stats.tcp6_connections_handled, 0);
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_increase_the_number_of_tcp4_announce_requests_handled_in_statistics() {
let http_tracker_server = start_public_http_tracker(Version::Axum).await;

Expand All @@ -1974,8 +1966,7 @@ mod axum_http_tracker_server {
assert_eq!(stats.tcp4_announces_handled, 1);
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_increase_the_number_of_tcp6_announce_requests_handled_in_statistics() {
let http_tracker_server = start_ipv6_http_tracker(Version::Axum).await;

Expand Down Expand Up @@ -2032,8 +2023,7 @@ mod axum_http_tracker_server {
assert_ne!(peer_addr.ip(), IpAddr::from_str("2.2.2.2").unwrap());
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn when_the_client_ip_is_a_loopback_ipv4_it_should_assign_to_the_peer_ip_the_external_ip_in_the_tracker_configuration(
) {
/* We assume that both the client and tracker share the same public IP.
Expand Down Expand Up @@ -2065,8 +2055,7 @@ mod axum_http_tracker_server {
assert_ne!(peer_addr.ip(), IpAddr::from_str("2.2.2.2").unwrap());
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn when_the_client_ip_is_a_loopback_ipv6_it_should_assign_to_the_peer_ip_the_external_ip_in_the_tracker_configuration(
) {
/* We assume that both the client and tracker share the same public IP.
Expand Down Expand Up @@ -2101,8 +2090,7 @@ mod axum_http_tracker_server {
assert_ne!(peer_addr.ip(), IpAddr::from_str("2.2.2.2").unwrap());
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn when_the_tracker_is_behind_a_reverse_proxy_it_should_assign_to_the_peer_ip_the_ip_in_the_x_forwarded_for_http_header(
) {
/*
Expand Down

0 comments on commit e85d115

Please sign in to comment.