Better error and tracker handling
This commit is contained in:
parent
5eb73b29ad
commit
930f50042d
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug",
|
||||||
|
"program": "${workspaceFolder}/<executable file>",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"rust-analyzer.linkedProjects": [
|
|
||||||
"./Cargo.toml"
|
|
||||||
]
|
|
||||||
}
|
|
1
downloads/README
Normal file
1
downloads/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is a test file
|
BIN
downloads/images/LOC_Main_Reading_Room_Highsmith.jpg
Normal file
BIN
downloads/images/LOC_Main_Reading_Room_Highsmith.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 MiB |
BIN
downloads/images/melk-abbey-library.jpg
Normal file
BIN
downloads/images/melk-abbey-library.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
174
src/main.rs
174
src/main.rs
@ -17,107 +17,111 @@ mod message;
|
|||||||
mod torrent;
|
mod torrent;
|
||||||
mod tracker;
|
mod tracker;
|
||||||
|
|
||||||
|
use core::panic;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
// Crate Imports
|
// Crate Imports
|
||||||
use crate::{
|
use crate::{
|
||||||
files::Files,
|
files::Files,
|
||||||
peer::Peer,
|
peer::Peer,
|
||||||
torrent::Torrent,
|
torrent::Torrent,
|
||||||
tracker::{
|
tracker::tracker::Tracker
|
||||||
AnnounceMessage, AnnounceMessageResponse,
|
|
||||||
ConnectionMessage,
|
|
||||||
FromBuffer,
|
|
||||||
Tracker
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc;
|
||||||
// External Ipmorts
|
// External Ipmorts
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::{ debug, info, LevelFilter };
|
use log::{ debug, info, LevelFilter, error };
|
||||||
|
use tokio::spawn;
|
||||||
|
|
||||||
/// Struct Respresenting needed arguments
|
/// Struct Respresenting needed arguments
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
log_file_path: Option<String>,
|
log_file_path: Option<String>,
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
torrent_file_path: String,
|
torrent_file_path: String,
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
download_path: String,
|
download_path: String,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
peer_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The root function
|
/// The root function
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
// Creates a log file to handle large amounts of data
|
|
||||||
let log_path = args.log_file_path.unwrap_or(String::from("./log/rustytorrent.log"));
|
|
||||||
simple_logging::log_to_file(&log_path, LevelFilter::Info).unwrap();
|
|
||||||
|
|
||||||
// Read the Torrent File
|
|
||||||
let torrent = Torrent::from_torrent_file(&args.torrent_file_path).await;
|
|
||||||
info!("Sucessfully read torrent file");
|
|
||||||
torrent.log_useful_information();
|
|
||||||
|
|
||||||
// Create the files that will be written to
|
|
||||||
let mut files = Files::new();
|
|
||||||
files.create_files(&torrent, &args.download_path).await;
|
|
||||||
|
|
||||||
// Gets peers from the given tracker
|
|
||||||
let (_remote_hostname, _remote_port) = torrent.get_tracker();
|
|
||||||
let (remote_hostname, remote_port) = ("tracker.opentrackr.org", 1337);
|
|
||||||
debug!("{}:{}", remote_hostname, remote_port);
|
|
||||||
|
|
||||||
let mut tracker = Tracker::new("0.0.0.0:61389", remote_hostname, remote_port).await;
|
|
||||||
info!("Successfully connected to tracker {}:{}", remote_hostname, remote_port);
|
|
||||||
let connection_message = ConnectionMessage::from_buffer(
|
|
||||||
&tracker.send_message(&ConnectionMessage::create_basic_connection()).await
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!("{:?}", connection_message);
|
|
||||||
|
|
||||||
let announce_message_response = AnnounceMessageResponse::from_buffer(
|
|
||||||
&tracker.send_message(&AnnounceMessage::new(
|
|
||||||
connection_message.connection_id,
|
|
||||||
&torrent.get_info_hash(),
|
|
||||||
"-MY0001-123456654321",
|
|
||||||
torrent.get_total_length() as i64
|
|
||||||
)).await
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!("{:?}", announce_message_response);
|
|
||||||
info!("Found Peers");
|
|
||||||
|
|
||||||
// Creates an assumed peer connection to the `SocketAddr` given
|
|
||||||
let mut peer = match Peer::create_connection(&format!("{}:{}", announce_message_response.ips[0], announce_message_response.ports[0])).await {
|
|
||||||
None => { return },
|
|
||||||
Some(peer) => peer
|
|
||||||
};
|
|
||||||
|
|
||||||
let num_pieces = torrent.info.pieces.len() / 20;
|
|
||||||
peer.handshake(&torrent).await;
|
|
||||||
peer.keep_alive_until_unchoke().await;
|
|
||||||
|
|
||||||
info!("Successfully Created Connection with peer: {}", peer.peer_id);
|
|
||||||
|
|
||||||
let mut len = 0;
|
|
||||||
|
|
||||||
for index in 0..num_pieces {
|
|
||||||
let piece= peer.request_piece(
|
|
||||||
index as u32, torrent.info.piece_length as u32,
|
|
||||||
&mut len, torrent.get_total_length() as u32
|
|
||||||
).await;
|
|
||||||
|
|
||||||
if torrent.check_piece(&piece, index as u32) {
|
// Creates a log file to handle large amounts of data
|
||||||
files.write_piece(piece).await;
|
let log_path = args.log_file_path.unwrap_or(String::from("./log/rustytorrent.log"));
|
||||||
} else {
|
//simple_logging::log_to_file(&log_path, LevelFilter::Debug).unwrap();
|
||||||
break
|
simple_logging::log_to_stderr(LevelFilter::Debug);
|
||||||
|
|
||||||
|
info!("==> WELCOME TO RUSTY-TORRENT <==");
|
||||||
|
|
||||||
|
// Read the Torrent File
|
||||||
|
let torrent = Torrent::from_torrent_file(&args.torrent_file_path).await;
|
||||||
|
torrent.log_useful_information();
|
||||||
|
|
||||||
|
// Create the files that will be written to
|
||||||
|
let mut files = Files::new();
|
||||||
|
files.create_files(&torrent, &args.download_path).await;
|
||||||
|
|
||||||
|
// Gets peers from the given tracker
|
||||||
|
|
||||||
|
let Some(socketaddrs) = torrent.get_trackers() else {
|
||||||
|
error!("couldn't find trackers");
|
||||||
|
panic!("couldn't find trackers")
|
||||||
|
};
|
||||||
|
let (remote_hostname, remote_port) = ("tracker.opentrackr.org", 1337);
|
||||||
|
debug!("{}:{}", remote_hostname, remote_port);
|
||||||
|
|
||||||
|
info!("");
|
||||||
|
info!("--> Finding Peers <--");
|
||||||
|
let listen_address = "0.0.0.0:61389".parse::<SocketAddr>().unwrap();
|
||||||
|
let Ok(mut tracker) = Tracker::new(listen_address, std::net::SocketAddr::V4(socketaddrs[0])).await else {
|
||||||
|
panic!("tracker couldn't be created")
|
||||||
|
};
|
||||||
|
info!("Successfully connected to tracker {}:{}", remote_hostname, remote_port);
|
||||||
|
|
||||||
|
let peers = tracker.find_peers(&torrent, &args.peer_id).await;
|
||||||
|
|
||||||
|
info!("Found Peers");
|
||||||
|
|
||||||
|
let num_pieces = torrent.info.pieces.len() / 20;
|
||||||
|
|
||||||
|
let mut peer = match Peer::create_connection(peers[0]).await {
|
||||||
|
None => { return },
|
||||||
|
Some(peer) => peer
|
||||||
|
};
|
||||||
|
|
||||||
|
peer.handshake(&torrent).await;
|
||||||
|
peer.keep_alive_until_unchoke().await;
|
||||||
|
info!("Successfully Created Connection with peer: {}", peer.peer_id);
|
||||||
|
|
||||||
|
println!("{}", peers.len());
|
||||||
|
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
|
for index in 0..num_pieces {
|
||||||
|
let piece= peer.request_piece(
|
||||||
|
index as u32, torrent.info.piece_length as u32,
|
||||||
|
&mut len, torrent.get_total_length() as u32
|
||||||
|
).await;
|
||||||
|
|
||||||
|
if torrent.check_piece(&piece, index as u32) {
|
||||||
|
files.write_piece(piece).await;
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
peer.disconnect().await;
|
||||||
peer.disconnect().await;
|
|
||||||
info!("Successfully completed download");
|
|
||||||
|
info!("Successfully completed download");
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ impl FromBuffer for Message {
|
|||||||
|
|
||||||
let message_length = u32::from_be_bytes(message_length);
|
let message_length = u32::from_be_bytes(message_length);
|
||||||
|
|
||||||
let payload: Option<Vec<u8>>;
|
let mut payload: Option<Vec<u8>> = None;
|
||||||
let message_type: MessageType;
|
let message_type: MessageType;
|
||||||
|
|
||||||
if message_length == 0 {
|
if message_length == 0 {
|
||||||
@ -66,9 +66,9 @@ impl FromBuffer for Message {
|
|||||||
if end_of_message > buf.len() {
|
if end_of_message > buf.len() {
|
||||||
error!("index too long");
|
error!("index too long");
|
||||||
debug!("{buf:?}");
|
debug!("{buf:?}");
|
||||||
}
|
} else {
|
||||||
|
payload = Some(buf[5..end_of_message].to_vec());
|
||||||
payload = Some(buf[5..end_of_message].to_vec());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
20
src/peer.rs
20
src/peer.rs
@ -9,7 +9,7 @@ use crate::{
|
|||||||
|
|
||||||
// External imports
|
// External imports
|
||||||
use log::{ debug, error };
|
use log::{ debug, error };
|
||||||
use std::net::SocketAddr;
|
use std::net::{SocketAddr, SocketAddrV4};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{ AsyncReadExt, AsyncWriteExt },
|
io::{ AsyncReadExt, AsyncWriteExt },
|
||||||
net::TcpStream
|
net::TcpStream
|
||||||
@ -20,7 +20,7 @@ pub struct Peer {
|
|||||||
/// The `TcpStream` that is used to communicate with the peeer
|
/// The `TcpStream` that is used to communicate with the peeer
|
||||||
connection_stream: TcpStream,
|
connection_stream: TcpStream,
|
||||||
/// The `SocketAddr` of the peer
|
/// The `SocketAddr` of the peer
|
||||||
pub socket_addr: SocketAddr,
|
pub socket_addr: SocketAddrV4,
|
||||||
/// The id of the peer
|
/// The id of the peer
|
||||||
pub peer_id: String,
|
pub peer_id: String,
|
||||||
/// Whether the peer is choking the client
|
/// Whether the peer is choking the client
|
||||||
@ -33,29 +33,21 @@ impl Peer {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `socket_address` - The socket address of the peer.
|
/// * `socket_address` - The socket address of the peer.
|
||||||
pub async fn create_connection(socket_address: &str) -> Option<Self> {
|
pub async fn create_connection(socket_address: SocketAddrV4) -> Option<Self> {
|
||||||
let socket_addr = match socket_address.parse::<SocketAddr>() {
|
let connection_stream = match TcpStream::connect(socket_address).await {
|
||||||
Err(err) => {
|
|
||||||
error!("error parsing address {}, err: {}", socket_address, err);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Ok(addr) => { addr }
|
|
||||||
};
|
|
||||||
|
|
||||||
let connection_stream = match TcpStream::connect(socket_addr).await {
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("unable to connect to {}, err: {}", socket_address, err);
|
error!("unable to connect to {}, err: {}", socket_address, err);
|
||||||
return None
|
return None
|
||||||
},
|
},
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
debug!("created tcpstream successfully to: {socket_addr}");
|
debug!("created tcpstream successfully to: {socket_address}");
|
||||||
stream
|
stream
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
connection_stream,
|
connection_stream,
|
||||||
socket_addr,
|
socket_addr: socket_address,
|
||||||
peer_id: String::new(),
|
peer_id: String::new(),
|
||||||
choking: true,
|
choking: true,
|
||||||
})
|
})
|
||||||
|
392
src/torrent.rs
392
src/torrent.rs
@ -1,8 +1,9 @@
|
|||||||
use log::{debug, info, error};
|
use log::{debug, info, error, warn, trace};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use tokio::{fs::File as TokioFile, io::AsyncReadExt};
|
use tokio::{fs::File as TokioFile, io::AsyncReadExt};
|
||||||
|
use std::net::{IpAddr, SocketAddrV4};
|
||||||
|
|
||||||
/// Represents a node in a DHT network.
|
/// Represents a node in a DHT network.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@ -11,203 +12,238 @@ struct Node(String, i64);
|
|||||||
/// Represents a file described in a torrent.
|
/// Represents a file described in a torrent.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub path: Vec<String>,
|
pub path: Vec<String>,
|
||||||
pub length: u64,
|
pub length: u64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
md5sum: Option<String>,
|
md5sum: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the metadata of a torrent.
|
/// Represents the metadata of a torrent.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Info {
|
pub struct Info {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(with = "serde_bytes")]
|
#[serde(with = "serde_bytes")]
|
||||||
pub pieces: Vec<u8>,
|
pub pieces: Vec<u8>,
|
||||||
#[serde(rename = "piece length")]
|
#[serde(rename = "piece length")]
|
||||||
pub piece_length: u64,
|
pub piece_length: u64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
md5sum: Option<String>,
|
md5sum: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub length: Option<i64>,
|
pub length: Option<i64>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub files: Option<Vec<File>>,
|
pub files: Option<Vec<File>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
private: Option<u8>,
|
private: Option<u8>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
path: Option<Vec<String>>,
|
path: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(rename = "root hash")]
|
#[serde(rename = "root hash")]
|
||||||
root_hash: Option<String>,
|
root_hash: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a torrent.
|
/// Represents a torrent.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Torrent {
|
pub struct Torrent {
|
||||||
pub info: Info,
|
pub info: Info,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub announce: Option<String>,
|
pub announce: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
nodes: Option<Vec<Node>>,
|
nodes: Option<Vec<Node>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
encoding: Option<String>,
|
encoding: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
httpseeds: Option<Vec<String>>,
|
httpseeds: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(rename = "announce-list")]
|
#[serde(rename = "announce-list")]
|
||||||
announce_list: Option<Vec<Vec<String>>>,
|
announce_list: Option<Vec<Vec<String>>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(rename = "creation date")]
|
#[serde(rename = "creation date")]
|
||||||
creation_date: Option<i64>,
|
creation_date: Option<i64>,
|
||||||
#[serde(rename = "comment")]
|
#[serde(rename = "comment")]
|
||||||
comment: Option<String>,
|
comment: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde(rename = "created by")]
|
#[serde(rename = "created by")]
|
||||||
created_by: Option<String>
|
created_by: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Torrent {
|
impl Torrent {
|
||||||
/// Reads a `.torrent` file and converts it into a `Torrent` struct.
|
/// Reads a `.torrent` file and converts it into a `Torrent` struct.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `path` - The path to the `.torrent` file.
|
/// * `path` - The path to the `.torrent` file.
|
||||||
pub async fn from_torrent_file(path: &str) -> Self {
|
pub async fn from_torrent_file(path: &str) -> Self {
|
||||||
// Read .torrent File
|
info!("");
|
||||||
let mut file = TokioFile::open(path).await.unwrap();
|
info!("--> Reading File <--");
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
|
||||||
|
let Ok(mut file) = TokioFile::open(path).await else {
|
||||||
match file.read_to_end(&mut buf).await {
|
error!("Unable to read file at {path}");
|
||||||
Err(err) => {
|
panic!("Unable to read file at {path}");
|
||||||
error!("Error reading file till end: {err}");
|
};
|
||||||
panic!("Error reading file till end: {err}");
|
info!("Found\t\t > {path}");
|
||||||
}
|
|
||||||
Ok(_) => { info!("Succesfully read {path}") }
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
}
|
let Ok(_) = file.read_to_end(&mut buf).await else {
|
||||||
|
error!("Error reading file > {path}");
|
||||||
match serde_bencode::from_bytes(&buf) {
|
panic!("Error reading file > {path}");
|
||||||
Err(err) => {
|
};
|
||||||
error!("Error deserializing file: {err}");
|
info!("Read\t\t > {path}");
|
||||||
panic!("Error deserializing file: {err}");
|
|
||||||
}
|
|
||||||
Ok(torrent) => { torrent }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Logs info about the *.torrent file
|
|
||||||
pub fn log_useful_information(&self) {
|
|
||||||
info!(" -> Torrent Information <- ");
|
|
||||||
info!("Name: {}", self.info.name);
|
|
||||||
info!("Tracker: {:?}", self.announce);
|
|
||||||
info!("Tracker List: {:?}", self.announce_list);
|
|
||||||
info!("Info Hash: {:X?}", self.get_info_hash());
|
|
||||||
info!("Length: {:?}", self.info.length);
|
|
||||||
|
|
||||||
info!("Files:");
|
|
||||||
|
|
||||||
match &self.info.files {
|
|
||||||
None => {
|
|
||||||
info!("> {}", self.info.name);
|
|
||||||
}
|
|
||||||
Some(files) => {
|
|
||||||
for file in files {
|
|
||||||
let mut path = String::new();
|
|
||||||
for section in &file.path {
|
|
||||||
path.push_str(&format!("{section}/"));
|
|
||||||
}
|
|
||||||
path.pop();
|
|
||||||
info!("> {}: {}B", path, file.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the info hash of the torrent.
|
|
||||||
pub fn get_info_hash(&self) -> Vec<u8> {
|
|
||||||
let buf = serde_bencode::to_bytes(&self.info).unwrap();
|
|
||||||
|
|
||||||
let mut hasher = Sha1::new();
|
|
||||||
hasher.update(buf);
|
|
||||||
let res = hasher.finalize();
|
|
||||||
res[..].to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a downloaded piece matches its hash.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `piece` - The downloaded piece.
|
|
||||||
/// * `index` - The index of the piece.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// * `true` if the piece is correct, `false` otherwise.
|
|
||||||
pub fn check_piece(&self, piece: &[u8], index: u32) -> bool {
|
|
||||||
let mut hasher = Sha1::new();
|
|
||||||
hasher.update(piece);
|
|
||||||
let result = hasher.finalize();
|
|
||||||
|
|
||||||
let piece_hash = &self.info.pieces[(index * 20) as usize..(index * 20 + 20) as usize];
|
|
||||||
|
|
||||||
if &result[..] == piece_hash {
|
|
||||||
info!("Downloaded Piece {}/{} Correct!, Piece Was: {}B long", index + 1, self.info.pieces.len() / 20, piece.len(),);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
debug!("{:?}", &result[..]);
|
|
||||||
debug!("{:?}", piece_hash);
|
|
||||||
debug!("{:?}", &result[..].len());
|
|
||||||
debug!("{:?}", piece_hash.len());
|
|
||||||
debug!("{}", piece.len());
|
|
||||||
error!("Piece downloaded incorrectly");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_total_length(&self) -> u64 {
|
|
||||||
match self.info.length {
|
|
||||||
None => {},
|
|
||||||
Some(n) => { return n as u64 }
|
|
||||||
};
|
|
||||||
|
|
||||||
match &self.info.files {
|
|
||||||
None => { 0 },
|
|
||||||
Some(files) => {
|
|
||||||
let mut n = 0;
|
|
||||||
|
|
||||||
|
let Ok(torrent) = serde_bencode::from_bytes(&buf) else {
|
||||||
|
error!("Error deserializing file > {path}");
|
||||||
|
panic!("Error deserializing file > {path}");
|
||||||
|
};
|
||||||
|
info!("Parsed\t > {path}");
|
||||||
|
|
||||||
|
torrent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Torrent {
|
||||||
|
/// Logs info about the *.torrent file
|
||||||
|
pub fn log_useful_information(&self) {
|
||||||
|
info!("");
|
||||||
|
info!("--> Torrent Information <--");
|
||||||
|
info!("Name:\t\t{}", self.info.name);
|
||||||
|
info!("Trackers");
|
||||||
|
if let Some(trackers) = &self.announce_list {
|
||||||
|
for tracker in trackers {
|
||||||
|
info!(" |> {}", tracker[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("InfoHash:\t{:X?}", self.get_info_hash());
|
||||||
|
info!("Length:\t{:?}", self.info.length);
|
||||||
|
|
||||||
|
info!("Files:");
|
||||||
|
let Some(mut files) = self.info.files.clone() else {
|
||||||
|
info!("./{}", self.info.name);
|
||||||
|
return
|
||||||
|
};
|
||||||
|
|
||||||
|
files.sort_by(|a, b| a.path.len().cmp(&b.path.len()) );
|
||||||
|
info!("./");
|
||||||
for file in files {
|
for file in files {
|
||||||
n += file.length;
|
if file.path.len() == 1 {
|
||||||
|
info!(" |--> {:?}", file.path);
|
||||||
|
} else {
|
||||||
|
let mut path = String::new();
|
||||||
|
file.path.iter().for_each(|s| { path.push_str(s); path.push('/') } );
|
||||||
|
path.pop();
|
||||||
|
|
||||||
|
info!(" |--> {}: {}B", path, file.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the info hash of the torrent.
|
||||||
|
pub fn get_info_hash(&self) -> Vec<u8> {
|
||||||
|
let buf = serde_bencode::to_bytes(&self.info).unwrap();
|
||||||
|
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(buf);
|
||||||
|
let res = hasher.finalize();
|
||||||
|
res[..].to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a downloaded piece matches its hash.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `piece` - The downloaded piece.
|
||||||
|
/// * `index` - The index of the piece.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `true` if the piece is correct, `false` otherwise.
|
||||||
|
pub fn check_piece(&self, piece: &[u8], index: u32) -> bool {
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(piece);
|
||||||
|
let result = hasher.finalize();
|
||||||
|
|
||||||
|
let piece_hash = &self.info.pieces[(index * 20) as usize..(index * 20 + 20) as usize];
|
||||||
|
|
||||||
|
if &result[..] == piece_hash {
|
||||||
|
info!("Piece {}/{} Correct!", index + 1, self.info.pieces.len() / 20);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
error!("Piece {}/{} incorrect :(", index + 1, self.info.pieces.len() / 20);
|
||||||
|
debug!("{:?}", &result[..]);
|
||||||
|
debug!("{:?}", piece_hash);
|
||||||
|
debug!("{:?}", &result[..].len());
|
||||||
|
debug!("{:?}", piece_hash.len());
|
||||||
|
debug!("{}", piece.len());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_total_length(&self) -> u64 {
|
||||||
|
if let Some(n) = self.info.length {
|
||||||
|
return n as u64
|
||||||
};
|
};
|
||||||
|
|
||||||
n
|
if let Some(files) = &self.info.files {
|
||||||
}
|
let mut n = 0;
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
n += file.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
return n
|
||||||
|
};
|
||||||
|
|
||||||
|
0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_tracker(&self) -> (&str, u16) {
|
|
||||||
let re = Regex::new(r"^udp://([^:/]+):(\d+)/announce$").unwrap();
|
|
||||||
|
|
||||||
if let Some(url) = &self.announce {
|
pub fn get_trackers(&self) -> Option<Vec<SocketAddrV4>> {
|
||||||
if let Some(captures) = re.captures(url) {
|
info!("");
|
||||||
let hostname = captures.get(1).unwrap().as_str();
|
info!("--> Locating Trackers <--");
|
||||||
let port = captures.get(2).unwrap().as_str();
|
|
||||||
|
let mut addresses = vec![];
|
||||||
|
|
||||||
|
// This is the current regex as I haven't implemented support for http trackers yet
|
||||||
|
let re = Regex::new(r"^udp://([^:/]+):(\d+)/announce$").unwrap();
|
||||||
|
|
||||||
return (hostname, port.parse().unwrap());
|
if let Some(url) = &self.announce {
|
||||||
} else {
|
if let Some(captures) = re.captures(url) {
|
||||||
println!("URL does not match the expected pattern | {}", url);
|
let hostname = captures.get(1).unwrap().as_str();
|
||||||
}
|
let port = captures.get(2).unwrap().as_str();
|
||||||
}
|
|
||||||
|
if let Ok(ip) = dns_lookup::lookup_host(hostname) {
|
||||||
for (i, url) in self.announce_list.as_ref().unwrap().iter().enumerate() {
|
for i in ip {
|
||||||
debug!("{:?}", url);
|
if let IpAddr::V4(j) = i {
|
||||||
if let Some(captures) = re.captures(&url[i]) {
|
addresses.push(SocketAddrV4::new(j, port.parse().unwrap()))
|
||||||
let hostname = captures.get(1).unwrap().as_str();
|
}
|
||||||
let port = captures.get(2).unwrap().as_str();
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("{url} does not match the expected url pattern");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (hostname, port.parse().unwrap());
|
if let Some(urls) = &self.announce_list {
|
||||||
} else {
|
for url in urls.iter() {
|
||||||
println!("URL does not match the expected pattern | {}", url[i]);
|
if let Some(captures) = re.captures(&url[0]) {
|
||||||
}
|
let hostname = captures.get(1).unwrap().as_str();
|
||||||
|
let port = captures.get(2).unwrap().as_str();
|
||||||
|
|
||||||
|
if let Ok(ip) = dns_lookup::lookup_host(hostname) {
|
||||||
|
for i in ip {
|
||||||
|
if let IpAddr::V4(j) = i {
|
||||||
|
addresses.push(SocketAddrV4::new(j, port.parse().unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("Sucessfully found tracker {}", url[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("{} does not match the expected url pattern", url[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addresses.len() > 0 {
|
||||||
|
Some(addresses)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
("", 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
1
src/tracker/mod.rs
Normal file
1
src/tracker/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod tracker;
|
@ -1,15 +1,17 @@
|
|||||||
use std::{net::{SocketAddr, Ipv4Addr}, vec};
|
use std::net::{SocketAddr, Ipv4Addr, SocketAddrV4};
|
||||||
|
|
||||||
use tokio::net::UdpSocket;
|
use tokio::net::UdpSocket;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
|
|
||||||
|
use crate::torrent::Torrent;
|
||||||
|
|
||||||
pub struct Tracker {
|
pub struct Tracker {
|
||||||
/// A UdpSocket used for communication.
|
/// A UdpSocket used for communication.
|
||||||
connection_stream: UdpSocket,
|
connection_stream: UdpSocket,
|
||||||
/// The local socket address requests are made from
|
/// The local socket address requests are made from
|
||||||
pub socket_addr: SocketAddr,
|
listen_address: SocketAddr,
|
||||||
/// The remote socket address of the tracker.
|
/// The remote socket address of the tracker.
|
||||||
pub remote_addr: SocketAddr
|
remote_address: SocketAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tracker {
|
impl Tracker {
|
||||||
@ -24,46 +26,30 @@ impl Tracker {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if there is an error parsing the given address or creating the UDP socket.
|
/// Panics if there is an error parsing the given address or creating the UDP socket.
|
||||||
pub async fn new(socket_address: &str, remote_hostname: &str, remote_port: u16) -> Self {
|
pub async fn new(listen_address: SocketAddr, remote_address: SocketAddr) -> Result<Self, ()> {
|
||||||
let socket_addr = match socket_address.parse::<SocketAddr>() {
|
let Ok(connection_stream) = UdpSocket::bind(listen_address).await else {
|
||||||
Err(err) => {
|
error!("error binding to udpsocket {listen_address}");
|
||||||
error!("error parsing address {}, err: {}", socket_address, err);
|
return Err(())
|
||||||
panic!("error parsing given address, {}", err);
|
|
||||||
}
|
|
||||||
Ok(addr) => { addr }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!("bound udpsocket successfully to: {listen_address}");
|
||||||
|
|
||||||
let remote_address = dns_lookup::lookup_host(remote_hostname).unwrap()[0];
|
match connection_stream.connect(remote_address).await {
|
||||||
|
|
||||||
let remote_addr = SocketAddr::new(remote_address, remote_port);
|
|
||||||
|
|
||||||
let connection_stream = match UdpSocket::bind(socket_addr).await {
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("unable to bind to {}, err: {}", socket_address, err);
|
error!("unable to connect to {}, err: {}", remote_address, err);
|
||||||
panic!("error creating udpsocket, {}", err);
|
|
||||||
},
|
|
||||||
Ok(stream) => {
|
|
||||||
debug!("bound udpsocket successfully to: {socket_addr}");
|
|
||||||
stream
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match connection_stream.connect(remote_addr).await {
|
|
||||||
Err(err) => {
|
|
||||||
error!("unable to connect to {}, err: {}", remote_addr, err);
|
|
||||||
panic!("error creating udpsocket, {}", err);
|
panic!("error creating udpsocket, {}", err);
|
||||||
},
|
},
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
debug!("successfully connected to: {remote_addr}");
|
debug!("successfully connected to: {remote_address}");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
connection_stream,
|
connection_stream,
|
||||||
socket_addr,
|
listen_address,
|
||||||
remote_addr
|
remote_address
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a message to the tracker and receives a response asynchronously.
|
/// Sends a message to the tracker and receives a response asynchronously.
|
||||||
@ -75,7 +61,7 @@ impl Tracker {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// A byte vector containing the received response.
|
/// A byte vector containing the received response.
|
||||||
pub async fn send_message<T: ToBuffer>(&mut self, message: &T) -> Vec<u8> {
|
async fn send_message<T: ToBuffer>(&mut self, message: &T) -> Vec<u8> {
|
||||||
let mut buf: Vec<u8> = vec![ 0; 16_384 ];
|
let mut buf: Vec<u8> = vec![ 0; 16_384 ];
|
||||||
|
|
||||||
self.connection_stream.send(&message.to_buffer()).await.unwrap();
|
self.connection_stream.send(&message.to_buffer()).await.unwrap();
|
||||||
@ -83,6 +69,33 @@ impl Tracker {
|
|||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn send_handshake(&mut self) -> i64 {
|
||||||
|
ConnectionMessage::from_buffer(
|
||||||
|
&self.send_message(&ConnectionMessage::create_basic_connection()).await
|
||||||
|
).connection_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_peers(&mut self, torrent: &Torrent, peer_id: &str) -> Vec<SocketAddrV4> {
|
||||||
|
let id = self.send_handshake().await;
|
||||||
|
|
||||||
|
let message = AnnounceMessage::new(
|
||||||
|
id,
|
||||||
|
&torrent.get_info_hash(),
|
||||||
|
peer_id,
|
||||||
|
torrent.get_total_length() as i64
|
||||||
|
);
|
||||||
|
|
||||||
|
let announce_message_response = AnnounceMessageResponse::from_buffer(&self.send_message(&message).await);
|
||||||
|
|
||||||
|
let mut peer_addresses = vec![];
|
||||||
|
|
||||||
|
for i in 0..announce_message_response.ips.len() {
|
||||||
|
peer_addresses.push(SocketAddrV4::new(announce_message_response.ips[i], announce_message_response.ports[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
peer_addresses
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for converting a type into a byte buffer.
|
/// A trait for converting a type into a byte buffer.
|
Loading…
Reference in New Issue
Block a user