Skip to content

refactor: send_handshake and receive_handshake#102

Merged
kurealnum merged 6 commits intomainfrom
refactor/send-and-receive-handshake
Aug 19, 2025
Merged

refactor: send_handshake and receive_handshake#102
kurealnum merged 6 commits intomainfrom
refactor/send-and-receive-handshake

Conversation

@kurealnum
Copy link
Collaborator

@kurealnum kurealnum commented Aug 19, 2025

See #97, and the TODO list under it.

Summary by CodeRabbit

  • Refactor

    • Simplified handshake flow: sending now only transmits handshake; a new receive step reads and decodes the incoming handshake and returns peer info.
    • Connection setup updated to perform send then receive, clarifying success/failure handling and logging.
    • Public API adjusted to reflect the split and streamline handshake responsibilities.
  • Tests

    • Replaced traced_test with explicit tracing initialization, making tests self-contained and improving log visibility.
  • Documentation

    • Removed outdated inline comments related to connection teardown.

artrixdotdev and others added 4 commits August 18, 2025 18:54
Also updated any calls to send_handshake to accomodate the changes

Co-authored-by: kurealnum <oscar.gaske.cs@gmail.com>
Updated any related code + documentation

Co-authored-by: kurealnum <oscar.gaske.cs@gmail.com>
Co-authored-by: kurealnum <oscar.gaske.cs@gmail.com>
Co-authored-by: kurealnum <oscar.gaske.cs@gmail.com>
@kurealnum kurealnum added this to the Engine implementation milestone Aug 19, 2025
@kurealnum kurealnum added the med prio Medium Priority label Aug 19, 2025
@kurealnum kurealnum linked an issue Aug 19, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 19, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

send_handshake was simplified to only transmit handshake bytes; a new recv_handshake reads/parses a 68-byte handshake and returns (PeerId, reserved). append_peer now calls send_handshake then recv_handshake; tests and logging were updated accordingly.

Changes

Cohort / File(s) Summary of Changes
Protocol Handshake Stream API
crates/libtortillas/src/protocol/stream.rs
send_handshake now only sends and returns (). Added recv_handshake to read a fixed 68-byte handshake and return (PeerId, [u8;8]). Removed receive_handshake and is_handshake helper. Trimmed unused tracing imports and added instrumentation/doc comments.
Torrent Peer Append Flow & Tests
crates/libtortillas/src/torrent/mod.rs
append_peer updated to call send_handshake then stream.recv_handshake() to obtain (peer_id, reserved); sets id, updates peer.reserved, and calls determine_supported().await. Handshake-failure logging split into explicit send/recv warnings. Tests replaced traced_test with an explicit tracing_subscriber setup in a plain tokio::test.
Engine comments cleanup
crates/libtortillas/src/engine/mod.rs
Removed two obsolete comment lines in the unknown-torrent branch; behavior unchanged (stream still dropped).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server

  Client->>Server: send_handshake(our_id, info_hash)
  Note right of Client: send-only, no response read

  Server->>Server: recv_handshake() — read 68 bytes & parse Handshake
  Server-->>Server: return (peer_id, reserved)

  Server->>Server: set id, update peer.reserved, determine_supported()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • kurealnum

Suggested labels

enhancement

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 194060e and 776cf87.

