From 74e82a012766ff2d5b1822cd6acf964cdc303702 Mon Sep 17 00:00:00 2001 From: Simon Bernier St-Pierre Date: Sun, 11 Dec 2016 22:03:27 -0500 Subject: [PATCH] tracker: add http tracker support --- Cargo.toml | 6 +++ docs.md | 2 +- examples/decode-torrent.rs | 16 ++++++- src/lib.rs | 6 +++ src/torrent.rs | 8 ++++ src/tracker/http.rs | 96 ++++++++++++++++++++++++++++++++++++++ src/tracker/mod.rs | 48 +++++++++++++++++++ 7 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 src/torrent.rs create mode 100644 src/tracker/http.rs create mode 100644 src/tracker/mod.rs diff --git a/Cargo.toml b/Cargo.toml index ea729d6..db3612b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,9 @@ version = "0.1.0" authors = ["Simon Bernier St-Pierre "] [dependencies] +sha1 = "0.2" +url = "1.2" + +[dependencies.hyper] +version = "0.9" +default-features = false diff --git a/docs.md b/docs.md index b208031..d661028 100644 --- a/docs.md +++ b/docs.md @@ -3,7 +3,7 @@ * [x] encoding * Tracker * [ ] udp [spec](http://www.bittorrent.org/beps/bep_0015.html) - * [ ] http [spec](http://www.bittorrent.org/beps/bep_0003.html#trackers) + * [x] http [spec](http://www.bittorrent.org/beps/bep_0003.html#trackers) * Metainfo * [ ] torrent files [spec](https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure) * [ ] magnet links diff --git a/examples/decode-torrent.rs b/examples/decode-torrent.rs index 9bf349a..8969b8f 100644 --- a/examples/decode-torrent.rs +++ b/examples/decode-torrent.rs @@ -7,6 +7,8 @@ use std::str; use magnolia::bencode::*; use magnolia::metainfo::Metainfo; +use magnolia::torrent::Torrent; +use magnolia::tracker::http; fn load_file(path: &str) -> io::Result<()> { let mut buf = Vec::new(); @@ -14,8 +16,18 @@ fn load_file(path: &str) -> io::Result<()> { f.read_to_end(&mut buf)?; let obj = decode(&buf).unwrap(); - let meta = Metainfo::from_bencode(obj); - println!("{:#?}", meta); + let meta = Metainfo::from_bencode(obj).unwrap(); + + let t = Torrent { + metainfo: meta, + uploaded: 0, + downloaded: 0, + left: 0, + }; + + let resp = http::get_peers([1u8; 20], 55555, &t).unwrap(); + + print!("{:?}", resp); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index d1c0084..8421c35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,9 @@ +extern crate hyper; +extern crate sha1; +extern crate url; + pub mod bencode; pub mod buffer; pub mod metainfo; +pub mod torrent; +pub mod tracker; diff --git a/src/torrent.rs b/src/torrent.rs new file mode 100644 index 0000000..87171e9 --- /dev/null +++ b/src/torrent.rs @@ -0,0 +1,8 @@ +use metainfo::Metainfo; + +pub struct Torrent { + pub metainfo: Metainfo, + pub downloaded: u64, + pub uploaded: u64, + pub left: u64, +} diff --git a/src/tracker/http.rs b/src/tracker/http.rs new file mode 100644 index 0000000..e5de564 --- /dev/null +++ b/src/tracker/http.rs @@ -0,0 +1,96 @@ +use std::io::Read; +use std::net::Ipv4Addr; + +use hyper::Client; +use url::form_urlencoded::byte_serialize; + +use bencode::{decode, Dict}; +use torrent::Torrent; +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: [u8; 20], port: u16, torrent: &Torrent) -> TrackerResult { + let url = format!("{}?info_hash={}&peer_id={}&port={}&uploaded={}\ + &downloaded={}&left={}&compact=1", + torrent.metainfo.announce, + urlencode(&torrent.metainfo.info_hash), + urlencode(&peer_id), + port, + torrent.uploaded, + torrent.downloaded, + torrent.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); +} diff --git a/src/tracker/mod.rs b/src/tracker/mod.rs new file mode 100644 index 0000000..3cd433b --- /dev/null +++ b/src/tracker/mod.rs @@ -0,0 +1,48 @@ +pub mod http; + +use std::io; +use std::net::Ipv4Addr; + +use hyper; + +use bencode::DecodeError; + +pub type TrackerResult = Result; + +#[derive(Debug)] +pub struct TrackerResponse { + pub interval: u64, + pub peers: Vec, +} + +#[derive(Debug)] +pub enum TrackerError { + IoError(io::Error), + HttpError(hyper::Error), + InvalidResponse, + TrackerFailure(String), +} + +impl From for TrackerError { + fn from(from: io::Error) -> Self { + TrackerError::IoError(from) + } +} + +impl From for TrackerError { + fn from(from: hyper::Error) -> Self { + TrackerError::HttpError(from) + } +} + +impl From for TrackerError { + fn from(_: DecodeError) -> Self { + TrackerError::InvalidResponse + } +} + +#[derive(Debug)] +pub struct Peer { + pub addr: Ipv4Addr, + pub port: u16, +}