@@ -44,6 +44,12 @@ pub(in crate::ed2k_tcp) struct ListenerUploadQueue {
4444 // session key, not the ephemeral socket source port).
4545 diag_peer : Option < String > ,
4646 diag_peer_hash : Option < [ u8 ; 16 ] > ,
47+ // Whether the peer bound to the current session was BANNED at admission
48+ // (mirrors the queue session key's `banned` flag). A banned client's slot
49+ // recycle must NOT get the OP_OUTOFPARTREQS courtesy packet (oracle
50+ // bRequeue=false, CheckForTimeOver, UploadQueue.cpp:2320-2321; requeue guard
51+ // in Process, UploadQueue.cpp:883-884).
52+ peer_banned : bool ,
4753 verified_reader : Option < ( Ed2kHash , Ed2kVerifiedRangeReader ) > ,
4854 // Per-connection ledger of requested upload blocks (fileHash, start, end,
4955 // count, first-seen) for MFC repeat_block_request parity. Bounded and pruned
@@ -64,6 +70,7 @@ impl ListenerUploadQueue {
6470 ever_granted : false ,
6571 diag_peer : None ,
6672 diag_peer_hash : None ,
73+ peer_banned : false ,
6774 verified_reader : None ,
6875 block_request_ledger : Vec :: new ( ) ,
6976 close_reason : None ,
@@ -111,13 +118,25 @@ impl ListenerUploadQueue {
111118 None
112119 }
113120
114- /// Capture the advertised peer identity for the `sched` diag emits.
121+ /// Capture the advertised peer identity for the `sched` diag emits and the
122+ /// banned-recycle packet suppression.
115123 fn record_diag_peer ( & mut self , peer_identity : & Ed2kUploadPeerIdentity ) {
116124 self . diag_peer = Some ( diag_sched:: peer_label (
117125 peer_identity. ip ,
118126 peer_identity. tcp_port ,
119127 ) ) ;
120128 self . diag_peer_hash = peer_identity. user_hash ;
129+ self . peer_banned = peer_identity. banned ;
130+ }
131+
132+ /// Whether a slot demotion/recycle owes the peer the OP_OUTOFPARTREQS
133+ /// courtesy packet: only a peer that actually saw OP_ACCEPTUPLOADREQ, and
134+ /// never a BANNED one (oracle bRequeue=false for `IsBanned()`,
135+ /// UploadQueue.cpp:2320-2321 -> the Process requeue guard skips
136+ /// SendOutOfPartReqsAndAddToWaitingQueue, UploadQueue.cpp:883-884; the
137+ /// banned client's queue entry is dropped, not re-added).
138+ const fn should_send_out_of_part_reqs ( & self ) -> bool {
139+ self . granted_sent && !self . peer_banned
121140 }
122141
123142 /// Emit `upload_slot_opened` once per grant transition (peer + file known).
@@ -186,28 +205,32 @@ impl ListenerUploadQueue {
186205 }
187206 Ed2kUploadSessionStatus :: Waiting { .. } => {
188207 // A slot we had granted was demoted back to the waiting queue (idle
189- // recycle): tell the downloader to go OnQueue with OP_OUTOFPARTREQS
190- // once, mirroring MFC SendOutOfPartReqsAndAddToWaitingQueue, then keep
191- // the connection rather than closing and shedding the peer.
192- // `granted_sent` is cleared so a later re-grant re-sends
193- // OP_ACCEPTUPLOADREQ; `ever_granted` stays set for the funnel.
208+ // recycle or session-cap rotation): tell the downloader to go OnQueue
209+ // with OP_OUTOFPARTREQS once, mirroring MFC
210+ // SendOutOfPartReqsAndAddToWaitingQueue, then keep the connection
211+ // rather than closing and shedding the peer. A BANNED peer gets no
212+ // packet (oracle bRequeue=false). `granted_sent` is cleared so a later
213+ // re-grant re-sends OP_ACCEPTUPLOADREQ; `ever_granted` stays set for
214+ // the funnel.
194215 if self . granted_sent {
195- let packet = encode_out_of_part_reqs ( ) ;
196- dump_ed2k_tcp_listener_send (
197- peer_addr,
198- transport. mode ,
199- "out_of_part_reqs" ,
200- & packet,
201- ) ;
202- transport. write_all ( & packet) . await ?;
203- if let ( Some ( peer) , Some ( file_hash) ) =
204- ( self . diag_peer . as_deref ( ) , self . file_hash . as_ref ( ) )
205- {
206- diag_sched:: out_of_part_reqs (
207- peer,
208- self . diag_peer_hash ,
209- & file_hash. to_string ( ) ,
216+ if self . should_send_out_of_part_reqs ( ) {
217+ let packet = encode_out_of_part_reqs ( ) ;
218+ dump_ed2k_tcp_listener_send (
219+ peer_addr,
220+ transport. mode ,
221+ "out_of_part_reqs" ,
222+ & packet,
210223 ) ;
224+ transport. write_all ( & packet) . await ?;
225+ if let ( Some ( peer) , Some ( file_hash) ) =
226+ ( self . diag_peer . as_deref ( ) , self . file_hash . as_ref ( ) )
227+ {
228+ diag_sched:: out_of_part_reqs (
229+ peer,
230+ self . diag_peer_hash ,
231+ & file_hash. to_string ( ) ,
232+ ) ;
233+ }
211234 }
212235 self . granted_sent = false ;
213236 }
@@ -238,8 +261,10 @@ impl ListenerUploadQueue {
238261 // MFC CUpDownClient::SendOutOfPartReqsAndAddToWaitingQueue. Without it
239262 // the downloader is dropped silently and reconnects immediately (churn)
240263 // instead of re-queueing with the stock out-of-part-reqs cooldown. A
241- // never-granted (Rejected) peer gets nothing, matching the master.
242- if self . granted_sent {
264+ // never-granted (Rejected) peer gets nothing, matching the master --
265+ // and so does a BANNED peer, whose recycle is dropped without the
266+ // packet (oracle bRequeue=false, UploadQueue.cpp:2320-2321).
267+ if self . should_send_out_of_part_reqs ( ) {
243268 let packet = encode_out_of_part_reqs ( ) ;
244269 dump_ed2k_tcp_listener_send (
245270 peer_addr,
@@ -490,6 +515,7 @@ impl ListenerUploadQueue {
490515 self . close_reason = None ;
491516 self . diag_peer = None ;
492517 self . diag_peer_hash = None ;
518+ self . peer_banned = false ;
493519 self . verified_reader = None ;
494520 }
495521
@@ -590,6 +616,28 @@ mod tests {
590616 use crate :: ed2k_transfer:: { ED2K_EMBLOCK_SIZE , Ed2kTransferRuntime } ;
591617 use crate :: paths:: unique_test_dir;
592618
619+ /// Oracle bRequeue=false (CheckForTimeOver, UploadQueue.cpp:2320-2321): a
620+ /// BANNED client's slot recycle must not get the OP_OUTOFPARTREQS courtesy
621+ /// packet, while a normal granted peer must. A never-granted peer gets
622+ /// nothing either way.
623+ #[ test]
624+ fn out_of_part_reqs_is_suppressed_for_banned_peers ( ) {
625+ let mut queue = ListenerUploadQueue :: new ( ) ;
626+
627+ // Never granted: no packet, banned or not.
628+ assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
629+ queue. peer_banned = true ;
630+ assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
631+
632+ // Granted + banned: suppressed (oracle bRequeue=false).
633+ queue. granted_sent = true ;
634+ assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
635+
636+ // Granted + not banned: the packet is owed.
637+ queue. peer_banned = false ;
638+ assert ! ( queue. should_send_out_of_part_reqs( ) ) ;
639+ }
640+
593641 #[ test]
594642 fn note_block_request_flags_repeat_within_window ( ) {
595643 let mut queue = ListenerUploadQueue :: new ( ) ;
0 commit comments