feat(shares): serve queued files to peers over F connections

Implement the upload path so a peer can download our shared files:
- fix the mis-coded PlaceInQueueResponse (43 -> 44) and add a real QueueUpload
  handler (code 43) -> PeerMessage::IncomingQueueUpload;
- the peer actor forwards it as ClientOperation::QueueUpload; the client looks
  the file up in its shares, mints an upload token (high half of the token
  space, disjoint from md5 download tokens), and offers it via ServeUpload ->
  build_upload_transfer_request (peer code 40, direction=upload);
- a TransferResponse for one of our serving tokens is handled by a surgical
  early-return that emits StartUpload, leaving the download path untouched;
- StartUpload resolves the downloader's address (GetPeerAddress, cached) and
  streams the file via a new peer::upload_peer::serve_file over an F connection.

Tested: build_upload_transfer_request decodes via the production Transfer
parser; serve_file streams a file correctly over a loopback F connection.
This commit is contained in:
michel
2026-07-19 18:46:06 +02:00
parent 9b6e533bed
commit 393f69e478
8 changed files with 396 additions and 7 deletions
+50 -1
View File
@@ -3,7 +3,7 @@ use crate::client::ClientOperation;
use crate::dispatcher::MessageDispatcher;
use crate::message::peer::{
FileSearchResponse, GetShareFileList, PeerInit, PlaceInQueueResponse,
TransferRequest, TransferResponse, UploadFailedHandler,
QueueUploadHandler, TransferRequest, TransferResponse, UploadFailedHandler,
};
use crate::message::server::MessageFactory;
use crate::message::{Handlers, Message, MessageReader, MessageType};
@@ -36,6 +36,14 @@ pub enum PeerMessage {
SetUsername(String),
QueueUpload(String),
RequestTransfer(Download),
/// A peer queued one of our shared files for download (they sent us code 43).
IncomingQueueUpload(String),
/// Offer the queued file to that peer: send an upload TransferRequest.
ServeUpload {
token: u32,
filename: String,
size: u64,
},
ProcessRead,
}
@@ -64,6 +72,9 @@ pub struct PeerActor {
/// Registry-assigned unique id, echoed in terminal notifications so the
/// client evicts only this actor and never a newer namesake.
id: u64,
/// Transfer tokens for uploads we are serving to this peer. A TransferResponse
/// for one of these is our upload being accepted, not a download offer.
serving_tokens: std::collections::HashSet<u32>,
}
impl PeerActor {
@@ -98,6 +109,7 @@ impl PeerActor {
established: false,
disconnect_reported: false,
id,
serving_tokens: std::collections::HashSet::new(),
}
}
@@ -138,6 +150,7 @@ impl PeerActor {
handlers.register_handler(GetShareFileList);
handlers.register_handler(UploadFailedHandler);
handlers.register_handler(PlaceInQueueResponse);
handlers.register_handler(QueueUploadHandler);
handlers.register_handler(PeerInit);
self.dispatcher = Some(MessageDispatcher::new(
@@ -234,6 +247,18 @@ impl PeerActor {
username, token, allowed
);
// If this token is one of our uploads, the peer just accepted
// our offer — start streaming. This leaves the download path
// (every other token) byte-for-byte unchanged.
if self.serving_tokens.remove(&token) {
if allowed {
let _ = self
.client_channel
.send(ClientOperation::StartUpload { token });
}
return;
}
if allowed {
debug!(
"[peer:{}] Transfer allowed, ready to connect with token {:}",
@@ -298,6 +323,30 @@ impl PeerActor {
MessageFactory::build_queue_upload_message(&filename);
self.send_message(message);
}
PeerMessage::IncomingQueueUpload(filename) => {
// A peer wants to download one of our shared files. Ask the
// client (which owns the shares) to prepare the upload.
let requester_key = self.peer_username();
if let Err(e) =
self.client_channel.send(ClientOperation::QueueUpload {
requester_key,
filename,
})
{
error!("[peer_actor] forward IncomingQueueUpload: {}", e);
}
}
PeerMessage::ServeUpload {
token,
filename,
size,
} => {
self.serving_tokens.insert(token);
let message = MessageFactory::build_upload_transfer_request(
&filename, token, size,
);
self.send_message(message);
}
PeerMessage::RequestTransfer(download) => {
let message = MessageFactory::build_transfer_request_message(
&download.filename,