mod query;
mod response;
use bendy::decoding::FromBencode;
use cyphernet::addr::{HostName, PartialAddr, i2p::I2pAddr};
use std::{
error::Error,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
time::Duration,
};
pub use query::Query;
pub struct Response {
pub interval: u32,
pub leechers: u32,
pub seeders: u32,
pub peers: Vec<PartialAddr<HostName, 0>>,
}
pub fn request(query: &Query, timeout: u64) -> Result<Response, Box<dyn Error>> {
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(timeout))
.build()?;
let response = client.get(query.to_string()).send()?;
if !response.status().is_success() {
return Err(format!("Tracker returned status: {}", response.status()).into());
}
let result = response::Response::from_bencode(&response.bytes()?)?;
let mut peers = Vec::with_capacity(100);
if !result.peers.is_empty() {
if query.url().host_str().is_some_and(|h| h.ends_with(".i2p")) {
for chunk in result.peers.chunks_exact(32) {
if let Ok(i2p) = I2pAddr::from_str(&data_encoding::BASE32_NOPAD.encode(chunk)) {
peers.push(PartialAddr {
host: HostName::I2p(i2p),
port: None,
})
}
}
} else {
for chunk in result.peers.chunks_exact(6) {
let ip_bytes: [u8; 4] = chunk[0..4].try_into()?;
peers.push(PartialAddr {
host: HostName::Ip(IpAddr::V4(Ipv4Addr::from(ip_bytes))),
port: Some(u16::from_be_bytes(chunk[4..6].try_into()?)),
})
}
}
}
if !result.peers6.is_empty() {
for chunk in result.peers6.chunks_exact(18) {
let ip_bytes: [u8; 16] = chunk[0..16].try_into()?;
peers.push(PartialAddr {
host: HostName::Ip(IpAddr::V6(Ipv6Addr::from(ip_bytes))),
port: Some(u16::from_be_bytes(chunk[16..18].try_into()?)),
})
}
}
Ok(Response {
interval: result.interval,
leechers: result.complete,
seeders: result.incomplete,
peers,
})
}