diff --git a/src/bencode.rs b/src/bencode.rs index ca64d01..0f5277c 100644 --- a/src/bencode.rs +++ b/src/bencode.rs @@ -20,6 +20,10 @@ impl Display for ByteString { } impl ByteString { + + pub fn to_vec(&self) -> &Vec { + &self.0 + } pub fn to_string(&self) -> Result { let result = std::str::from_utf8(&self.0[..])?; Ok(result.to_string()) diff --git a/src/main.rs b/src/main.rs index 4f655ac..87ca417 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,140 @@ -use std::{env, fs}; +use std::{env, fs, path}; +use std::collections::HashMap; use anyhow::{anyhow, Result}; -use crate::bencode::Bencode; + +use crate::bencode::{Bencode, ByteString}; mod bencode; +#[derive(Debug)] +struct FileInfo { + length: i64, + path: String, +} + +#[derive(Debug)] +struct TorrentInfo { + files: Option>, + length: Option, + name: String, + piece_length: i64, + pieces: Vec<[u8; 20]>, +} + +#[derive(Debug)] +struct Torrent { + announce: String, + info: TorrentInfo, + info_hash: [u8; 20], + extra: HashMap +} + +impl Torrent { + pub fn from(input: &Bencode) -> Result { + let Bencode::Dict(decoded) = input else { + return Err(anyhow!("Torrent has wrong type. Not a dict")) + }; + let b_info = decoded.get(&ByteString::from_str("info")) + .ok_or(anyhow!("Could not find key \"info\""))?; + let Bencode::Dict(info_dict) = b_info else { + return Err(anyhow!("Info has wrong type. Not a dict")) + }; + + let Bencode::Bytes(name) = info_dict.get(&ByteString::from_str("name")) + .ok_or(anyhow!("Could not find key \"name\""))? else { + return Err(anyhow!("name not a string")) + }; + + let Bencode::Integer(piece_length) = *info_dict.get(&ByteString::from_str("piece length")) + .ok_or(anyhow!("Could not find key \"piece length\""))? else { + return Err(anyhow!("piece length not an integer")) + }; + + let Bencode::Bytes(all_pieces) = info_dict.get(&ByteString::from_str("pieces")) + .ok_or(anyhow!("Could not find key \"pieces\""))? else { + return Err(anyhow!("pieces not an string")) + }; + + if all_pieces.to_vec().len() % 20 != 0 { + return Err(anyhow!("Pieces string length not a multiple of 20")) + } + + // TODO maybe handle the error, even though it should be checked above + let pieces = all_pieces.to_vec().chunks(20).map(|x| x.try_into().unwrap()).collect(); + + let info; + if let Some(b_length) = info_dict.get(&ByteString::from_str("length")) { + let Bencode::Integer(length) = b_length else { + return Err(anyhow!("length not an integer")) + }; + info = TorrentInfo { + files: None, + length: Some(*length), + name: name.to_string()?, + piece_length, + pieces, + } + } else if let Some(b_files) = info_dict.get(&ByteString::from_str("files")) { + let Bencode::List(files_list) = b_files else { + return Err(anyhow!("files not an list")) + }; + + let mut files = Vec::new(); + for b_file_info in files_list { + let Bencode::Dict(file_info) = b_file_info else { + return Err(anyhow!("file_info not a dict")) + }; + let Bencode::List(b_path) = file_info.get(&ByteString::from_str("path")) + .ok_or(anyhow!("Could not find key \"path\""))? else { + return Err(anyhow!("path not a list")) + }; + let mut v_path = Vec::new(); + for b_path_part in b_path { + let Bencode::Bytes(path_part) = b_path_part else { + return Err(anyhow!("path part not a string")) + }; + v_path.push(path_part.to_string()?); + } + + let Bencode::Integer(length) = *file_info.get(&ByteString::from_str("length")) + .ok_or(anyhow!("Could not find key \"length\""))? else { + return Err(anyhow!("length not an integer")) + }; + files.push(FileInfo{ + path: v_path.join(path::MAIN_SEPARATOR_STR), + length + }) + } + + info = TorrentInfo { + files: Some(files), + length: None, + name: name.to_string()?, + piece_length, + pieces, + } + } else { + return Err(anyhow!("Could not find key \"length\" or \"files\"")) + } + + + let b_announce = decoded.get(&ByteString::from_str("announce")) + .ok_or(anyhow!("Could not find key \"announce\""))?; + let Bencode::Bytes(announce) = b_announce else { + return Err(anyhow!("announce not a string")) + }; + let result = Self { + announce: announce.to_string()?, + info_hash: b_info.sha1(), + info, + // TODO + extra: HashMap::new(), + }; + Ok(result) + } +} + fn main() -> Result<()> { let args: Vec = env::args().collect(); if args.len() < 2 { @@ -14,9 +144,14 @@ fn main() -> Result<()> { let torrent_bytes = fs::read(&file_path)?; - let _torrent_decoded = Bencode::decode(&torrent_bytes)?; + let torrent_parsed = Bencode::parse(&torrent_bytes)?; - println!("torrent decoded: {_torrent_decoded}"); + println!("torrent file parsed"); + // println!("parsed file: {torrent_parsed}"); + + let torrent = Torrent::from(&torrent_parsed)?; + + println!("torrent decoded: {torrent:?}"); Ok(()) }