Rust中Torrent文件Info哈希计算与Tracker请求异常排查
在Rust中计算Torrent info字典SHA1哈希及Tracker请求问题解决
问题背景
处理.torrent文件时,由于info字典包含无效UTF-8字符,使用serde::bencode反序列化能正常获取Torrent信息,但生成的info_hash用于Tracker GET请求时,始终返回provided invalid infohash错误。
核心问题分析
- info_hash计算逻辑错误:若先反序列化整个Torrent结构,再将info字段重新序列化后计算哈希,会改变原始bencode的字节结构(比如键的排序、空白字符等),导致哈希值与Tracker期望的不一致。
- URL编码方式错误:将
info_hash与其他参数统一传入请求构建方法(如reqwest的query)时,库会把二进制的info_hash当作UTF-8字符串处理,造成编码错误,Tracker无法识别。
解决步骤
1. 正确计算info_hash
必须直接对info字典的原始bencode字节流进行SHA1哈希计算,确保哈希值与Torrent文件原始定义一致:
use serde_bencode::{de::from_bytes, ser::to_bytes, value::Value}; use sha1::{Digest, Sha1}; use std::fs; fn get_info_hash(torrent_path: &str) -> Result<[u8; 20], Box<dyn std::error::Error>> { // 读取torrent文件原始字节数据 let torrent_data = fs::read(torrent_path)?; // 解析为bencode通用Value类型 let bencode_value: Value = from_bytes(&torrent_data)?; // 提取info字段并序列化回原始bencode字节(保证与原文件结构完全一致) let info_bytes = to_bytes(&bencode_value["info"])?; // 计算SHA1哈希 let mut hasher = Sha1::new(); hasher.update(&info_bytes); let hash_result = hasher.finalize(); // 转换为固定长度数组返回 Ok(hash_result.into()) }
2. 正确构建Tracker请求URL
手动拆分参数编码逻辑,避免info_hash被错误处理:
- 对除
info_hash外的所有参数进行常规URL编码 - 将info_hash的每个二进制字节转换为
%XX的十六进制格式 - 手动拼接完整请求URL
示例代码:
use reqwest::Client; use url::form_urlencoded; fn build_announce_url( tracker_base: &str, info_hash: [u8; 20], peer_id: &str, port: u16, ) -> String { // 编码普通请求参数 let encoded_params = form_urlencoded::Serializer::new(String::new()) .append_pair("peer_id", peer_id) .append_pair("port", &port.to_string()) .append_pair("uploaded", "0") .append_pair("downloaded", "0") .append_pair("left", "0") .append_pair("compact", "1") .finish(); // 按字节编码info_hash为%XX格式 let encoded_info_hash = info_hash .iter() .map(|byte| format!("%{:02X}", byte)) .collect::<String>(); // 拼接完整请求URL format!("{}?{}&info_hash={}", tracker_base, encoded_params, encoded_info_hash) } // 发起Tracker请求示例 async fn fetch_tracker_peers() -> Result<(), Box<dyn std::error::Error>> { let info_hash = get_info_hash("example.torrent")?; let tracker_url = "http://tracker.example.com/announce"; let peer_id = "-RUSTTOR-000000000000"; let announce_url = build_announce_url(tracker_url, info_hash, peer_id, 6881); let client = Client::new(); let response = client.get(&announce_url).send().await?; let response_body = response.text().await?; println!("Tracker response: {}", response_body); Ok(()) }
关键注意事项
- 必须使用info字典的原始bencode字节计算哈希,避免反序列化再序列化导致的字节结构变化
- info_hash是二进制数据,必须按字节逐个编码为
%XX格式,不能当作普通字符串处理 - 手动拼接URL时,确保参数之间的
&分隔符正确,避免编码冲突
内容的提问来源于stack exchange,提问作者loki
相关产品推荐
相关产品推荐

