I dont understand how to sha1 hash the info dictionary of a .torrent info dictionary in rust. I know i cant extract the info dictionary dicrectly cause it has invalid ut8 characters so i use serde::bencode to extract that info.
#[derive(Deserialize,Clone,Debug)]
struct Info {
name:String, // Name of the torrent file
#[serde(rename = "piece length")]
length:usize, // Size of each piece as bytes
pieces:ByteBuf, // raw SHA1 Hashes of pieces
#[serde(flatten)]
keys:Keys // Can be lenght if its single file torrent or 'files' if its a multi file torrent
}
#[derive(Clone,Debug)]
struct Torrent {
announce:String,
raw_info:Vec<u8>,
info:Info,
}
#[derive(Deserialize,Clone,Debug)]
#[serde(untagged)]
enum Keys{
SingleFile {
length:usize
},
MultiFile {
files:Vec<File>
}
}
#[derive(Deserialize,Clone,Debug)]
struct File{
length:usize, // File size in bytes
path:Vec<String> //File path split in directories
}
impl<'de> Deserialize<'de> for Torrent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let mut map = BTreeMap::<String, Value>::deserialize(deserializer)?;
// Extract announce
let announce_value = map
.remove("announce")
.ok_or_else(|| serde::de::Error::missing_field("announce"))?;
let announce = match announce_value {
Value::Bytes(bytes) => String::from_utf8(bytes)
.map_err(|e| serde::de::Error::custom(e.to_string()))?,
_ => return Err(serde::de::Error::custom("announce must be a bencoded string")),
};
// Extract raw info as bencoded dictionary (not bytes)
let raw_info_value = map
.remove("info")
.ok_or_else(|| serde::de::Error::missing_field("info"))?;
// Debugging raw_info_value to see the structure
//println!("Raw info value: {:?}", raw_info_value);
let raw_info_bytes = match raw_info_value {
Value::Dict(_) => {
// Serialize the `info` dictionary to bencoded bytes
serde_bencode::to_bytes(&raw_info_value)
.map_err(|e| serde::de::Error::custom(format!("Bencode serialization error: {}", e)))?
}
_ => return Err(serde::de::Error::custom("info must be a bencoded dictionary")),
};
//println!("raw_info_byts: {:?}",raw_info_bytes);
// Return the Torrent struct with the correct info_hash
let info: Info = serde_bencode::from_bytes(&raw_info_bytes)
.map_err(|e| serde::de::Error::custom(format!("Bencode deserialization error: {}", e)))?;
Ok(Torrent {
announce,
raw_info: raw_info_bytes,
info,
})
}
}
from this i can get the info about the torrent but getting the correct info hash has been a struggle as i cant make torrent get request without the correct info hash. Here is how i have tried it :
fn makequery(path: String) -> TorrentGetRequest {
let torrent_file = std::fs::read(path).expect("Failed to read torrent file");
let data: Torrent = serde_bencode::from_bytes(&torrent_file).expect("Parsing failed");
let url1 = data.announce;
println!("info hash raw:{:?}",data.raw_info);
let info_hash = compute_info_hash(&data.raw_info);
println!("compute info hash:{:?}",info_hash);
let encoded_info_hash = url_encode_info_hash(&info_hash);
println!("econded_info_hash:{:?}",encoded_info_hash);
TorrentGetRequest {
url: url1,
info_hash: encoded_info_hash,
peer_id: "11111222223333344444".to_string(),
port: 6881,
uploaded: 0,
dowloaded: 0,
left: 0,
compact: 1,
}
}
pub fn compute_info_hash(raw_info: &[u8]) -> [u8; 20] {
let hash_result = Sha1::digest(raw_info);
let mut info_hash = [0u8; 20];
info_hash.copy_from_slice(&hash_result[..20]);
println!("Computed Info Hash (Hex): {}", hex::encode(&info_hash));
info_hash
}
fn url_encode_info_hash(info_hash: &[u8]) -> String {
form_urlencoded::byte_serialize(info_hash).collect::<String>()
}
along with my termnial response :
info hash raw:[100, 54, 58, 108, 101, 110, 103, 116, 104, 105, 57, 50, 48, 54, 51, 101, 52, 58, 110, 97, 109, 101, 49, 48, 58, 115, 97, 109, 112, 108, 101, 46, 116, 120, 116, 49, 50, 58, 112, 105, 101, 99, 101, 32, 108, 101, 110,
103, 116, 104, 105, 51, 50, 55, 54, 56, 101, 54, 58, 112, 105, 101, 99, 101, 115, 54, 48, 58, 232, 118, 246, 122, 42, 136, 134, 232, 243, 107, 19, 103, 38, 195, 15, 162, 151, 3, 2, 45, 110, 34, 117, 230, 4, 160, 118, 102, 86, 115, 110, 129, 255, 16, 181, 82, 4, 173, 141, 53, 240, 13, 147, 122, 2, 19, 223, 25, 130, 188, 141, 9, 114, 39, 173, 158, 144, 154, 204, 23, 101]
Computed Info Hash (Hex): d69f91e6b2ae4c542468d1073a71d4ea13879a7f
compute info hash:[214, 159, 145, 230, 178, 174, 76, 84, 36, 104, 209, 7, 58, 113, 212, 234, 19, 135, 154, 127]
econded_info_hash:"%D6%9F%91%E6%B2%AELT%24h%D1%07%3Aq%D4%EA%13%87%9A%7F"
Url request to
info hash: %D6%9F%91%E6%B2%AELT%24h%D1%07%3Aq%D4%EA%13%87%9A%7F
Response: d14:failure reason25:provided invalid infohashe
I am not to sure if its how im deserializing the torrent file but i have seen other people take this same approach but mine just doesnt work and im not sure why.