use std::io::Read; use std::net::Ipv4Addr; use hyper::Client; use url::form_urlencoded::byte_serialize; use bencode::{decode, Dict}; use metainfo::{Hash, Metainfo}; use tracker::{Peer, TrackerError, TrackerResponse, TrackerResult}; macro_rules! ts { ( $e:expr ) => { match $e { Some(x) => x, None => return Err(TrackerError::InvalidResponse), } } } pub fn get_peers(peer_id: Hash, port: u16, metainfo: &Metainfo, uploaded: u64, downloaded: u64, left: u64) -> TrackerResult { let url = format!("{}?info_hash={}&peer_id={}&port={}&uploaded={}\ &downloaded={}&left={}&compact=1", metainfo.announce, urlencode(&metainfo.info_hash), urlencode(&peer_id), port, uploaded, downloaded, left); let client = Client::new(); let mut resp = client.get(&url).send()?; let mut buff = Vec::new(); resp.read_to_end(&mut buff)?; let obj = decode(&buff)?; let info = ts!(obj.as_dict()); parse_object(info) } fn parse_object(info: &Dict) -> TrackerResult { if let Some(reason) = info.get_bytes("failure reason") { Err(TrackerError::TrackerFailure(ts!(reason.string()))) } else { let interval = ts!(info.get_int("interval")); let peerstring = ts!(info.get_bytes("peers")); if peerstring.len() % 6 != 0 { return Err(TrackerError::InvalidResponse) } let peers = peerstring.chunks(6).map(|chunk| { Peer { addr: Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]), port: (chunk[4] as u16) << 8 | (chunk[5] as u16) << 0, } }).collect(); Ok(TrackerResponse { interval: interval as u64, peers: peers, }) } } fn urlencode(bytes: &[u8]) -> String { byte_serialize(bytes).collect::>().join("") } #[test] fn test_failure() { let mut info = Dict::new(); info.insert("failure reason", "unknown hash"); assert!(parse_object(&info).is_err()); } #[test] fn test_invalid_peer_list() { let mut info = Dict::new(); info.insert("interval", 900); info.insert("peers", vec![1u8, 1, 1, 1]); assert!(parse_object(&info).is_err()); } #[test] fn test_success() { let mut info = Dict::new(); info.insert("interval", 900); info.insert("peers", vec![1u8, 1, 1, 1, 1, 1]); let resp = parse_object(&info).unwrap(); assert_eq!(resp.interval, 900); assert_eq!(resp.peers.len(), 1); assert_eq!(resp.peers[0].addr.octets(), [1, 1, 1, 1]); assert_eq!(resp.peers[0].port, 257); }