Skip to content

Commit

Permalink
refactor: [#1268] move announce logic from axum to http_tracker_core …
Browse files Browse the repository at this point in the history
…package
  • Loading branch information
josecelano committed Feb 14, 2025
1 parent e48aaf5 commit 74815ab
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 57 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/http-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ percent-encoding = "2"
serde = { version = "1", features = ["derive"] }
serde_bencode = "0"
thiserror = "2"
torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" }
torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" }
torrust-tracker-contrib-bencode = { version = "3.0.0-develop", path = "../../contrib/bencode" }
torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" }
Expand Down
13 changes: 13 additions & 0 deletions packages/http-protocol/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
//! Primitive types and function for `BitTorrent` HTTP trackers.
pub mod percent_encoding;
pub mod v1;

use torrust_tracker_clock::clock;

/// This code needs to be copied into each crate.
/// Working version, for production.
#[cfg(not(test))]
#[allow(dead_code)]
pub(crate) type CurrentClock = clock::Working;

/// Stopped version, for testing.
#[cfg(test)]
#[allow(dead_code)]
pub(crate) type CurrentClock = clock::Stopped;
33 changes: 32 additions & 1 deletion packages/http-protocol/src/v1/requests/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
//!
//! Data structures and logic for parsing the `announce` request.
use std::fmt;
use std::net::{IpAddr, SocketAddr};
use std::panic::Location;
use std::str::FromStr;

use aquatic_udp_protocol::{NumberOfBytes, PeerId};
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
use bittorrent_primitives::info_hash::{self, InfoHash};
use thiserror::Error;
use torrust_tracker_clock::clock::Time;
use torrust_tracker_located_error::{Located, LocatedError};
use torrust_tracker_primitives::peer;

use crate::percent_encoding::{percent_decode_info_hash, percent_decode_peer_id};
use crate::v1::query::{ParseQueryError, Query};
use crate::v1::responses;
use crate::CurrentClock;

// Query param names
const INFO_HASH: &str = "info_hash";
Expand Down Expand Up @@ -373,6 +376,34 @@ fn extract_numwant(query: &Query) -> Result<Option<u32>, ParseAnnounceQueryError
}
}

/// It builds a `Peer` from the announce request.
///
/// It ignores the peer address in the announce request params.
#[must_use]
pub fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> peer::Peer {
peer::Peer {
peer_id: announce_request.peer_id,
peer_addr: SocketAddr::new(*peer_ip, announce_request.port),
updated: CurrentClock::now(),
uploaded: announce_request.uploaded.unwrap_or(NumberOfBytes::new(0)),
downloaded: announce_request.downloaded.unwrap_or(NumberOfBytes::new(0)),
left: announce_request.left.unwrap_or(NumberOfBytes::new(0)),
event: map_to_torrust_event(&announce_request.event),
}
}

#[must_use]
pub fn map_to_torrust_event(event: &Option<Event>) -> AnnounceEvent {
match event {
Some(event) => match &event {
Event::Started => AnnounceEvent::Started,
Event::Stopped => AnnounceEvent::Stopped,
Event::Completed => AnnounceEvent::Completed,
},
None => AnnounceEvent::None,
}
}

#[cfg(test)]
mod tests {

Expand Down
53 changes: 53 additions & 0 deletions src/packages/http_tracker_core/services/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
use std::net::IpAddr;
use std::sync::Arc;

use bittorrent_http_protocol::v1::requests::announce::{peer_from_request, Announce};
use bittorrent_http_protocol::v1::responses;
use bittorrent_http_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources};
use bittorrent_primitives::info_hash::InfoHash;
use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted};
use bittorrent_tracker_core::authentication::service::AuthenticationService;
use bittorrent_tracker_core::whitelist;
use torrust_tracker_configuration::Core;
use torrust_tracker_primitives::core::AnnounceData;
use torrust_tracker_primitives::peer;

Expand All @@ -27,6 +33,53 @@ use crate::packages::http_tracker_core;
/// > **NOTICE**: as the HTTP tracker does not requires a connection request
/// > like the UDP tracker, the number of TCP connections is incremented for
/// > each `announce` request.
///
/// # Errors
///
/// This function will return an error if:
///
/// - The tracker is running in `listed` mode and the torrent is not whitelisted.
/// - There is an error when resolving the client IP address.
#[allow(clippy::too_many_arguments)]
pub async fn handle_announce(
core_config: &Arc<Core>,
announce_handler: &Arc<AnnounceHandler>,
_authentication_service: &Arc<AuthenticationService>,
whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
announce_request: &Announce,
client_ip_sources: &ClientIpSources,
) -> Result<AnnounceData, responses::error::Error> {
// Authorization
match whitelist_authorization.authorize(&announce_request.info_hash).await {
Ok(()) => (),
Err(error) => return Err(responses::error::Error::from(error)),
}

let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) {
Ok(peer_ip) => peer_ip,
Err(error) => return Err(responses::error::Error::from(error)),
};

let mut peer = peer_from_request(announce_request, &peer_ip);

let peers_wanted = match announce_request.numwant {
Some(numwant) => PeersWanted::only(numwant),
None => PeersWanted::AsManyAsPossible,
};

