mod response;
use bendy::decoding::FromBencode;
use rand::RngExt;
use std::{
error::Error,
net::{Ipv4Addr, Ipv6Addr},
time::Duration,
};
use url::Url;
pub fn handle(
info_hash: &[u8; 20],
mut url: Url,
port: u16,
timeout: u64,
) -> Result<(), Box<dyn Error>> {
fn url_encode_bytes(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("%{:02x}", b)).collect()
}
let mut rng = rand::rng();
let mut peer_id = [0u8; 20];
rng.fill(&mut peer_id);
url.set_query(Some(&format!(
"info_hash={}&peer_id={}&port={port}&uploaded=0&downloaded=0&left=1000&compact=1",
url_encode_bytes(info_hash),
url_encode_bytes(&peer_id)
)));
let target = url.as_str();
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(timeout))
.build()?;
println!("Sending HTTP announce to `{target}`...");
let response = client.get(target).send()?;
if !response.status().is_success() {
return Err(format!("Tracker returned status: {}", response.status()).into());
}
let response = client.get(target).send()?;
if !response.status().is_success() {
return Err(format!("Tracker returned status: {}", response.status()).into());
}
let body = response.bytes()?;
let result = response::Response::from_bencode(&body)?;
println!("Update interval: {}s", result.interval);
println!("L: {}, S: {}", result.incomplete, result.complete);
if !result.peers.is_empty() {
for chunk in result.peers.chunks_exact(6) {
let ip_bytes: [u8; 4] = chunk[0..4].try_into()?;
let ip = Ipv4Addr::from(ip_bytes);
let port = u16::from_be_bytes(chunk[4..6].try_into()?);
println!("{ip}:{port}");
}
}
if !result.peers6.is_empty() {
for chunk in result.peers6.chunks_exact(18) {
let ip_bytes: [u8; 16] = chunk[0..16].try_into()?;
let ip = Ipv6Addr::from(ip_bytes);
let port = u16::from_be_bytes(chunk[16..18].try_into()?);
println!("[{ip}]:{port}");
}
}
if result.peers.is_empty() && result.peers6.is_empty() {
println!("No peers.");
}
Ok(())
}