Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ea01059

Browse files
committedJan 29, 2024
feat: [torrust#654] UDP tracker client: scrape
```text cargo run --bin udp_tracker_client scrape 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq ``` Scrape response: ```json { "transaction_id": -888840697, "torrent_stats": [ { "completed": 0, "leechers": 0, "seeders": 0 }, { "completed": 0, "leechers": 0, "seeders": 0 } ] } ```
1 parent 1b34d93 commit ea01059

File tree

1 file changed

+111
-21
lines changed

1 file changed

+111
-21
lines changed
 

‎src/bin/udp_tracker_client.rs

+111-21
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! Announce request:
66
//!
77
//! ```text
8-
//! cargo run --bin udp_tracker_client 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
8+
//! cargo run --bin udp_tracker_client announce 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
99
//! ```
1010
//!
1111
//! Announce response:
@@ -20,16 +20,42 @@
2020
//! "123.123.123.123:51289"
2121
//! ],
2222
//! }
23-
/// ````
23+
//! ```
24+
//!
25+
//! Scrape request:
26+
//!
27+
//! ```text
28+
//! cargo run --bin udp_tracker_client scrape 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
29+
//! ```
30+
//!
31+
//! Scrape response:
32+
//!
33+
//! ```json
34+
//! {
35+
//! "transaction_id": -888840697,
36+
//! "torrent_stats": [
37+
//! {
38+
//! "completed": 0,
39+
//! "leechers": 0,
40+
//! "seeders": 0
41+
//! },
42+
//! {
43+
//! "completed": 0,
44+
//! "leechers": 0,
45+
//! "seeders": 0
46+
//! }
47+
//! ]
48+
//! }
49+
//! ```
2450
use std::net::{Ipv4Addr, SocketAddr};
2551
use std::str::FromStr;
2652

2753
use anyhow::Context;
2854
use aquatic_udp_protocol::common::InfoHash;
29-
use aquatic_udp_protocol::Response::{AnnounceIpv4, AnnounceIpv6};
55+
use aquatic_udp_protocol::Response::{AnnounceIpv4, AnnounceIpv6, Scrape};
3056
use aquatic_udp_protocol::{
3157
AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, Port, Response,
32-
TransactionId,
58+
ScrapeRequest, TransactionId,
3359
};
3460
use clap::{Parser, Subcommand};
3561
use log::{debug, LevelFilter};
@@ -55,6 +81,12 @@ enum Command {
5581
#[arg(value_parser = parse_info_hash)]
5682
info_hash: TorrustInfoHash,
5783
},
84+
Scrape {
85+
#[arg(value_parser = parse_socket_addr)]
86+
tracker_socket_addr: SocketAddr,
87+
#[arg(value_parser = parse_info_hash, num_args = 1..=74, value_delimiter = ' ')]
88+
info_hashes: Vec<TorrustInfoHash>,
89+
},
5890
}
5991