let announce_data = invoke(
announce_handler.clone(),
opt_http_stats_event_sender.clone(),
announce_request.info_hash,
&mut peer,
&peers_wanted,
)
.await;

Ok(announce_data)
}

pub async fn invoke(
announce_handler: Arc<AnnounceHandler>,
opt_http_stats_event_sender: Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
Expand Down
69 changes: 13 additions & 56 deletions src/servers/http/v1/handlers/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,29 @@
//!
//! The handlers perform the authentication and authorization of the request,
//! and resolve the client IP address.
use std::net::{IpAddr, SocketAddr};
use std::panic::Location;
use std::sync::Arc;

use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
use aquatic_udp_protocol::AnnounceEvent;
use axum::extract::State;
use axum::response::{IntoResponse, Response};
use bittorrent_http_protocol::v1::requests::announce::{Announce, Compact, Event};
use bittorrent_http_protocol::v1::responses::{self};
use bittorrent_http_protocol::v1::services::peer_ip_resolver;
use bittorrent_http_protocol::v1::services::peer_ip_resolver::ClientIpSources;
use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted};
use bittorrent_tracker_core::announce_handler::AnnounceHandler;
use bittorrent_tracker_core::authentication::service::AuthenticationService;
use bittorrent_tracker_core::authentication::Key;
use bittorrent_tracker_core::whitelist;
use hyper::StatusCode;
use torrust_tracker_clock::clock::Time;
use torrust_tracker_configuration::Core;
use torrust_tracker_primitives::core::AnnounceData;
use torrust_tracker_primitives::peer;

use super::common::auth::map_auth_error_to_error_response;
use crate::packages::http_tracker_core;
use crate::packages::http_tracker_core::services::{self};
use crate::servers::http::v1::extractors::announce_request::ExtractRequest;
use crate::servers::http::v1::extractors::authentication_key::Extract as ExtractKey;
use crate::servers::http::v1::extractors::client_ip_sources::Extract as ExtractClientIpSources;
use crate::servers::http::v1::handlers::common::auth;
use crate::CurrentClock;

/// It handles the `announce` request when the HTTP tracker does not require
/// authentication (no PATH `key` parameter required).
Expand Down Expand Up @@ -129,12 +123,6 @@ async fn handle(
build_response(announce_request, announce_data)
}

/* code-review: authentication, authorization and peer IP resolution could be moved
from the handler (Axum) layer into the app layer `services::announce::invoke`.
That would make the handler even simpler and the code more reusable and decoupled from Axum.
See https://github.com/torrust/torrust-tracker/discussions/240.
*/

#[allow(clippy::too_many_arguments)]
async fn handle_announce(
core_config: &Arc<Core>,
Expand All @@ -146,6 +134,8 @@ async fn handle_announce(
client_ip_sources: &ClientIpSources,
maybe_key: Option<Key>,
) -> Result<AnnounceData, responses::error::Error> {
// todo: move authentication inside `http_tracker_core::services::announce::handle_announce`

// Authentication
if core_config.private {
match maybe_key {
Expand All @@ -161,33 +151,16 @@ async fn handle_announce(
}
}

// Authorization
match whitelist_authorization.authorize(&announce_request.info_hash).await {
Ok(()) => (),
Err(error) => return Err(responses::error::Error::from(error)),
}

let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) {
Ok(peer_ip) => peer_ip,
Err(error) => return Err(responses::error::Error::from(error)),
};

let mut peer = peer_from_request(announce_request, &peer_ip);
let peers_wanted = match announce_request.numwant {
Some(numwant) => PeersWanted::only(numwant),
None => PeersWanted::AsManyAsPossible,
};

let announce_data = services::announce::invoke(
announce_handler.clone(),
opt_http_stats_event_sender.clone(),
announce_request.info_hash,
&mut peer,
&peers_wanted,
http_tracker_core::services::announce::handle_announce(
&core_config.clone(),
&announce_handler.clone(),
&authentication_service.clone(),
&whitelist_authorization.clone(),
&opt_http_stats_event_sender.clone(),
announce_request,
client_ip_sources,
)
.await;

Ok(announce_data)
.await
}

fn build_response(announce_request: &Announce, announce_data: AnnounceData) -> Response {
Expand All @@ -202,22 +175,6 @@ fn build_response(announce_request: &Announce, announce_data: AnnounceData) -> R
}
}

/// It builds a `Peer` from the announce request.
///
/// It ignores the peer address in the announce request params.
#[must_use]
fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> peer::Peer {
peer::Peer {
peer_id: announce_request.peer_id,
peer_addr: SocketAddr::new(*peer_ip, announce_request.port),
updated: CurrentClock::now(),
uploaded: announce_request.uploaded.unwrap_or(NumberOfBytes::new(0)),
downloaded: announce_request.downloaded.unwrap_or(NumberOfBytes::new(0)),
left: announce_request.left.unwrap_or(NumberOfBytes::new(0)),
event: map_to_torrust_event(&announce_request.event),
}
}

#[must_use]
pub fn map_to_aquatic_event(event: &Option<Event>) -> aquatic_udp_protocol::AnnounceEvent {
match event {
Expand Down

0 comments on commit 74815ab

Please sign in to comment.