feat: direct peer connections — download() connects to unconnected peers

Implement the DIRECT connection-initiation mode so a download can target a
peer the client is not already connected to:

- Fix the outbound peer handshake. on_connection_established sent
  build_watch_user() (a server WatchUser message, wrongly framed with
  get_data()) for outbound connections; it now sends a proper length-prefixed
  PeerInit (peer code 1) with our own username. own_username is plumbed in
  via PeerRegistry -> PeerActor; inbound peers (outbound=false) are unchanged.
- download() now auto-connects: if there is no control connection to the peer
  it sends GetPeerAddress and defers the QueueUpload until the outbound stream
  handshakes, signalled by a new ClientOperation::PeerConnected. A never-
  connected client still resolves the download to Failed (unit test).
- Add Client::connect_peer(username) to open/pre-warm a control connection.

Verified end to end against soulfind: a_file_downloads_from_a_peer_via_direct_connection
logs a mock peer into the server, advertises its port via SetWaitPort, and the
client resolves the address, dials it, and downloads the file — bytes match.
The F-serve leg is shared with the passive-download mock.
This commit is contained in:
michel
2026-07-19 17:12:17 +02:00
parent 06cd638736
commit e4093b1ea8
5 changed files with 387 additions and 29 deletions
@@ -142,7 +142,6 @@ impl MessageFactory {
.clone()
}
#[allow(dead_code)]
#[must_use]
pub fn build_peer_init_message(
own_username: &str,
@@ -184,6 +183,22 @@ fn test_build_login_message() {
assert_eq!(expect, message.get_data());
}
#[test]
fn test_build_peer_init_message() {
use crate::peer::ConnectionType;
let message =
MessageFactory::build_peer_init_message("bob", ConnectionType::P, 7);
// [1][len=3]"bob"[len=1]"P"[token=7] — no length prefix in get_data()
let expect: Vec<u8> = [
1, // PeerInit code (int8)
3, 0, 0, 0, 98, 111, 98, // username "bob"
1, 0, 0, 0, 80, // connection type "P"
7, 0, 0, 0, // token
]
.to_vec();
assert_eq!(expect, message.get_data());
}
#[test]
fn test_build_message_user() {
let message = MessageFactory::build_message_user("bob", "hi");