tracker: add http tracker support
This commit is contained in:
@@ -4,3 +4,9 @@ version = "0.1.0"
|
||||
authors = ["Simon Bernier St-Pierre <sbernierstpierre@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
sha1 = "0.2"
|
||||
url = "1.2"
|
||||
|
||||
[dependencies.hyper]
|
||||
version = "0.9"
|
||||
default-features = false
|
||||
|
||||
2
docs.md
2
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
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
8
src/torrent.rs
Normal file
8
src/torrent.rs
Normal 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
96
src/tracker/http.rs
Normal 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
48
src/tracker/mod.rs
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user