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>"]
|
authors = ["Simon Bernier St-Pierre <sbernierstpierre@gmail.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[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
|
* [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
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
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