Skip to content

Commit 82ec491

Browse files
committed
refactor: [#639] Tracker Checker: extract checker:Client to check UDP servers
It will be used in teh Tracker Checker too.
1 parent 8d07a34 commit 82ec491

File tree

4 files changed

+230
-115
lines changed

4 files changed

+230
-115
lines changed

src/console/clients/udp/app.rs

+30-115
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,22 @@
5656
//! ```
5757
//!
5858
//! The protocol (`udp://`) in the URL is mandatory. The path (`\scrape`) is optional. It always uses `\scrape`.
59-
use std::net::{Ipv4Addr, SocketAddr, ToSocketAddrs};
59+
use std::net::{SocketAddr, ToSocketAddrs};
6060
use std::str::FromStr;
6161

6262
use anyhow::Context;
63-
use aquatic_udp_protocol::common::InfoHash;
6463
use aquatic_udp_protocol::Response::{AnnounceIpv4, AnnounceIpv6, Scrape};
65-
use aquatic_udp_protocol::{
66-
AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, Port, Response,
67-
ScrapeRequest, TransactionId,
68-
};
64+
use aquatic_udp_protocol::{Port, TransactionId};
6965
use clap::{Parser, Subcommand};
7066
use log::{debug, LevelFilter};
7167
use serde_json::json;
7268
use url::Url;
7369

70+
use crate::console::clients::udp::checker;
7471
use crate::shared::bit_torrent::info_hash::InfoHash as TorrustInfoHash;
75-
use crate::shared::bit_torrent::tracker::udp::client::{UdpClient, UdpTrackerClient};
72+
use crate::shared::bit_torrent::tracker::udp::client::UdpClient;
7673

77-
const ASSIGNED_BY_OS: i32 = 0;
74+
const ASSIGNED_BY_OS: u16 = 0;
7875
const RANDOM_TRANSACTION_ID: i32 = -888_840_697;
7976

8077
#[derive(Parser, Debug)]
@@ -119,32 +116,42 @@ pub async fn run() -> anyhow::Result<()> {
119116
debug!("Binding to: {local_bind_to}");
120117
let udp_client = UdpClient::bind(&local_bind_to).await;
121118
let bound_to = udp_client.socket.local_addr().context("binding local address")?;
122-
debug!("Bound to: {bound_to}");
123-
124-
let transaction_id = TransactionId(transaction_id);
119+
debug!("Bound to: {bound_to}");
125120

126121
let response = match args.command {
127122
Command::Announce {
128123
tracker_socket_addr,
129124
info_hash,
130125
} => {
131-
let (connection_id, udp_tracker_client) = connect(&tracker_socket_addr, udp_client, transaction_id).await;
132-
133-
send_announce_request(
134-
connection_id,
135-
transaction_id,
136-
info_hash,
137-
Port(bound_to.port()),
138-
&udp_tracker_client,
139-
)
140-
.await
126+
let transaction_id = TransactionId(transaction_id);
127+
128+
let mut new_udp_client = checker::Client::default();
129+
let _bound_to = new_udp_client.bind(local_port).await?;
130+
131+
new_udp_client.connect(&tracker_socket_addr, udp_client).await;
132+
133+
let connection_id = new_udp_client.send_connection_request(transaction_id).await?;
134+
135+
new_udp_client
136+
.send_announce_request(connection_id, transaction_id, info_hash, Port(bound_to.port()))
137+
.await?
141138
}
142139
Command::Scrape {
143140
tracker_socket_addr,
144141
info_hashes,
145142
} => {
146-
let (connection_id, udp_tracker_client) = connect(&tracker_socket_addr, udp_client, transaction_id).await;
147-
send_scrape_request(connection_id, transaction_id, info_hashes, &udp_tracker_client).await
143+
let transaction_id = TransactionId(transaction_id);
144+
145+
let mut new_udp_client = checker::Client::default();
146+
let _bound_to = new_udp_client.bind(local_port).await?;
147+
148+
new_udp_client.connect(&tracker_socket_addr, udp_client).await;
149+
150+
let connection_id = new_udp_client.send_connection_request(transaction_id).await?;
151+
152+
new_udp_client
153+
.send_scrape_request(connection_id, transaction_id, info_hashes)
154+
.await?
148155
}
149156
};
150157

@@ -265,95 +272,3 @@ fn parse_info_hash(info_hash_str: &str) -> anyhow::Result<TorrustInfoHash> {
265272
TorrustInfoHash::from_str(info_hash_str)
266273
.map_err(|e| anyhow::Error::msg(format!("failed to parse info-hash `{info_hash_str}`: {e:?}")))
267274
}
268-
269-
async fn connect(
270-
tracker_socket_addr: &SocketAddr,
271-
udp_client: UdpClient,
272-
transaction_id: TransactionId,
273-
) -> (ConnectionId, UdpTrackerClient) {
274-
debug!("Connecting to tracker: udp://{tracker_socket_addr}");
275-
276-
udp_client.connect(&tracker_socket_addr.to_string()).await;
277-
278-
let udp_tracker_client = UdpTrackerClient { udp_client };
279-
280-
let connection_id = send_connection_request(transaction_id, &udp_tracker_client).await;
281-
282-
(connection_id, udp_tracker_client)
283-
}
284-
285-
async fn send_connection_request(transaction_id: TransactionId, client: &UdpTrackerClient) -> ConnectionId {
286-
debug!("Sending connection request with transaction id: {transaction_id:#?}");
287-
288-
let connect_request = ConnectRequest { transaction_id };
289-
290-
client.send(connect_request.into()).await;
291-
292-
let response = client.receive().await;
293-
294-
debug!("connection request response:\n{response:#?}");
295-
296-
match response {
297-
Response::Connect(connect_response) => connect_response.connection_id,
298-
_ => panic!("error connecting to udp server. Unexpected response"),
299-
}
300-
}
301-
302-
async fn send_announce_request(
303-
connection_id: ConnectionId,
304-
transaction_id: TransactionId,
305-
info_hash: TorrustInfoHash,
306-
port: Port,
307-
client: &UdpTrackerClient,
308-
) -> Response {
309-
debug!("Sending announce request with transaction id: {transaction_id:#?}");
310-
311-
let announce_request = AnnounceRequest {
312-
connection_id,
313-
transaction_id,
314-
info_hash: InfoHash(info_hash.bytes()),
315-
peer_id: PeerId(*b"-qB00000000000000001"),
316-
bytes_downloaded: NumberOfBytes(0i64),
317-
bytes_uploaded: NumberOfBytes(0i64),
318-
bytes_left: NumberOfBytes(0i64),
319-
event: AnnounceEvent::Started,
320-
ip_address: Some(Ipv4Addr::new(0, 0, 0, 0)),
321-
key: PeerKey(0u32),
322-
peers_wanted: NumberOfPeers(1i32),
323-
port,
324-
};
325-
326-
client.send(announce_request.into()).await;
327-
328-
let response = client.receive().await;
329-
330-
debug!("announce request response:\n{response:#?}");
331-
332-
response
333-
}
334-
335-
async fn send_scrape_request(
336-
connection_id: ConnectionId,
337-
transaction_id: TransactionId,
338-
info_hashes: Vec<TorrustInfoHash>,
339-
client: &UdpTrackerClient,
340-
) -> Response {
341-
debug!("Sending scrape request with transaction id: {transaction_id:#?}");
342-
343-
let scrape_request = ScrapeRequest {
344-
connection_id,
345-
transaction_id,
346-
info_hashes: info_hashes
347-
.iter()
348-
.map(|torrust_info_hash| InfoHash(torrust_info_hash.bytes()))
349-
.collect(),
350-
};
351-
352-
client.send(scrape_request.into()).await;
353-
354-
let response = client.receive().await;
355-
356-
debug!("scrape request response:\n{response:#?}");
357-
358-
response
359-
}

src/console/clients/udp/checker.rs

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use std::net::{Ipv4Addr, SocketAddr};
2+
3+
use anyhow::Context;
4+
use aquatic_udp_protocol::common::InfoHash;
5+
use aquatic_udp_protocol::{
6+
AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, Port, Response,
7+
ScrapeRequest, TransactionId,
8+
};
9+
use log::debug;
10+
use thiserror::Error;
11+
12+
use crate::shared::bit_torrent::info_hash::InfoHash as TorrustInfoHash;
13+
use crate::shared::bit_torrent::tracker::udp::client::{UdpClient, UdpTrackerClient};
14+
15+
#[derive(Error, Debug)]
16+
pub enum ClientError {
17+
#[error("Not connected to remote tracker UDP socket. Try connecting before making requests.")]
18+
NotConnected,
19+
#[error("Unexpected response while connecting the the remote server.")]
20+
UnexpectedConnectionResponse,
21+
}
22+
23+
/// A UDP Tracker client to make test requests (checks).
24+
#[derive(Debug, Default)]
25+
pub struct Client {
26+
/// Local UDP socket. It could be 0 to assign a free port.
27+
local_binding_address: Option<SocketAddr>,
28+
29+
/// Local UDP socket after binding. It's equals to binding address if a
30+
/// non- zero port was used.
31+
local_bound_address: Option<SocketAddr>,
32+
33+
/// Remote UDP tracker socket
34+
remote_socket: Option<SocketAddr>,
35+
36+
/// The client used to make UDP requests to the tracker.
37+
udp_tracker_client: Option<UdpTrackerClient>,
38+
39+
/// The client used to make UDP requests to a generic UDP server.
40+
udp_client: Option<UdpClient>,
41+
}
42+
43+
impl Client {
44+
/// Binds local client socket.
45+
///
46+
/// # Errors
47+
///
48+
/// Will return an error if it can't get the bound local address.
49+
pub async fn bind(&mut self, local_port: u16) -> anyhow::Result<SocketAddr> {
50+
let local_bind_to = format!("0.0.0.0:{local_port}");
51+
let binding_address = local_bind_to.parse().context("binding local address")?;
52+
53+
debug!("Binding to: {local_bind_to}");
54+
let udp_client = UdpClient::bind(&local_bind_to).await;
55+
56+
let bound_to = udp_client.socket.local_addr().context("bound local address")?;
57+
debug!("Bound to: {bound_to}");
58+
59+
self.local_binding_address = Some(binding_address);
60+
self.local_bound_address = Some(bound_to);
61+
self.udp_client = Some(udp_client);
62+
63+
Ok(bound_to)
64+
}
65+
66+
/// Connects to the remote server socket.
67+
///
68+
/// # Errors
69+
///
70+
/// Will return and error if it can't make a connection request successfully
71+
/// to the remote UDP server.
72+
pub async fn connect(&mut self, tracker_socket_addr: &SocketAddr, udp_client: UdpClient) {
73+
debug!("Connecting to tracker: udp://{tracker_socket_addr}");
74+
75+
udp_client.connect(&tracker_socket_addr.to_string()).await;
76+
77+
self.remote_socket = Some(*tracker_socket_addr);
78+
79+
self.udp_tracker_client = Some(UdpTrackerClient { udp_client });
80+
}
81+
82+
/// Sends a connection request to the UDP Tracker server.
83+
///
84+
/// # Errors
85+
///
86+
/// Will return and error if
87+
///
88+
/// - It can't connect to the remote UDP socket.
89+
/// - It can't make a connection request successfully to the remote UDP
90+
/// server (after successfully connecting to the remote UDP socket).
91+
///
92+
/// # Panics
93+
///
94+
/// Will panic if it receives an unexpected response.
95+
pub async fn send_connection_request(&self, transaction_id: TransactionId) -> anyhow::Result<ConnectionId> {
96+
debug!("Sending connection request with transaction id: {transaction_id:#?}");
97+
98+
let connect_request = ConnectRequest { transaction_id };
99+
100+
match &self.udp_tracker_client {
101+
Some(client) => {
102+
client.send(connect_request.into()).await;
103+
104+
let response = client.receive().await;
105+
106+
debug!("connection request response:\n{response:#?}");
107+
108+
match response {
109+
Response::Connect(connect_response) => Ok(connect_response.connection_id),
110+
_ => Err(ClientError::UnexpectedConnectionResponse.into()),
111+
}
112+
}
113+
None => Err(ClientError::NotConnected.into()),
114+
}
115+
}
116+
117+
/// Sends an announce request to the UDP Tracker server.
118+
///
119+
/// # Errors
120+
///
121+
/// Will return and error if the client is not connected. You have to connect
122+
/// before calling this function.
123+
pub async fn send_announce_request(
124+
&self,
125+
connection_id: ConnectionId,
126+
transaction_id: TransactionId,
127+
info_hash: TorrustInfoHash,
128+
client_port: Port,
129+
) -> anyhow::Result<Response> {
130+
debug!("Sending announce request with transaction id: {transaction_id:#?}");
131+
132+
let announce_request = AnnounceRequest {
133+
connection_id,
134+
transaction_id,
135+
info_hash: InfoHash(info_hash.bytes()),
136+
peer_id: PeerId(*b"-qB00000000000000001"),
137+
bytes_downloaded: NumberOfBytes(0i64),
138+
bytes_uploaded: NumberOfBytes(0i64),
139+
bytes_left: NumberOfBytes(0i64),
140+
event: AnnounceEvent::Started,
141+
ip_address: Some(Ipv4Addr::new(0, 0, 0, 0)),
142+
key: PeerKey(0u32),
143+
peers_wanted: NumberOfPeers(1i32),
144+
port: client_port,
145+
};
146+
147+
match &self.udp_tracker_client {
148+
Some(client) => {
149+
client.send(announce_request.into()).await;
150+
151+
let response = client.receive().await;
152+
153+
debug!("announce request response:\n{response:#?}");
154+
155+
Ok(response)
156+
}
157+
None => Err(ClientError::NotConnected.into()),
158+
}
159+
}
160+
161+
/// Sends a scrape request to the UDP Tracker server.
162+
///
163+
/// # Errors
164+
///
165+
/// Will return and error if the client is not connected. You have to connect
166+
/// before calling this function.
167+
pub async fn send_scrape_request(
168+
&self,
169+
connection_id: ConnectionId,
170+
transaction_id: TransactionId,
171+
info_hashes: Vec<TorrustInfoHash>,
172+
) -> anyhow::Result<Response> {
173+
debug!("Sending scrape request with transaction id: {transaction_id:#?}");
174+
175+
let scrape_request = ScrapeRequest {
176+
connection_id,
177+
transaction_id,
178+
info_hashes: info_hashes
179+
.iter()
180+
.map(|torrust_info_hash| InfoHash(torrust_info_hash.bytes()))
181+
.collect(),
182+
};
183+
184+
match &self.udp_tracker_client {
185+
Some(client) => {
186+
client.send(scrape_request.into()).await;
187+
188+
let response = client.receive().await;
189+
190+
debug!("scrape request response:\n{response:#?}");
191+
192+
Ok(response)
193+
}
194+
None => Err(ClientError::NotConnected.into()),
195+
}
196+
}
197+
}

src/console/clients/udp/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod app;
2+
pub mod checker;

0 commit comments

Comments
 (0)