6092
#[tokio::main]
@@ -65,29 +97,23 @@ async fn main() -> anyhow::Result<()> {
6597

6698
// Configuration
6799
let local_port = ASSIGNED_BY_OS;
100+
let local_bind_to = format!("0.0.0.0:{local_port}");
68101
let transaction_id = RANDOM_TRANSACTION_ID;
69-
let bind_to = format!("0.0.0.0:{local_port}");
70102

71103
// Bind to local port
72-
debug!("Binding to: {bind_to}");
73-
let udp_client = UdpClient::bind(&bind_to).await;
104+
debug!("Binding to: {local_bind_to}");
105+
let udp_client = UdpClient::bind(&local_bind_to).await;
74106
let bound_to = udp_client.socket.local_addr().unwrap();
75107
debug!("Bound to: {bound_to}");
76108

109+
let transaction_id = TransactionId(transaction_id);
110+
77111
let response = match args.command {
78112
Command::Announce {
79113
tracker_socket_addr,
80114
info_hash,
81115
} => {
82-
debug!("Connecting to remote: udp://{tracker_socket_addr}");
83-
84-
udp_client.connect(&tracker_socket_addr.to_string()).await;
85-
86-
let udp_tracker_client = UdpTrackerClient { udp_client };
87-
88-
let transaction_id = TransactionId(transaction_id);
89-
90-
let connection_id = send_connection_request(transaction_id, &udp_tracker_client).await;
116+
let (connection_id, udp_tracker_client) = connect(&tracker_socket_addr, udp_client, transaction_id).await;
91117

92118
send_announce_request(
93119
connection_id,
@@ -98,6 +124,13 @@ async fn main() -> anyhow::Result<()> {
98124
)
99125
.await
100126
}
127+
Command::Scrape {
128+
tracker_socket_addr,
129+
info_hashes,
130+
} => {
131+
let (connection_id, udp_tracker_client) = connect(&tracker_socket_addr, udp_client, transaction_id).await;
132+
send_scrape_request(connection_id, transaction_id, info_hashes, &udp_tracker_client).await
133+
}
101134
};
102135

103136
match response {
@@ -123,7 +156,19 @@ async fn main() -> anyhow::Result<()> {
123156
let pretty_json = serde_json::to_string_pretty(&json).unwrap();
124157
println!("{pretty_json}");
125158
}
126-
_ => println!("{response:#?}"),
159+
Scrape(scrape) => {
160+
let json = json!({
161+
"transaction_id": scrape.transaction_id.0,
162+
"torrent_stats": scrape.torrent_stats.iter().map(|torrent_scrape_statistics| json!({
163+
"seeders": torrent_scrape_statistics.seeders.0,
164+
"completed": torrent_scrape_statistics.completed.0,
165+
"leechers": torrent_scrape_statistics.leechers.0,
166+
})).collect::<Vec<_>>(),
167+
});
168+
let pretty_json = serde_json::to_string_pretty(&json).unwrap();
169+
println!("{pretty_json}");
170+
}
171+
_ => println!("{response:#?}"), // todo: serialize to JSON all responses.
127172
}
128173

129174
Ok(())
@@ -150,12 +195,31 @@ fn setup_logging(level: LevelFilter) {
150195
debug!("logging initialized.");
151196
}
152197

153-
fn parse_socket_addr(s: &str) -> anyhow::Result<SocketAddr> {
154-
s.parse().with_context(|| format!("failed to parse socket address: `{s}`"))
198+
fn parse_socket_addr(socket_addr_str: &str) -> anyhow::Result<SocketAddr> {
199+
socket_addr_str
200+
.parse()
201+
.with_context(|| format!("failed to parse socket address: `{socket_addr_str}`"))
155202
}
156203

157-
fn parse_info_hash(s: &str) -> anyhow::Result<TorrustInfoHash> {
158-
TorrustInfoHash::from_str(s).map_err(|e| anyhow::Error::msg(format!("failed to parse info-hash `{s}`: {e:?}")))
204+
fn parse_info_hash(info_hash_str: &str) -> anyhow::Result<TorrustInfoHash> {
205+
TorrustInfoHash::from_str(info_hash_str)
206+
.map_err(|e| anyhow::Error::msg(format!("failed to parse info-hash `{info_hash_str}`: {e:?}")))
207+
}
208+
209+
async fn connect(
210+
tracker_socket_addr: &SocketAddr,
211+
udp_client: UdpClient,
212+
transaction_id: TransactionId,
213+
) -> (ConnectionId, UdpTrackerClient) {
214+
debug!("Connecting to tracker: udp://{tracker_socket_addr}");
215+
216+
udp_client.connect(&tracker_socket_addr.to_string()).await;
217+
218+
let udp_tracker_client = UdpTrackerClient { udp_client };
219+
220+
let connection_id = send_connection_request(transaction_id, &udp_tracker_client).await;
221+
222+
(connection_id, udp_tracker_client)
159223
}
160224

161225
async fn send_connection_request(transaction_id: TransactionId, client: &UdpTrackerClient) -> ConnectionId {
@@ -207,3 +271,29 @@ async fn send_announce_request(
207271

208272
response
209273
}
274+
275+
async fn send_scrape_request(
276+
connection_id: ConnectionId,
277+
transaction_id: TransactionId,
278+
info_hashes: Vec<TorrustInfoHash>,
279+
client: &UdpTrackerClient,
280+
) -> Response {
281+
debug!("Sending scrape request with transaction id: {transaction_id:#?}");
282+
283+
let scrape_request = ScrapeRequest {
284+
connection_id,
285+
transaction_id,
286+
info_hashes: info_hashes
287+
.iter()
288+
.map(|torrust_info_hash| InfoHash(torrust_info_hash.bytes()))
289+
.collect(),
290+
};
291+
292+
client.send(scrape_request.into()).await;
293+
294+
let response = client.receive().await;
295+
296+
debug!("scrape request response:\n{response:#?}");
297+
298+
response
299+
}

0 commit comments

Comments
 (0)
Please sign in to comment.