torrust_tracker/console/clients/udp/
checker.rs

1use std::net::{Ipv4Addr, SocketAddr};
2use std::num::NonZeroU16;
3use std::time::Duration;
4
5use aquatic_udp_protocol::common::InfoHash;
6use aquatic_udp_protocol::{
7    AnnounceActionPlaceholder, AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, NumberOfBytes, NumberOfPeers,
8    PeerId, PeerKey, Port, Response, ScrapeRequest, TransactionId,
9};
10use torrust_tracker_primitives::info_hash::InfoHash as TorrustInfoHash;
11
12use super::Error;
13use crate::shared::bit_torrent::tracker::udp::client::UdpTrackerClient;
14
15/// A UDP Tracker client to make test requests (checks).
16#[derive(Debug)]
17pub struct Client {
18    client: UdpTrackerClient,
19}
20
21impl Client {
22    /// Creates a new `[Client]` for checking a UDP Tracker Service
23    ///
24    /// # Errors
25    ///
26    /// It will error if unable to bind and connect to the udp remote address.
27    ///
28    pub async fn new(remote_addr: SocketAddr, timeout: Duration) -> Result<Self, Error> {
29        let client = UdpTrackerClient::new(remote_addr, timeout)
30            .await
31            .map_err(|err| Error::UnableToBindAndConnect { remote_addr, err })?;
32
33        Ok(Self { client })
34    }
35
36    /// Returns the local addr of this [`Client`].
37    ///
38    /// # Errors
39    ///
40    /// This function will return an error if the socket is somehow not bound.
41    pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
42        self.client.client.socket.local_addr()
43    }
44
45    /// Sends a connection request to the UDP Tracker server.
46    ///
47    /// # Errors
48    ///
49    /// Will return and error if
50    ///
51    /// - It can't connect to the remote UDP socket.
52    /// - It can't make a connection request successfully to the remote UDP
53    ///   server (after successfully connecting to the remote UDP socket).
54    ///
55    /// # Panics
56    ///
57    /// Will panic if it receives an unexpected response.
58    pub async fn send_connection_request(&self, transaction_id: TransactionId) -> Result<ConnectionId, Error> {
59        tracing::debug!("Sending connection request with transaction id: {transaction_id:#?}");
60
61        let connect_request = ConnectRequest { transaction_id };
62
63        let _ = self
64            .client
65            .send(connect_request.into())
66            .await
67            .map_err(|err| Error::UnableToSendConnectionRequest { err })?;
68
69        let response = self
70            .client
71            .receive()
72            .await
73            .map_err(|err| Error::UnableToReceiveConnectResponse { err })?;
74
75        match response {
76            Response::Connect(connect_response) => Ok(connect_response.connection_id),
77            _ => Err(Error::UnexpectedConnectionResponse { response }),
78        }
79    }
80
81    /// Sends an announce request to the UDP Tracker server.
82    ///
83    /// # Errors
84    ///
85    /// Will return and error if the client is not connected. You have to connect
86    /// before calling this function.
87    ///
88    /// # Panics
89    ///
90    /// It will panic if the `local_address` has a zero port.
91    pub async fn send_announce_request(
92        &self,
93        transaction_id: TransactionId,
94        connection_id: ConnectionId,
95        info_hash: TorrustInfoHash,
96    ) -> Result<Response, Error> {
97        tracing::debug!("Sending announce request with transaction id: {transaction_id:#?}");
98
99        let port = NonZeroU16::new(
100            self.client
101                .client
102                .socket
103                .local_addr()
104                .expect("it should get the local address")
105                .port(),
106        )
107        .expect("it should no be zero");
108
109        let announce_request = AnnounceRequest {
110            connection_id,
111            action_placeholder: AnnounceActionPlaceholder::default(),
112            transaction_id,
113            info_hash: InfoHash(info_hash.bytes()),
114            peer_id: PeerId(*b"-qB00000000000000001"),
115            bytes_downloaded: NumberOfBytes(0i64.into()),
116            bytes_uploaded: NumberOfBytes(0i64.into()),
117            bytes_left: NumberOfBytes(0i64.into()),
118            event: AnnounceEvent::Started.into(),
119            ip_address: Ipv4Addr::new(0, 0, 0, 0).into(),
120            key: PeerKey::new(0i32),
121            peers_wanted: NumberOfPeers(1i32.into()),
122            port: Port::new(port),
123        };
124
125        let _ = self
126            .client
127            .send(announce_request.into())
128            .await
129            .map_err(|err| Error::UnableToSendAnnounceRequest { err })?;
130
131        let response = self
132            .client
133            .receive()
134            .await
135            .map_err(|err| Error::UnableToReceiveAnnounceResponse { err })?;
136
137        Ok(response)
138    }
139
140    /// Sends a scrape request to the UDP Tracker server.
141    ///
142    /// # Errors
143    ///
144    /// Will return and error if the client is not connected. You have to connect
145    /// before calling this function.
146    pub async fn send_scrape_request(
147        &self,
148        connection_id: ConnectionId,
149        transaction_id: TransactionId,
150        info_hashes: &[TorrustInfoHash],
151    ) -> Result<Response, Error> {
152        tracing::debug!("Sending scrape request with transaction id: {transaction_id:#?}");
153
154        let scrape_request = ScrapeRequest {
155            connection_id,
156            transaction_id,
157            info_hashes: info_hashes
158                .iter()
159                .map(|torrust_info_hash| InfoHash(torrust_info_hash.bytes()))
160                .collect(),
161        };
162
163        let _ = self
164            .client
165            .send(scrape_request.into())
166            .await
167            .map_err(|err| Error::UnableToSendScrapeRequest { err })?;
168
169        let response = self
170            .client
171            .receive()
172            .await
173            .map_err(|err| Error::UnableToReceiveScrapeResponse { err })?;
174
175        Ok(response)
176    }
177}