tracker: add http tracker support

This commit is contained in:
2016-12-11 22:03:27 -05:00
parent 2e9503d2d7
commit 74e82a0127
7 changed files with 179 additions and 3 deletions

View File

@@ -4,3 +4,9 @@ version = "0.1.0"
authors = ["Simon Bernier St-Pierre <sbernierstpierre@gmail.com>"] authors = ["Simon Bernier St-Pierre <sbernierstpierre@gmail.com>"]
[dependencies] [dependencies]
sha1 = "0.2"
url = "1.2"
[dependencies.hyper]
version = "0.9"
default-features = false

View File

@@ -3,7 +3,7 @@
* [x] encoding * [x] encoding
* Tracker * Tracker
* [ ] udp [spec](http://www.bittorrent.org/beps/bep_0015.html) * [ ] 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 * Metainfo
* [ ] torrent files [spec](https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure) * [ ] torrent files [spec](https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure)
* [ ] magnet links * [ ] magnet links

View File

@@ -7,6 +7,8 @@ use std::str;
use magnolia::bencode::*; use magnolia::bencode::*;
use magnolia::metainfo::Metainfo; use magnolia::metainfo::Metainfo;
use magnolia::torrent::Torrent;
use magnolia::tracker::http;
fn load_file(path: &str) -> io::Result<()> { fn load_file(path: &str) -> io::Result<()> {
let mut buf = Vec::new(); let mut buf = Vec::new();
@@ -14,8 +16,18 @@ fn load_file(path: &str) -> io::Result<()> {
f.read_to_end(&mut buf)?; f.read_to_end(&mut buf)?;
let obj = decode(&buf).unwrap(); let obj = decode(&buf).unwrap();
let meta = Metainfo::from_bencode(obj); let meta = Metainfo::from_bencode(obj).unwrap();
println!("{:#?}", meta);
let t = Torrent {
metainfo: meta,
uploaded: 0,
downloaded: 0,
left: 0,
};
let resp = http::get_peers([1u8; 20], 55555, &t).unwrap();
print!("{:?}", resp);
Ok(()) Ok(())
} }

View File

@@ -1,3 +1,9 @@
extern crate hyper;
extern crate sha1;
extern crate url;
pub mod bencode; pub mod bencode;
pub mod buffer; pub mod buffer;
pub mod metainfo; pub mod metainfo;
pub mod torrent;
pub mod tracker;

8
src/torrent.rs Normal file
View File

@@ -0,0 +1,8 @@
use metainfo::Metainfo;
pub struct Torrent {
pub metainfo: Metainfo,
pub downloaded: u64,
pub uploaded: u64,
pub left: u64,
}

96
src/tracker/http.rs Normal file
View File

@@ -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::<Vec<_>>().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);
}

48
src/tracker/mod.rs Normal file
View File

@@ -0,0 +1,48 @@
pub mod http;
use std::io;
use std::net::Ipv4Addr;
use hyper;
use bencode::DecodeError;
pub type TrackerResult = Result<TrackerResponse, TrackerError>;
#[derive(Debug)]
pub struct TrackerResponse {
pub interval: u64,
pub peers: Vec<Peer>,
}
#[derive(Debug)]
pub enum TrackerError {
IoError(io::Error),
HttpError(hyper::Error),
InvalidResponse,
TrackerFailure(String),
}
impl From<io::Error> for TrackerError {
fn from(from: io::Error) -> Self {
TrackerError::IoError(from)
}
}
impl From<hyper::Error> for TrackerError {
fn from(from: hyper::Error) -> Self {
TrackerError::HttpError(from)
}
}
impl From<DecodeError> for TrackerError {
fn from(_: DecodeError) -> Self {
TrackerError::InvalidResponse
}
}
#[derive(Debug)]
pub struct Peer {
pub addr: Ipv4Addr,
pub port: u16,
}