diff --git a/data/archlinux-2023.10.14-x86_64.iso.torrent b/data/archlinux-2023.10.14-x86_64.iso.torrent deleted file mode 100644 index 94c137d..0000000 Binary files a/data/archlinux-2023.10.14-x86_64.iso.torrent and /dev/null differ diff --git a/src/hex.rs b/src/hex.rs index e2f5092..1fdf1b9 100644 --- a/src/hex.rs +++ b/src/hex.rs @@ -1,5 +1,3 @@ -use crate::types::ByteString; - -pub fn hex(str: &ByteString) -> String { - str.iter().map(|c| format!("{:x?}", c)).collect::() +pub fn hex(str: &[u8]) -> String { + str.iter().map(|c| format!("{:02x}", c)).collect::() } diff --git a/src/main.rs b/src/main.rs index 2ae6113..1cc27be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use peer::handshake; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::{fs, path::PathBuf}; @@ -6,12 +7,13 @@ use types::ByteString; use crate::{ metainfo::Metainfo, - tracker::{tracker_request, TrackerRequest}, + tracker::{tracker_request, TrackerRequest, TrackerResponse}, }; mod bencode; mod hex; mod metainfo; +mod peer; mod sha1; mod tracker; mod types; @@ -23,12 +25,12 @@ fn main() { (Some(metadata), left) if left.is_empty() => metadata, _ => panic!("metadata file parsing error"), }; - println!("{metainfo_dict:#?}"); + println!("{metainfo_dict:?}"); let metainfo = match Metainfo::try_from(metainfo_dict.clone()) { Ok(info) => info, Err(e) => panic!("metadata file structure error: {e}"), }; - println!("{metainfo:#?}"); + println!("{metainfo:?}"); let info_dict_str = match metainfo_dict { bencode::BencodeValue::Dict(d) => d.get("info").unwrap().encode(), _ => unreachable!(), @@ -38,10 +40,26 @@ fn main() { 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), + TrackerRequest::new( + info_hash.clone(), + peer_id.clone(), + tracker::TrackerEvent::Started, + None, + ), ) .expect("request failed"); println!("tracker response: {tracker_response:?}"); + if let TrackerResponse::Success(resp) = tracker_response { + for p in resp.peers { + match handshake(&p, &info_hash, &peer_id) { + Ok(_) => { + println!("successfull handshake with peer {:?}", p); + break; + } + Err(e) => eprintln!("{}", e), + } + } + } } /// Generate random 20 byte string, starting with -<2 byte client name><4 byte client version>- diff --git a/src/peer.rs b/src/peer.rs new file mode 100644 index 0000000..a01d674 --- /dev/null +++ b/src/peer.rs @@ -0,0 +1,64 @@ +use std::io::{self, Read, Write}; +use std::net::{IpAddr, SocketAddr, TcpStream}; +use std::str::FromStr; +use std::thread::sleep; +use std::time::Duration; + +use crate::hex::hex; +use crate::tracker::TrackerPeer; +use crate::types::ByteString; + +pub fn handshake( + peer: &TrackerPeer, + info_hash: &ByteString, + peer_id: &ByteString, +) -> io::Result<()> { + println!("connecting to peer {peer:?}"); + let timeout = Duration::new(2, 0); + let mut stream = TcpStream::connect_timeout( + &SocketAddr::new(IpAddr::from_str(&peer.ip).unwrap(), peer.port as u16), + timeout, + )?; + stream.set_read_timeout(Some(timeout))?; + stream.set_write_timeout(Some(timeout))?; + let handshake = handshake_packet(info_hash, peer_id); + println!("writing handshake {}", hex(&handshake.to_vec())); + match stream.write_all(&handshake) { + Err(e) => { + eprintln!("write error: {}", e); + return Err(e); + } + _ => println!("write ok"), + }; + stream.flush()?; + let mut reader = stream; + let mut read_packet = vec![]; + println!("reading response"); + let mut retry = 0; + loop { + if retry > 3 { + return Err(io::Error::new(io::ErrorKind::Other, "read timeout")); + }; + match reader.read_to_end(&mut read_packet) { + Err(e) => { + eprintln!("read error: {}", e); + return Err(e); + } + Ok(n) if n > 0 => { + println!("peer response: {}", hex(&read_packet.to_vec())); + } + _ => { + println!("no data"); + retry += 1; + sleep(Duration::new(1, 0)); + } + }; + } +} + +pub fn handshake_packet(info_hash: &ByteString, peer_id: &ByteString) -> Vec { + let pstr = "BitTorrent protocol"; + let pstrlen = &[pstr.len() as u8]; + let reserved = &[0u8; 8]; + [pstrlen, pstr.as_bytes(), reserved, &info_hash, &peer_id].concat() +} diff --git a/src/tracker.rs b/src/tracker.rs index d11e81b..585d23b 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -168,19 +168,19 @@ impl TryFrom for TrackerResponse { #[allow(dead_code)] #[derive(Debug)] pub struct TrackerResponseSuccess { - peers: Vec, - interval: i64, - warning_message: Option, - min_interval: Option, - tracker_id: Option, - complete: Option, - incomplete: Option, + pub peers: Vec, + pub interval: i64, + pub warning_message: Option, + pub min_interval: Option, + pub tracker_id: Option, + pub complete: Option, + pub incomplete: Option, } pub struct TrackerPeer { - peer_id: ByteString, - ip: String, - port: i64, + pub peer_id: ByteString, + pub ip: String, + pub port: i64, } impl fmt::Debug for TrackerPeer { @@ -217,9 +217,10 @@ pub fn tracker_request( let resp = Client::new() .get(format!("{announce}{query}")) .send() - .map_err(|e| e.to_string())? + .map_err(|e| format!("request error: {}", e))? .bytes() - .map_err(|e| e.to_string())?; + .map_err(|e| format!("request body error: {}", e))?; + println!("{}", String::from_utf8_lossy(&resp)); let resp_dict = parse_bencoded(resp.to_vec()) .0 .ok_or("Malformed response")?;