Skip to content

Commit 424e3e5

Browse files
committed
feat(http): [#184] normal (non-compact) announce response in axum tracker
Implemeneted the normal (non-compact) announce response in the new Axum implementation for the HTTP tracker.
1 parent 42bd313 commit 424e3e5

File tree

5 files changed

+154
-52
lines changed

5 files changed

+154
-52
lines changed
Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,56 @@
1+
use std::net::{IpAddr, SocketAddr};
12
use std::sync::Arc;
23

4+
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
35
use axum::extract::State;
4-
use axum::response::Json;
5-
use axum_client_ip::{InsecureClientIp, SecureClientIp};
6-
use log::debug;
6+
use axum::response::{IntoResponse, Response};
7+
use axum_client_ip::SecureClientIp;
78

8-
use crate::http::axum_implementation::requests::announce::ExtractAnnounceRequest;
9-
use crate::http::axum_implementation::resources::ok::Ok;
10-
use crate::http::axum_implementation::responses::ok;
11-
use crate::tracker::Tracker;
9+
use crate::http::axum_implementation::requests::announce::{Announce, ExtractAnnounceRequest};
10+
use crate::http::axum_implementation::responses;
11+
use crate::protocol::clock::{Current, Time};
12+
use crate::tracker::peer::Peer;
13+
use crate::tracker::{statistics, Tracker};
1214

1315
/// WIP
1416
#[allow(clippy::unused_async)]
1517
pub async fn handle(
16-
State(_tracker): State<Arc<Tracker>>,
18+
State(tracker): State<Arc<Tracker>>,
1719
ExtractAnnounceRequest(announce_request): ExtractAnnounceRequest,
18-
insecure_ip: InsecureClientIp,
1920
secure_ip: SecureClientIp,
20-
) -> Json<Ok> {
21-
/* todo:
22-
- Extract remote client ip from request
23-
- Build the `Peer`
24-
- Call the `tracker.announce` method
25-
- Send event for stats
26-
- Move response from Warp to shared mod
27-
- Send response
28-
*/
29-
30-
// Sample announce URL used for debugging:
31-
// 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
21+
) -> Response {
22+
// todo: compact response and optional params
3223

3324
let info_hash = announce_request.info_hash;
25+
let remote_client_ip = secure_ip.0;
3426

35-
debug!("http announce request: {:#?}", announce_request);
36-
debug!("info_hash: {:#?}", &info_hash);
37-
debug!("remote client ip, insecure_ip: {:#?}", &insecure_ip);
38-
debug!("remote client ip, secure_ip: {:#?}", &secure_ip);
27+
let mut peer = peer_from_request(&announce_request, &remote_client_ip);
3928

40-
ok::response(&insecure_ip.0, &secure_ip.0)
29+
let response = tracker.announce(&info_hash, &mut peer, &remote_client_ip).await;
30+
31+
match remote_client_ip {
32+
IpAddr::V4(_) => {
33+
tracker.send_stats_event(statistics::Event::Tcp4Announce).await;
34+
}
35+
IpAddr::V6(_) => {
36+
tracker.send_stats_event(statistics::Event::Tcp6Announce).await;
37+
}
38+
}
39+
40+
responses::announce::Announce::from(response).into_response()
41+
}
42+
43+
#[must_use]
44+
fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> Peer {
45+
#[allow(clippy::cast_possible_truncation)]
46+
Peer {
47+
peer_id: announce_request.peer_id,
48+
peer_addr: SocketAddr::new(*peer_ip, announce_request.port),
49+
updated: Current::now(),
50+
// todo: optional parameters not included in the announce request yet
51+
uploaded: NumberOfBytes(i128::from(0) as i64),
52+
downloaded: NumberOfBytes(i128::from(0) as i64),
53+
left: NumberOfBytes(i128::from(0) as i64),
54+
event: AnnounceEvent::None,
55+
}
4156
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use std::net::IpAddr;
2+
3+
use axum::http::StatusCode;
4+
use axum::response::{IntoResponse, Response};
5+
use serde::{self, Deserialize, Serialize};
6+
7+
use crate::tracker::{self, AnnounceResponse};
8+
9+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
10+
pub struct Announce {
11+
pub interval: u32,
12+
#[serde(rename = "min interval")]
13+
pub interval_min: u32,
14+
pub complete: u32,
15+
pub incomplete: u32,
16+
pub peers: Vec<Peer>,
17+
}
18+
19+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
20+
pub struct Peer {
21+
pub peer_id: String,
22+
pub ip: IpAddr,
23+
pub port: u16,
24+
}
25+
26+
impl From<tracker::peer::Peer> for Peer {
27+
fn from(peer: tracker::peer::Peer) -> Self {
28+
Peer {
29+
peer_id: peer.peer_id.to_string(),
30+
ip: peer.peer_addr.ip(),
31+
port: peer.peer_addr.port(),
32+
}
33+
}
34+
}
35+
36+
impl Announce {
37+
/// # Panics
38+
///
39+
/// It would panic if the `Announce` struct contained an inappropriate type.
40+
#[must_use]
41+
pub fn write(&self) -> String {
42+
serde_bencode::to_string(&self).unwrap()
43+
}
44+
}
45+
46+
impl IntoResponse for Announce {
47+
fn into_response(self) -> Response {
48+
(StatusCode::OK, self.write()).into_response()
49+
}
50+
}
51+
52+
impl From<AnnounceResponse> for Announce {
53+
fn from(domain_announce_response: AnnounceResponse) -> Self {
54+
let peers: Vec<Peer> = domain_announce_response.peers.iter().map(|peer| Peer::from(*peer)).collect();
55+
56+
Self {
57+
interval: domain_announce_response.interval,
58+
interval_min: domain_announce_response.interval_min,
59+
complete: domain_announce_response.swam_stats.seeders,
60+
incomplete: domain_announce_response.swam_stats.leechers,
61+
peers,
62+
}
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
69+
use std::net::IpAddr;
70+
use std::str::FromStr;
71+
72+
use super::{Announce, Peer};
73+
74+
#[test]
75+
fn announce_response_can_be_bencoded() {
76+
let response = Announce {
77+
interval: 1,
78+
interval_min: 2,
79+
complete: 3,
80+
incomplete: 4,
81+
peers: vec![Peer {
82+
peer_id: "-qB00000000000000001".to_string(),
83+
ip: IpAddr::from_str("127.0.0.1").unwrap(),
84+
port: 8080,
85+
}],
86+
};
87+
88+
// cspell:disable-next-line
89+
assert_eq!(response.write(), "d8:completei3e10:incompletei4e8:intervali1e12:min intervali2e5:peersld2:ip9:127.0.0.17:peer_id20:-qB000000000000000014:porti8080eeee");
90+
}
91+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod error;
22
pub mod ok;
3+
pub mod announce;

src/tracker/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub struct TorrentsMetrics {
4646
pub struct AnnounceResponse {
4747
pub peers: Vec<Peer>,
4848
pub swam_stats: SwamStats,
49+
pub interval: u32,
50+
pub interval_min: u32,
4951
}
5052

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

95-
AnnounceResponse { peers, swam_stats }
97+
AnnounceResponse {
98+
peers,
99+
swam_stats,
100+
interval: self.config.announce_interval,
101+
interval_min: self.config.min_announce_interval,
102+
}
96103
}
97104

98105
/// # Errors

tests/http_tracker.rs

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,8 +1470,7 @@ mod axum_http_tracker_server {
14701470
start_ipv6_http_tracker, start_public_http_tracker,
14711471
};
14721472

1473-
//#[tokio::test]
1474-
#[allow(dead_code)]
1473+
#[tokio::test]
14751474
async fn should_respond_if_only_the_mandatory_fields_are_provided() {
14761475
let http_tracker_server = start_default_http_tracker(Version::Axum).await;
14771476

@@ -1742,8 +1741,7 @@ mod axum_http_tracker_server {
17421741
}
17431742
}
17441743

1745-
//#[tokio::test]
1746-
#[allow(dead_code)]
1744+
#[tokio::test]
17471745
async fn should_return_no_peers_if_the_announced_peer_is_the_first_one() {
17481746
let http_tracker_server = start_public_http_tracker(Version::Axum).await;
17491747

@@ -1768,8 +1766,7 @@ mod axum_http_tracker_server {
17681766
.await;
17691767
}
17701768

1771-
//#[tokio::test]
1772-
#[allow(dead_code)]
1769+
#[tokio::test]
17731770
async fn should_return_the_list_of_previously_announced_peers() {
17741771
let http_tracker_server = start_public_http_tracker(Version::Axum).await;
17751772

@@ -1793,7 +1790,7 @@ mod axum_http_tracker_server {
17931790
)
17941791
.await;
17951792

1796-
// It should only contain teh previously announced peer
1793+
// It should only contain the previously announced peer
17971794
assert_announce_response(
17981795
response,
17991796
&Announce {
@@ -1807,8 +1804,7 @@ mod axum_http_tracker_server {
18071804
.await;
18081805
}
18091806

1810-
//#[tokio::test]
1811-
#[allow(dead_code)]
1807+
#[tokio::test]
18121808
async fn should_consider_two_peers_to_be_the_same_when_they_have_the_same_peer_id_even_if_the_ip_is_different() {
18131809
let http_tracker_server = start_public_http_tracker(Version::Axum).await;
18141810

@@ -1872,8 +1868,7 @@ mod axum_http_tracker_server {
18721868
assert_compact_announce_response(response, &expected_response).await;
18731869
}
18741870

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

1915-
//#[tokio::test]
1916-
#[allow(dead_code)]
1910+
#[tokio::test]
19171911
async fn should_increase_the_number_of_tcp4_connections_handled_in_statistics() {
19181912
let http_tracker_server = start_public_http_tracker(Version::Axum).await;
19191913

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

1929-
//#[tokio::test]
1930-
#[allow(dead_code)]
1923+
#[tokio::test]
19311924
async fn should_increase_the_number_of_tcp6_connections_handled_in_statistics() {
19321925
let http_tracker_server = start_ipv6_http_tracker(Version::Axum).await;
19331926

@@ -1960,8 +1953,7 @@ mod axum_http_tracker_server {
19601953
assert_eq!(stats.tcp6_connections_handled, 0);
19611954
}
19621955

1963-
//#[tokio::test]
1964-
#[allow(dead_code)]
1956+
#[tokio::test]
19651957
async fn should_increase_the_number_of_tcp4_announce_requests_handled_in_statistics() {
19661958
let http_tracker_server = start_public_http_tracker(Version::Axum).await;
19671959

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

1977-
//#[tokio::test]
1978-
#[allow(dead_code)]
1969+
#[tokio::test]
19791970
async fn should_increase_the_number_of_tcp6_announce_requests_handled_in_statistics() {
19801971
let http_tracker_server = start_ipv6_http_tracker(Version::Axum).await;
19811972

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

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

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

2104-
//#[tokio::test]
2105-
#[allow(dead_code)]
2093+
#[tokio::test]
21062094
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(
21072095
) {
21082096
/*

0 commit comments

Comments
 (0)