Implement torrent file deserialization

This commit is contained in:
Fabian 2024-07-25 01:12:19 +02:00
parent 1129348248
commit 1c2c21ec98
2 changed files with 143 additions and 4 deletions

View File

@ -20,6 +20,10 @@ impl Display for ByteString {
} }
impl ByteString { impl ByteString {
pub fn to_vec(&self) -> &Vec<u8> {
&self.0
}
pub fn to_string(&self) -> Result<String> { pub fn to_string(&self) -> Result<String> {
let result = std::str::from_utf8(&self.0[..])?; let result = std::str::from_utf8(&self.0[..])?;
Ok(result.to_string()) Ok(result.to_string())

View File

@ -1,10 +1,140 @@
use std::{env, fs}; use std::{env, fs, path};
use std::collections::HashMap;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use crate::bencode::Bencode;
use crate::bencode::{Bencode, ByteString};
mod bencode; mod bencode;
#[derive(Debug)]
struct FileInfo {
length: i64,
path: String,
}
#[derive(Debug)]
struct TorrentInfo {
files: Option<Vec<FileInfo>>,
length: Option<i64>,
name: String,
piece_length: i64,
pieces: Vec<[u8; 20]>,
}
#[derive(Debug)]
struct Torrent {
announce: String,
info: TorrentInfo,
info_hash: [u8; 20],
extra: HashMap<String, String>
}
impl Torrent {
pub fn from(input: &Bencode) -> Result<Self> {
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<()> { fn main() -> Result<()> {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
if args.len() < 2 { if args.len() < 2 {
@ -14,9 +144,14 @@ fn main() -> Result<()> {
let torrent_bytes = fs::read(&file_path)?; 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(()) Ok(())
} }