@@ -18,7 +18,7 @@ use tokio::sync::mpsc;
1818use crate :: {
1919 buddy_socket:: BuddySocketRegistry ,
2020 ed2k_server:: Ed2kServerState ,
21- ed2k_transfer:: { Ed2kTransferRuntime , Ed2kUploadPeerIdentity } ,
21+ ed2k_transfer:: { Ed2kTransferRuntime , Ed2kUploadPeerIdentity , Ed2kUploadPendingPromotion } ,
2222 kad_firewall:: KadFirewallState ,
2323} ;
2424
@@ -36,7 +36,7 @@ use super::super::dump::{
3636} ;
3737use super :: super :: hello:: {
3838 DecodedHelloProfile , build_hello_responses, decode_emule_info_profile, decode_hello_profile,
39- encode_emule_info_answer,
39+ encode_emule_info_answer, encode_hello_request ,
4040} ;
4141use super :: super :: identity:: { Ed2kPeerSecureIdentState , begin_secure_ident_probe} ;
4242use super :: super :: {
@@ -71,6 +71,20 @@ use shared_file::{
7171use upload_payload:: { UploadPayloadOutcome , UploadPayloadRequest , serve_upload_payload} ;
7272use upload_queue:: { ListenerQueuePoll , ListenerUploadQueue } ;
7373
74+ /// Transport source for one eD2k peer session: an accepted inbound socket, or
75+ /// an already-established OUTBOUND connection dialed to hand an upload slot to
76+ /// a disconnected waiter (oracle `AddUpNextClient` US_CONNECTING connect-out,
77+ /// UploadQueue.cpp:327-361).
78+ pub ( in crate :: ed2k_tcp) enum Ed2kSessionSource {
79+ Inbound ( TcpStream ) ,
80+ PromotedUpload {
81+ // Boxed: the established transport dwarfs the plain inbound socket
82+ // (clippy::large_enum_variant).
83+ transport : Box < Ed2kTransport > ,
84+ grant : Box < Ed2kUploadPendingPromotion > ,
85+ } ,
86+ }
87+
7488pub ( in crate :: ed2k_tcp) struct Ed2kConnectionContext < ' a > {
7589 pub ( in crate :: ed2k_tcp) dht : & ' a DhtNode ,
7690 pub ( in crate :: ed2k_tcp) server_state : & ' a Arc < RwLock < Ed2kServerState > > ,
@@ -87,7 +101,7 @@ pub(in crate::ed2k_tcp) struct Ed2kConnectionContext<'a> {
87101
88102#[ allow( clippy:: cognitive_complexity) ]
89103pub ( in crate :: ed2k_tcp) async fn handle_connection (
90- stream : TcpStream ,
104+ source : Ed2kSessionSource ,
91105 peer_addr : SocketAddr ,
92106 context : Ed2kConnectionContext < ' _ > ,
93107) -> Result < ( ) > {
@@ -101,15 +115,6 @@ pub(in crate::ed2k_tcp) async fn handle_connection(
101115 reachability,
102116 buddy_registry,
103117 } = context;
104- let local_addr = stream. local_addr ( ) . with_context ( || {
105- format ! ( "failed to resolve local eD2k listener address for {peer_addr}" )
106- } ) ?;
107- dump_ed2k_tcp_listener_meta (
108- peer_addr,
109- None ,
110- "tcp_accept" ,
111- format ! ( "local_addr={local_addr}" ) ,
112- ) ;
113118 let kad_udp_port = dht
114119 . bind_addr ( )
115120 . context ( "failed to resolve Kad bind address for eD2k hello response" ) ?
@@ -125,51 +130,83 @@ pub(in crate::ed2k_tcp) async fn handle_connection(
125130 } ;
126131 let response_identity =
127132 enrich_hello_identity ( response_identity, server_state, kad_firewall) . await ;
128- let mut transport = match tokio:: time:: timeout (
129- ED2K_CONNECTION_IDLE_TIMEOUT ,
130- Ed2kTransport :: accept ( stream, hello_identity. user_hash ) ,
131- )
132- . await
133- {
134- Ok ( Ok ( transport) ) => transport,
135- Ok ( Err ( error) ) => {
133+ let ( mut transport, promoted_grant) = match source {
134+ Ed2kSessionSource :: Inbound ( stream) => {
135+ let local_addr = stream. local_addr ( ) . with_context ( || {
136+ format ! ( "failed to resolve local eD2k listener address for {peer_addr}" )
137+ } ) ?;
136138 dump_ed2k_tcp_listener_meta (
137139 peer_addr,
138140 None ,
139- "accept_failed " ,
140- format ! ( "local_addr={local_addr} error={error:#} " ) ,
141+ "tcp_accept " ,
142+ format ! ( "local_addr={local_addr}" ) ,
141143 ) ;
142- return Err ( error) . with_context ( || {
143- format ! ( "failed to accept inbound eD2k peer transport from {peer_addr}" )
144- } ) ;
144+ let transport = match tokio:: time:: timeout (
145+ ED2K_CONNECTION_IDLE_TIMEOUT ,
146+ Ed2kTransport :: accept ( stream, hello_identity. user_hash ) ,
147+ )
148+ . await
149+ {
150+ Ok ( Ok ( transport) ) => transport,
151+ Ok ( Err ( error) ) => {
152+ dump_ed2k_tcp_listener_meta (
153+ peer_addr,
154+ None ,
155+ "accept_failed" ,
156+ format ! ( "local_addr={local_addr} error={error:#}" ) ,
157+ ) ;
158+ return Err ( error) . with_context ( || {
159+ format ! ( "failed to accept inbound eD2k peer transport from {peer_addr}" )
160+ } ) ;
161+ }
162+ Err ( _) => {
163+ dump_ed2k_tcp_listener_meta (
164+ peer_addr,
165+ None ,
166+ "accept_timeout" ,
167+ format ! (
168+ "local_addr={local_addr} idle_timeout_secs={}" ,
169+ ED2K_CONNECTION_IDLE_TIMEOUT . as_secs( )
170+ ) ,
171+ ) ;
172+ anyhow:: bail!( "timed out waiting for initial eD2k peer bytes" ) ;
173+ }
174+ } ;
175+ ( transport, None )
145176 }
146- Err ( _) => {
177+ // A slot grant for a disconnected waiter arrives on a connection WE
178+ // dialed (master AddUpNextClient US_CONNECTING connect-out); the
179+ // transport handshake already happened in the promote driver.
180+ Ed2kSessionSource :: PromotedUpload { transport, grant } => {
147181 dump_ed2k_tcp_listener_meta (
148182 peer_addr,
149- None ,
150- "accept_timeout" ,
151- format ! (
152- "local_addr={local_addr} idle_timeout_secs={}" ,
153- ED2K_CONNECTION_IDLE_TIMEOUT . as_secs( )
154- ) ,
183+ Some ( transport. mode ) ,
184+ "promote_connect" ,
185+ format ! ( "file_hash={}" , grant. file_hash) ,
155186 ) ;
156- anyhow :: bail! ( "timed out waiting for initial eD2k peer bytes" ) ;
187+ ( * transport , Some ( * grant ) )
157188 }
158189 } ;
190+ let local_addr = transport
191+ . stream
192+ . local_addr ( )
193+ . with_context ( || format ! ( "failed to resolve local eD2k session address for {peer_addr}" ) ) ?;
159194 transport
160195 . stream
161196 . set_nodelay ( true )
162- . with_context ( || format ! ( "failed to enable TCP_NODELAY for inbound peer {peer_addr}" ) ) ?;
197+ . with_context ( || format ! ( "failed to enable TCP_NODELAY for peer {peer_addr}" ) ) ?;
163198 debug ! (
164- "accepted eD2k TCP peer from {peer_addr} transport={}" ,
199+ "eD2k TCP peer session with {peer_addr} transport={}" ,
165200 transport. mode. as_str( )
166201 ) ;
167- dump_ed2k_tcp_listener_meta (
168- peer_addr,
169- Some ( transport. mode ) ,
170- "accept" ,
171- format ! ( "udp_port={kad_udp_port}" ) ,
172- ) ;
202+ if promoted_grant. is_none ( ) {
203+ dump_ed2k_tcp_listener_meta (
204+ peer_addr,
205+ Some ( transport. mode ) ,
206+ "accept" ,
207+ format ! ( "udp_port={kad_udp_port}" ) ,
208+ ) ;
209+ }
173210 let mut peer_secure_ident = Ed2kPeerSecureIdentState :: default ( ) ;
174211 let mut requested_file_hash: Option < Ed2kHash > = None ;
175212 let mut peer_supports_aich = false ;
@@ -192,13 +229,52 @@ pub(in crate::ed2k_tcp) async fn handle_connection(
192229 // a flood of these is a share-probe and the peer is banned (MFC file_request_flood).
193230 let mut failed_file_req_count: u32 = 0 ;
194231
232+ // A promoted-upload outbound session announces itself and pushes the slot
233+ // grant before entering the dispatch loop: the oracle sends OP_HELLO and
234+ // then OP_ACCEPTUPLOADREQ on the fresh connection it opened for the
235+ // promoted waiter (ConnectionEstablished, BaseClient.cpp:1634-1641).
236+ if let Some ( grant) = promoted_grant {
237+ peer_upload_identity = grant. peer . clone ( ) ;
238+ peer_upload_identity. should_crypt = transport. mode . is_obfuscated ( ) ;
239+ bound_user_hash = grant. peer . user_hash ;
240+ let file_hash = Ed2kHash :: from_str ( & grant. file_hash )
241+ . with_context ( || format ! ( "invalid promoted upload file hash {}" , grant. file_hash) ) ?;
242+ requested_file_hash = Some ( file_hash) ;
243+ let hello_packet = encode_hello_request ( response_identity) ;
244+ dump_ed2k_tcp_listener_send ( peer_addr, transport. mode , "hello_request" , & hello_packet) ;
245+ transport. write_all ( & hello_packet) . await . with_context ( || {
246+ format ! ( "failed to send OP_HELLO to promoted upload peer {peer_addr}" )
247+ } ) ?;
248+ let attached = upload_queue
249+ . attach_promoted_grant (
250+ transfer_runtime,
251+ & grant. peer ,
252+ grant. handle ,
253+ file_hash,
254+ & mut transport,
255+ peer_addr,
256+ )
257+ . await ?;
258+ if !attached {
259+ // The grant went stale while connecting (aged out or re-owned by an
260+ // inbound reconnect): nothing to serve on this connection.
261+ return Ok ( ( ) ) ;
262+ }
263+ }
264+
195265 // Run the session loop inside a fallible async scope so that EVERY exit
196266 // path -- a clean `break`, a propagated `?` I/O error, or any other early
197267 // return from the loop body -- lands in `result` and falls through to the
198- // unconditional `upload_queue.release(...)` below. The master always frees
199- // the slot on teardown (`CUpDownClient::Disconnected`, BaseClient.cpp:1172);
200- // without this wrapper an in-loop `?` would skip the release and leave the
201- // slot pinned until the idle reaper reclaimed it.
268+ // unconditional `upload_queue.release(...)` below. The master frees an
269+ // ACTIVE slot on teardown (`CUpDownClient::Disconnected` removes
270+ // US_UPLOADING/US_CONNECTING, BaseClient.cpp:1172-1175) while a waiting
271+ // queue entry survives the disconnect (BaseClient.cpp:1229); the runtime
272+ // release applies exactly that split. Without this wrapper an in-loop `?`
273+ // would skip the release and leave the slot pinned until the idle reaper
274+ // reclaimed it.
275+ // Last time the peer actually sent us a packet: feeds the waiting-connection
276+ // idle close (oracle socket timeout) inside poll_on_timeout.
277+ let mut last_packet_at = tokio:: time:: Instant :: now ( ) ;
202278 let result: Result < ( ) > = async {
203279 loop {
204280 let read_timeout = upload_queue. read_timeout ( ) ;
@@ -233,7 +309,12 @@ pub(in crate::ed2k_tcp) async fn handle_connection(
233309 Ok ( packet) => packet
234310 . with_context( || format!( "failed to read eD2k packet from {peer_addr}" ) ) ?,
235311 Err ( _) => match upload_queue
236- . poll_on_timeout( transfer_runtime, & mut transport, peer_addr)
312+ . poll_on_timeout(
313+ transfer_runtime,
314+ & mut transport,
315+ peer_addr,
316+ last_packet_at. elapsed( ) ,
317+ )
237318 . await ?
238319 {
239320 ListenerQueuePoll :: Continue => continue ,
@@ -248,7 +329,12 @@ pub(in crate::ed2k_tcp) async fn handle_connection(
248329 packet. with_context ( || format ! ( "failed to read eD2k packet from {peer_addr}" ) ) ?
249330 }
250331 Err ( _) => match upload_queue
251- . poll_on_timeout ( transfer_runtime, & mut transport, peer_addr)
332+ . poll_on_timeout (
333+ transfer_runtime,
334+ & mut transport,
335+ peer_addr,
336+ last_packet_at. elapsed ( ) ,
337+ )
252338 . await ?
253339 {
254340 ListenerQueuePoll :: Continue => continue ,
@@ -259,6 +345,7 @@ pub(in crate::ed2k_tcp) async fn handle_connection(
259345 let Some ( packet) = packet else {
260346 break Ok ( ( ) ) ;
261347 } ;
348+ last_packet_at = tokio:: time:: Instant :: now ( ) ;
262349 dump_ed2k_tcp_listener_recv ( peer_addr, transport. mode , "session" , & packet) ;
263350
264351 match ( packet. protocol , packet. opcode ) {
0 commit comments