📒 Files selected for processing (2)
  • crates/libtortillas/src/engine/mod.rs (0 hunks)
  • crates/libtortillas/src/torrent/mod.rs (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/send-and-receive-handshake

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai bot added the enhancement New feature or request label Aug 19, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
crates/libtortillas/src/torrent/mod.rs (1)

390-394: Use try_init to avoid panics when multiple tests initialize tracing

init() will panic if called more than once across tests. Use try_init().ok() to make it idempotent.

-      tracing_subscriber::fmt()
+      tracing_subscriber::fmt()
          .with_target(true)
          .with_env_filter("libtortillas=trace,off")
          .pretty()
-         .init();
+         .try_init()
+         .ok();
crates/libtortillas/src/protocol/stream.rs (1)

141-151: Avoid unwrap in instrumentation fields for remote_addr

remote_addr().unwrap() inside #[instrument(...)] will panic if the socket is closed before this call. Prefer deferring remote_addr into the function body where you can handle errors, or record it conditionally.

Example:

let remote = self.remote_addr().map(|a| a.to_string()).unwrap_or_else(|_| "<unknown>".into());
trace!(%remote, "Sending handshake");

If you want, I can patch this to move context fields into explicit trace! calls for safety.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cd6b94c and 705703e.

📒 Files selected for processing (2)
  • crates/libtortillas/src/protocol/stream.rs (4 hunks)
  • crates/libtortillas/src/torrent/mod.rs (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T18:25:24.810Z
Learnt from: kurealnum
PR: artrixdotdev/tortillas#99
File: crates/libtortillas/src/engine/mod.rs:153-155
Timestamp: 2025-08-18T18:25:24.810Z
Learning: The peer stream handling logic in the Engine's EngineMessage::IncomingPeer handler in crates/libtortillas/src/engine/mod.rs is temporary and planned to be migrated to separate code in the future, so extensive refactoring may not be worthwhile in this location.

Applied to files:

  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/protocol/stream.rs
🧬 Code Graph Analysis (2)
crates/libtortillas/src/torrent/mod.rs (4)
crates/libtortillas/src/protocol/stream.rs (1)
  • fmt (217-219)
crates/libtortillas/src/peer/mod.rs (3)
  • fmt (45-54)
  • fmt (71-73)
  • Peer (31-42)
crates/libtortillas/src/peer/supports.rs (1)
  • Peer (33-65)
crates/libtortillas/src/protocol/messages.rs (2)
  • Handshake (505-592)
  • Handshake (494-503)
crates/libtortillas/src/protocol/stream.rs (1)
crates/libtortillas/src/protocol/messages.rs (6)
  • new (471-473)
  • new (507-521)
  • from_bytes (169-371)
  • from_bytes (545-591)
  • Handshake (494-503)
  • Handshake (505-592)

Co-authored-by: kurealnum <oscar.gaske.cs@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
crates/libtortillas/src/protocol/stream.rs (1)

165-181: Return the full Handshake and support variable-length parsing; enables validation upstream

Currently, recv_handshake discards protocol/info_hash and assumes a fixed 68 bytes. Returning the full Handshake allows callers to validate against MAGIC_STRING and the expected info_hash (you already have validate_handshake available), and reading pstrlen first makes parsing robust.

Apply this diff:

-   #[instrument(skip(self), fields(%self))]
-   pub async fn recv_handshake(&mut self) -> Result<(PeerId, [u8; 8]), PeerActorError> {
-      // Handshakes will always be 68 bytes
-      //
-      // mem::size_of::<Handshake>() is 72 bytes, for some reason. We think it might
-      // be due to the Arc<>(s) in the Handshake struct
-      let mut buf = [0u8; 68];
-
-      self.read_exact(&mut buf).await?;
-
-      let Handshake {
-         peer_id, reserved, ..
-      } = Handshake::from_bytes(&buf)
-         .map_err(|e| PeerActorError::HandshakeFailed { reason: e.into() })?;
-
-      Ok((peer_id, reserved))
-   }
+   #[instrument(skip(self), fields(protocol = self.protocol()))]
+   pub async fn recv_handshake(&mut self) -> Result<Handshake, PeerActorError> {
+      // Read protocol string length (pstrlen), then the rest dynamically:
+      let mut pstrlen = [0u8; 1];
+      self.read_exact(&mut pstrlen).await?;
+      let protocol_len = pstrlen[0] as usize;
+
+      // Remaining bytes: protocol_len + 8 (reserved) + 20 (info_hash) + 20 (peer_id)
+      let mut rest = vec![0u8; protocol_len + 8 + 20 + 20];
+      self.read_exact(&mut rest).await?;
+
+      let mut buf = Vec::with_capacity(1 + rest.len());
+      buf.push(pstrlen[0]);
+      buf.extend_from_slice(&rest);
+
+      let handshake = Handshake::from_bytes(&buf)
+         .map_err(|e| PeerActorError::HandshakeFailed { reason: e.into() })?;
+
+      Ok(handshake)
+   }

If you want, I can follow up with the minimal call-site changes in torrent/mod.rs (included below) and tests.

crates/libtortillas/src/torrent/mod.rs (1)

112-129: Validate the received handshake before accepting the session

The new flow sets peer_id/reserved without validating protocol string or info_hash, which can accept a peer from the wrong torrent. Use validate_handshake with the full Handshake (see stream.rs suggestion to return Handshake).

Apply this diff (assumes recv_handshake returns Handshake):

-               match stream.send_handshake(our_id, Arc::clone(&info_hash)).await {
-                  Ok(_) => match stream.recv_handshake().await {
-                     Ok((peer_id, reserved)) => {
-                        id = Some(peer_id);
-                        peer.reserved = reserved;
-                        peer.determine_supported().await;
-                        stream
-                     }
-                     Err(err) => {
-                        warn!(error = %err, "Failed to receive handshake from peer; exiting");
-                        return;
-                     }
-                  },
-                  Err(err) => {
-                     warn!(error = %err, "Failed to send handshake to peer; exiting");
-                     return;
-                  }
-               }
+               match stream.send_handshake(our_id, Arc::clone(&info_hash)).await {
+                  Ok(_) => match stream.recv_handshake().await {
+                     Ok(handshake) => {
+                        if let Err(err) = validate_handshake(
+                           &handshake,
+                           peer.socket_addr(),
+                           Arc::clone(&info_hash),
+                        ) {
+                           warn!(error = %err, "Received invalid handshake from peer; exiting");
+                           return;
+                        }
+                        id = Some(handshake.peer_id);
+                        peer.reserved = handshake.reserved;
+                        peer.determine_supported().await;
+                        stream
+                     }
+                     Err(err) => {
+                        warn!(error = %err, "Failed to receive handshake from peer; exiting");
+                        return;
+                     }
+                  },
+                  Err(err) => {
+                     warn!(error = %err, "Failed to send handshake to peer; exiting");
+                     return;
+                  }
+               }

Additionally, add the missing import (outside this hunk):

use crate::protocol::stream::{PeerSend, PeerStream, validate_handshake};

Related fix in the Some(mut stream) branch to avoid moving info_hash (existing compile hazard):

// Replace at the handshake construction line in the Some(mut stream) arm:
let handshake = Handshake::new(Arc::clone(&info_hash), our_id);

I can submit a consolidated patch if you want this change adopted across both files.

🧹 Nitpick comments (4)
crates/libtortillas/src/protocol/stream.rs (3)

146-150: Avoid unwrap() in instrumentation field; prevent panic when formatting remote_addr

Using unwrap() inside the #[instrument(...)] fields can panic if peer_addr retrieval fails. Prefer a fallible conversion.

Apply this diff:

-            remote_addr = self.remote_addr().unwrap().to_string(),
+            remote_addr = self
+               .remote_addr()
+               .map(|a| a.to_string())
+               .unwrap_or_else(|e| format!("<err:{e}>")),

165-166: Nit: Avoid using %self in instrumentation because Display for PeerStream unwraps

Display for PeerStream calls remote_addr().unwrap(), which can panic. Either avoid %self here or change Display/remote_addr to be infallible/logging. The diff above switches to a safer field.


372-385: Test update aligns with new handshake split; consider validating protocol/info_hash too

Good coverage for the new send-then-recv flow. Once recv_handshake returns a Handshake, you can assert protocol and info_hash as well to catch cross-torrent handshakes in tests.

crates/libtortillas/src/torrent/mod.rs (1)

391-395: Use try_init() in tests to avoid global subscriber re-initialization panics

tracing_subscriber::fmt().init() will panic if another test already set the global subscriber. try_init() is safer in a multi-threaded test suite.

Apply this diff:

-      tracing_subscriber::fmt()
-         .with_target(true)
-         .with_env_filter("libtortillas=trace,off")
-         .pretty()
-         .init();
+      let _ = tracing_subscriber::fmt()
+         .with_target(true)
+         .with_env_filter("libtortillas=trace,off")
+         .pretty()
+         .try_init();
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 705703e and 194060e.

📒 Files selected for processing (2)
  • crates/libtortillas/src/protocol/stream.rs (4 hunks)
  • crates/libtortillas/src/torrent/mod.rs (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T18:25:24.810Z
Learnt from: kurealnum
PR: artrixdotdev/tortillas#99
File: crates/libtortillas/src/engine/mod.rs:153-155
Timestamp: 2025-08-18T18:25:24.810Z
Learning: The peer stream handling logic in the Engine's EngineMessage::IncomingPeer handler in crates/libtortillas/src/engine/mod.rs is temporary and planned to be migrated to separate code in the future, so extensive refactoring may not be worthwhile in this location.

Applied to files:

  • crates/libtortillas/src/torrent/mod.rs
  • crates/libtortillas/src/protocol/stream.rs
🧬 Code Graph Analysis (2)
crates/libtortillas/src/torrent/mod.rs (3)
crates/libtortillas/src/protocol/stream.rs (1)
  • fmt (214-216)
crates/libtortillas/src/peer/supports.rs (1)
  • Peer (33-65)
crates/libtortillas/src/protocol/messages.rs (2)
  • Handshake (505-592)
  • Handshake (494-503)
crates/libtortillas/src/protocol/stream.rs (1)
crates/libtortillas/src/protocol/messages.rs (6)
  • new (471-473)
  • new (507-521)
  • from_bytes (169-371)
  • from_bytes (545-591)
  • Handshake (505-592)
  • Handshake (494-503)
🔇 Additional comments (2)
crates/libtortillas/src/protocol/stream.rs (2)

18-18: Import cleanup looks good

Narrowing tracing imports to what’s actually used keeps things tidy.


152-160: Refactor of send_handshake to “send-only” is solid; error propagation via ? is idiomatic

The simplified API and ? propagation is clean. Once the instrumentation unwrap is addressed, this is good to go.

Co-authored-by: kurealnum <oscar.gaske.cs@gmail.com>
@kurealnum kurealnum merged commit 778bb29 into main Aug 19, 2025
2 of 3 checks passed
@kurealnum kurealnum deleted the refactor/send-and-receive-handshake branch August 19, 2025 03:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request med prio Medium Priority

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Refactor send_handshake and receive_handshake

2 participants