use sveppa_bencode::{Bencode, BencodeByteStr, BencodeDict}; use crate::parser::*; ///! A Torrent file! ///! reference: ///! https://en.wikipedia.org/wiki/Torrent_file /// the top level torrent object. #[derive(Debug)] pub struct Torrent { /// the URL of the tracker pub announce: String, /// this maps to a dictionary whose keys are dependent on whether one or more /// files are being shared pub info: TorrentInfo, } #[derive(Debug)] pub struct TorrentInfo { /// a list of dictionaries each corresponding to a file (only when multiple files are being /// shared) pub files: Option, /// size of the file in bytes (only when one file is being shared) pub length: Option, /// suggested filename where the file is to be saved (if one file) /// suggested directory name where the files are to be saved (if multiple files) pub name: String, /// number of bytes per piece. This is commonly 2^8 KiB = 256 KiB = 262,144 B pub piece_length: usize, /// i.e., a concatenation of each piece's SHA-1 hash. As SHA-1 returns a 160-bit hash, /// pieces will be a string whose length is a multiple of 20 bytes. If the torrent contains /// multiple files, the pieces are formed by concatenating the files in the order they appear in /// the files dictionary (i.e. all pieces in the torrent are the full piece length except for the /// last piece, which may be shorter). pub pieces: Vec, } /// when there is more than one file. #[derive(Debug)] pub struct TorrentFiles { /// size of the file in bytes pub length: usize, /// a list of strings corresponding to subdirectory names, the last of which is the actual file /// name pub path: Vec, } impl Torrent { pub fn from_bytes(bytes: &[u8]) -> Torrent { Torrent::parse( &Bencode::from_bytes(bytes) .expect("Could not parse bencode") .0 ) } pub fn parse(bencode_tree: &Bencode) -> Torrent { let child = unpackage_dict(bencode_tree); Torrent { announce: unpackage_string(child, &b"announce".to_vec()), info: TorrentInfo::parse(unpackage_subdict(child, &b"info".to_vec())), } } } impl TorrentInfo { pub fn parse(dict: &BencodeDict) -> TorrentInfo { TorrentInfo { files: apply_or_none(&dict, &b"files".to_vec(), &TorrentFiles::parse), length: get_optional_usize(dict, &b"length".to_vec()), name: unpackage_string(dict, &b"name".to_vec()), piece_length: unpackage_usize(dict, &b"piece length".to_vec()), pieces: unpackage_bytearray(dict, &b"pieces".to_vec()), } } } impl TorrentFiles { pub fn parse(bencode_tree: &BencodeDict) -> Option { let child_opt = bencode_tree.get(&b"files".to_vec()); if child_opt.is_none() { return None; } let child = unpackage_dict(child_opt.unwrap()); Some(TorrentFiles { length: unpackage_usize(child, &b"length".to_vec()), path: unpackage_list_typed( child.get(&b"path".to_vec()).expect("Could not unpack path into List"), &|bstr| String::from_utf8(bstr.to_vec()).expect("Could not parse Vec into String"), ), }) } }