Skip to content

Commit d2f15af

Browse files
author
Threepwood-7
committed
RUST-REF-005: extract listener upload queue tests
1 parent f77008b commit d2f15af

2 files changed

Lines changed: 395 additions & 395 deletions

File tree

crates/emulebb-ed2k/src/ed2k_tcp/listener/session/upload_queue.rs

Lines changed: 1 addition & 395 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)