[main] Switched to using serde impls
This commit is contained in:
parent
6eb7f08388
commit
0cfc7e19a4
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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"
|
||||
|
187
src/main.rs
187
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<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct TorrentInfo {
|
||||
files: Option<Vec<FileInfo>>,
|
||||
length: Option<i64>,
|
||||
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<Vec<[u8; 20]>, 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<S>(v: &Vec<[u8; 20]>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<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"))
|
||||
};
|
||||
fn from(input: &[u8]) -> Result<Self> {
|
||||
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:?}");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user