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
+11 -2
View File
@@ -15,6 +15,7 @@ pub struct PeerRegistry {
peers: Arc<Mutex<HashMap<String, ActorHandle<PeerMessage>>>>,
actor_system: Arc<ActorSystem>,
client_channel: Sender<ClientOperation>,
own_username: String,
}
impl PeerRegistry {
@@ -22,11 +23,13 @@ impl PeerRegistry {
pub fn new(
actor_system: Arc<ActorSystem>,
client_channel: Sender<ClientOperation>,
own_username: String,
) -> Self {
Self {
peers: Arc::new(Mutex::new(HashMap::new())),
actor_system,
client_channel,
own_username,
}
}
@@ -38,8 +41,13 @@ impl PeerRegistry {
) -> Result<ActorHandle<PeerMessage>, String> {
let username = peer.username.clone();
let actor =
PeerActor::new(peer, stream, reader, self.client_channel.clone());
let actor = PeerActor::new(
peer,
stream,
reader,
self.client_channel.clone(),
self.own_username.clone(),
);
let handle =
self.actor_system.spawn_with_handle(actor, |actor, handle| {
@@ -157,6 +165,7 @@ impl Clone for PeerRegistry {
peers: self.peers.clone(),
actor_system: self.actor_system.clone(),
client_channel: self.client_channel.clone(),
own_username: self.own_username.clone(),
}
}
}