From 0cfc7e19a424aae319872ee0e3bb077e1b732592 Mon Sep 17 00:00:00 2001 From: Faerbit Date: Sun, 11 Aug 2024 23:10:27 +0200 Subject: [PATCH] [main] Switched to using serde impls --- Cargo.lock | 10 +++ Cargo.toml | 1 + src/main.rs | 187 ++++++++++++++-------------------------------------- 3 files changed, 59 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2ea1b9..3e1853a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -865,6 +865,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.204" @@ -1070,6 +1079,7 @@ dependencies = [ "rand", "reqwest", "serde", + "serde_bytes", "sha1", "urlencoding", ] diff --git a/Cargo.toml b/Cargo.toml index c8fbe43..d9fb7e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ reqwest = { version = "0.12.5", features = ["blocking"] } rand = "0.8.5" urlencoding = "2.1.3" serde = { version = "1.0.204", features = ["derive"] } +serde_bytes = "0.11.15" [dev-dependencies] hex-literal = "0.4.1" diff --git a/src/main.rs b/src/main.rs index 5450eec..333c81c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,169 +1,83 @@ // who do you think you are? my mom?! #![allow(uncommon_codepoints)] -use std::{env, fs, path}; -use std::collections::HashMap; +use std::{env, fs}; +use crate::bencode::de::from_bytes; +use crate::bencode::ser::to_bytes; use anyhow::{anyhow, Result}; use rand::prelude::*; use reqwest::blocking::Client; use reqwest::Url; - -use crate::bencode::custom::{Bencode, ByteString}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use serde_bytes::ByteBuf; +use sha1::{Digest, Sha1}; mod bencode; -#[derive(Debug)] +#[derive(Serialize, Deserialize, Debug)] struct FileInfo { length: i64, - path: String, + // TODO with os.path.join + path: Vec, } -#[derive(Debug)] +#[derive(Serialize, Deserialize, Debug)] struct TorrentInfo { files: Option>, length: Option, name: String, + #[serde(rename(serialize = "piece length", deserialize = "piece length"))] piece_length: i64, + #[serde(deserialize_with = "deserialize_pieces", serialize_with = "serialize_pieces")] pieces: Vec<[u8; 20]>, } -#[derive(Debug)] +fn deserialize_pieces<'de, D>(deserializer: D) -> Result, D::Error> +where D: Deserializer<'de> { + let all_pieces = ByteBuf::deserialize(deserializer)?.into_vec(); + if all_pieces.len() % 20 != 0 { + return Err(de::Error::custom("Pieces string length not a multiple of 20")) + } + + // TODO maybe handle the error, even though it should be checked above + let pieces = all_pieces.chunks(20).map(|x| x.try_into().unwrap()).collect(); + Ok(pieces) +} + +fn serialize_pieces(v: &Vec<[u8; 20]>, serializer: S) -> Result +where S: serde::ser::Serializer +{ + let mut buf = Vec::new(); + for i in v { + buf.extend(i) + } + serializer.serialize_bytes(&buf[..]) +} + +#[derive(Serialize, Deserialize, Debug)] struct Torrent { announce: String, info: TorrentInfo, + #[serde(skip)] 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")) - }; + fn from(input: &[u8]) -> Result { + let mut torrent: Self = from_bytes(input)?; + torrent.compute_info_hash()?; + Ok(torrent) + } - 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")) - }; + fn compute_info_hash(&mut self) -> Result<()> { + let mut hasher = Sha1::new(); - 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 info_str = to_bytes(&self.info)?; + hasher.update(info_str); - 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 a 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 mut _extra = HashMap::new(); - for (k, v) in decoded { - if k == &ByteString::from_str("announce") || k == &ByteString::from_str("info") { - continue - } - let Ok(k_str) = k.to_string() else { - println!("Ignoring non-UTF-8 key {k:?}"); - continue; - }; - match v { - Bencode::Integer(i) => { - _extra.insert(k_str, format!("{}", i)); - } - Bencode::Bytes(s) => { - let Ok(s_str) = s.to_string() else { - println!("Ignoring non-UTF-8 value of key {k_str}"); - continue; - }; - _extra.insert(k_str, s_str); - } - _ => { - println!("Ignoring extra of type list or dict") - } - } - } - - let result = Self { - announce: announce.to_string()?, - info_hash: b_info.sha1(), - info, - _extra, - }; - Ok(result) + self.info_hash = hasher.finalize().into(); + Ok(()) } } @@ -176,12 +90,7 @@ fn main() -> Result<()> { let torrent_bytes = fs::read(&file_path)?; - let torrent_parsed = Bencode::parse(&torrent_bytes)?; - - println!("torrent file parsed"); - // println!("parsed file: {torrent_parsed}"); - - let torrent = Torrent::from(&torrent_parsed)?; + let torrent = Torrent::from(&torrent_bytes)?; // println!("torrent decoded: {torrent:?}");