pub mod response;
pub use response::{Announce, Scrape};
use crate::peer::Buffer;
use anyhow::{Result, bail};
use rand::RngExt;
use std::net::{SocketAddr, UdpSocket};
pub const MAGIC_BYTES: u64 = 0x41727101980u64;
pub trait Server {
fn announce(&self, id20: &[u8; 20], tracker: &SocketAddr) -> Result<Announce>;
fn scrape(&self, id20: &[[u8; 20]], tracker: &SocketAddr) -> Result<Scrape>;
}
impl Server for UdpSocket {
fn announce(&self, id20: &[u8; 20], tracker: &SocketAddr) -> Result<Announce> {
let mut rng = rand::rng();
let transaction_id: u32 = rng.random();
let mut c = Vec::with_capacity(16);
c.extend_from_slice(&MAGIC_BYTES.to_be_bytes()); c.extend_from_slice(&0u32.to_be_bytes());
c.extend_from_slice(&transaction_id.to_be_bytes());
self.send_to(&c, tracker)?;
let mut buf = [0u8; 2048];
let (amt, _) = self.recv_from(&mut buf)?;
if amt < 16 {
bail!("Tracker response too short");
}
let action = u32::from_be_bytes(buf[0..4].try_into()?);
let res_transaction_id = u32::from_be_bytes(buf[4..8].try_into()?);
if res_transaction_id != transaction_id {
bail!("Transaction ID missmatch");
}
if action == 3 {
bail!("Tracker error: `{}`", String::from_utf8_lossy(&buf[8..amt]));
}
let connection_id = u64::from_be_bytes(buf[8..16].try_into()?);
let mut a = Vec::with_capacity(98);
let announce_trans_id: u32 = rng.random();
let mut peer_id = [0u8; 20];
rng.fill(&mut peer_id);
a.extend_from_slice(&connection_id.to_be_bytes());
a.extend_from_slice(&1u32.to_be_bytes()); a.extend_from_slice(&announce_trans_id.to_be_bytes());
a.extend_from_slice(id20);
a.extend_from_slice(&peer_id);
a.extend_from_slice(&0u64.to_be_bytes()); a.extend_from_slice(&1024u64.to_be_bytes()); a.extend_from_slice(&0u64.to_be_bytes()); a.extend_from_slice(&0u32.to_be_bytes()); a.extend_from_slice(&0u32.to_be_bytes()); a.extend_from_slice(&0u32.to_be_bytes()); a.extend_from_slice(&(-1i32).to_be_bytes()); a.extend_from_slice(&self.local_addr()?.port().to_be_bytes());
self.send_to(&a, tracker)?;
let (amt, _) = self.recv_from(&mut buf)?;
let res_action = u32::from_be_bytes(buf[0..4].try_into()?);
let res_announce_trans_id = u32::from_be_bytes(buf[4..8].try_into()?);
if res_announce_trans_id != announce_trans_id {
bail!("Announce Transaction ID does not match.");
}
if res_action == 3 {
bail!(
"Tracker announce declined: {}",
String::from_utf8_lossy(&buf[8..amt])
);
}
if amt < 20 {
bail!("Unexpected prefix len");
}
Ok(Announce {
interval: u32::from_be_bytes(buf[8..12].try_into()?),
leechers: u32::from_be_bytes(buf[12..16].try_into()?),
seeders: u32::from_be_bytes(buf[16..20].try_into()?),
peers: {
let peers = &buf[20..amt];
if tracker.is_ipv6() {
Buffer::from_peers6(peers)?
} else {
Buffer::from_peers(peers)?
}
},
})
}
fn scrape(&self, id20: &[[u8; 20]], tracker: &SocketAddr) -> Result<Scrape> {
let mut response = Scrape::default();
self.send_to(
&{
let mut request = Vec::new();
request.extend_from_slice(&MAGIC_BYTES.to_be_bytes());
request.extend_from_slice(&0u32.to_be_bytes());
request.extend_from_slice(&rand::rng().random::<u32>().to_be_bytes());
request
},
tracker,
)?;
let mut b = [0u8; 16];
if self.recv(&mut b)? < 16 {
bail!("unexpected buffer len")
}
self.send_to(
&{
let mut q = Vec::new();
q.extend_from_slice(&u64::from_be_bytes(b[8..16].try_into()?).to_be_bytes());
q.extend_from_slice(&2u32.to_be_bytes());
q.extend_from_slice(&rand::rng().random::<u32>().to_be_bytes());
if id20.len() > 74 {
bail!(
"up to about 74 torrents can be scraped at once (given {})",
id20.len()
)
}
for i in id20 {
q.extend_from_slice(i);
}
q
},
tracker,
)?;
let mut b = [0u8; 1024];
let l = self.recv(&mut b)?;
if l < 20 {
bail!("unexpected response len")
}
response.seeders += u32::from_be_bytes(b[8..12].try_into()?);
response.leechers += u32::from_be_bytes(b[12..16].try_into()?);
response.peers += u32::from_be_bytes(b[16..20].try_into()?);
Ok(response)
}
}