Skip to content

Commit

Permalink
Fetch tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Oct 22, 2023
1 parent 64697ab commit 5f28d2b
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 26 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ edition = "2021"
description = "BitTorrent client written in Rust"

[dependencies]
rand = "0.8.5"
reqwest = { version = "0.11", features=["blocking"] }
sha1 = "0.10.6"
urlencoding = "2.1.3"
5 changes: 5 additions & 0 deletions src/hex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use crate::types::ByteString;

pub fn hex(str: &ByteString) -> String {
str.iter().map(|c| format!("{:x?}", c)).collect::<String>()
}
39 changes: 37 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::{fs, path::PathBuf};

use bencode::parse_bencoded;
use types::ByteString;

use crate::metainfo::Metainfo;
use crate::{
metainfo::Metainfo,
tracker::{tracker_request, TrackerRequest},
};

mod bencode;
mod hex;
mod metainfo;
mod sha1;
mod tracker;
mod types;

fn main() {
let path = PathBuf::from("data/academic_test.torrent");
Expand All @@ -15,9 +24,35 @@ fn main() {
_ => panic!("metadata file parsing error"),
};
println!("{metainfo_dict:#?}");
let metainfo = match Metainfo::try_from(metainfo_dict) {
let metainfo = match Metainfo::try_from(metainfo_dict.clone()) {
Ok(info) => info,
Err(e) => panic!("metadata file structure error: {e}"),
};
println!("{metainfo:#?}");
let info_dict_str = match metainfo_dict {
bencode::BencodeValue::Dict(d) => d.get("info").unwrap().encode(),
_ => unreachable!(),
};
let info_hash = sha1::encode(info_dict_str);
let peer_id = generate_peer_id();
println!("peer id {}", String::from_utf8_lossy(peer_id.as_slice()));
let tracker_response = tracker_request(
metainfo.announce,
TrackerRequest::new(info_hash, peer_id, tracker::TrackerEvent::Started, None),
)
.expect("request failed");
println!("tracker response: {tracker_response:?}");
}

/// Generate random 20 byte string, starting with -<2 byte client name><4 byte client version>-
fn generate_peer_id() -> ByteString {
let rand = thread_rng()
.sample_iter(&Alphanumeric)
.take(12)
.collect::<Vec<_>>();
vec!["-ER0000-".as_bytes(), &rand]
.into_iter()
.flatten()
.cloned()
.collect()
}
34 changes: 10 additions & 24 deletions src/metainfo.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::fmt;
use std::path::PathBuf;

use crate::bencode::BencodeValue;
use crate::{bencode::BencodeValue, hex::hex, types::ByteString};

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Metainfo {
Expand All @@ -15,25 +15,19 @@ pub struct Metainfo {
}

#[derive(PartialEq, Eq, Hash)]
pub struct PieceHash(Vec<u8>);
pub struct PieceHash(ByteString);

impl fmt::Debug for PieceHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"#{}",
self.0
.iter()
.map(|c| format!("{:x?}", c))
.collect::<String>()
)
write!(f, "#{}", hex(&self.0))
}
}

#[derive(PartialEq, Eq, Hash)]
pub struct Info {
pub piece_length: i64,
pub pieces: Vec<PieceHash>,
pub name: String,
pub file_info: FileInfo,
pub private: Option<bool>,
}
Expand All @@ -52,12 +46,10 @@ impl fmt::Debug for Info {
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum FileInfo {
Single {
name: String,
length: i64,
md5_sum: Option<String>,
},
Multi {
name: String,
files: Vec<FilesInfo>,
},
}
Expand Down Expand Up @@ -90,19 +82,15 @@ impl TryFrom<BencodeValue> for Metainfo {
_ => return Err("'pieces' missing".into()),
};
let name = match info_dict.get("name") {
Some(BencodeValue::String(s)) => {
String::from_utf8(s.clone()).map_err(|_| "'name' is not utf-8")?
}
Some(BencodeValue::String(s)) => String::from_utf8_lossy(s.as_slice()).into(),
_ => return Err("'name' missing".into()),
};
let file_info = if info_dict.get("files").is_some() {
FileInfo::Multi {
name,
files: parse_files_info(info_dict.get("files").unwrap().clone())?,
}
} else {
FileInfo::Single {
name,
length: match info_dict.get("length") {
Some(BencodeValue::Int(v)) => *v,
_ => return Err("'length' missing".into()),
Expand All @@ -118,14 +106,13 @@ impl TryFrom<BencodeValue> for Metainfo {
_ => return Err("'piece length' missing".into()),
},
pieces,
name,
file_info,
// TODO
private: None,
},
announce: match dict.get("announce") {
Some(BencodeValue::String(s)) => {
String::from_utf8(s.clone()).map_err(|_| "'announce' is not utf-8")?
}
Some(BencodeValue::String(s)) => String::from_utf8_lossy(s.as_slice()).into(),
_ => return Err("'announce' missing".into()),
},
// TODO
Expand Down Expand Up @@ -153,10 +140,9 @@ fn parse_files_info(value: BencodeValue) -> Result<Vec<FilesInfo>, String> {
Some(BencodeValue::List(p)) => p
.iter()
.map(|dir| match dir {
BencodeValue::String(dir) => match String::from_utf8(dir.clone()) {
Ok(str) => Ok(PathBuf::from(str)),
_ => Err("'path' is not utf-8".into()),
},
BencodeValue::String(dir) => Ok(PathBuf::from(
String::from_utf8_lossy(dir.as_slice()).to_string(),
)),
_ => Err("'path' item is not a string".into()),
})
.collect::<Result<PathBuf, String>>()?,
Expand Down
9 changes: 9 additions & 0 deletions src/sha1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use sha1::{Sha1, Digest};

use crate::types::ByteString;

pub fn encode(value: ByteString) -> ByteString {
let mut sha = Sha1::default();
sha.update(value);
sha.finalize().to_vec()
}
Loading

0 comments on commit 5f28d2b

Please sign in to comment.