use std::net::{Ipv4Addr, SocketAddr};
use std::num::NonZeroU16;
use std::time::Duration;
use aquatic_udp_protocol::common::InfoHash;
use aquatic_udp_protocol::{
AnnounceActionPlaceholder, AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, NumberOfBytes, NumberOfPeers,
PeerId, PeerKey, Port, Response, ScrapeRequest, TransactionId,
};
use torrust_tracker_primitives::info_hash::InfoHash as TorrustInfoHash;
use super::Error;
use crate::shared::bit_torrent::tracker::udp::client::UdpTrackerClient;
#[derive(Debug)]
pub struct Client {
client: UdpTrackerClient,
}
impl Client {
pub async fn new(remote_addr: SocketAddr, timeout: Duration) -> Result<Self, Error> {
let client = UdpTrackerClient::new(remote_addr, timeout)
.await
.map_err(|err| Error::UnableToBindAndConnect { remote_addr, err })?;
Ok(Self { client })
}
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
self.client.client.socket.local_addr()
}
pub async fn send_connection_request(&self, transaction_id: TransactionId) -> Result<ConnectionId, Error> {
tracing::debug!("Sending connection request with transaction id: {transaction_id:#?}");
let connect_request = ConnectRequest { transaction_id };
let _ = self
.client
.send(connect_request.into())
.await
.map_err(|err| Error::UnableToSendConnectionRequest { err })?;
let response = self
.client
.receive()
.await
.map_err(|err| Error::UnableToReceiveConnectResponse { err })?;
match response {
Response::Connect(connect_response) => Ok(connect_response.connection_id),
_ => Err(Error::UnexpectedConnectionResponse { response }),
}
}
pub async fn send_announce_request(
&self,
transaction_id: TransactionId,
connection_id: ConnectionId,
info_hash: TorrustInfoHash,
) -> Result<Response, Error> {
tracing::debug!("Sending announce request with transaction id: {transaction_id:#?}");
let port = NonZeroU16::new(
self.client
.client
.socket
.local_addr()
.expect("it should get the local address")
.port(),
)
.expect("it should no be zero");
let announce_request = AnnounceRequest {
connection_id,
action_placeholder: AnnounceActionPlaceholder::default(),
transaction_id,
info_hash: InfoHash(info_hash.bytes()),
peer_id: PeerId(*b"-qB00000000000000001"),
bytes_downloaded: NumberOfBytes(0i64.into()),
bytes_uploaded: NumberOfBytes(0i64.into()),
bytes_left: NumberOfBytes(0i64.into()),
event: AnnounceEvent::Started.into(),
ip_address: Ipv4Addr::new(0, 0, 0, 0).into(),
key: PeerKey::new(0i32),
peers_wanted: NumberOfPeers(1i32.into()),
port: Port::new(port),
};
let _ = self
.client
.send(announce_request.into())
.await
.map_err(|err| Error::UnableToSendAnnounceRequest { err })?;
let response = self
.client
.receive()
.await
.map_err(|err| Error::UnableToReceiveAnnounceResponse { err })?;
Ok(response)
}
pub async fn send_scrape_request(
&self,
connection_id: ConnectionId,
transaction_id: TransactionId,
info_hashes: &[TorrustInfoHash],
) -> Result<Response, Error> {
tracing::debug!("Sending scrape request with transaction id: {transaction_id:#?}");
let scrape_request = ScrapeRequest {
connection_id,
transaction_id,
info_hashes: info_hashes
.iter()
.map(|torrust_info_hash| InfoHash(torrust_info_hash.bytes()))
.collect(),
};
let _ = self
.client
.send(scrape_request.into())
.await
.map_err(|err| Error::UnableToSendScrapeRequest { err })?;
let response = self
.client
.receive()
.await
.map_err(|err| Error::UnableToReceiveScrapeResponse { err })?;
Ok(response)
}
}