diff --git a/examples/decode-torrent.rs b/examples/decode-torrent.rs index e2dc744..9bf349a 100644 --- a/examples/decode-torrent.rs +++ b/examples/decode-torrent.rs @@ -6,6 +6,7 @@ use std::io::{self, Read}; use std::str; use magnolia::bencode::*; +use magnolia::metainfo::Metainfo; fn load_file(path: &str) -> io::Result<()> { let mut buf = Vec::new(); @@ -13,7 +14,8 @@ fn load_file(path: &str) -> io::Result<()> { f.read_to_end(&mut buf)?; let obj = decode(&buf).unwrap(); - println!("{:#?}", obj); + let meta = Metainfo::from_bencode(obj); + println!("{:#?}", meta); Ok(()) } diff --git a/src/bencode/decode.rs b/src/bencode/decode.rs index c6134a0..ac33fa6 100644 --- a/src/bencode/decode.rs +++ b/src/bencode/decode.rs @@ -46,7 +46,7 @@ fn decode_object(buf: &mut Buffer) -> DecodeResult { } list.push(decode_object(buf)?); } - Ok(List::new(list)) + Ok(List::wrap(list)) } Some(b'd') => { buf.advance(1); @@ -60,7 +60,7 @@ fn decode_object(buf: &mut Buffer) -> DecodeResult { let val = decode_object(buf)?; dict.insert(Bytes(key), val); } - Ok(Dict::new(dict)) + Ok(Dict::wrap(dict)) } _ => Err(DecodeError), } @@ -84,7 +84,7 @@ fn _decode_int(buf: &mut Buffer, term: u8) -> DecodeResult { } fn decode_bytes(buf: &mut Buffer) -> DecodeResult { - _decode_bytes(buf).map(|bytes| Bytes::new(bytes)) + _decode_bytes(buf).map(|bytes| Bytes::wrap(bytes)) } fn _decode_bytes(buf: &mut Buffer) -> Result, DecodeError> { @@ -116,7 +116,7 @@ fn test_int_neg() { fn test_bytes() { let mut buf = Buffer::new(b"5:hello"); - assert_eq!(decode_bytes(&mut buf).unwrap(), Bytes::new(b"hello".to_vec())); + assert_eq!(decode_bytes(&mut buf).unwrap(), Bytes::wrap(b"hello".to_vec())); assert_eq!(buf.pos(), 7); } diff --git a/src/bencode/encode.rs b/src/bencode/encode.rs index a9592ee..dca2622 100644 --- a/src/bencode/encode.rs +++ b/src/bencode/encode.rs @@ -57,7 +57,7 @@ fn test_int_neg() { fn test_bytes() { use bencode::Bytes; - assert_eq!(encode(Bytes::new(b"hello".to_vec())), b"5:hello"); + assert_eq!(encode(Bytes::wrap(b"hello".to_vec())), b"5:hello"); } #[test] @@ -65,7 +65,7 @@ fn test_list() { use bencode::List; let list = vec![Object::Int(1), Object::Int(2), Object::Int(3)]; - assert_eq!(encode(List::new(list)), b"li1ei2ei3ee"); + assert_eq!(encode(List::wrap(list)), b"li1ei2ei3ee"); } #[test] @@ -75,5 +75,5 @@ fn test_dict() { let mut dict = BTreeMap::new(); dict.insert(Bytes(b"hello".to_vec()), Object::Int(1337)); - assert_eq!(encode(Dict::new(dict)), b"d5:helloi1337ee") + assert_eq!(encode(Dict::wrap(dict)), b"d5:helloi1337ee") } diff --git a/src/bencode/mod.rs b/src/bencode/mod.rs index 2d8847b..10fd9c7 100644 --- a/src/bencode/mod.rs +++ b/src/bencode/mod.rs @@ -87,8 +87,12 @@ impl fmt::Debug for Object { pub struct Bytes(Vec); impl Bytes { - pub fn new(bytes: Vec) -> Object { - Object::Bytes(Bytes(bytes)) + pub fn new(bytes: A) -> Bytes where A: Into> { + Bytes(bytes.into()) + } + + pub fn wrap(bytes: A) -> Object where A: Into> { + Object::Bytes(Bytes(bytes.into())) } pub fn str(&self) -> Option<&str> { @@ -140,7 +144,11 @@ impl<'a> AsBytes for &'a [u8] { pub struct List(Vec); impl List { - pub fn new(list: Vec) -> Object { + pub fn new() -> List { + List(Vec::new()) + } + + pub fn wrap(list: Vec) -> Object { Object::List(List(list)) } } @@ -163,7 +171,11 @@ impl DerefMut for List { pub struct Dict(BTreeMap); impl Dict { - pub fn new(dict: BTreeMap) -> Object { + pub fn new() -> Dict { + Dict(BTreeMap::new()) + } + + pub fn wrap(dict: BTreeMap) -> Object { Object::Dict(Dict(dict)) } diff --git a/src/lib.rs b/src/lib.rs index 842fca0..d1c0084 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod bencode; pub mod buffer; +pub mod metainfo; diff --git a/src/metainfo.rs b/src/metainfo.rs new file mode 100644 index 0000000..98bbd03 --- /dev/null +++ b/src/metainfo.rs @@ -0,0 +1,169 @@ +use std::path::PathBuf; + +use sha1::Sha1; + +use bencode::{encode, Object}; + +macro_rules! ts { + ( $e:expr ) => { + match $e { + Some(x) => x, + None => return None, + } + } +} + +#[derive(Debug)] +pub struct Metainfo { + pub announce: String, + pub announce_list: Vec>, + pub files: Vec, + pub info_hash: [u8; 20], + pub piece_length: u64, + pub pieces: Vec<[u8; 20]>, +} + +impl Metainfo { + pub fn from_bencode(obj: Object) -> Option { + 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 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())); + } + + files.push(MetainfoFile { + length: ts!(file.get_int("length")) as u64, + path: path, + }); + } + } else { + let mut path = PathBuf::new(); + path.push(name); + + files.push(MetainfoFile{ + length: ts!(info.get_int("length")) as u64, + path: path, + }) + } + + Some(Metainfo { + announce: announce, + announce_list: announce_list, + files: files, + info_hash: info_hash, + piece_length: piece_length as u64, + pieces: pieces.chunks(20).map(|c| piece_from_slice(c)).collect::>(), + }) + } +} + +#[derive(Debug)] +pub struct MetainfoFile { + pub length: u64, + pub path: PathBuf, +} + +fn sha1(bytes: &[u8]) -> [u8; 20] { + let mut hasher = Sha1::new(); + hasher.update(bytes); + hasher.digest().bytes() +} + +fn piece_from_slice(src: &[u8]) -> [u8; 20] { + assert_eq!(src.len(), 20); + + let mut dst = [0u8; 20]; + dst.copy_from_slice(src); + dst +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use bencode::{Bytes, Dict, List, Object}; + use metainfo::Metainfo; + + #[test] + fn test_single_file() { + let mut meta = Dict::new(); + meta.insert(Bytes::new("announce"), Bytes::wrap("http://ubuntu.com/tracker:6969")); + + let mut info = Dict::new(); + info.insert(Bytes::new("name"), Bytes::wrap("ubuntu-16.04-desktop.iso")); + info.insert(Bytes::new("length"), Object::Int(1024 * 1024 * 1024)); + info.insert(Bytes::new("piece length"), Object::Int(1024 * 512)); + info.insert(Bytes::new("pieces"), Bytes::wrap("")); + + meta.insert(Bytes::new("info"), Object::Dict(info)); + + let metainfo = Metainfo::from_bencode(Object::Dict(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(Bytes::new("announce"), Bytes::wrap("http://ubuntu.com/tracker:6969")); + + let mut info = Dict::new(); + info.insert(Bytes::new("name"), Bytes::wrap("base_folder")); + info.insert(Bytes::new("piece length"), Object::Int(1024 * 512)); + info.insert(Bytes::new("pieces"), Bytes::wrap("")); + + let mut files = List::new(); + let mut file1 = Dict::new(); + file1.insert(Bytes::new("path"), List::wrap(vec![Bytes::wrap("folder2"), Bytes::wrap("image.txt")])); + file1.insert(Bytes::new("length"), Object::Int(512)); + files.push(Object::Dict(file1)); + let mut file2 = Dict::new(); + file2.insert(Bytes::new("path"), List::wrap(vec![Bytes::wrap("txt.jpg")])); + file2.insert(Bytes::new("length"), Object::Int(64)); + files.push(Object::Dict(file2)); + info.insert(Bytes::new("files"), Object::List(files)); + + meta.insert(Bytes::new("info"), Object::Dict(info)); + + let metainfo = Metainfo::from_bencode(Object::Dict(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); + } +}