metainfo parsing
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ fn decode_object(buf: &mut Buffer) -> DecodeResult<Object> {
|
||||
}
|
||||
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<Object> {
|
||||
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<i64> {
|
||||
}
|
||||
|
||||
fn decode_bytes(buf: &mut Buffer) -> DecodeResult<Object> {
|
||||
_decode_bytes(buf).map(|bytes| Bytes::new(bytes))
|
||||
_decode_bytes(buf).map(|bytes| Bytes::wrap(bytes))
|
||||
}
|
||||
|
||||
fn _decode_bytes(buf: &mut Buffer) -> Result<Vec<u8>, 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -87,8 +87,12 @@ impl fmt::Debug for Object {
|
||||
pub struct Bytes(Vec<u8>);
|
||||
|
||||
impl Bytes {
|
||||
pub fn new(bytes: Vec<u8>) -> Object {
|
||||
Object::Bytes(Bytes(bytes))
|
||||
pub fn new<A>(bytes: A) -> Bytes where A: Into<Vec<u8>> {
|
||||
Bytes(bytes.into())
|
||||
}
|
||||
|
||||
pub fn wrap<A>(bytes: A) -> Object where A: Into<Vec<u8>> {
|
||||
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<Object>);
|
||||
|
||||
impl List {
|
||||
pub fn new(list: Vec<Object>) -> Object {
|
||||
pub fn new() -> List {
|
||||
List(Vec::new())
|
||||
}
|
||||
|
||||
pub fn wrap(list: Vec<Object>) -> Object {
|
||||
Object::List(List(list))
|
||||
}
|
||||
}
|
||||
@@ -163,7 +171,11 @@ impl DerefMut for List {
|
||||
pub struct Dict(BTreeMap<Bytes, Object>);
|
||||
|
||||
impl Dict {
|
||||
pub fn new(dict: BTreeMap<Bytes, Object>) -> Object {
|
||||
pub fn new() -> Dict {
|
||||
Dict(BTreeMap::new())
|
||||
}
|
||||
|
||||
pub fn wrap(dict: BTreeMap<Bytes, Object>) -> Object {
|
||||
Object::Dict(Dict(dict))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod bencode;
|
||||
pub mod buffer;
|
||||
pub mod metainfo;
|
||||
|
||||
169
src/metainfo.rs
Normal file
169
src/metainfo.rs
Normal file
@@ -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<Vec<String>>,
|
||||
pub files: Vec<MetainfoFile>,
|
||||
pub info_hash: [u8; 20],
|
||||
pub piece_length: u64,
|
||||
pub pieces: Vec<[u8; 20]>,
|
||||
}
|
||||
|
||||
impl Metainfo {
|
||||
pub fn from_bencode(obj: Object) -> Option<Metainfo> {
|
||||
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::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user