use rand::RngExt;
use std::{
error::Error,
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket},
time::Duration,
};
pub fn handle(
info_hash: &[u8; 20],
address: &SocketAddr,
port: u16,
timeout: u64,
) -> Result<(), Box<dyn Error>> {
let socket = UdpSocket::bind(if address.is_ipv4() {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port))
} else {
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0))
})?;
socket.set_read_timeout(Some(Duration::from_secs(timeout)))?;
let mut rng = rand::rng();
let transaction_id: u32 = rng.random();
let mut c = Vec::with_capacity(16);
c.extend_from_slice(&0x41727101980u64.to_be_bytes()); c.extend_from_slice(&0u32.to_be_bytes());
c.extend_from_slice(&transaction_id.to_be_bytes());
socket.send_to(&c, address)?;
let mut buf = [0u8; 2048];
let (amt, _) = socket.recv_from(&mut buf)?;
if amt < 16 {
return Err("Tracker response too short".into());
}
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 {
return Err("Transaction ID missmatch".into());
}
if action == 3 {
return Err(format!("Tracker error: `{}`", String::from_utf8_lossy(&buf[8..amt])).into());
}
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(info_hash);
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(&port.to_be_bytes());
println!("Sending UDP announce to `{address}`...");
socket.send_to(&a, address)?;
let (amt, _) = socket.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 {
return Err("Announce Transaction ID does not match.".into());
}
if res_action == 3 {
return Err(format!(
"Tracker announce declined: {}",
String::from_utf8_lossy(&buf[8..amt])
)
.into());
}
if amt < 20 {
return Err("Unexpected prefix len".into());
}
let interval = u32::from_be_bytes(buf[8..12].try_into()?);
let leechers = u32::from_be_bytes(buf[12..16].try_into()?);
let seeders = u32::from_be_bytes(buf[16..20].try_into()?);
println!("Update interval: {interval}s",);
println!("L: {leechers}, S: {seeders}",);
let peers_data = &buf[20..amt];
if peers_data.is_empty() {
println!("No peers.");
} else if address.is_ipv6() {
for chunk in peers_data.chunks_exact(18) {
let ip_bytes: [u8; 16] = chunk[0..16].try_into()?;
let ip = std::net::Ipv6Addr::from(ip_bytes);
let port = u16::from_be_bytes(chunk[16..18].try_into()?);
println!("[{ip}]:{port}")
}
} else {
for chunk in peers_data.chunks_exact(6) {
let ip_bytes: [u8; 4] = chunk[0..4].try_into()?;
let ip = std::net::Ipv4Addr::from(ip_bytes);
let port = u16::from_be_bytes(chunk[4..6].try_into()?);
println!("{ip}:{port}")
}
}
Ok(())
}