@@ -719,398 +719,4 @@ impl ListenerUploadQueue {
719719}
720720
721721#[ cfg( test) ]
722- mod tests {
723- use std:: {
724- fs,
725- net:: { IpAddr , Ipv4Addr , SocketAddr } ,
726- str:: FromStr ,
727- } ;
728-
729- use emulebb_kad_proto:: Ed2kHash ;
730-
731- use super :: { ListenerUploadQueue , encode_accept_upload_req, encode_queue_ranking} ;
732- use crate :: ed2k_transfer:: {
733- ED2K_EMBLOCK_SIZE , Ed2kTransferRuntime , Ed2kUploadPeerIdentity , Ed2kUploadQueueConfig ,
734- } ;
735- use crate :: paths:: unique_test_dir;
736-
737- fn emule_identity ( peer_addr : SocketAddr ) -> Ed2kUploadPeerIdentity {
738- let mut identity = super :: super :: upload_peer_identity_from_socket ( peer_addr) ;
739- identity. is_emule_client = true ;
740- identity
741- }
742-
743- async fn use_one_slot_queue ( runtime : & Ed2kTransferRuntime ) {
744- runtime
745- . configure_upload_queue ( Ed2kUploadQueueConfig {
746- active_slots : 1 ,
747- waiting_capacity : 8 ,
748- ..Default :: default ( )
749- } )
750- . await ;
751- }
752-
753- /// Oracle bRequeue=false (CheckForTimeOver, UploadQueue.cpp:2320-2321): a
754- /// BANNED client's slot recycle must not get the OP_OUTOFPARTREQS courtesy
755- /// packet, while a normal granted peer must. A never-granted peer gets
756- /// nothing either way.
757- #[ test]
758- fn out_of_part_reqs_is_suppressed_for_banned_peers ( ) {
759- let mut queue = ListenerUploadQueue :: new ( ) ;
760-
761- // Never granted: no packet, banned or not.
762- assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
763- queue. peer_banned = true ;
764- assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
765-
766- // Granted + banned: suppressed (oracle bRequeue=false).
767- queue. granted_sent = true ;
768- assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
769-
770- // Granted + not banned: the packet is owed.
771- queue. peer_banned = false ;
772- assert ! ( queue. should_send_out_of_part_reqs( ) ) ;
773- }
774-
775- /// REG-2: on a recycle demote the oracle sends OP_OUTOFPARTREQS AND THEN
776- /// OP_QUEUERANKING (SendOutOfPartReqsAndAddToWaitingQueue ->
777- /// AddClientToQueue(this, true) -> SendRankingInfo(),
778- /// UploadQueue.cpp:883-885,1980-1986). The rank is gated on the eMule extended
779- /// protocol exactly like every other rank (round-17 339764d), and a banned
780- /// peer reaches neither send (oracle bRequeue=false). This checks the two
781- /// composed gates the demote path emits through.
782- #[ test]
783- fn recycle_demote_rank_follows_out_of_part_reqs_only_for_emule_and_never_banned ( ) {
784- let mut queue = ListenerUploadQueue :: new ( ) ;
785- queue. granted_sent = true ;
786-
787- // eMule waiter: the courtesy OP_OUTOFPARTREQS is owed AND the rank follows.
788- queue. peer_ext_protocol = true ;
789- queue. peer_banned = false ;
790- assert ! ( queue. should_send_out_of_part_reqs( ) ) ;
791- assert_eq ! ( queue. rank_packet( 3 ) , Some ( encode_queue_ranking( 3 ) ) ) ;
792-
793- // Plain-eDonkey waiter: OUTOFPARTREQS is owed but the rank is suppressed
794- // (SendRankingInfo `!ExtProtocolAvailable()` early return).
795- queue. peer_ext_protocol = false ;
796- assert ! ( queue. should_send_out_of_part_reqs( ) ) ;
797- assert_eq ! ( queue. rank_packet( 3 ) , None ) ;
798-
799- // Banned waiter: neither the courtesy packet nor the rank.
800- queue. peer_banned = true ;
801- queue. peer_ext_protocol = true ;
802- assert ! ( !queue. should_send_out_of_part_reqs( ) ) ;
803- }
804-
805- #[ test]
806- fn note_block_request_flags_repeat_within_window ( ) {
807- let mut queue = ListenerUploadQueue :: new ( ) ;
808- let file = Ed2kHash ( [ 7u8 ; 16 ] ) ;
809- // First request for a block is not a repeat.
810- assert_eq ! ( queue. note_block_request( & file, 0 , 180_000 ) , None ) ;
811- // The same block again on this connection climbs the repeat count.
812- assert_eq ! ( queue. note_block_request( & file, 0 , 180_000 ) , Some ( 2 ) ) ;
813- assert_eq ! ( queue. note_block_request( & file, 0 , 180_000 ) , Some ( 3 ) ) ;
814- // A different block on the same file is tracked independently.
815- assert_eq ! ( queue. note_block_request( & file, 180_000 , 360_000 ) , None ) ;
816- // A different file is independent too.
817- let other = Ed2kHash ( [ 9u8 ; 16 ] ) ;
818- assert_eq ! ( queue. note_block_request( & other, 0 , 180_000 ) , None ) ;
819- }
820-
821- /// UP-3: a re-ask on a STALE tracked session runs a FRESH admission — the
822- /// oracle treats a re-ask from a client it no longer tracks as a plain
823- /// `AddClientToQueue` and answers with the REAL state (SendRankingInfo,
824- /// UploadQueue.cpp:1986) — never the old synthesized rank-1
825- /// OP_QUEUERANKING.
826- #[ tokio:: test]
827- async fn stale_reask_runs_a_fresh_admission_with_the_real_reply ( ) {
828- let root = unique_test_dir ( "ed2k-listener-stale-reask" ) ;
829- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
830- let peer_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 21 ) ) , 4662 ) ;
831- let identity = emule_identity ( peer_addr) ;
832- let file_hash = Ed2kHash :: from_bytes ( [ 0x44 ; 16 ] ) ;
833-
834- let mut queue = ListenerUploadQueue :: new ( ) ;
835- let first = queue
836- . start_upload_reply ( & runtime, identity. clone ( ) , & file_hash)
837- . await ;
838- assert_eq ! ( first, Some ( encode_accept_upload_req( ) ) ) ;
839-
840- // Drop the runtime entry behind the listener's back: the next poll on
841- // the retained handle reports Stale.
842- let stale_handle = queue. session . clone ( ) . unwrap ( ) ;
843- runtime. release_upload_session ( & stale_handle) . await ;
844- assert ! ( runtime. upload_queue_snapshot( ) . await . is_empty( ) ) ;
845-
846- // The re-ask is a fresh admission; the queue is empty, so the peer is
847- // granted a REAL slot, not told a synthesized waiting rank.
848- let reask = queue
849- . start_upload_reply ( & runtime, identity, & file_hash)
850- . await ;
851- assert_eq ! ( reask, Some ( encode_accept_upload_req( ) ) ) ;
852- assert_eq ! ( runtime. upload_queue_snapshot( ) . await . len( ) , 1 ) ;
853- }
854-
855- /// UP-3: a refused admission sends NOTHING — the oracle AddClientToQueue
856- /// early-returns without a packet (per-IP cap, UploadQueue.cpp:1905-1915;
857- /// queue caps 1939-1941) — where rust previously synthesized
858- /// OP_QUEUERANKING(0xFFFF).
859- #[ tokio:: test]
860- async fn rejected_admission_sends_no_packet ( ) {
861- let root = unique_test_dir ( "ed2k-listener-rejected-admission" ) ;
862- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
863- use_one_slot_queue ( & runtime) . await ;
864- let file_hash = Ed2kHash :: from_bytes ( [ 0x55 ; 16 ] ) ;
865- let shared_ip = IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 30 ) ) ;
866-
867- // Occupy the single slot, then fill the per-IP waiter cap (3).
868- let mut queues = Vec :: new ( ) ;
869- for ( index, port) in [ 4661u16 , 4662 , 4663 , 4664 ] . into_iter ( ) . enumerate ( ) {
870- let mut queue = ListenerUploadQueue :: new ( ) ;
871- let reply = queue
872- . start_upload_reply (
873- & runtime,
874- emule_identity ( SocketAddr :: new ( shared_ip, port) ) ,
875- & file_hash,
876- )
877- . await ;
878- let expected = if index == 0 {
879- encode_accept_upload_req ( )
880- } else {
881- encode_queue_ranking ( u16:: try_from ( index) . unwrap ( ) )
882- } ;
883- assert_eq ! ( reply, Some ( expected) ) ;
884- queues. push ( queue) ;
885- }
886-
887- // The 4th same-IP candidate is refused: silence on the wire, no
888- // retained session handle, and the queue is unchanged.
889- let mut rejected = ListenerUploadQueue :: new ( ) ;
890- let reply = rejected
891- . start_upload_reply (
892- & runtime,
893- emule_identity ( SocketAddr :: new ( shared_ip, 4665 ) ) ,
894- & file_hash,
895- )
896- . await ;
897- assert_eq ! ( reply, None , "a rejected admission must stay silent" ) ;
898- assert ! ( rejected. session. is_none( ) ) ;
899- assert_eq ! ( runtime. upload_queue_snapshot( ) . await . len( ) , 4 ) ;
900- }
901-
902- /// REG-1: a BANNED peer's STARTUPLOADREQ is refused at admission (master
903- /// `AddClientToQueue` `if (client->IsBanned()) return;`, UploadQueue.cpp:1854):
904- /// no reply packet reaches the wire and no queue entry is created.
905- #[ tokio:: test]
906- async fn banned_peer_start_upload_req_gets_no_reply_and_no_queue_entry ( ) {
907- let root = unique_test_dir ( "ed2k-listener-banned-admission" ) ;
908- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
909- let file_hash = Ed2kHash :: from_bytes ( [ 0x77 ; 16 ] ) ;
910- let peer_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 50 ) ) , 4662 ) ;
911- let mut identity = emule_identity ( peer_addr) ;
912- identity. banned = true ;
913-
914- let mut queue = ListenerUploadQueue :: new ( ) ;
915- let reply = queue
916- . start_upload_reply ( & runtime, identity, & file_hash)
917- . await ;
918- assert_eq ! ( reply, None , "a banned peer's admission must stay silent" ) ;
919- assert ! (
920- queue. session. is_none( ) ,
921- "no session is retained for a banned peer"
922- ) ;
923- assert ! (
924- runtime. upload_queue_snapshot( ) . await . is_empty( ) ,
925- "a banned peer must not create a queue entry"
926- ) ;
927- }
928-
929- /// UP-3: OP_QUEUERANKING is gated on the eMule extended protocol — the
930- /// oracle's SendRankingInfo early-returns for a plain-eDonkey peer
931- /// (`!ExtProtocolAvailable()`, UploadClient.cpp:962-963) and never sends
932- /// the legacy edonkey OP_QUEUERANK — while a tracked eMule waiter's
933- /// re-ask still earns its real rank (UP-1 re-attach).
934- #[ tokio:: test]
935- async fn queue_rank_is_sent_only_to_emule_extended_protocol_peers ( ) {
936- let root = unique_test_dir ( "ed2k-listener-rank-family-gate" ) ;
937- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
938- use_one_slot_queue ( & runtime) . await ;
939- let file_hash = Ed2kHash :: from_bytes ( [ 0x66 ; 16 ] ) ;
940-
941- // Slot occupant.
942- let mut granted = ListenerUploadQueue :: new ( ) ;
943- let occupant_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 41 ) ) , 4661 ) ;
944- let reply = granted
945- . start_upload_reply ( & runtime, emule_identity ( occupant_addr) , & file_hash)
946- . await ;
947- assert_eq ! ( reply, Some ( encode_accept_upload_req( ) ) ) ;
948-
949- // A plain-eDonkey waiter is enqueued but hears nothing.
950- let edonkey_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 42 ) ) , 4662 ) ;
951- let edonkey_identity = super :: super :: upload_peer_identity_from_socket ( edonkey_addr) ;
952- assert ! ( !edonkey_identity. is_emule_client) ;
953- let mut edonkey_queue = ListenerUploadQueue :: new ( ) ;
954- let reply = edonkey_queue
955- . start_upload_reply ( & runtime, edonkey_identity. clone ( ) , & file_hash)
956- . await ;
957- assert_eq ! ( reply, None , "a plain-eDonkey waiter must hear nothing" ) ;
958- assert ! (
959- edonkey_queue. session. is_some( ) ,
960- "the silent waiter is still enqueued"
961- ) ;
962-
963- // An eMule waiter behind it gets its real rank on the wire.
964- let emule_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 43 ) ) , 4663 ) ;
965- let mut emule_queue = ListenerUploadQueue :: new ( ) ;
966- let reply = emule_queue
967- . start_upload_reply ( & runtime, emule_identity ( emule_addr) , & file_hash)
968- . await ;
969- assert_eq ! ( reply, Some ( encode_queue_ranking( 2 ) ) ) ;
970-
971- // A tracked eMule waiter's re-ask still answers with the real rank...
972- let reply = emule_queue
973- . start_upload_reply ( & runtime, emule_identity ( emule_addr) , & file_hash)
974- . await ;
975- assert_eq ! ( reply, Some ( encode_queue_ranking( 2 ) ) ) ;
976-
977- // ...and the eDonkey waiter's re-ask stays silent.
978- let reply = edonkey_queue
979- . start_upload_reply ( & runtime, edonkey_identity, & file_hash)
980- . await ;
981- assert_eq ! ( reply, None ) ;
982- assert_eq ! ( runtime. upload_queue_snapshot( ) . await . len( ) , 3 ) ;
983- }
984-
985- /// FIX 5 invariant: the upload slot must be reclaimed on EVERY exit path.
986- /// `handle_connection` now always falls through to `release` (the loop body
987- /// runs inside a fallible scope, so an in-loop `?` lands in `result` instead
988- /// of escaping past the release). This test proves the property the
989- /// fall-through relies on: `release` frees the runtime slot and is safe to
990- /// call again (idempotent), so calling it after an in-loop release -- or on
991- /// an error path that already released -- never panics or double-frees.
992- #[ tokio:: test]
993- async fn release_reclaims_slot_and_is_idempotent ( ) {
994- let root = unique_test_dir ( "ed2k-listener-upload-release" ) ;
995- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
996-
997- let peer_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 7 ) ) , 4662 ) ;
998- let identity = super :: super :: upload_peer_identity_from_socket ( peer_addr) ;
999- let file_hash = Ed2kHash :: from_bytes ( [ 0x33 ; 16 ] ) ;
1000-
1001- let mut queue = ListenerUploadQueue :: new ( ) ;
1002- // An empty queue grants the first requester a slot.
1003- let _reply = queue
1004- . start_upload_reply ( & runtime, identity, & file_hash)
1005- . await ;
1006- assert_eq ! (
1007- runtime. upload_queue_snapshot( ) . await . len( ) ,
1008- 1 ,
1009- "the granted session must occupy a slot"
1010- ) ;
1011-
1012- // First release frees the slot.
1013- queue. release ( & runtime) . await ;
1014- assert ! (
1015- runtime. upload_queue_snapshot( ) . await . is_empty( ) ,
1016- "release must reclaim the slot deterministically"
1017- ) ;
1018-
1019- // The unconditional post-loop release (or an error path that already
1020- // released) calling it a second time must be a harmless no-op.
1021- queue. release ( & runtime) . await ;
1022- assert ! ( runtime. upload_queue_snapshot( ) . await . is_empty( ) ) ;
1023- }
1024-
1025- /// FIX (END_OF_DOWNLOAD on the wrong hash): `slot_file_hash` must report the
1026- /// file the granted slot is keyed on, so OP_END_OF_DOWNLOAD compares against
1027- /// the held file rather than the mutable per-session `requested_file_hash`
1028- /// (which any later file-touching handler overwrites). Before a slot exists
1029- /// it is `None`; after a grant it is the granted file; after release it is
1030- /// `None` again.
1031- #[ tokio:: test]
1032- async fn slot_file_hash_tracks_the_granted_slot_not_the_last_request ( ) {
1033- let root = unique_test_dir ( "ed2k-listener-slot-file-hash" ) ;
1034- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
1035-
1036- let peer_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 9 ) ) , 4662 ) ;
1037- let identity = super :: super :: upload_peer_identity_from_socket ( peer_addr) ;
1038- let file_a = Ed2kHash :: from_bytes ( [ 0xA1 ; 16 ] ) ;
1039-
1040- let mut queue = ListenerUploadQueue :: new ( ) ;
1041- // No slot yet: nothing to release on END_OF_DOWNLOAD.
1042- assert_eq ! ( queue. slot_file_hash( ) , None ) ;
1043-
1044- // Granting a slot for file A keys the slot on A.
1045- let _reply = queue. start_upload_reply ( & runtime, identity, & file_a) . await ;
1046- assert_eq ! (
1047- queue. slot_file_hash( ) ,
1048- Some ( file_a) ,
1049- "the granted slot must report the file it is keyed on"
1050- ) ;
1051-
1052- // After release the slot is gone, so a stray END_OF_DOWNLOAD matches
1053- // nothing (the post-loop unconditional release still guarantees cleanup).
1054- queue. release ( & runtime) . await ;
1055- assert_eq ! ( queue. slot_file_hash( ) , None ) ;
1056- }
1057-
1058- #[ tokio:: test]
1059- async fn verified_reader_cache_survives_repeated_parts_requests_for_slot_file ( ) {
1060- let root = unique_test_dir ( "ed2k-listener-upload-reader-cache" ) ;
1061- let runtime = Ed2kTransferRuntime :: load_or_create ( & root) . unwrap ( ) ;
1062- let library = root. join ( "library" ) ;
1063- fs:: create_dir_all ( & library) . unwrap ( ) ;
1064- let source_path = library. join ( "shared-upload-cache.bin" ) ;
1065- let file_len = usize:: try_from ( ED2K_EMBLOCK_SIZE * 3 ) . unwrap ( ) ;
1066- let bytes = ( 0 ..file_len)
1067- . map ( |index| ( index % 251 ) as u8 )
1068- . collect :: < Vec < _ > > ( ) ;
1069- fs:: write ( & source_path, & bytes) . unwrap ( ) ;
1070- let summary = runtime
1071- . ingest_local_file ( & source_path, "shared-upload-cache.bin" )
1072- . await
1073- . unwrap ( ) ;
1074- let hash = Ed2kHash :: from_str ( & summary. file_hash ) . unwrap ( ) ;
1075-
1076- let peer_addr = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 0 , 2 , 10 ) ) , 4662 ) ;
1077- let identity = super :: super :: upload_peer_identity_from_socket ( peer_addr) ;
1078- let mut queue = ListenerUploadQueue :: new ( ) ;
1079- let _reply = queue. start_upload_reply ( & runtime, identity, & hash) . await ;
1080-
1081- let mut reader = queue
1082- . take_verified_reader ( & runtime, & hash)
1083- . await
1084- . unwrap ( )
1085- . unwrap ( ) ;
1086- let first = reader
1087- . read_range_with_read_ahead ( 0 , ED2K_EMBLOCK_SIZE , ED2K_EMBLOCK_SIZE * 3 )
1088- . await
1089- . unwrap ( )
1090- . unwrap ( ) ;
1091- assert_eq ! ( first, bytes[ 0 ..ED2K_EMBLOCK_SIZE as usize ] ) ;
1092- assert_eq ! ( reader. disk_read_count( ) , 1 ) ;
1093- queue. store_verified_reader ( & hash, reader) ;
1094-
1095- let mut reader = queue
1096- . take_verified_reader ( & runtime, & hash)
1097- . await
1098- . unwrap ( )
1099- . unwrap ( ) ;
1100- let second = reader
1101- . read_range ( ED2K_EMBLOCK_SIZE , ED2K_EMBLOCK_SIZE * 2 )
1102- . await
1103- . unwrap ( )
1104- . unwrap ( ) ;
1105- assert_eq ! (
1106- second,
1107- bytes[ ED2K_EMBLOCK_SIZE as usize ..( ED2K_EMBLOCK_SIZE * 2 ) as usize ]
1108- ) ;
1109- assert_eq ! (
1110- reader. disk_read_count( ) ,
1111- 1 ,
1112- "second OP_REQUESTPARTS should reuse the cached read-ahead window"
1113- ) ;
1114- assert_eq ! ( reader. cache_hit_count( ) , 1 ) ;
1115- }
1116- }
722+ mod tests;
0 commit comments