Repository: artrixdotdev/tortillas
Pull request #42: feat: Add support for TCP peers
Author: login: kurealnum
Labels: name: high prio
description: GET DONE ASAP!!
Created: 2025-04-28T19:38:53Z | Merged: 2025-04-29T04:53:54Z

This PR should not be merged before confirming all tests are working as intended. 

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced TCP-based peer transport, enabling connections and data exchange over TCP in addition to existing protocols.
  - Added a new public TCP module to support TCP peer communication.

- **Tests**
  - Added comprehensive tests for TCP peer handshakes and data transmission.
  - Included a new test magnet URI file to support testing scenarios.

- **Refactor**
  - Reordered import statements for improved code organization and clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/crates/libtortillas/src/peers/mod.rs b/crates/libtortillas/src/peers/mod.rs
index 897f7215..13e301c9 100644
--- a/crates/libtortillas/src/peers/mod.rs
+++ b/crates/libtortillas/src/peers/mod.rs
@@ -1,6 +1,6 @@
 use anyhow::Result;
 use async_trait::async_trait;
-use messages::{Handshake, MAGIC_STRING, PeerMessages};
+use messages::{Handshake, PeerMessages, MAGIC_STRING};
 use std::{
    fmt::Display,
    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
@@ -9,7 +9,7 @@ use std::{
 };
 use tokio::{
    sync::mpsc::{self, Receiver, Sender},
-   time::{Instant, timeout},
+   time::{timeout, Instant},
 };
 use tracing::{error, trace};
 use transport_messages::TransportCommand;
@@ -19,6 +19,7 @@ use crate::{
    hashes::{Hash, InfoHash},
 };
 pub mod messages;
+pub mod tcp;
 mod transport_messages;
 pub mod utp;
 pub type PeerKey = SocketAddr;
@@ -231,7 +232,7 @@ impl<P: TransportProtocol + 'static> TransportHandler<P> {
                tokio::spawn(async move {
                   // Peers should be able to finish their handshake after two seconds
                   const TIMEOUT_DURATION: u64 = 2;
-                  let connect = transport_clone.connect_peer(&mut peer, info_hash, id);
+                  let connect = transport_clone.connect_peer(&mut peer, id, info_hash);
                   let res = timeout(Duration::from_secs(TIMEOUT_DURATION), connect)
                      .await
                      .map_err(|e| error!("Error connecting to peer: {e}"));
diff --git a/crates/libtortillas/src/peers/tcp.rs b/crates/libtortillas/src/peers/tcp.rs
new file mode 100644
index 00000000..4b88f4c7
--- /dev/null
+++ b/crates/libtortillas/src/peers/tcp.rs
@@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
+      // Completely chat gippity generated code, do not trust
+      // Update total bytes uploaded
+      peer.bytes_uploaded += data.len() as u64;
+
+      // Calculate upload rate based on a time window
+      let now = Instant::now();
+      if let Some(last_time) = peer.last_message_sent {
+         let elapsed_secs = last_time.elapsed().as_secs_f64();
+         if elapsed_secs > 0.0 {
+            // Use an exponential moving average for smoother rate calculation
+            const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+            let current_rate = data.len() as f64 / elapsed_secs;
+            peer.upload_rate = if peer.upload_rate > 0.0 {
+               (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
+            } else {
+               current_rate
+            };
+         }
+      }
+
+      peer.last_message_sent = Some(now);
+      Ok(())
+   }
+
+   async fn receive_data(
+      &mut self,
+      info_hash: Arc<InfoHash>,
+      id: Arc<Hash<20>>,
+   ) -> Result<(PeerKey, Vec<u8>), PeerTransportError> {
+      let mut tcp_stream = self.listener.accept().await.unwrap().0;
+      // First 4 bytes is the big endian encoded length field and the 5th byte is a PeerMessage tag
+      let mut buf = vec![0; 5];
+
+      tcp_stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let addr = tcp_stream.peer_addr().unwrap();
+      trace!(message_type = buf[4], ip = %addr, "Recieved message headers, requesting rest...");
+      let mut is_handshake = false;
+      let length = if buf[0] as usize == MAGIC_STRING.len() && buf[1..5] == MAGIC_STRING[0..4] {
+         // This is a handshake.
+         // The length of a handshake is always 68 and we already have the
+         // first 5 bytes of it, so we need 68 - 5 bytes (the current buffer length)
+         is_handshake = true;
+         68 - buf.len() as u32
+      } else {
+         // This is not a handshake
+         // Non handshake messages have a length field from bytes 0-4
+         u32::from_be_bytes(buf[..4].try_into().unwrap())
+      };
+
+      let mut rest = vec![0; length as usize];
+
+      tcp_stream.read_exact(&mut rest).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let full_length = length + buf.len() as u32;
+
+      debug!(
+         "Read {} action ({} bytes) from {} ",
+         buf[4], full_length, addr
+      );
+      buf.extend_from_slice(&rest);
+
+      if let Some(mutex) = self.peers.get_mut(&addr) {
+         let peer = &mut mutex.lock().await.0;
+
+         // Completely chat gippity generated code, do not trust
+         // Update total bytes uploaded
+         peer.bytes_uploaded += full_length as u64;
+
+         // Calculate upload rate based on a time window
+         let now = Instant::now();
+         if let Some(last_time) = peer.last_message_received {
+            let elapsed_secs = last_time.elapsed().as_secs_f64();
+            if elapsed_secs > 0.0 {
+               // Use an exponential moving average for smoother rate calculation
+               const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+               let current_rate = full_length as f64 / elapsed_secs;
+               peer.download_rate = if peer.download_rate > 0.0 {
+                  (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.download_rate)
+               } else {
+                  current_rate
+               };
+            }
+         }
+
+         peer.last_message_received = Some(now);
+      };
+
+      if is_handshake {
+         let handshake =
+            Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+         let (handshake, peer) = self.validate_handshake(handshake, addr, info_hash, id)?;
+
+         trace!(peer_id = %peer.id.unwrap(), "Successfully validated handshake");
+
+         // Creates a new handshake and sends it
+         self
+            .peers
+            .insert(addr, Arc::new(Mutex::new((peer.clone(), tcp_stream))));
+         let message = PeerMessages::Handshake(handshake);
+         self.send_data(addr, message.to_bytes().unwrap()).await?;
+
+         info!(%peer, "Peer connected");
+      }
+
+      Ok((addr, buf))
+   }
+
+   fn close_connection(&mut self, peer_key: PeerKey) -> Result<()> {
+      self.peers.remove(&peer_key);
+      Ok(())
+   }
+
+   fn is_peer_connected(&self, peer_key: PeerKey) -> bool {
+      self.peers.contains_key(&peer_key)
+   }
+
+   async fn get_connected_peer(&self, peer_key: PeerKey) -> Option<Peer> {
+      let mutex = self.peers.get(&peer_key)?;
+      let (peer, _) = &mut *mutex.lock().await;
+      Some(peer.clone())
+   }
+}
+
+#[cfg(test)]
+mod tests {
+
+   use rand::random_range;
+   use tokio::{sync::mpsc, task::JoinSet};
+   use tracing::info;
+   use tracing_test::traced_test;
+
+   use crate::{
+      parser::{MagnetUri, MetaInfo},
+      peers::{transport_messages::TransportCommand, Transport, TransportHandler},
+      tracker::{http::HttpTracker, Tracker, TrackerTrait},
+   };
+
+   use super::*;
+   #[tokio::test(flavor = "multi_thread", worker_threads = 50)]
+   #[traced_test]
+   async fn test_tcp_peer_handshake() {
+      let path = std::env::current_dir()
+         .unwrap()
+         .join("tests/magneturis/test1.txt");
+      let contents = tokio::fs::read_to_string(path).await.unwrap();
+
+      let metainfo = MagnetUri::parse(contents).await.unwrap();
+
+      match metainfo {
+         MetaInfo::MagnetUri(magnet) => {
+            let info_hash = magnet.info_hash().unwrap();
+
+            let announce_list: Vec<Tracker> = magnet
+               .announce_list
+               .unwrap_or_default()
+               .into_iter()
+               .filter_map(|e| match e {
+                  Tracker::Http(_) => Some(e),
+                  _ => None,
+               })
+               .collect();
+
+            let announce_url = announce_list[0].uri();
+            let port: u16 = random_range(1024..65535);
+
+            let mut tracker = HttpTracker::new(
+               announce_url,
+               info_hash,
+               Some(SocketAddr::from(([0, 0, 0, 0], port))),
+            );
+            let peer_id = tracker.peer_id;
+            let peers = tracker.get_peers().await.unwrap();
+
+            // Skip if no peers found
+            if peers.is_empty() {
+               error!("No peers found, skipping test");
+               return;
+            }
+
+            let info_hash_clone = Arc::new(info_hash);
+
+            // Create a single TCP transport instance
+            let client_peer_id = Hash::new(rand::random::<[u8; 20]>());
+            let client_port: u16 = random_range(20001..30000);
+            let client_addr = SocketAddr::from(([127, 0, 0, 1], client_port));
+
+            info!("Running transport on {client_addr}");
+
+            let protocol = TcpProtocol::new(Some(client_addr)).await;
+            let mut tcp_transport_handler =
+               TransportHandler::new(protocol, Arc::new(client_peer_id), info_hash_clone);
+
+            let tx = tcp_transport_handler.tx.clone();
+
+            // This is how TcpTransports should be handled async
+
+            // Create a vector to hold all the join handles
+            let mut join_set = JoinSet::new();
+
+            // For each peer, spawn a task to connect
+            let num_of_peers = peers.len();
+            for peer in peers {
+               // Clone tx (see Tokio docs on why we need to clone tx: <https://tokio.rs/tokio/tutorial/channels>)
+               let tx = tx.clone();
+               join_set.spawn(async move {
+                  let cmd = TransportCommand::Connect { peer };
+
+                  match tx.send(cmd).await {
+                     Ok(()) => Ok(peer_id),
+                     Err(_) => Err("Connection failed".to_string()),
+                  }
+               });
+            }
+
+            let (tx, mut rx) = mpsc::channel(100);
+
+            // Start handling mpsc messages from the join set
+            tokio::spawn(async move {
+               tcp_transport_handler.handle_commands(tx).await.unwrap();
+            });
+
+            // Await the join_set.spawn()
+            join_set.join_all().await;
+
+            // Collect responses from handle_message
+            tokio::spawn(async move {
+               let mut total_peers_seen = 0;
+               while let Some(_res) = rx.recv().await {
+                  total_peers_seen += 1;
+                  if num_of_peers == total_peers_seen {
+                     break;
+                  }
+               }
+            })
+            .await
+            .unwrap();
+         }
+         _ => panic!("Expected Torrent"),
+      }
+   }
+
+   #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+   #[traced_test]
+   async fn test_tcp_incoming_handshake() {
+      // Generate random info hash and peer IDs
+      let info_hash = InfoHash::new(rand::random::<[u8; 20]>());
+      let server_peer_id = Hash::new(rand::random::<[u8; 20]>());
+      let client_peer_id = Hash::new(rand::random::<[u8; 20]>());
+
+      let server_addr = SocketAddr::from(([127, 0, 0, 1], 9883));
+      let client_addr = SocketAddr::from(([127, 0, 0, 1], 9884));
+
+      // Create shared info hash for both sides
+      let info_hash_arc = Arc::new(info_hash);
+      let info_hash_clone = Arc::clone(&info_hash_arc);
+
+      let mut set = JoinSet::new();
+
+      // Spawn the server in a separate task
+      set.spawn(async move {
+         // Create server transport
+         info!("Server listening on {}", server_addr);
+         let protocol = TcpProtocol::new(Some(server_addr)).await;
+         let mut server_transport =
+            TransportHandler::new(protocol, Arc::new(server_peer_id), info_hash_arc);
+         // Accept incoming connection
+         match tokio::time::timeout(std::time::Duration::from_secs(5), server_transport.recv())
+            .await
+         {
+            Ok(Ok((key, _))) => {
+               let peer = server_transport.get_peer(key).await;
+               assert!(peer.is_some(), "Peer should exist");
+
+               let peer = peer.unwrap();
+
+               info!(%peer, "Server accepted connection",);
+               assert!(
+                  peer.id.is_some(),
+                  "Peer ID should be present after handshake"
+               );
+               assert_eq!(
+                  peer.id.unwrap(),
+                  client_peer_id,
+                  "Received peer ID should match client's"
+               );
+               Ok(peer.id)
+            }
+            Ok(Err(e)) => Err(format!("Server error accepting connection: {}", e)),
+            Err(_) => Err("Server timed out waiting for connection".to_string()),
+         }
+      });
+
+      // Create client and connect to server
+      set.spawn(async move {
+         // Give the server a moment to start up
+         tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+
+         // Create client transport
+         let protocol = TcpProtocol::new(Some(client_addr)).await;
+         let mut client_transport =
+            TransportHandler::new(protocol, Arc::new(client_peer_id), info_hash_clone);
+
+         // Create a peer representation of the server
+         let mut server_peer = Peer::from_socket_addr(server_addr);
+
+         // Connect to server
+         match tokio::time::timeout(
+            std::time::Duration::from_secs(5),
+            client_transport.connect(&mut server_peer),
+         )
+         .await
+         {
+            Ok(Ok(peer_key)) => {
+               info!(
+                  "Client connected to server, received peer ID: {:?}",
+                  peer_key
+               );
+               assert_eq!(
+                  peer_key,
+                  server_peer.socket_addr(),
+                  "Received peer ID should match server's"
+               );
+               let peer = client_transport.get_peer(peer_key).await.unwrap();
+
+               Ok(peer.id)
+            }
+            Ok(Err(e)) => Err(format!("Client error connecting: {}", e)),
+            Err(_) => Err("Client timed out connecting to server".to_string()),
+         }
+      });
+
+      // Wait for both operations to complete
+      let set = set.join_all().await;
+      let server_result = &set[0];
+      let client_result = &set[1];
+
+      // Verify both sides completed successfully
+      assert!(
+         server_result.is_ok(),
+         "Server error: {:?}",
+         server_result.clone().err()
+      );
+      assert!(
+         client_result.is_ok(),
+         "Client error: {:?}",
+         client_result.clone().err()
+      );
+
+      info!("TCP handshake test completed successfully");
+   }
+}
diff --git a/crates/libtortillas/src/peers/utp.rs b/crates/libtortillas/src/peers/utp.rs
index f0d38b6d..7ea4d75b 100644
--- a/crates/libtortillas/src/peers/utp.rs
+++ b/crates/libtortillas/src/peers/utp.rs
@@ -2,10 +2,10 @@ use super::{Peer, PeerKey, TransportProtocol};
 use crate::{
    errors::PeerTransportError,
    hashes::{Hash, InfoHash},
-   peers::PeerMessages,
    peers::messages::{Handshake, MAGIC_STRING},
+   peers::PeerMessages,
 };
-use anyhow::{Result, anyhow};
+use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use librqbit_utp::{UtpSocket, UtpSocketUdp, UtpStream};
 use std::{collections::HashMap, net::SocketAddr, str::FromStr, sync::Arc};
@@ -58,7 +58,7 @@ impl TransportProtocol for UtpProtocol {
       id: Arc<Hash<20>>,
       info_hash: Arc<InfoHash>,
    ) -> Result<PeerKey, PeerTransportError> {
-      trace!("Attempting connection...");
+      trace!("Attemping connection to {}", peer.socket_addr());
 
       // Connect to the peer
       let mut stream = self.socket.connect(peer.socket_addr()).await.map_err(|e| {
@@ -258,8 +258,8 @@ mod tests {
 
    use crate::{
       parser::{MagnetUri, MetaInfo},
-      peers::{Transport, TransportHandler, transport_messages::TransportCommand},
-      tracker::{Tracker, TrackerTrait, http::HttpTracker},
+      peers::{transport_messages::TransportCommand, Transport, TransportHandler},
+      tracker::{http::HttpTracker, Tracker, TrackerTrait},
    };
 
    use super::*;
@@ -268,7 +268,7 @@ mod tests {
    async fn test_utp_peer_handshake() {
       let path = std::env::current_dir()
          .unwrap()
-         .join("tests/magneturis/zenshuu.txt");
+         .join("tests/magneturis/test1.txt");
       let contents = tokio::fs::read_to_string(path).await.unwrap();
 
       let metainfo = MagnetUri::parse(contents).await.unwrap();
diff --git a/crates/libtortillas/tests/magneturis/test1.txt b/crates/libtortillas/tests/magneturis/test1.txt
new file mode 100644
index 00000000..f9a1cefe
--- /dev/null
+++ b/crates/libtortillas/tests/magneturis/test1.txt
@@ -0,0 +1 @@
+magnet:?xt=urn:btih:302cb241668282a011e5d13d09eb0afb7c5c8f3c&dn=%5BYameii%5D%20Aharen-san%20wa%20Hakarenai%20-%20S02E02%20%5BEnglish%20Dub%5D%20%5BCR%20WEB-DL%201080p%5D%20%5B37065F56%5D%20%28Aharen%20Is%20Indecipherable%20Season%202%20%7C%20S2%29&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 5**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/mod.rs (1)</summary><blockquote>

`22-22`: **Publicly exposing the new TCP module without feature-gating may surprise downstream crates**

`pub mod tcp;` immediately makes the new implementation part of the public API.  
If the TCP transport is still experimental (the PR description explicitly warns that tests must still be validated), consider hiding it behind a Cargo feature or at least marking it as `#[cfg(feature = "tcp")]` until it stabilises.  
This avoids breaking semver guarantees for crates depending on `libtortillas`.

</blockquote></details>
<details>
<summary>crates/libtortillas/src/peers/tcp.rs (3)</summary><blockquote>

`52-58`: **Avoid unnecessary `to_string()` conversion and propagate connection errors**

`TcpStream::connect` accepts a `SocketAddr`; converting to `String` allocates and also hides DNS-fail/panic scenarios behind another level.  
Additionally, the closure argument `e` is unused in the `map_err` – use `|e|` not `|e|` inside the `map_err` but you shadow it with another `e`. Minor nit.

```diff
- let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+ let mut stream = TcpStream::connect(peer.socket_addr())
```

---

`170-177`: **Download statistics are written to `bytes_uploaded`**

Within `receive_data` the branch updates `peer.bytes_uploaded` even though the peer just *received* data. This should update `bytes_downloaded`, otherwise upload/download metrics are skewed.

```diff
- peer.bytes_uploaded += full_length as u64;
+ peer.bytes_downloaded += full_length as u64;
```

---

`23-27`: **Cloning `TcpProtocol` duplicates the peer map**

`TcpProtocol` derives `Clone`, but its `peers` `HashMap` is cloned *by value*, yielding independent maps for each clone.  
In `TransportHandler::handle_commands` a fresh clone is created per command, so modifications (new peers, statistics) won’t be visible elsewhere.

Consider wrapping `peers` in an `Arc<Mutex<…>>` so all clones share the same state, or make `TcpProtocol` non-`Clone` and share it explicitly.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 132714442a8c09c9b084430625a77630a4b87485 and 91c9f71820ab725d12ef6bb5681ced4622d86e57.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `crates/libtortillas/src/peers/mod.rs` (3 hunks)
* `crates/libtortillas/src/peers/tcp.rs` (1 hunks)
* `crates/libtortillas/src/peers/utp.rs` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>crates/libtortillas/src/peers/tcp.rs (2)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/mod.rs (16)</summary>

* `new` (49-67)
* `new` (204-214)
* `socket_addr` (80-82)
* `connect_peer` (99-104)
* `id` (156-156)
* `id` (264-266)
* `info_hash` (158-158)
* `info_hash` (268-270)
* `connect` (163-163)
* `connect` (272-277)
* `send_data` (105-105)
* `receive_data` (106-110)
* `close_connection` (111-111)
* `is_peer_connected` (112-112)
* `get_connected_peer` (113-113)
* `from_socket_addr` (85-87)

</details>
<details>
<summary>crates/libtortillas/src/peers/utp.rs (7)</summary>

* `new` (26-35)
* `connect_peer` (55-109)
* `send_data` (199-230)
* `receive_data` (111-197)
* `close_connection` (241-244)
* `is_peer_connected` (246-248)
* `get_connected_peer` (232-236)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build_and_test

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/utp.rs (1)</summary>

`6-8`: **Import re-ordering only – looks good**

Reordering imports has no functional impact. No issues spotted.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T19:42:17Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 5**

<details>
<summary>♻️ Duplicate comments (5)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/tcp.rs (5)</summary><blockquote>

`30-39`: _⚠️ Potential issue_

**Don't `unwrap()` on listener creation – surface the error instead.**

Binding to a socket can legitimately fail (e.g., port already in use, insufficient permissions).
Calling `unwrap()` will panic the whole application. Return a `Result` from `new` (as `UtpProtocol::new` does) or propagate the error upwards.


```diff
-   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> Result<TcpProtocol> {
...
-      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+      let listener = Arc::new(TcpListener::bind(socket_addr).await?);
...
-      TcpProtocol { listener, peers: HashMap::new() }
+      Ok(TcpProtocol { listener, peers: HashMap::new() })
```

---

`59-62`: _⚠️ Potential issue_

**Unused binding and unchecked `unwrap()`.**

`write_all` returns `()` – storing it as `handshake_bytes` and then never using it is misleading.  
More importantly, `unwrap()` will panic; prefer `?` to propagate an I/O failure.


```diff
- let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+ stream.write_all(&handshake.to_bytes()).await?;
```

---

`77-80`: _⚠️ Potential issue_

**`unwrap()` on handshake validation can panic – bubble the error.**

`validate_handshake` already returns a rich `PeerTransportError`.  Using `unwrap()` throws that information away and risks terminating the task. Use `?` instead.


```diff
- let (_, new_peer) = self
-    .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
-    .unwrap();
+ let (_, new_peer) =
+    self.validate_handshake(handshake, peer.socket_addr(), info_hash, id)?;
```

---

`95-103`: _⚠️ Potential issue_

**Potential panic when peer key not present.**

`self.peers.get_mut(&to).unwrap()` will panic if the peer disconnected or was never added.  
Return a `PeerTransportError::UnknownPeer` (or similar) instead of crashing.


```diff
- let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+ let mutex = self.peers.get_mut(&to)
+    .ok_or(PeerTransportError::UnknownPeer(to.to_string()))?;
+ let (peer, socket) = &mut *mutex.lock().await;
```

---

`133-142`: _⚠️ Potential issue_

**Unchecked `unwrap()` on listener accept & `peer_addr`.**

`self.listener.accept().await.unwrap()` and `tcp_stream.peer_addr().unwrap()` can both fail (e.g., closed listener). Replace with `?` and propagate an appropriate error variant.


```diff
- let mut tcp_stream = self.listener.accept().await.unwrap().0;
+ let (mut tcp_stream, _addr) = self.listener.accept().await
+     .map_err(|e| PeerTransportError::Other(anyhow!("Failed to accept connection: {}", e)))?;

- let addr = tcp_stream.peer_addr().unwrap();
+ let addr = tcp_stream.peer_addr()
+     .map_err(|e| PeerTransportError::Other(anyhow!("Failed to get peer address: {}", e)))?;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/tcp.rs (2)</summary><blockquote>

`104-126`: **Consider adding a comment about the EMA calculation for upload rate.**

The exponential moving average (EMA) calculation for upload rate works correctly, but a brief comment explaining the chosen alpha value and its implications would be helpful for future maintainers.

```diff
 // Calculate upload rate based on a time window
 let now = Instant::now();
 if let Some(last_time) = peer.last_message_sent {
    let elapsed_secs = last_time.elapsed().as_secs_f64();
    if elapsed_secs > 0.0 {
       // Use an exponential moving average for smoother rate calculation
       const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+      // ALPHA=0.3 gives moderate weight to new values while maintaining stability
+      // Higher values would make the rate more reactive to recent changes
       let current_rate = data.len() as f64 / elapsed_secs;
       peer.upload_rate = if peer.upload_rate > 0.0 {
          (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
       } else {
          current_rate
       };
    }
 }
```

---

`144-154`: **Consider extracting handshake identification logic into a shared utility.**

This handshake identification logic appears in both the TCP and uTP implementations and could be extracted to reduce duplication.

Consider moving this logic to a utility function in the `messages` module to avoid duplication between the transport implementations.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 91c9f71820ab725d12ef6bb5681ced4622d86e57 and b933aabdc9380bf5b3a1bab93b3d182a6bb002ef.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `crates/libtortillas/src/peers/mod.rs` (4 hunks)
* `crates/libtortillas/src/peers/tcp.rs` (1 hunks)
* `crates/libtortillas/src/peers/utp.rs` (4 hunks)
* `crates/libtortillas/tests/magneturis/test1.txt` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* crates/libtortillas/tests/magneturis/test1.txt

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* crates/libtortillas/src/peers/utp.rs

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>crates/libtortillas/src/peers/tcp.rs (2)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/utp.rs (7)</summary>

* `new` (26-35)
* `connect_peer` (55-109)
* `send_data` (199-230)
* `receive_data` (111-197)
* `close_connection` (241-244)
* `is_peer_connected` (246-248)
* `get_connected_peer` (232-236)

</details>
<details>
<summary>crates/libtortillas/src/peers/mod.rs (16)</summary>

* `new` (49-67)
* `new` (204-214)
* `socket_addr` (80-82)
* `connect_peer` (99-104)
* `id` (156-156)
* `id` (264-266)
* `info_hash` (158-158)
* `info_hash` (268-270)
* `connect` (163-163)
* `connect` (272-277)
* `send_data` (105-105)
* `receive_data` (106-110)
* `close_connection` (111-111)
* `is_peer_connected` (112-112)
* `get_connected_peer` (113-113)
* `from_socket_addr` (85-87)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Actions: ✅ Checks</summary>

<details>
<summary>crates/libtortillas/src/peers/mod.rs</summary>

[error] 1-20: cargo fmt --check failed due to formatting differences. Code style does not match expected formatting.

</details>
<details>
<summary>crates/libtortillas/src/peers/tcp.rs</summary>

[error] 4-250: cargo fmt --check failed due to formatting differences. Code style does not match expected formatting.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>crates/libtortillas/src/peers/mod.rs (2)</summary>

`22-22`: **LGTM: New TCP module added.**

The addition of the TCP module extends the peer transport capabilities.

---

`235-235`: <details>
<summary>✅ Verification successful</summary>

**Verify the parameter order change in connect_peer call.**

The argument order in the `connect_peer` call has been changed from `(peer, info_hash, id)` to `(peer, id, info_hash)` to match the new TCP implementation's method signature.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if all connect_peer calls have been updated with the correct argument order
rg -A 2 -B 2 "connect_peer" | grep -A 1 -B 1 "peer.*id.*info_hash\|peer.*info_hash.*id"
```

Length of output: 920

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify the parameter order in the utp.rs connect_peer signature
rg -n "async fn connect_peer" -A 5 crates/libtortillas/src/peers/utp.rs
```

Length of output: 260

---



</details>

**All connect_peer calls now match the new signature**  
I’ve confirmed that `async fn connect_peer` in `crates/libtortillas/src/peers/utp.rs` takes parameters in the order `(peer, id, info_hash)`, and both call sites in `crates/libtortillas/src/peers/mod.rs` have been updated accordingly. No further changes required.

</details>
<details>
<summary>crates/libtortillas/src/peers/tcp.rs (4)</summary>

`1-3`: **LGTM: Clear comment about implementation approach.**

Good practice to include this note, which helps maintainers understand the relationship between the TCP and uTP implementations.

---

`23-27`: **LGTM: TcpProtocol struct design.**

The struct design is appropriate, mirroring the uTP implementation with TCP-specific components.

---

`4-16`: **LGTM: Import organization follows standard practice.**

The imports are well-organized, grouping standard library imports, third-party crates, and internal modules.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: ✅ Checks</summary>

[error] 4-250: cargo fmt --check failed due to formatting differences. Code style does not match expected formatting.

</details>

</details>

---

`232-459`: **LGTM: Comprehensive test coverage.**

The tests cover both outgoing and incoming TCP handshake scenarios, which is crucial for verifying the transport protocol implementation works correctly in both directions.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T23:24:25Z

Review comments:
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Potential panic when peer key not present**

`self.peers.get_mut(&to).unwrap()` will panic if the peer disconnected or was never added.  
Return a `PeerTransportError::UnknownPeer` (or similar) instead of crashing.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T19:42:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
+      // Completely chat gippity generated code, do not trust
+      // Update total bytes uploaded
+      peer.bytes_uploaded += data.len() as u64;
+
+      // Calculate upload rate based on a time window
+      let now = Instant::now();
+      if let Some(last_time) = peer.last_message_sent {
+         let elapsed_secs = last_time.elapsed().as_secs_f64();
+         if elapsed_secs > 0.0 {
+            // Use an exponential moving average for smoother rate calculation
+            const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+            let current_rate = data.len() as f64 / elapsed_secs;
+            peer.upload_rate = if peer.upload_rate > 0.0 {
+               (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
+            } else {
+               current_rate
+            };
+         }
+      }
+
+      peer.last_message_sent = Some(now);
+      Ok(())
+   }
+
+   async fn receive_data(
+      &mut self,
+      info_hash: Arc<InfoHash>,
+      id: Arc<Hash<20>>,
+   ) -> Result<(PeerKey, Vec<u8>), PeerTransportError> {
+      let mut tcp_stream = self.listener.accept().await.unwrap().0;
+      // First 4 bytes is the big endian encoded length field and the 5th byte is a PeerMessage tag
+      let mut buf = vec![0; 5];
+
+      tcp_stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unchecked `unwrap()` on listener accept & `peer_addr`**

`self.listener.accept().await.unwrap()` and `tcp_stream.peer_addr().unwrap()` can both fail (e.g. closed listener). Replace with `?` and propagate an appropriate error variant.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T19:42:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`unwrap()` on handshake validation can panic – bubble the error**

`validate_handshake` already returns a rich `PeerTransportError`.  Using `unwrap()` throws that information away and risks terminating the task. Use `?` instead:

```diff
- let (_, new_peer) = self
-    .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
-    .unwrap();
+ let (_, new_peer) =
+    self.validate_handshake(handshake, peer.socket_addr(), info_hash, id)?;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      let (_, new_peer) =
          self.validate_handshake(handshake, peer.socket_addr(), info_hash, id)?;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T19:42:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unused binding and unchecked `unwrap()`**

`write_all` returns `()` – storing it as `handshake_bytes` and then never using it is misleading.  
More importantly, `unwrap()` will panic; prefer `?` to propagate an I/O failure.

```diff
- let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+ stream.write_all(&handshake.to_bytes()).await?;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      let handshake = Handshake::new(info_hash.clone(), id.clone());
-     let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+     stream.write_all(&handshake.to_bytes()).await?;
      trace!("Sent handshake to peer");
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T19:42:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Don’t `unwrap()` on listener creation – surface the error instead**

Binding to a socket can legitimately fail (e.g. port already in use, insufficient permissions).  
Calling `unwrap()` will panic the whole application. Return a `Result` from `new` (mirrors what `UtpProtocol::new` does) or propagate the error upwards.

```diff
-   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> Result<TcpProtocol> {
...
-      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+      let listener = Arc::new(TcpListener::bind(socket_addr).await?);
...
-      TcpProtocol { listener, peers: HashMap::new() }
+      Ok(TcpProtocol { listener, peers: HashMap::new() })
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T19:42:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,6 +1,6 @@
 use anyhow::Result;
 use async_trait::async_trait;
-use messages::{Handshake, MAGIC_STRING, PeerMessages};
+use messages::{Handshake, PeerMessages, MAGIC_STRING};
path: crates/libtortillas/src/peers/mod.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix import order for code style violations.**

The order of imports in these lines doesn't match the formatting style expected by cargo fmt, causing pipeline failures.

Please run `cargo fmt` to fix these formatting issues:
```diff
-use messages::{Handshake, PeerMessages, MAGIC_STRING};
+use messages::{MAGIC_STRING, Handshake, PeerMessages};
```

```diff
-   time::{timeout, Instant},
+   time::{Instant, timeout},
```


Also applies to: 12-12

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T23:24:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
+      // Completely chat gippity generated code, do not trust
+      // Update total bytes uploaded
+      peer.bytes_uploaded += data.len() as u64;
+
+      // Calculate upload rate based on a time window
+      let now = Instant::now();
+      if let Some(last_time) = peer.last_message_sent {
+         let elapsed_secs = last_time.elapsed().as_secs_f64();
+         if elapsed_secs > 0.0 {
+            // Use an exponential moving average for smoother rate calculation
+            const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+            let current_rate = data.len() as f64 / elapsed_secs;
+            peer.upload_rate = if peer.upload_rate > 0.0 {
+               (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
+            } else {
+               current_rate
+            };
+         }
+      }
+
+      peer.last_message_sent = Some(now);
+      Ok(())
+   }
+
+   async fn receive_data(
+      &mut self,
+      info_hash: Arc<InfoHash>,
+      id: Arc<Hash<20>>,
+   ) -> Result<(PeerKey, Vec<u8>), PeerTransportError> {
+      let mut tcp_stream = self.listener.accept().await.unwrap().0;
+      // First 4 bytes is the big endian encoded length field and the 5th byte is a PeerMessage tag
+      let mut buf = vec![0; 5];
+
+      tcp_stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let addr = tcp_stream.peer_addr().unwrap();
+      trace!(message_type = buf[4], ip = %addr, "Recieved message headers, requesting rest...");
+      let mut is_handshake = false;
+      let length = if buf[0] as usize == MAGIC_STRING.len() && buf[1..5] == MAGIC_STRING[0..4] {
+         // This is a handshake.
+         // The length of a handshake is always 68 and we already have the
+         // first 5 bytes of it, so we need 68 - 5 bytes (the current buffer length)
+         is_handshake = true;
+         68 - buf.len() as u32
+      } else {
+         // This is not a handshake
+         // Non handshake messages have a length field from bytes 0-4
+         u32::from_be_bytes(buf[..4].try_into().unwrap())
+      };
+
+      let mut rest = vec![0; length as usize];
+
+      tcp_stream.read_exact(&mut rest).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let full_length = length + buf.len() as u32;
+
+      debug!(
+         "Read {} action ({} bytes) from {} ",
+         buf[4], full_length, addr
+      );
+      buf.extend_from_slice(&rest);
+
+      if let Some(mutex) = self.peers.get_mut(&addr) {
+         let peer = &mut mutex.lock().await.0;
+
+         // Completely chat gippity generated code, do not trust
+         // Update total bytes uploaded
+         peer.bytes_uploaded += full_length as u64;
+
+         // Calculate upload rate based on a time window
+         let now = Instant::now();
+         if let Some(last_time) = peer.last_message_received {
+            let elapsed_secs = last_time.elapsed().as_secs_f64();
+            if elapsed_secs > 0.0 {
+               // Use an exponential moving average for smoother rate calculation
+               const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+               let current_rate = full_length as f64 / elapsed_secs;
+               peer.download_rate = if peer.download_rate > 0.0 {
+                  (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.download_rate)
+               } else {
+                  current_rate
+               };
+            }
+         }
+
+         peer.last_message_received = Some(now);
+      };
+
+      if is_handshake {
+         let handshake =
+            Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+         let (handshake, peer) = self.validate_handshake(handshake, addr, info_hash, id)?;
+
+         trace!(peer_id = %peer.id.unwrap(), "Successfully validated handshake");
+
+         // Creates a new handshake and sends it
+         self
+            .peers
+            .insert(addr, Arc::new(Mutex::new((peer.clone(), tcp_stream))));
+         let message = PeerMessages::Handshake(handshake);
+         self.send_data(addr, message.to_bytes().unwrap()).await?;
+
+         info!(%peer, "Peer connected");
+      }
+
+      Ok((addr, buf))
+   }
+
+   fn close_connection(&mut self, peer_key: PeerKey) -> Result<()> {
+      self.peers.remove(&peer_key);
+      Ok(())
+   }
+
+   fn is_peer_connected(&self, peer_key: PeerKey) -> bool {
+      self.peers.contains_key(&peer_key)
+   }
+
+   async fn get_connected_peer(&self, peer_key: PeerKey) -> Option<Peer> {
+      let mutex = self.peers.get(&peer_key)?;
+      let (peer, _) = &mut *mutex.lock().await;
+      Some(peer.clone())
+   }
+}
+
+#[cfg(test)]
+mod tests {
+
+   use rand::random_range;
+   use tokio::{sync::mpsc, task::JoinSet};
+   use tracing::info;
+   use tracing_test::traced_test;
+
+   use crate::{
+      parser::{MagnetUri, MetaInfo},
+      peers::{transport_messages::TransportCommand, Transport, TransportHandler},
+      tracker::{http::HttpTracker, Tracker, TrackerTrait},
+   };
+
+   use super::*;
+   #[tokio::test(flavor = "multi_thread", worker_threads = 50)]
+   #[traced_test]
+   async fn test_tcp_peer_handshake() {
+      let path = std::env::current_dir()
+         .unwrap()
+         .join("tests/magneturis/test1.txt");
+      let contents = tokio::fs::read_to_string(path).await.unwrap();
+
+      let metainfo = MagnetUri::parse(contents).await.unwrap();
+
+      match metainfo {
+         MetaInfo::MagnetUri(magnet) => {
+            let info_hash = magnet.info_hash().unwrap();
+
+            let announce_list: Vec<Tracker> = magnet
+               .announce_list
+               .unwrap_or_default()
+               .into_iter()
+               .filter_map(|e| match e {
+                  Tracker::Http(_) => Some(e),
+                  _ => None,
+               })
+               .collect();
+
+            let announce_url = announce_list[0].uri();
+            let port: u16 = random_range(1024..65535);
+
+            let mut tracker = HttpTracker::new(
+               announce_url,
+               info_hash,
+               Some(SocketAddr::from(([0, 0, 0, 0], port))),
+            );
+            let peer_id = tracker.peer_id;
+            let peers = tracker.get_peers().await.unwrap();
+
+            // Skip if no peers found
+            if peers.is_empty() {
+               error!("No peers found, skipping test");
+               return;
+            }
+
+            let info_hash_clone = Arc::new(info_hash);
+
+            // Create a single TCP transport instance
+            let client_peer_id = Hash::new(rand::random::<[u8; 20]>());
+            let client_port: u16 = random_range(20001..30000);
+            let client_addr = SocketAddr::from(([127, 0, 0, 1], client_port));
+
+            info!("Running transport on {client_addr}");
+
+            let protocol = TcpProtocol::new(Some(client_addr)).await;
+            let mut tcp_transport_handler =
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Update call sites when changing TcpProtocol::new return type.**

Once you change `TcpProtocol::new` to return a `Result`, you'll need to update all test call sites to handle the Result.

```diff
- let protocol = TcpProtocol::new(Some(client_addr)).await;
+ let protocol = TcpProtocol::new(Some(client_addr)).await?;

- let protocol = TcpProtocol::new(Some(server_addr)).await;
+ let protocol = TcpProtocol::new(Some(server_addr)).await?;

- let protocol = TcpProtocol::new(Some(client_addr)).await;
+ let protocol = TcpProtocol::new(Some(client_addr)).await?;
```


Also applies to: 371-373, 407-409

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T23:24:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
+      // Completely chat gippity generated code, do not trust
+      // Update total bytes uploaded
+      peer.bytes_uploaded += data.len() as u64;
+
+      // Calculate upload rate based on a time window
+      let now = Instant::now();
+      if let Some(last_time) = peer.last_message_sent {
+         let elapsed_secs = last_time.elapsed().as_secs_f64();
+         if elapsed_secs > 0.0 {
+            // Use an exponential moving average for smoother rate calculation
+            const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+            let current_rate = data.len() as f64 / elapsed_secs;
+            peer.upload_rate = if peer.upload_rate > 0.0 {
+               (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
+            } else {
+               current_rate
+            };
+         }
+      }
+
+      peer.last_message_sent = Some(now);
+      Ok(())
+   }
+
+   async fn receive_data(
+      &mut self,
+      info_hash: Arc<InfoHash>,
+      id: Arc<Hash<20>>,
+   ) -> Result<(PeerKey, Vec<u8>), PeerTransportError> {
+      let mut tcp_stream = self.listener.accept().await.unwrap().0;
+      // First 4 bytes is the big endian encoded length field and the 5th byte is a PeerMessage tag
+      let mut buf = vec![0; 5];
+
+      tcp_stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let addr = tcp_stream.peer_addr().unwrap();
+      trace!(message_type = buf[4], ip = %addr, "Recieved message headers, requesting rest...");
+      let mut is_handshake = false;
+      let length = if buf[0] as usize == MAGIC_STRING.len() && buf[1..5] == MAGIC_STRING[0..4] {
+         // This is a handshake.
+         // The length of a handshake is always 68 and we already have the
+         // first 5 bytes of it, so we need 68 - 5 bytes (the current buffer length)
+         is_handshake = true;
+         68 - buf.len() as u32
+      } else {
+         // This is not a handshake
+         // Non handshake messages have a length field from bytes 0-4
+         u32::from_be_bytes(buf[..4].try_into().unwrap())
+      };
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unchecked `unwrap()` on byte conversion.**

The conversion of bytes to a u32 with `try_into().unwrap()` could panic if the data is malformed.

```diff
- u32::from_be_bytes(buf[..4].try_into().unwrap())
+ u32::from_be_bytes(buf[..4].try_into()
+     .map_err(|_| PeerTransportError::InvalidPeerResponse("Invalid message length field".into()))?)
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
         u32::from_be_bytes(
-            buf[..4].try_into().unwrap()
+            buf[..4].try_into()
+                .map_err(|_| PeerTransportError::InvalidPeerResponse("Invalid message length field".into()))?
         )
      };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T23:24:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
+      // Completely chat gippity generated code, do not trust
+      // Update total bytes uploaded
+      peer.bytes_uploaded += data.len() as u64;
+
+      // Calculate upload rate based on a time window
+      let now = Instant::now();
+      if let Some(last_time) = peer.last_message_sent {
+         let elapsed_secs = last_time.elapsed().as_secs_f64();
+         if elapsed_secs > 0.0 {
+            // Use an exponential moving average for smoother rate calculation
+            const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+            let current_rate = data.len() as f64 / elapsed_secs;
+            peer.upload_rate = if peer.upload_rate > 0.0 {
+               (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
+            } else {
+               current_rate
+            };
+         }
+      }
+
+      peer.last_message_sent = Some(now);
+      Ok(())
+   }
+
+   async fn receive_data(
+      &mut self,
+      info_hash: Arc<InfoHash>,
+      id: Arc<Hash<20>>,
+   ) -> Result<(PeerKey, Vec<u8>), PeerTransportError> {
+      let mut tcp_stream = self.listener.accept().await.unwrap().0;
+      // First 4 bytes is the big endian encoded length field and the 5th byte is a PeerMessage tag
+      let mut buf = vec![0; 5];
+
+      tcp_stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let addr = tcp_stream.peer_addr().unwrap();
+      trace!(message_type = buf[4], ip = %addr, "Recieved message headers, requesting rest...");
+      let mut is_handshake = false;
+      let length = if buf[0] as usize == MAGIC_STRING.len() && buf[1..5] == MAGIC_STRING[0..4] {
+         // This is a handshake.
+         // The length of a handshake is always 68 and we already have the
+         // first 5 bytes of it, so we need 68 - 5 bytes (the current buffer length)
+         is_handshake = true;
+         68 - buf.len() as u32
+      } else {
+         // This is not a handshake
+         // Non handshake messages have a length field from bytes 0-4
+         u32::from_be_bytes(buf[..4].try_into().unwrap())
+      };
+
+      let mut rest = vec![0; length as usize];
+
+      tcp_stream.read_exact(&mut rest).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let full_length = length + buf.len() as u32;
+
+      debug!(
+         "Read {} action ({} bytes) from {} ",
+         buf[4], full_length, addr
+      );
+      buf.extend_from_slice(&rest);
+
+      if let Some(mutex) = self.peers.get_mut(&addr) {
+         let peer = &mut mutex.lock().await.0;
+
+         // Completely chat gippity generated code, do not trust
+         // Update total bytes uploaded
+         peer.bytes_uploaded += full_length as u64;
+
+         // Calculate upload rate based on a time window
+         let now = Instant::now();
+         if let Some(last_time) = peer.last_message_received {
+            let elapsed_secs = last_time.elapsed().as_secs_f64();
+            if elapsed_secs > 0.0 {
+               // Use an exponential moving average for smoother rate calculation
+               const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+               let current_rate = full_length as f64 / elapsed_secs;
+               peer.download_rate = if peer.download_rate > 0.0 {
+                  (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.download_rate)
+               } else {
+                  current_rate
+               };
+            }
+         }
+
+         peer.last_message_received = Some(now);
+      };
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Incorrect statistics update logic for received data.**

The comment on line 175 says "Update total bytes uploaded" but this is in the download logic and should be updating `bytes_downloaded`.

```diff
- // Update total bytes uploaded
- peer.bytes_uploaded += full_length as u64;
+ // Update total bytes downloaded
+ peer.bytes_downloaded += full_length as u64;
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      if let Some(mutex) = self.peers.get_mut(&addr) {
         let peer = &mut mutex.lock().await.0;

         // Completely chat gippity generated code, do not trust
-        // Update total bytes uploaded
-        peer.bytes_uploaded += full_length as u64;
+        // Update total bytes downloaded
+        peer.bytes_downloaded += full_length as u64;

         // Calculate upload rate based on a time window
         let now = Instant::now();
         if let Some(last_time) = peer.last_message_received {
            let elapsed_secs = last_time.elapsed().as_secs_f64();
            if elapsed_secs > 0.0 {
               // Use an exponential moving average for smoother rate calculation
               const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
               let current_rate = full_length as f64 / elapsed_secs;
               peer.download_rate = if peer.download_rate > 0.0 {
                  (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.download_rate)
               } else {
                  current_rate
               };
            }
         }

         peer.last_message_received = Some(now);
      };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T23:24:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,459 @@
+// Note to reader:
+// This is almost a 1:1 copy of utp.rs
+
+use std::str::FromStr;
+use std::{collections::HashMap, net::SocketAddr, sync::Arc};
+
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::time::Instant;
+use tokio::{
+   net::{TcpListener, TcpStream},
+   sync::Mutex,
+};
+use tracing::{debug, error, info, trace};
+
+use crate::errors::PeerTransportError;
+use crate::hashes::{Hash, InfoHash};
+use crate::peers::messages::{Handshake, MAGIC_STRING};
+
+use super::{Peer, PeerKey, PeerMessages, TransportProtocol};
+
+#[derive(Clone)]
+pub struct TcpProtocol {
+   pub listener: Arc<TcpListener>,
+   pub peers: HashMap<PeerKey, Arc<Mutex<(Peer, TcpStream)>>>,
+}
+
+impl TcpProtocol {
+   pub async fn new(socket_addr: Option<SocketAddr>) -> TcpProtocol {
+      let socket_addr = socket_addr.unwrap_or(SocketAddr::from_str("0.0.0.0:6881").unwrap());
+      trace!("Creating TCP socket at {}", socket_addr);
+      let listener = Arc::new(TcpListener::bind(socket_addr).await.unwrap());
+
+      TcpProtocol {
+         listener,
+         peers: HashMap::new(),
+      }
+   }
+}
+
+#[async_trait]
+#[allow(unused_variables)]
+impl TransportProtocol for TcpProtocol {
+   async fn connect_peer(
+      &mut self,
+      peer: &mut Peer,
+      id: Arc<Hash<20>>,
+      info_hash: Arc<InfoHash>,
+   ) -> Result<PeerKey, PeerTransportError> {
+      trace!("Attemping connection to {}", peer.socket_addr());
+      let mut stream = TcpStream::connect(peer.socket_addr().to_string())
+         .await
+         .map_err(|e| {
+            error!("Failed to connect to peer: {e}: {}", peer.socket_addr());
+            PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+         })?;
+
+      let handshake = Handshake::new(info_hash.clone(), id.clone());
+      let handshake_bytes = stream.write_all(&handshake.to_bytes()).await.unwrap();
+      trace!("Sent handshake to peer");
+
+      // Calculate expected size for response
+      // 1 byte + protocol + reserved + hashes
+      const EXPECTED_SIZE: usize = 1 + MAGIC_STRING.len() + 8 + 40;
+      let mut buf = [0u8; EXPECTED_SIZE];
+
+      // Read response handshake
+      stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Failed to read handshake from peer: {}", e);
+         PeerTransportError::ConnectionFailed(peer.socket_addr().to_string())
+      })?;
+
+      let handshake =
+         Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+
+      let (_, new_peer) = self
+         .validate_handshake(handshake, peer.socket_addr(), info_hash, id)
+         .unwrap();
+
+      // Store peer information
+      let peer_id = new_peer.id.unwrap();
+      peer.id = Some(peer_id);
+
+      self.peers.insert(
+         peer.socket_addr(),
+         Arc::new(Mutex::new((peer.clone(), stream))),
+      );
+
+      info!(%peer, "Peer connected");
+
+      Ok(peer.socket_addr())
+   }
+
+   async fn send_data(&mut self, to: PeerKey, data: Vec<u8>) -> Result<(), PeerTransportError> {
+      trace!("Attempting to send message...");
+
+      let (peer, socket) = &mut *self.peers.get_mut(&to).unwrap().lock().await;
+      socket.write_all(&data).await.map_err(|e| {
+         error!("Failed to send message to peer: {e}");
+         PeerTransportError::MessageFailed
+      })?;
+
+      // Completely chat gippity generated code, do not trust
+      // Update total bytes uploaded
+      peer.bytes_uploaded += data.len() as u64;
+
+      // Calculate upload rate based on a time window
+      let now = Instant::now();
+      if let Some(last_time) = peer.last_message_sent {
+         let elapsed_secs = last_time.elapsed().as_secs_f64();
+         if elapsed_secs > 0.0 {
+            // Use an exponential moving average for smoother rate calculation
+            const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+            let current_rate = data.len() as f64 / elapsed_secs;
+            peer.upload_rate = if peer.upload_rate > 0.0 {
+               (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.upload_rate)
+            } else {
+               current_rate
+            };
+         }
+      }
+
+      peer.last_message_sent = Some(now);
+      Ok(())
+   }
+
+   async fn receive_data(
+      &mut self,
+      info_hash: Arc<InfoHash>,
+      id: Arc<Hash<20>>,
+   ) -> Result<(PeerKey, Vec<u8>), PeerTransportError> {
+      let mut tcp_stream = self.listener.accept().await.unwrap().0;
+      // First 4 bytes is the big endian encoded length field and the 5th byte is a PeerMessage tag
+      let mut buf = vec![0; 5];
+
+      tcp_stream.read_exact(&mut buf).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let addr = tcp_stream.peer_addr().unwrap();
+      trace!(message_type = buf[4], ip = %addr, "Recieved message headers, requesting rest...");
+      let mut is_handshake = false;
+      let length = if buf[0] as usize == MAGIC_STRING.len() && buf[1..5] == MAGIC_STRING[0..4] {
+         // This is a handshake.
+         // The length of a handshake is always 68 and we already have the
+         // first 5 bytes of it, so we need 68 - 5 bytes (the current buffer length)
+         is_handshake = true;
+         68 - buf.len() as u32
+      } else {
+         // This is not a handshake
+         // Non handshake messages have a length field from bytes 0-4
+         u32::from_be_bytes(buf[..4].try_into().unwrap())
+      };
+
+      let mut rest = vec![0; length as usize];
+
+      tcp_stream.read_exact(&mut rest).await.map_err(|e| {
+         error!("Error occurred when reading the peer's response: {e}");
+         PeerTransportError::InvalidPeerResponse("Error occured".into())
+      })?;
+      let full_length = length + buf.len() as u32;
+
+      debug!(
+         "Read {} action ({} bytes) from {} ",
+         buf[4], full_length, addr
+      );
+      buf.extend_from_slice(&rest);
+
+      if let Some(mutex) = self.peers.get_mut(&addr) {
+         let peer = &mut mutex.lock().await.0;
+
+         // Completely chat gippity generated code, do not trust
+         // Update total bytes uploaded
+         peer.bytes_uploaded += full_length as u64;
+
+         // Calculate upload rate based on a time window
+         let now = Instant::now();
+         if let Some(last_time) = peer.last_message_received {
+            let elapsed_secs = last_time.elapsed().as_secs_f64();
+            if elapsed_secs > 0.0 {
+               // Use an exponential moving average for smoother rate calculation
+               const ALPHA: f64 = 0.3; // Smoothing factor (0.0-1.0)
+               let current_rate = full_length as f64 / elapsed_secs;
+               peer.download_rate = if peer.download_rate > 0.0 {
+                  (ALPHA * current_rate) + ((1.0 - ALPHA) * peer.download_rate)
+               } else {
+                  current_rate
+               };
+            }
+         }
+
+         peer.last_message_received = Some(now);
+      };
+
+      if is_handshake {
+         let handshake =
+            Handshake::from_bytes(&buf).map_err(|e| PeerTransportError::Other(anyhow!("{e}")))?;
+         let (handshake, peer) = self.validate_handshake(handshake, addr, info_hash, id)?;
+
+         trace!(peer_id = %peer.id.unwrap(), "Successfully validated handshake");
+
+         // Creates a new handshake and sends it
+         self
+            .peers
+            .insert(addr, Arc::new(Mutex::new((peer.clone(), tcp_stream))));
+         let message = PeerMessages::Handshake(handshake);
+         self.send_data(addr, message.to_bytes().unwrap()).await?;
path: crates/libtortillas/src/peers/tcp.rs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unchecked `unwrap()` on message bytes conversion.**

The conversion to bytes with `message.to_bytes().unwrap()` could panic if there's an error in serialization.

```diff
- self.send_data(addr, message.to_bytes().unwrap()).await?;
+ let message_bytes = message.to_bytes()
+     .map_err(|e| PeerTransportError::Other(anyhow!("Failed to serialize handshake: {}", e)))?;
+ self.send_data(addr, message_bytes).await?;
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
         let message_bytes = message.to_bytes()
             .map_err(|e| PeerTransportError::Other(anyhow!("Failed to serialize handshake: {}", e)))?;
         self.send_data(addr, message_bytes).await?;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-28T23:24:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-28T19:38:59Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

```markdown
## Walkthrough

The changes introduce a new TCP-based peer transport protocol by adding a `tcp` module to the peers subsystem. This new module implements the `TcpProtocol` struct, which provides asynchronous methods for connecting to peers, sending and receiving data, managing handshakes, and tracking connection statistics. The code includes comprehensive tests for TCP handshake scenarios. Additionally, some import statements in the `mod.rs` and `utp.rs` files are reordered for consistency, but no logic is changed in those files.

## Changes

| File(s)                                                        | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                         |
|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| crates/libtortillas/src/peers/mod.rs                          | Reordered import statements for `messages` and `tokio::time`. Added a new public module declaration: `pub mod tcp;`. Changed argument order in `TransportHandler::handle_commands` when calling `connect_peer` from `(peer, info_hash, id)` to `(peer, id, info_hash)`.                                                                                                                                                  |
| crates/libtortillas/src/peers/tcp.rs                          | Added a new file implementing a TCP-based peer transport protocol with asynchronous connection handling, handshake management, data transmission, peer lifecycle management, error handling, and rate tracking. Includes comprehensive async tests for TCP handshake scenarios. Exports the `TcpProtocol` struct and associated methods implementing the `TransportProtocol` trait.                                         |
| crates/libtortillas/src/peers/utp.rs                          | Reordered import statements in multiple places for consistency. Changed a trace log message to include the peer address. Updated test magnet URI file path in `test_utp_peer_handshake`. No changes to logic or exported/public entities.                                                                                                                                                                               |
| crates/libtortillas/tests/magneturis/test1.txt                | Added a new test file containing a magnet URI with info hash, display name, and tracker URLs for use in tests.                                                                                                                                                                                                                                                                                                          |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant LocalPeer as Local Peer (TcpProtocol)
    participant RemotePeer as Remote Peer
    participant TcpListener as TcpListener

    LocalPeer->>TcpListener: Bind and listen (optional socket_addr)
    LocalPeer->>RemotePeer: Initiate TCP connection (connect_peer)
    LocalPeer->>RemotePeer: Send handshake message
    RemotePeer->>LocalPeer: Receive handshake, validate
    RemotePeer->>LocalPeer: Send handshake response
    LocalPeer->>RemotePeer: Validate handshake response
    LocalPeer->>LocalPeer: Store connection and stream
    LocalPeer->>RemotePeer: Send/receive data (send_data/receive_data)
    LocalPeer->>LocalPeer: Update statistics and manage connection
```

## Possibly related PRs

- artrixdotdev/tortillas#33: Implements the uTP transport protocol with its own `connect_peer` method and peer transport infrastructure, related by extending the peer transport system alongside the new TCP transport.
```

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=artrixdotdev/tortillas&utm_content=42):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: kurealnum
created_at: 2025-04-29T04:52:59Z
body: The "issue" with linting is with my formatting being a different version than Artrix's rust-fmt, despite being on the latest version (?). The issue with uTP peers not connecting (#43) is present, but it has been present for multiple branches/PR's/weeks. Thus, because there are no issues with the TCP peers, this PR will be merged.
author_association: COLLABORATOR