Better error and tracker handling

This commit is contained in:
Arlo Filley 2023-10-06 16:40:43 +01:00
parent 5eb73b29ad
commit 930f50042d
11 changed files with 377 additions and 319 deletions

16
.vscode/launch.json vendored Normal file
View 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}"
}
]
}

View File

@ -1,5 +0,0 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
]
}

1
downloads/README Normal file
View File

@ -0,0 +1 @@
This is a test file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -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");
} }

View File

@ -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 {

View File

@ -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,
}) })

View File

@ -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
View File

@ -0,0 +1 @@
pub mod tracker;

View File

@ -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.