diff --git a/Cargo.toml b/Cargo.toml index e31d883..52ecfdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,15 @@ authors = ["Simon Bernier St-Pierre "] [dependencies] byteorder = "0.5" -libc = "0.2" +failure = "0.1" +rand = "0.3" +reqwest = "0.8" +serde = "1.0" +serde_bencode = "0.2.0" +serde_derive = "1.0" +serde_bytes = "0.10" sha1 = "0.2" -url = "1.2" +url = "1.6" [dependencies.hyper] version = "0.9" diff --git a/examples/decode-torrent.rs b/examples/decode-torrent.rs deleted file mode 100644 index 01ca503..0000000 --- a/examples/decode-torrent.rs +++ /dev/null @@ -1,38 +0,0 @@ -extern crate magnolia; - -use std::env; -use std::fs::File; -use std::io::{self, Read}; -use std::thread; -use std::time::Duration; -use std::str; - -use magnolia::bencode::*; -use magnolia::metainfo::Metainfo; -use magnolia::tracker::http; -use magnolia::net::session::Session; - -fn load_file(path: &str) -> io::Result<()> { - let mut buf = Vec::new(); - let mut f = File::open(path)?; - f.read_to_end(&mut buf)?; - - let obj = decode(&buf).unwrap(); - let meta = Metainfo::from_bencode(obj).unwrap(); - - println!("{}", meta.pieces.len()); - - let s = Session::new(); - s.add_torrent(meta); - - loop { - thread::sleep(Duration::from_secs(1)); - } - - Ok(()) -} - -fn main() { - let path = env::args().nth(1).expect("need path to .torrent file"); - load_file(&path).unwrap(); -} diff --git a/examples/print_metainfo.rs b/examples/print_metainfo.rs new file mode 100644 index 0000000..37a0e9a --- /dev/null +++ b/examples/print_metainfo.rs @@ -0,0 +1,19 @@ +extern crate failure; +extern crate magnolia; + +use std::env; + +use failure::Fail; +use magnolia::metainfo::Metainfo; + +fn main() { + let args: Vec = env::args().collect(); + match Metainfo::open(&args[1]) { + Ok(meta) => println!("{:?}", meta), + Err(e) => { + for cause in e.causes() { + println!("{}", cause); + } + } + } +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..4234321 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,6 @@ +same_line_attributes = false +reorder_imports = true +reorder_imports_in_group = true +reorder_extern_crates = true +reorder_extern_crates_in_group = true +indent_style = "Visual" diff --git a/src/bencode/buffer.rs b/src/bencode/buffer.rs deleted file mode 100644 index 8bbebec..0000000 --- a/src/bencode/buffer.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; - -pub struct Buffer<'b> { - inner: &'b [u8], - pos: usize, -} - -impl<'b> Buffer<'b> { - pub fn new(inner: &[u8]) -> Buffer { - Buffer { - inner: inner, - pos: 0, - } - } - - pub fn advance(&mut self, amt: usize) { - self.pos += amt - } - - pub fn find(&self, byte: u8) -> Option { - self[..].iter().position(|&b| b == byte) - } - - pub fn get(&self, idx: usize) -> Option { - self[..].get(idx).map(|&b| b) - } - - pub fn pos(&self) -> usize { - self.pos - } -} - -impl<'b> Index> for Buffer<'b> { - type Output = [u8]; - - fn index(&self, r: Range) -> &[u8] { - &self.inner[self.pos + r.start..self.pos + r.end] - } -} - -impl<'b> Index> for Buffer<'b> { - type Output = [u8]; - - fn index(&self, r: RangeFrom) -> &[u8] { - &self.inner[self.pos + r.start..] - } -} - -impl<'b> Index> for Buffer<'b> { - type Output = [u8]; - - fn index(&self, r: RangeTo) -> &[u8] { - &self.inner[self.pos..self.pos + r.end] - } -} - -impl<'b> Index for Buffer<'b> { - type Output = [u8]; - - fn index(&self, _: RangeFull) -> &[u8] { - &self.inner[self.pos..] - } -} - -#[test] -fn test_advance() { - let mut b = Buffer::new(b"hello"); - b.advance(2); - assert_eq!(b.pos(), 2); -} - -#[test] -fn test_find() { - let b = Buffer::new(b"hello"); - assert_eq!(b.find(b'l'), Some(2)); - assert_eq!(b.find(b'a'), None); -} - -#[test] -fn test_get() { - let mut b = Buffer::new(b"hello"); - b.advance(2); - assert_eq!(b.get(0), Some(b'l')); - assert_eq!(b.get(1), Some(b'l')); - assert_eq!(b.get(2), Some(b'o')); -} - -#[test] -fn test_range() { - let mut b = Buffer::new(b"hello"); - b.advance(2); - assert_eq!(b[1..2], b"l"[..]); - assert_eq!(b[1..], b"lo"[..]); - assert_eq!(b[..2], b"ll"[..]); - assert_eq!(b[..], b"llo"[..]); -} diff --git a/src/bencode/decode.rs b/src/bencode/decode.rs deleted file mode 100644 index 61a7ab9..0000000 --- a/src/bencode/decode.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::collections::BTreeMap; -use std::num::ParseIntError; -use std::str::{self, Utf8Error}; - -use bencode::{Bytes, Object}; -use bencode::buffer::Buffer; - -#[derive(Debug)] -pub struct DecodeError; - -impl From for DecodeError { - fn from(_: Utf8Error) -> Self { - DecodeError - } -} - -impl From for DecodeError { - fn from(_: ParseIntError) -> Self { - DecodeError - } -} - -pub type DecodeResult = Result; - -pub fn decode(data: &[u8]) -> DecodeResult { - let mut buf = Buffer::new(data); - decode_object(&mut buf) -} - -fn decode_object(buf: &mut Buffer) -> DecodeResult { - match buf.get(0) { - Some(b'i') => { - buf.advance(1); - decode_int(buf, b'e') - }, - Some(b'0' ... b'9') => { - decode_bytes(buf) - } - Some(b'l') => { - buf.advance(1); - let mut list = Vec::new(); - while let Some(term) = buf.get(0) { - if term == b'e' { - buf.advance(1); - break; - } - list.push(decode_object(buf)?); - } - Ok(list.into()) - } - Some(b'd') => { - buf.advance(1); - let mut dict = BTreeMap::new(); - while let Some(term) = buf.get(0) { - if term == b'e' { - buf.advance(1); - break; - } - let key = _decode_bytes(buf)?; - let val = decode_object(buf)?; - dict.insert(Bytes(key), val); - } - Ok(dict.into()) - } - _ => Err(DecodeError), - } -} - -fn decode_int(buf: &mut Buffer, term: u8) -> DecodeResult { - _decode_int(buf, term).map(|num| Object::Int(num)) -} - -fn _decode_int(buf: &mut Buffer, term: u8) -> DecodeResult { - if let Some(end) = buf.find(term) { - let obj = { - let num = str::from_utf8(&buf[..end])?; - num.parse()? - }; - buf.advance(end + 1); - Ok(obj) - } else { - Err(DecodeError) - } -} - -fn decode_bytes(buf: &mut Buffer) -> DecodeResult { - _decode_bytes(buf).map(Into::into) -} - -fn _decode_bytes(buf: &mut Buffer) -> Result, DecodeError> { - let size = _decode_int(buf, b':')? as usize; - let bytes = buf[..size].to_vec(); - buf.advance(size); - Ok(bytes) -} - -#[test] -fn test_int_pos() { - let mut buf = Buffer::new(b"i1337e"); - buf.advance(1); - - assert_eq!(decode_int(&mut buf, b'e').unwrap(), Object::Int(1337)); - assert_eq!(buf.pos(), 6); -} - -#[test] -fn test_int_neg() { - let mut buf = Buffer::new(b"i-1337e"); - buf.advance(1); - - assert_eq!(decode_int(&mut buf, b'e').unwrap(), Object::Int(-1337)); - assert_eq!(buf.pos(), 7); -} - -#[test] -fn test_bytes() { - let mut buf = Buffer::new(b"5:hello"); - - assert_eq!(decode_bytes(&mut buf).unwrap(), "hello".into()); - assert_eq!(buf.pos(), 7); -} - -#[test] -fn test_list() { - use bencode::List; - - let mut buf = Buffer::new(b"li1ei2ei3ee"); - - let obj = decode_object(&mut buf).unwrap(); - let list = obj.as_list().unwrap(); - - assert_eq!(list, &List(vec![Object::Int(1), Object::Int(2), Object::Int(3)])); - assert_eq!(buf.pos(), 11); -} - -#[test] -fn test_dict() { - let mut buf = Buffer::new(b"d5:helloi1337ee"); - - let obj = decode_object(&mut buf).unwrap(); - let dict = obj.as_dict().unwrap(); - - assert_eq!(dict.get_int("hello").unwrap(), 1337); - assert_eq!(buf.pos(), 15); -} diff --git a/src/bencode/encode.rs b/src/bencode/encode.rs deleted file mode 100644 index c5b5ad0..0000000 --- a/src/bencode/encode.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::borrow::Cow; - -use bencode::{Dict, Object}; - -/// Allows the encode function to take something that can be converted into an object, -/// or a reference to an object. -pub trait Encodable<'a> { - fn get_object(self) -> Cow<'a, Object>; -} - -impl<'a, T> Encodable<'a> for T where T: Into { - fn get_object(self) -> Cow<'a, Object> { - Cow::Owned(self.into()) - } -} - -impl<'a> Encodable<'a> for &'a Object { - fn get_object(self) -> Cow<'a, Object> { - Cow::Borrowed(self) - } -} - -pub fn encode<'a, O>(obj: O) -> Vec where O: Encodable<'a> { - let mut buff = Vec::new(); - encode_object(&mut buff, &obj.get_object()); - buff -} - -fn encode_object(buff: &mut Vec, obj: &Object) { - match *obj { - Object::Int(num) => encode_int(buff, num), - Object::Bytes(ref bytes) => encode_bytes(buff, bytes), - Object::List(ref list) => encode_list(buff, list), - Object::Dict(ref dict) => encode_dict(buff, dict), - } -} - -fn encode_int(buff: &mut Vec, num: i64) { - buff.extend_from_slice(format!("i{}e", num).as_bytes()); -} - -fn encode_bytes(buff: &mut Vec, bytes: &[u8]) { - buff.extend_from_slice(format!("{}:", bytes.len()).as_bytes()); - buff.extend_from_slice(bytes); -} - -fn encode_list(buff: &mut Vec, list: &[Object]) { - buff.push(b'l'); - for obj in list { - encode_object(buff, obj); - } - buff.push(b'e'); -} - -fn encode_dict(buff: &mut Vec, dict: &Dict) { - buff.push(b'd'); - for (key, val) in dict.iter() { - encode_bytes(buff, key); - encode_object(buff, val); - } - buff.push(b'e'); -} - -#[test] -fn test_int_pos() { - assert_eq!(encode(1337), b"i1337e"); -} - -#[test] -fn test_int_neg() { - assert_eq!(encode(-1337), b"i-1337e"); -} - -#[test] -fn test_bytes() { - assert_eq!(encode("hello"), b"5:hello"); -} - -#[test] -fn test_list() { - let list = vec![Object::Int(1), Object::Int(2), Object::Int(3)]; - assert_eq!(encode(list), b"li1ei2ei3ee"); -} - -#[test] -fn test_dict() { - use bencode::Dict; - - let mut dict = Dict::new(); - dict.insert("hello", 1337); - assert_eq!(encode(dict), b"d5:helloi1337ee") -} diff --git a/src/bencode/mod.rs b/src/bencode/mod.rs deleted file mode 100644 index 38d527e..0000000 --- a/src/bencode/mod.rs +++ /dev/null @@ -1,305 +0,0 @@ -mod buffer; -mod decode; -mod encode; - -use std::collections::BTreeMap; -use std::fmt; -use std::ops::{Deref, DerefMut}; -use std::str; - -pub use self::decode::{decode, DecodeError, DecodeResult}; -pub use self::encode::{encode, Encodable}; - -#[derive(Clone, Eq, PartialEq)] -pub enum Object { - Int(i64), - Bytes(Bytes), - List(List), - Dict(Dict), -} - -impl Object { - pub fn as_int(&self) -> Option { - match *self { - Object::Int(num) => Some(num), - _ => None, - } - } - - pub fn as_bytes(&self) -> Option<&Bytes> { - match *self { - Object::Bytes(ref bytes) => Some(bytes), - _ => None, - } - } - - pub fn as_str(&self) -> Option<&str> { - self.as_bytes().and_then(|b| str::from_utf8(b).ok()) - } - - pub fn as_string(&self) -> Option { - self.as_str().map(|s| s.to_string()) - } - - pub fn as_list(&self) -> Option<&List> { - match *self { - Object::List(ref list) => Some(list), - _ => None, - } - } - - pub fn as_dict(&self) -> Option<&Dict> { - match *self { - Object::Dict(ref dict) => Some(dict), - _ => None, - } - } -} - -impl fmt::Debug for Object { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Object::Int(num) => write!(f, "{}", num), - Object::Bytes(ref bytes) => bytes.fmt(f), - Object::List(ref list) => list.fmt(f), - Object::Dict(ref dict) => dict.fmt(f), - } - } -} - -#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] -pub struct Bytes(Vec); - -impl Bytes { - pub fn new(bytes: A) -> Bytes where A: Into> { - Bytes(bytes.into()) - } - - pub fn str(&self) -> Option<&str> { - str::from_utf8(&self.0).ok() - } - - pub fn string(&self) -> Option { - self.str().map(|s| s.to_string()) - } -} - -impl Deref for Bytes { - type Target = Vec; - - fn deref(&self) -> &Vec { - &self.0 - } -} - -impl DerefMut for Bytes { - fn deref_mut(&mut self) -> &mut Vec { - &mut self.0 - } -} - -impl fmt::Debug for Bytes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match str::from_utf8(&self.0) { - Ok(s) => write!(f, "\"{}\"", s), - Err(_) => { - write!(f, "\"")?; - for &b in self.0.iter() { - write!(f, "{:x}", b)?; - } - write!(f, "\"")?; - Ok(()) - } - } - } -} - -#[derive(Clone, Eq, PartialEq)] -pub struct List(Vec); - -impl List { - pub fn new() -> List { - List(Vec::new()) - } - - pub fn push(&mut self, val: V) where V: Into { - self.0.push(val.into()) - } -} - -impl Deref for List { - type Target = Vec; - - fn deref(&self) -> &Vec { - &self.0 - } -} - -impl DerefMut for List { - fn deref_mut(&mut self) -> &mut Vec { - &mut self.0 - } -} - -impl fmt::Debug for List { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.iter()).finish() - } -} - -#[derive(Clone, Eq, PartialEq)] -pub struct Dict(BTreeMap); - -impl Dict { - pub fn new() -> Dict { - Dict(BTreeMap::new()) - } - - pub fn insert(&mut self, key: K, val: V) where K: Into, V: Into { - self.0.insert(key.into(), val.into()); - } - - pub fn get_object(&self, key: A) -> Option<&Object> where A: Into { - self.0.get(&key.into()) - } - - pub fn get_int(&self, key: A) -> Option where A: Into { - self.0.get(&key.into()).and_then(|o| o.as_int()) - } - - pub fn get_bytes(&self, key: A) -> Option<&Bytes> where A: Into { - self.0.get(&key.into()).and_then(|o| o.as_bytes()) - } - - pub fn get_str(&self, key: A) -> Option<&str> where A: Into { - self.0.get(&key.into()).and_then(|o| o.as_bytes().and_then(|b| str::from_utf8(b).ok())) - } - - pub fn get_string(&self, key: A) -> Option where A: Into { - self.get_str(key).map(|s| s.to_string()) - } - - pub fn get_list(&self, key: A) -> Option<&List> where A: Into { - self.0.get(&key.into()).and_then(|o| o.as_list()) - } - - pub fn get_dict(&self, key: A) -> Option<&Dict> where A: Into { - self.0.get(&key.into()).and_then(|o| o.as_dict()) - } -} - -impl Deref for Dict { - type Target = BTreeMap; - - fn deref(&self) -> &BTreeMap { - &self.0 - } -} - -impl DerefMut for Dict { - fn deref_mut(&mut self) -> &mut BTreeMap { - &mut self.0 - } -} - -impl fmt::Debug for Dict { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_map() - .entries(self.iter()) - .finish() - } -} - -// convertion utilities - -// int - -impl From for Object { - fn from(from: i64) -> Self { - Object::Int(from) - } -} - -// bytes - -impl<'a> From<&'a [u8]> for Bytes { - fn from(from: &'a [u8]) -> Self { - Bytes(from.to_vec()) - } -} - -impl<'a> From<&'a str> for Bytes { - fn from(from: &'a str) -> Self { - Bytes(from.as_bytes().to_vec()) - } -} - -impl From> for Bytes { - fn from(from: Vec) -> Self { - Bytes(from) - } -} - -impl<'a> From<&'a [u8]> for Object { - fn from(from: &'a [u8]) -> Self { - Object::Bytes(Bytes(from.to_vec())) - } -} - -impl<'a> From<&'a str> for Object { - fn from(from: &'a str) -> Self { - Object::Bytes(Bytes(from.as_bytes().to_vec())) - } -} - -impl From> for Object { - fn from(from: Vec) -> Self { - Object::Bytes(Bytes(from)) - } -} - -impl From for Object { - fn from(from: Bytes) -> Self { - Object::Bytes(from) - } -} - -// list - -impl From> for List where T: Into { - fn from(from: Vec) -> Self { - List(from.into_iter().map(Into::into).collect()) - } -} - -impl From> for Object where T: Into { - fn from(from: Vec) -> Self { - Object::List(List(from.into_iter().map(Into::into).collect())) - } -} - -impl From for Object { - fn from(from: List) -> Self { - Object::List(from) - } -} - -// dict - -impl From> for Dict { - fn from(from: BTreeMap) -> Self { - Dict(from) - } -} - -impl From> for Object { - fn from(from: BTreeMap) -> Self { - Object::Dict(Dict(from)) - } -} - -impl From for Object { - fn from(from: Dict) -> Self { - Object::Dict(from) - } -} diff --git a/src/bitfield.rs b/src/bitfield.rs new file mode 100644 index 0000000..bca144e --- /dev/null +++ b/src/bitfield.rs @@ -0,0 +1,183 @@ +use std::u32; + +pub struct Bitfield { + inner: Vec, + len: usize, +} + + +impl Bitfield { + pub fn new(len: usize) -> Bitfield { + Bitfield { + inner: vec![0u32; f64::ceil(len as f64 / 32.) as usize], + len: len, + } + } + + #[inline] + fn calc_size(&self, idx: usize) -> (usize, usize) { + (idx / 32, idx % 32) + } + + #[inline] + fn check_index(&self, idx: usize) { + if idx >= self.len() { + panic!("index out of range"); + } + } + + #[inline] + pub fn len(&self) -> usize { + self.len + } + + #[inline] + pub fn get(&self, idx: usize) -> bool { + self.check_index(idx); + let (index, offset) = self.calc_size(idx); + unsafe { self.inner.get_unchecked(index) & (1 << offset) != 0 } + } + + #[inline] + pub fn set(&mut self, idx: usize, val: bool) { + self.check_index(idx); + let (index, offset) = self.calc_size(idx); + + let word = unsafe { self.inner.get_unchecked_mut(index) }; + if val { + *word |= 1 << offset; + } else { + *word &= !(1 << offset); + } + } + + #[inline] + pub fn iter(&self) -> Iter { + Iter { + bitfield: self, + index: 0, + } + } + + #[inline] + pub fn num_set(&self) -> usize { + self.inner + .iter() + .map(|&x| x.count_ones() as usize) + .sum::() + } + + #[inline] + pub fn ratio(&self) -> f64 { + self.num_set() as f64 / self.len() as f64 + } + + #[inline] + pub fn all(&self) -> bool { + self.num_set() == self.len() + } +} + +pub struct Iter<'a> { + bitfield: &'a Bitfield, + index: usize, +} + +impl<'a> Iterator for Iter<'a> { + type Item = bool; + + fn next(&mut self) -> Option { + if self.index < self.bitfield.len() { + let bit = self.bitfield.get(self.index); + self.index += 1; + Some(bit) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.bitfield.len(), Some(self.bitfield.len())) + } +} + +#[test] +fn test_new_len() { + let b = Bitfield::new(50); + assert_eq!(b.len, 50); + assert_eq!(b.inner.len(), 2); + assert_eq!(b.len(), 50); + // make sure every bit is set to 0 + for i in 0..b.len() { + assert_eq!(b.get(i), false); + } +} + +#[test] +fn test_get_set() { + let mut b = Bitfield::new(150); + b.set(0, true); + b.set(45, true); + b.set(140, true); + assert_eq!(b.get(0), true); + assert_eq!(b.get(45), true); + assert_eq!(b.get(140), true); + + b.set(140, false); + assert_eq!(b.get(140), false); +} + +#[test] +#[should_panic] +fn test_get_outside() { + let b = Bitfield::new(2); + b.get(2); +} + +#[test] +#[should_panic] +fn test_set_outside() { + let mut b = Bitfield::new(2); + b.set(2, true); +} + +#[test] +fn test_iter() { + let mut b = Bitfield::new(5); + b.set(0, true); + b.set(2, true); + b.set(4, true); + assert_eq!( + b.iter().collect::>(), + vec![true, false, true, false, true] + ); +} + +#[test] +fn test_num_set() { + let mut b = Bitfield::new(70); + b.set(0, true); + b.set(45, true); + b.set(68, true); + assert_eq!(b.num_set(), 3); + + b.set(45, false); + assert_eq!(b.num_set(), 2); +} + +#[test] +fn test_ratio() { + let mut b = Bitfield::new(4); + b.set(0, true); + b.set(1, true); + assert_eq!(b.ratio(), 0.5); +} + +#[test] +fn test_all() { + let mut b = Bitfield::new(2); + b.set(0, true); + assert_eq!(b.all(), false); + b.set(1, true); + assert_eq!(b.all(), true); +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f879147 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,69 @@ +use std::io; + +use failure; +use reqwest; +use serde_bencode; +use url; + +#[derive(Debug, Fail)] +pub enum Error { + #[fail(display = "Bencode Error {}", _0)] + BencodeError(#[cause] + serde_bencode::Error), + + #[fail(display = "Http Error {}", _0)] + HttpError(#[cause] + reqwest::Error), + + #[fail(display = "IO Error {}", _0)] + IoError(#[cause] + io::Error), + + #[fail(display = "Tracker Error {}", _0)] + Tracker(String), + + #[fail(display = "URL Error {}", _0)] + UrlError(#[cause] + url::ParseError), + + #[fail(display = "Error {}", _0)] + Custom(failure::Error), +} + +impl Error { + pub fn tracker(msg: M) -> Self + where M: Into + { + Error::Tracker(msg.into()) + } +} + +impl From for Error { + fn from(err: serde_bencode::Error) -> Error { + Error::BencodeError(err) + } +} + +impl From for Error { + fn from(err: reqwest::Error) -> Error { + Error::HttpError(err) + } +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::IoError(err) + } +} + +impl From for Error { + fn from(err: url::ParseError) -> Error { + Error::UrlError(err) + } +} + +impl From for Error { + fn from(err: failure::Error) -> Error { + Error::Custom(err) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7c9c340..129def9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,21 @@ extern crate byteorder; -extern crate hyper; -extern crate libc; +#[macro_use] +extern crate failure; +extern crate rand; +extern crate reqwest; +extern crate serde; +extern crate serde_bencode; +extern crate serde_bytes; +#[macro_use] +extern crate serde_derive; extern crate sha1; extern crate url; -pub mod bencode; +#[macro_use] +mod macros; +pub mod bitfield; +pub mod error; pub mod metainfo; -pub mod net; +// pub mod net; +pub mod torrent; pub mod tracker; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..4d93bb8 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,23 @@ +#[macro_export] +macro_rules! bail2 { + ($e:expr) => { + return Err(::failure::err_msg($e).into()); + }; + ($fmt:expr, $($arg:tt)+) => { + return Err(::failure::err_msg(format!($fmt, $($arg)+)).into()); + }; +} + +#[macro_export] +macro_rules! ensure2 { + ($cond:expr, $e:expr) => { + if !($cond) { + bail2!($e); + } + }; + ($cond:expr, $fmt:expr, $($arg:tt)+) => { + if !($cond) { + bail2!($fmt, $($arg)+); + } + }; +} diff --git a/src/metainfo.rs b/src/metainfo.rs index ceb7b8d..dad9d0c 100644 --- a/src/metainfo.rs +++ b/src/metainfo.rs @@ -1,154 +1,130 @@ -use std::fmt; +use std::fmt::{self, Write}; +use std::fs; +use std::io::Read; use std::ops::{Deref, DerefMut}; -use std::path::PathBuf; +use std::path::Path; +use rand::{thread_rng, Rng}; +use serde_bencode; +use serde_bytes::ByteBuf; use sha1::Sha1; -use bencode::{encode, Object}; +use error::Error; -macro_rules! ts { - ( $e:expr ) => { - match $e { - Some(x) => x, - None => return None, - } - } +#[derive(Debug, Deserialize, Serialize)] +pub struct Metainfo { + pub info: Info, + pub announce: String, + #[serde(rename = "announce-list")] + pub announce_list: Option>>, + #[serde(rename = "creation date")] + pub creation_date: Option, + pub comment: Option, + #[serde(rename = "created by")] + pub created_by: Option, + #[serde(default)] + pub encoding: Option, } -#[derive(Debug)] -pub struct Metainfo { - pub announce: String, - pub announce_list: Vec>, - pub files: Vec, - pub info_hash: Hash, - pub piece_length: u32, - pub num_pieces: u32, - pub pieces: Vec, +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Info { + Single { + #[serde(rename = "piece length")] + piece_length: u64, + pieces: ByteBuf, + private: Option, + name: String, + length: u64, + md5sum: Option, + }, + Multiple { + #[serde(rename = "piece length")] + piece_length: u64, + pieces: ByteBuf, + private: Option, + name: String, + files: Vec, + }, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct File { pub length: u64, + pub md5sum: Option, + pub path: Vec, } impl Metainfo { - pub fn from_bencode(obj: O) -> Option where O: Into { - let obj = obj.into(); - let metainfo = ts!(obj.as_dict()); - let info = ts!(metainfo.get_dict("info")); - - let announce = ts!(metainfo.get_string("announce")); - let info_hash = sha1(&encode(ts!(metainfo.get_object("info")))); - let name = ts!(info.get_str("name")); - let piece_length = ts!(info.get_int("piece length")); - let pieces = ts!(info.get_bytes("pieces")); - let mut total_length = 0; - - if pieces.len() % 20 != 0 { - return None; - } - - let mut announce_list = vec![]; - if let Some(list) = metainfo.get_list("announce-list") { - for tier in list.iter() { - let mut urls = vec![]; - for url in ts!(tier.as_list()).iter() { - urls.push(ts!(url.as_string())); - } - announce_list.push(urls); - } - } - - let mut files = vec![]; - if let Some(list) = info.get_list("files") { - for obj in list.iter() { - let file = ts!(obj.as_dict()); - - let mut path = PathBuf::new(); - path.push(name); - for component in ts!(file.get_list("path")).iter() { - path.push(ts!(component.as_str())); - } - - let length = ts!(file.get_int("length")) as u64; - total_length += length; - - files.push(MetainfoFile { - length: length, - path: path, - }); - } - } else { - let mut path = PathBuf::new(); - path.push(name); - - total_length = ts!(info.get_int("length")) as u64; - - files.push(MetainfoFile{ - length: total_length, - path: path, - }) - } - - let pieces = pieces.chunks(20).map(Hash::from_slice).collect::>(); - - Some(Metainfo { - announce: announce, - announce_list: announce_list, - files: files, - info_hash: info_hash, - piece_length: piece_length as u32, - num_pieces: pieces.len() as u32, - pieces: pieces, - length: total_length, - }) + /// Create a Metainfo structure from reading a file. + pub fn open>(path: P) -> Result { + let mut f = fs::File::open(path.as_ref())?; + let mut buf = Vec::new(); + f.read_to_end(&mut buf)?; + let meta = serde_bencode::de::from_bytes(&buf)?; + Ok(meta) } } -#[derive(Debug)] -pub struct MetainfoFile { - pub length: u64, - pub path: PathBuf, -} - -impl MetainfoFile { - pub fn new(length: u64, path: PathBuf) -> Self { - MetainfoFile { - length: length, - path: path, - } - } -} - -fn sha1(bytes: &[u8]) -> Hash { - let mut hasher = Sha1::new(); - hasher.update(bytes); - Hash::new(hasher.digest().bytes()) -} - - #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub struct Hash([u8; 20]); + impl Hash { + /// Create a Hash filled with zeroes. pub fn alloc() -> Hash { Hash([0u8; 20]) } + /// Create a Hash from a 20 byte array. pub fn new(inner: [u8; 20]) -> Hash { Hash(inner) } + /// Create a Hash from a 20 byte slice. + /// + /// # Panics + /// If the slice is not of length 20, this function panics. pub fn from_slice(bytes: &[u8]) -> Hash { assert_eq!(bytes.len(), 20); let mut hash = Hash::alloc(); hash.copy_from_slice(bytes); hash } + + /// Create a Hash filled with random bytes. + pub fn random() -> Hash { + let mut hash = Hash::alloc(); + thread_rng().fill_bytes(&mut hash); + hash + } + + /// Compute the SHA-1 hash of the slice. + pub fn sha1(bytes: &[u8]) -> Hash { + let mut hasher = Sha1::new(); + hasher.update(bytes); + Hash::new(hasher.digest().bytes()) + } + + /// Compute the info hash of a Metainfo structure. + pub fn info_hash(metainfo: &Metainfo) -> Hash { + let buf = serde_bencode::to_bytes(&metainfo.info).expect("unable to compute info hash"); + Hash::sha1(&buf) + } + + /// Create a hexadecimal representation of this Hash. + pub fn hex(&self) -> String { + let mut buf = String::with_capacity(40); + for byte in self.0.iter() { + let _ = write!(buf, "{:02x}", byte); + } + buf + } } impl fmt::Debug for Hash { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for &b in self.0.iter() { - write!(f, "{:x}", b)? - } - Ok(()) + write!(f, "Hash({})", self.hex()) } } @@ -166,68 +142,9 @@ impl DerefMut for Hash { } } -#[cfg(test)] -mod tests { - use std::path::Path; - - use bencode::{Dict, List, Object}; - use metainfo::Metainfo; - - #[test] - fn test_single_file() { - let mut meta = Dict::new(); - meta.insert("announce", "http://ubuntu.com/tracker:6969"); - - let mut info = Dict::new(); - info.insert("name", "ubuntu-16.04-desktop.iso"); - info.insert("length", 1024 * 1024 * 1024); - info.insert("piece length", 1024 * 512); - info.insert("pieces", ""); - - meta.insert("info", info); - - let metainfo = Metainfo::from_bencode(meta).unwrap(); - assert_eq!(metainfo.announce, "http://ubuntu.com/tracker:6969"); - assert_eq!(metainfo.piece_length, 1024 * 512); - - let file = &metainfo.files[0]; - assert_eq!(file.path.to_str().unwrap(), "ubuntu-16.04-desktop.iso"); - assert_eq!(file.length, 1024 * 1024 * 1024); - } - - #[test] - fn test_multiple_files() { - let mut meta = Dict::new(); - meta.insert("announce", "http://ubuntu.com/tracker:6969"); - - let mut info = Dict::new(); - info.insert("name", "base_folder"); - info.insert("piece length", 1024 * 512); - info.insert("pieces", ""); - - let mut files = List::new(); - let mut file1 = Dict::new(); - file1.insert("path", vec!["folder2", "image.txt"]); - file1.insert("length", 512); - files.push(file1); - let mut file2 = Dict::new(); - file2.insert("path", vec!["txt.jpg"]); - file2.insert("length", 64); - files.push(file2); - info.insert("files", Object::List(files)); - - meta.insert("info", info); - - let metainfo = Metainfo::from_bencode(meta).unwrap(); - assert_eq!(metainfo.announce, "http://ubuntu.com/tracker:6969"); - assert_eq!(metainfo.piece_length, 1024 * 512); - - let file1 = &metainfo.files[0]; - assert_eq!(file1.path, Path::new("base_folder/folder2/image.txt")); - assert_eq!(file1.length, 512); - - let file2 = &metainfo.files[1]; - assert_eq!(file2.path, Path::new("base_folder/txt.jpg")); - assert_eq!(file2.length, 64); - } +#[test] +fn test_hash() { + let metainfo = Metainfo::open("samples/debian-9.1.0-amd64-netinst.iso.torrent").unwrap(); + let info_hash = Hash::info_hash(&metainfo); + assert_eq!(info_hash.hex(), "fd5fdf21aef4505451861da97aa39000ed852988"); } diff --git a/src/torrent.rs b/src/torrent.rs new file mode 100644 index 0000000..a210c12 --- /dev/null +++ b/src/torrent.rs @@ -0,0 +1,32 @@ +use metainfo::{Hash, Info, Metainfo}; + +pub struct Torrent { + pub info_hash: Hash, + pub metainfo: Metainfo, + pub uploaded: u64, + pub downloaded: u64, + pub left: u64, + pub size: u64, +} + +impl Torrent { + pub fn new(metainfo: Metainfo) -> Self { + let size = match metainfo.info { + Info::Single { length, .. } => length, + Info::Multiple { ref files, .. } => { + let mut size = 0; + for file in files.iter() { + size += file.length; + } + size + } + }; + + Torrent { info_hash: Hash::info_hash(&metainfo), + metainfo: metainfo, + uploaded: 0, + downloaded: 0, + left: size, + size: size, } + } +} diff --git a/src/tracker/bin.rs b/src/tracker/bin.rs new file mode 100644 index 0000000..b194121 --- /dev/null +++ b/src/tracker/bin.rs @@ -0,0 +1,184 @@ +use std::error; +use std::fmt::{self, Display}; +use std::io::{self, Write}; +use std::marker::PhantomData; + +use byteorder::{ByteOrder, WriteBytesExt}; +use serde::ser::{self, Impossible}; + + +#[derive(Debug)] +pub enum Error { + Custom(String), + Io(io::Error), + NotImplemented, +} + +impl error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::Custom(_) => "custom", + Error::Io(ref err) => err.description(), + Error::NotImplemented => "not implemented", + } + } +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::Io(err) + } +} + +impl Display for Error { + fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { + Ok(()) + } +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self + where T: Display + { + Error::Custom(format!("{}", msg)) + } +} + +pub struct Serializer<'a, W: Write + 'a, B: ByteOrder> { + buf: &'a mut W, + _order: PhantomData, +} + +impl<'a, W: Write + 'a, B: ByteOrder> ser::Serializer for Serializer<'a, W, B> { + type Ok = (); + type Error = Error; + + type SerializeSeq = Impossible<(), Error>; + type SerializeTuple = Impossible<(), Error>; + type SerializeTupleStruct = Impossible<(), Error>; + type SerializeTupleVariant = Impossible<(), Error>; + type SerializeMap = Impossible<(), Error>; + type SerializeStruct = Impossible<(), Error>; + type SerializeStructVariant = Impossible<(), Error>; + + fn serialize_bool(self, v: bool) -> Result { + self.buf.write_u8(v as u8)?; + Ok(()) + } + fn serialize_i8(self, v: i8) -> Result { + self.buf.write_i8(v)?; + Ok(()) + } + fn serialize_i16(self, v: i16) -> Result { + Err(Error::NotImplemented) + } + fn serialize_i32(self, v: i32) -> Result { + Err(Error::NotImplemented) + } + fn serialize_i64(self, v: i64) -> Result { + Err(Error::NotImplemented) + } + fn serialize_u8(self, v: u8) -> Result { + Err(Error::NotImplemented) + } + fn serialize_u16(self, v: u16) -> Result { + Err(Error::NotImplemented) + } + fn serialize_u32(self, v: u32) -> Result { + Err(Error::NotImplemented) + } + fn serialize_u64(self, v: u64) -> Result { + Err(Error::NotImplemented) + } + fn serialize_f32(self, v: f32) -> Result { + Err(Error::NotImplemented) + } + fn serialize_f64(self, v: f64) -> Result { + Err(Error::NotImplemented) + } + fn serialize_char(self, v: char) -> Result { + Err(Error::NotImplemented) + } + fn serialize_str(self, v: &str) -> Result { + Err(Error::NotImplemented) + } + fn serialize_bytes(self, v: &[u8]) -> Result { + Err(Error::NotImplemented) + } + fn serialize_none(self) -> Result { + Err(Error::NotImplemented) + } + fn serialize_some(self, value: &T) -> Result + where T: ser::Serialize + { + Err(Error::NotImplemented) + } + fn serialize_unit(self) -> Result { + Err(Error::NotImplemented) + } + fn serialize_unit_struct(self, name: &'static str) -> Result { + Err(Error::NotImplemented) + } + fn serialize_unit_variant(self, + name: &'static str, + variant_index: u32, + variant: &'static str) + -> Result { + Err(Error::NotImplemented) + } + fn serialize_newtype_struct(self, + name: &'static str, + value: &T) + -> Result + where T: ser::Serialize + { + Err(Error::NotImplemented) + } + fn serialize_newtype_variant(self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T) + -> Result + where T: ser::Serialize + { + Err(Error::NotImplemented) + } + fn serialize_seq(self, len: Option) -> Result { + Err(Error::NotImplemented) + } + fn serialize_tuple(self, len: usize) -> Result { + Err(Error::NotImplemented) + } + fn serialize_tuple_struct(self, + name: &'static str, + len: usize) + -> Result { + Err(Error::NotImplemented) + } + fn serialize_tuple_variant(self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize) + -> Result { + Err(Error::NotImplemented) + } + fn serialize_map(self, len: Option) -> Result { + Err(Error::NotImplemented) + } + fn serialize_struct(self, + name: &'static str, + len: usize) + -> Result { + Err(Error::NotImplemented) + } + fn serialize_struct_variant(self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize) + -> Result { + Err(Error::NotImplemented) + } +} diff --git a/src/tracker/http.rs b/src/tracker/http.rs index cc39f4f..a95c2cf 100644 --- a/src/tracker/http.rs +++ b/src/tracker/http.rs @@ -1,96 +1,100 @@ +use std::fmt::Display; use std::io::Read; use std::net::Ipv4Addr; -use hyper::Client; -use url::form_urlencoded::byte_serialize; +use reqwest; +use serde_bencode; +use serde_bytes::ByteBuf; +use url::Url; -use bencode::{decode, Dict}; -use metainfo::{Hash, Metainfo}; -use tracker::{Peer, TrackerError, TrackerResponse, TrackerResult}; +use super::{Event, Peer}; +use error::Error; +use metainfo::Hash; +use torrent::Torrent; -macro_rules! ts { - ( $e:expr ) => { - match $e { - Some(x) => x, - None => return Err(TrackerError::InvalidResponse), +pub struct QueryBuilder { + params: Vec<(&'static str, String)>, +} + +impl QueryBuilder { + pub fn new() -> Self { + QueryBuilder { params: Vec::new() } + } + + pub fn add(&mut self, name: &'static str, val: &Display) { + self.params.push((name, format!("{}", val))); + } + + pub fn apply_to(&self, url: &mut Url) { + let mut query_mut = url.query_pairs_mut(); + for &(key, ref val) in self.params.iter() { + query_mut.append_pair(key, &val); } } } -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 peer_from_bytes(chunk: &[u8]) -> Peer { + assert!(chunk.len() == 6); + Peer { addr: Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]), + port: (chunk[4] as u16) << 8 | (chunk[5] as u16) << 0, } } -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) +pub fn fetch_peers(mut url: Url, + event: Option, + torrent: &Torrent, + peer_id: &Hash, + port: u16) + -> Result, Error> { + let mut query = QueryBuilder::new(); + query.add("info_hash", &torrent.info_hash.hex()); + query.add("peer_id", &peer_id.hex()); + query.add("port", &port); + query.add("uploaded", &torrent.uploaded); + query.add("downloaded", &torrent.downloaded); + query.add("left", &torrent.left); + query.add("compact", &1); + + if let Some(event) = event { + query.add("event", &event.as_str()); + } + + query.apply_to(&mut url); + + let mut resp = reqwest::get(url)?; + ensure2!(resp.status().is_success(), + "Tracker failed, http code {}", + resp.status().as_u16()); + + let mut buf = Vec::new(); + resp.read_to_end(&mut buf)?; + + match serde_bencode::from_bytes(&buf)? { + Response::Failure { reason } => bail2!("Tracker failure, {}", reason), + Response::Ok { peers, .. } => { + ensure2!(peers.len() % 6 == 0, "Tracker blob is invalid"); + let peers = peers.chunks(6).map(peer_from_bytes).collect(); + Ok(peers) } - - 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("") -} +#[derive(Deserialize, Serialize)] +pub enum Response { + Failure { + #[serde(rename = "failure reason")] + reason: String, + }, -#[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); + Ok { + #[serde(rename = "warning message")] + warning: Option, + interval: i64, + #[serde(rename = "min interval")] + min_interval: Option, + tracker_id: String, + complete: u64, + incomplete: u64, + peers: ByteBuf, // only supports the compact format for now + }, } diff --git a/src/tracker/mod.rs b/src/tracker/mod.rs index 17d9a0e..91d9a86 100644 --- a/src/tracker/mod.rs +++ b/src/tracker/mod.rs @@ -1,48 +1,58 @@ -pub mod http; - -use std::io; use std::net::Ipv4Addr; -use hyper; +use url::Url; -use bencode::DecodeError; +mod bin; +mod http; +mod udp; -pub type TrackerResult = Result; +use failure; -#[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 - } -} +use error::Error; +use metainfo::Hash; +use torrent::Torrent; #[derive(Copy, Clone, Debug)] pub struct Peer { pub addr: Ipv4Addr, pub port: u16, } + +pub fn fetch_peers(url: &str, + event: Option, + torrent: &Torrent, + peer_id: &Hash, + port: u16) + -> Result, Error> { + let url = Url::parse(url)?; + match url.scheme() { + "http" | "https" => http::fetch_peers(url, event, torrent, peer_id, port), + "udp" => udp::fetch_peers(url, event, torrent, peer_id, port), + _ => bail2!("Unknown tracker scheme: %s {}", url.scheme()), + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Event { + Completed, + Started, + Stopped, +} + +impl Event { + pub fn as_str(&self) -> &'static str { + match *self { + Event::Completed => "completed", + Event::Started => "started", + Event::Stopped => "stopped", + } + } + + pub fn as_int(&self) -> u32 { + match *self { + Event::Completed => 1, + Event::Started => 2, + Event::Stopped => 3, + } + } +} diff --git a/src/tracker/udp.rs b/src/tracker/udp.rs new file mode 100644 index 0000000..9085896 --- /dev/null +++ b/src/tracker/udp.rs @@ -0,0 +1,76 @@ +use std::io::{self, Cursor, Read, Write}; +use std::net::UdpSocket; + +use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; +use rand::{thread_rng, Rng}; +use serde::ser; +use url::Url; + +use super::{Event, Peer}; +use error::Error; +use metainfo::Hash; +use torrent::Torrent; + +const CONNECT: u32 = 0; +const ANNOUNCE: u32 = 1; + +pub fn fetch_peers(mut url: Url, + event: Option, + torrent: &Torrent, + peer_id: &Hash, + port: u16) + -> Result, Error> { + let host = url.host_str().ok_or_else(|| Error::tracker("invalid host"))?; + let port = url.port().ok_or_else(|| Error::tracker("invalid port"))?; + + let mut sock = UdpSocket::bind("0.0.0.0:0")?; + + let trans = send_connect(&mut sock)?; + let conn = recv_connect(&mut sock, trans); + + + Ok(Vec::new()) +} + +fn send_connect(sock: &mut UdpSocket) -> io::Result { + let trans = thread_rng().next_u32(); + let mut buf = [0u8; 128]; + let mut cur = Cursor::new(&mut buf[..]); + + cur.write_u64::(0x41727101980); + cur.write_u32::(CONNECT); + cur.write_u32::(trans); + + Ok(trans) +} + +fn recv_connect(sock: &mut UdpSocket, own_trans: u32) -> io::Result { + let mut buf = [0u8; 128]; + let _ = sock.recv_from(&mut buf); + let mut cur = Cursor::new(&mut buf[..]); + + let action = cur.read_u32::()?; + let trans = cur.read_u32::()?; + let conn = cur.read_u64::()?; + + assert!(action == CONNECT); // TODO error + assert!(trans == own_trans); // TODO error + Ok(conn) +} + +fn send_announce(sock: &mut UdpSocket, + conn: u64, + event: Option, + torrent: &Torrent, + peer_id: &Hash, + port: u16) + -> io::Result<(u32)> { + let trans = thread_rng().next_u32(); + let mut buf = [0u8; 256]; + let mut cur = Cursor::new(&mut buf[..]); + + cur.write_u64::(conn); + cur.write_u32::(ANNOUNCE); + + Ok(trans) +}