1use std::collections::HashMap;
2use std::net::SocketAddr;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::time::{Duration, Instant};
6use tracing::{debug, info, warn};
7
8use crate::error::{Aria2Error, Result};
9
10use super::bt_upload_session::{BtSeedingConfig, BtUploadSession, PieceDataProvider};
11use super::choking_algorithm::{ChokeAction, ChokingAlgorithm};
12
13pub const BAD_DATA_THRESHOLD: u32 = 3;
18
19#[derive(Debug, Clone)]
24pub struct UploadSession {
25 pub peer_addr: SocketAddr,
27 pub uploaded_bytes: u64,
29 pub upload_speed: u64,
31 pub last_upload_time: Instant,
33 pub is_active: bool,
35}
36
37impl UploadSession {
38 pub fn new(peer_addr: SocketAddr) -> Self {
40 Self {
41 peer_addr,
42 uploaded_bytes: 0,
43 upload_speed: 0,
44 last_upload_time: Instant::now(),
45 is_active: true,
46 }
47 }
48
49 pub fn record_upload(&mut self, bytes: u64) {
51 self.uploaded_bytes += bytes;
52 self.last_upload_time = Instant::now();
53 }
54
55 pub fn update_speed(&mut self, speed: u64) {
57 self.upload_speed = speed;
58 }
59
60 pub fn deactivate(&mut self) {
62 self.is_active = false;
63 }
64}
65
66#[derive(Debug, Clone, Default)]
67pub struct SeedExitCondition {
68 pub seed_time: Option<Duration>,
69 pub seed_ratio: Option<f64>,
70}
71
72impl SeedExitCondition {
73 pub fn infinite() -> Self {
74 Self {
75 seed_time: None,
76 seed_ratio: None,
77 }
78 }
79
80 pub fn with_time(secs: u64) -> Self {
81 if secs == 0 {
82 Self::infinite()
83 } else {
84 Self {
85 seed_time: Some(Duration::from_secs(secs)),
86 seed_ratio: None,
87 }
88 }
89 }
90
91 pub fn with_ratio(ratio: f64) -> Self {
92 if ratio <= 0.0 {
93 Self::infinite()
94 } else {
95 Self {
96 seed_time: None,
97 seed_ratio: Some(ratio),
98 }
99 }
100 }
101
102 pub fn with_time_and_ratio(secs: u64, ratio: f64) -> Self {
103 let time = if secs == 0 {
104 None
105 } else {
106 Some(Duration::from_secs(secs))
107 };
108 let r = if ratio <= 0.0 { None } else { Some(ratio) };
109 Self {
110 seed_time: time,
111 seed_ratio: r,
112 }
113 }
114
115 pub fn check_seed_condition(uploaded: u64, downloaded: u64, seed_ratio: f64) -> bool {
128 if seed_ratio <= 0.0 {
129 return false; }
131 if downloaded == 0 {
132 return false; }
134 (uploaded as f64 / downloaded as f64) >= seed_ratio
135 }
136
137 pub fn check_seed_time(
150 seeding_started_at: Instant,
151 seed_time_secs: u64,
152 is_pure_seeding: bool,
153 ) -> bool {
154 if seed_time_secs == 0 {
155 return false; }
157 if !is_pure_seeding {
158 return false; }
160 seeding_started_at.elapsed().as_secs() >= seed_time_secs
161 }
162}
163
164pub struct BtSeedManager {
165 info_hash: [u8; 20],
167 sessions: Vec<BtUploadSession>,
168 piece_data: Arc<dyn PieceDataProvider>,
169 config: BtSeedingConfig,
170 exit_condition: SeedExitCondition,
171 pub total_uploaded: u64,
172 total_downloaded: u64,
173 pub seeding_start_time: Instant,
174 last_optimistic_unchoke: Instant,
175 optimistic_round: usize,
176 pub choking_algo: Option<ChokingAlgorithm>,
179
180 uploaded_bytes_atomic: AtomicU64,
183 upload_speed_atomic: AtomicU64,
185 last_upload_time: std::sync::Mutex<Instant>,
189
190 seed_ratio: f64,
193 seed_time: Duration,
195
196 active_uploads: HashMap<SocketAddr, UploadSession>,
199 max_uploads: usize,
201
202 max_upload_speed: Option<u64>,
205 last_throttle_check: std::sync::Mutex<Instant>,
209 throttle_window_bytes: AtomicU64,
211}
212
213impl BtSeedManager {
214 pub fn new(
224 connections: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection>,
225 piece_data: Arc<dyn PieceDataProvider>,
226 config: BtSeedingConfig,
227 exit_condition: SeedExitCondition,
228 total_downloaded: u64,
229 ) -> Self {
230 Self::new_with_choking_algo(
231 connections,
232 piece_data,
233 config,
234 exit_condition,
235 total_downloaded,
236 None,
237 )
238 }
239
240 pub fn new_with_choking_algo(
246 connections: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection>,
247 piece_data: Arc<dyn PieceDataProvider>,
248 config: BtSeedingConfig,
249 exit_condition: SeedExitCondition,
250 total_downloaded: u64,
251 choking_algo: Option<ChokingAlgorithm>,
252 ) -> Self {
253 let sessions = connections
254 .into_iter()
255 .map(|conn| BtUploadSession::new(conn, &config))
256 .collect();
257
258 let seed_ratio = exit_condition.seed_ratio.unwrap_or(0.0);
259 let seed_time = exit_condition.seed_time.unwrap_or(Duration::ZERO);
260 let max_uploads = config.max_peers_to_unchoke;
261 let max_upload_speed = config.max_upload_bytes_per_sec;
262
263 Self {
264 info_hash: [0u8; 20], sessions,
266 piece_data,
267 config,
268 exit_condition,
269 total_uploaded: 0,
270 total_downloaded,
271 seeding_start_time: Instant::now(),
272 last_optimistic_unchoke: Instant::now(),
273 optimistic_round: 0,
274 choking_algo,
275 uploaded_bytes_atomic: AtomicU64::new(0),
276 upload_speed_atomic: AtomicU64::new(0),
277 last_upload_time: std::sync::Mutex::new(Instant::now()),
278 seed_ratio,
279 seed_time,
280 active_uploads: HashMap::new(),
281 max_uploads,
282 max_upload_speed,
283 last_throttle_check: std::sync::Mutex::new(Instant::now()),
284 throttle_window_bytes: AtomicU64::new(0),
285 }
286 }
287
288 pub fn new_with_info_hash(
292 info_hash: [u8; 20],
293 connections: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection>,
294 piece_data: Arc<dyn PieceDataProvider>,
295 config: BtSeedingConfig,
296 exit_condition: SeedExitCondition,
297 total_downloaded: u64,
298 ) -> Self {
299 let mut manager = Self::new(
300 connections,
301 piece_data,
302 config,
303 exit_condition,
304 total_downloaded,
305 );
306 manager.info_hash = info_hash;
307 manager
308 }
309
310 pub async fn handle_piece_request(
326 &mut self,
327 peer: SocketAddr,
328 index: u32,
329 begin: u32,
330 length: u32,
331 ) -> Result<Vec<u8>> {
332 if let Some(max_speed) = self.max_upload_speed {
334 self.throttle_upload(max_speed, length as u64).await?;
335 }
336
337 let piece_data = self
339 .piece_data
340 .get_piece_data(index, begin, length)
341 .ok_or_else(|| {
342 Aria2Error::Recoverable(crate::error::RecoverableError::InvalidPieceIndex {
343 index,
344 max_index: self.piece_data.num_pieces(),
345 })
346 })?;
347
348 self.uploaded_bytes_atomic
350 .fetch_add(piece_data.len() as u64, Ordering::Relaxed);
351 self.total_uploaded += piece_data.len() as u64;
352
353 if let Some(session) = self.active_uploads.get_mut(&peer) {
355 session.record_upload(piece_data.len() as u64);
356 } else {
357 let mut session = UploadSession::new(peer);
358 session.record_upload(piece_data.len() as u64);
359 self.active_uploads.insert(peer, session);
360 }
361
362 if let Ok(mut last) = self.last_upload_time.lock() {
364 *last = Instant::now();
365 }
366
367 debug!(
368 "Handled piece request: peer={}, index={}, offset={}, len={}, total_uploaded={}",
369 peer, index, begin, length, self.total_uploaded
370 );
371
372 Ok(piece_data)
373 }
374
375 async fn throttle_upload(&self, max_speed: u64, bytes_to_upload: u64) -> Result<()> {
386 if max_speed == 0 {
387 return Ok(()); }
389
390 let now = Instant::now();
391 let window_bytes = self.throttle_window_bytes.load(Ordering::Relaxed);
392
393 let should_reset = if let Ok(last_check) = self.last_throttle_check.lock() {
395 now.duration_since(*last_check) >= Duration::from_secs(1)
396 } else {
397 false
398 };
399
400 if should_reset {
401 self.throttle_window_bytes.store(0, Ordering::Relaxed);
403 if let Ok(mut last_check) = self.last_throttle_check.lock() {
404 *last_check = now;
405 }
406 }
407
408 if window_bytes + bytes_to_upload > max_speed {
410 let bytes_over = window_bytes + bytes_to_upload - max_speed;
412 let wait_secs = bytes_over as f64 / max_speed as f64;
413 let wait_duration = Duration::from_secs_f64(wait_secs.min(1.0));
414
415 debug!(
416 "Throttling upload: {} bytes over limit, waiting {:?}",
417 bytes_over, wait_duration
418 );
419
420 tokio::time::sleep(wait_duration).await;
421
422 self.throttle_window_bytes.store(0, Ordering::Relaxed);
424 if let Ok(mut last_check) = self.last_throttle_check.lock() {
425 *last_check = Instant::now();
426 }
427 }
428
429 self.throttle_window_bytes
431 .fetch_add(bytes_to_upload, Ordering::Relaxed);
432
433 Ok(())
434 }
435
436 pub fn should_stop_seeding(&self, downloaded_bytes: u64) -> bool {
446 if self.seed_ratio > 0.0 && downloaded_bytes > 0 {
448 let uploaded = self.uploaded_bytes_atomic.load(Ordering::Relaxed);
449 let ratio = uploaded as f64 / downloaded_bytes as f64;
450 if ratio >= self.seed_ratio {
451 info!(
452 "Seed ratio reached: {:.2} >= {:.2} (uploaded={}, downloaded={})",
453 ratio, self.seed_ratio, uploaded, downloaded_bytes
454 );
455 return true;
456 }
457 }
458
459 if self.seed_time > Duration::ZERO {
461 let elapsed = self.seeding_start_time.elapsed();
462 if elapsed >= self.seed_time {
463 info!("Seed time reached: {:?} >= {:?}", elapsed, self.seed_time);
464 return true;
465 }
466 }
467
468 false
469 }
470
471 pub fn get_upload_stats(&self) -> (u64, u64) {
475 let uploaded = self.uploaded_bytes_atomic.load(Ordering::Relaxed);
476 let speed = self.upload_speed_atomic.load(Ordering::Relaxed);
477 (uploaded, speed)
478 }
479
480 pub fn update_upload_speed(&self, speed: u64) {
484 self.upload_speed_atomic.store(speed, Ordering::Relaxed);
485 }
486
487 pub fn info_hash(&self) -> &[u8; 20] {
489 &self.info_hash
490 }
491
492 pub fn set_info_hash(&mut self, info_hash: [u8; 20]) {
494 self.info_hash = info_hash;
495 }
496
497 pub fn num_active_uploads(&self) -> usize {
499 self.active_uploads.values().filter(|s| s.is_active).count()
500 }
501
502 pub fn max_uploads(&self) -> usize {
504 self.max_uploads
505 }
506
507 pub fn set_max_uploads(&mut self, max: usize) {
509 self.max_uploads = max;
510 }
511
512 pub fn seed_ratio(&self) -> f64 {
514 self.seed_ratio
515 }
516
517 pub fn seed_time(&self) -> Duration {
519 self.seed_time
520 }
521
522 pub fn active_uploads(&self) -> &HashMap<SocketAddr, UploadSession> {
524 &self.active_uploads
525 }
526
527 pub fn total_downloaded(&self) -> u64 {
529 self.total_downloaded
530 }
531
532 pub async fn run_seeding_loop(&mut self) -> Result<()> {
533 info!(
534 "Seeding started: {} peers, condition={:?}, choking_algo={}",
535 self.sessions.len(),
536 self.exit_condition,
537 self.choking_algo.is_some()
538 );
539
540 for session in &mut self.sessions {
541 session.unchoke_peer().await.ok();
542 }
543
544 let choke_rotation_secs = self
546 .choking_algo
547 .as_ref()
548 .map(|c| c.config().choke_rotation_interval_secs)
549 .unwrap_or(10);
550 let mut choke_interval = tokio::time::interval(Duration::from_secs(choke_rotation_secs));
551 choke_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
553
554 loop {
555 if let Some(ref mut algo) = self.choking_algo {
557 choke_interval.tick().await;
558 let actions = algo.rotate_choke();
559 for action in actions {
560 match action {
561 ChokeAction::Unchoke(idx) => {
562 if let Some(session) = self.sessions.get_mut(idx)
563 && !session.is_dead()
564 && session.is_peer_choked()
565 {
566 debug!("ChokingAlgo: Unchoke peer #{}", idx);
567 session.unchoke_peer().await.ok();
568 }
569 }
570 ChokeAction::Choke(idx) => {
571 if let Some(session) = self.sessions.get_mut(idx)
572 && !session.is_dead()
573 && !session.is_peer_choked()
574 {
575 debug!("ChokingAlgo: Choke peer #{}", idx);
576 session.choke_peer().await.ok();
577 }
578 }
579 ChokeAction::NoChange(_) => {}
580 }
581 }
582 }
583
584 let mut alive_sessions = Vec::new();
586 for session in &mut self.sessions {
587 if !session.is_dead() {
588 match session
589 .handle_incoming_messages(self.piece_data.as_ref())
590 .await
591 {
592 Ok(uploaded) => {
593 self.total_uploaded += uploaded;
594 }
595 Err(e) => {
596 warn!("Upload session error: {}", e);
597 session.is_dead = true;
598 }
599 }
600 }
601 if !session.is_dead() {
602 alive_sessions.push(session.uploaded_bytes());
603 }
604 }
605
606 if self.choking_algo.is_none() {
608 self.maybe_optimistic_unchoke().await;
609 }
610
611 if self.should_exit() {
612 info!(
613 "Seeding exit condition met after {:?}",
614 self.seeding_start_time.elapsed()
615 );
616 break;
617 }
618
619 if alive_sessions.is_empty() && !self.sessions.is_empty() {
620 debug!("All upload peers disconnected");
621 break;
622 }
623
624 tokio::time::sleep(Duration::from_millis(50)).await;
625 }
626
627 for session in &mut self.sessions {
628 if !session.is_dead() {
629 session.choke_peer().await.ok();
630 }
631 }
632
633 Ok(())
634 }
635
636 pub fn should_exit(&self) -> bool {
637 let elapsed = self.seeding_start_time.elapsed();
638
639 if let Some(max_time) = self.exit_condition.seed_time
640 && elapsed >= max_time
641 {
642 return true;
643 }
644
645 if let Some(ratio) = self.exit_condition.seed_ratio
646 && self.total_downloaded > 0
647 {
648 let actual_ratio = self.total_uploaded as f64 / self.total_downloaded as f64;
649 if actual_ratio >= ratio {
650 return true;
651 }
652 }
653
654 false
655 }
656
657 async fn maybe_optimistic_unchoke(&mut self) {
658 let interval = Duration::from_secs(self.config.optimistic_unchoke_interval_secs);
659 if self.last_optimistic_unchoke.elapsed() < interval {
660 return;
661 }
662 self.last_optimistic_unchoke = Instant::now();
663 self.optimistic_round += 1;
664
665 let choked_indices: Vec<usize> = self
666 .sessions
667 .iter()
668 .enumerate()
669 .filter(|(_, s)| !s.is_dead() && s.is_peer_choked())
670 .map(|(i, _)| i)
671 .collect();
672
673 if choked_indices.is_empty() {
674 return;
675 }
676
677 let idx = self.optimistic_round % choked_indices.len();
678 let target = choked_indices[idx];
679 if let Some(session) = self.sessions.get_mut(target) {
680 debug!("Optimistic unchoke peer #{}", target);
681 session.unchoke_peer().await.ok();
682 }
683 }
684
685 pub fn total_uploaded(&self) -> u64 {
686 self.total_uploaded
687 }
688
689 pub fn seeding_duration(&self) -> Duration {
690 self.seeding_start_time.elapsed()
691 }
692
693 pub fn num_alive_peers(&self) -> usize {
694 self.sessions.iter().filter(|s| !s.is_dead()).count()
695 }
696
697 pub fn num_total_peers(&self) -> usize {
698 self.sessions.len()
699 }
700
701 pub fn sync_choking_algo_stats(&mut self) {
710 if let Some(ref mut algo) = self.choking_algo {
711 for (i, session) in self.sessions.iter().enumerate() {
712 if let Some(peer) = algo.get_peer_mut(i) {
713 let session_uploaded = session.uploaded_bytes();
715 if session_uploaded > peer.uploaded_bytes {
716 peer.on_data_sent(session_uploaded - peer.uploaded_bytes);
717 }
718 }
719 }
720 }
721 }
722
723 pub fn choking_algo(&self) -> Option<&ChokingAlgorithm> {
725 self.choking_algo.as_ref()
726 }
727
728 pub fn choking_algo_mut(&mut self) -> Option<&mut ChokingAlgorithm> {
730 self.choking_algo.as_mut()
731 }
732
733 pub fn max_upload_speed(&self) -> Option<u64> {
741 self.max_upload_speed
742 }
743
744 pub fn set_max_upload_speed(&mut self, speed: Option<u64>) {
748 self.max_upload_speed = speed.filter(|&s| s > 0);
749 }
750
751 pub fn current_upload_speed(&self) -> u64 {
753 self.upload_speed_atomic.load(Ordering::Relaxed)
754 }
755
756 pub fn calculate_upload_speed(&self) -> u64 {
761 let now = Instant::now();
762 if let Ok(mut last_time) = self.last_upload_time.lock() {
763 let elapsed = now.duration_since(*last_time);
764 if elapsed >= Duration::from_secs(1) {
765 let uploaded = self.uploaded_bytes_atomic.load(Ordering::Relaxed);
766 let speed = uploaded / elapsed.as_secs().max(1);
767 self.upload_speed_atomic.store(speed, Ordering::Relaxed);
768 *last_time = now;
769 return speed;
770 }
771 }
772 self.upload_speed_atomic.load(Ordering::Relaxed)
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779 use crate::engine::bt_upload_session::InMemoryPieceProvider;
780 use crate::engine::choking_algorithm::{ChokeAction, ChokingConfig};
781 use crate::engine::peer_stats::PeerStats;
782
783 #[test]
784 fn test_exit_condition_default_infinite() {
785 let cond = SeedExitCondition::default();
786 assert!(cond.seed_time.is_none());
787 assert!(cond.seed_ratio.is_none());
788 }
789
790 #[test]
791 fn test_exit_condition_with_time_zero_is_infinite() {
792 let cond = SeedExitCondition::with_time(0);
793 assert!(cond.seed_time.is_none());
794 }
795
796 #[test]
797 fn test_exit_condition_with_time_positive() {
798 let cond = SeedExitCondition::with_time(60);
799 assert_eq!(cond.seed_time, Some(Duration::from_secs(60)));
800 }
801
802 #[test]
803 fn test_exit_condition_with_ratio_zero_is_infinite() {
804 let cond = SeedExitCondition::with_ratio(0.0);
805 assert!(cond.seed_ratio.is_none());
806 }
807
808 #[test]
809 fn test_exit_condition_with_ratio_positive() {
810 let cond = SeedExitCondition::with_ratio(1.5);
811 assert_eq!(cond.seed_ratio, Some(1.5));
812 }
813
814 #[test]
815 fn test_exit_condition_combined() {
816 let cond = SeedExitCondition::with_time_and_ratio(120, 2.0);
817 assert_eq!(cond.seed_time, Some(Duration::from_secs(120)));
818 assert_eq!(cond.seed_ratio, Some(2.0));
819 }
820
821 #[test]
822 fn test_should_exit_by_time() {
823 let manager = make_test_manager(SeedExitCondition::with_time(1), 1000, 500);
824 assert!(!manager.should_exit());
825
826 let mut manager = manager;
827 manager.seeding_start_time = Instant::now() - Duration::from_secs(2);
828 assert!(manager.should_exit());
829 }
830
831 #[test]
832 fn test_should_exit_by_ratio() {
833 let manager = make_test_manager(SeedExitCondition::with_ratio(1.0), 1000, 499);
834 assert!(!manager.should_exit());
835
836 let mut manager = manager;
837 manager.total_uploaded = 1500;
838 assert!(manager.should_exit());
839 }
840
841 #[test]
842 fn test_should_not_exit_early() {
843 let manager = make_test_manager(SeedExitCondition::with_time_and_ratio(10, 3.0), 1000, 100);
844 assert!(!manager.should_exit());
845
846 let mut manager = manager;
847 manager.total_uploaded = 2000;
848 manager.seeding_start_time = Instant::now() - Duration::from_secs(5);
849 assert!(!manager.should_exit(), "Neither time nor ratio reached yet");
850 }
851
852 #[test]
853 fn test_seed_manager_stats() {
854 let manager = make_test_manager(SeedExitCondition::infinite(), 1024 * 100, 51200);
855 assert_eq!(manager.num_total_peers(), 0);
856 assert_eq!(manager.num_alive_peers(), 0);
857 assert_eq!(manager.total_uploaded(), 51200);
858 }
859
860 fn make_test_manager(
861 exit_cond: SeedExitCondition,
862 downloaded: u64,
863 uploaded: u64,
864 ) -> BtSeedManager {
865 let provider = Arc::new(InMemoryPieceProvider::new(16384, 10));
866 let config = BtSeedingConfig::default();
867 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
868 let mut mgr = BtSeedManager::new(conns, provider, config, exit_cond, downloaded);
869 mgr.total_uploaded = uploaded;
870 mgr
871 }
872
873 fn make_test_manager_with_choking_algo(
874 exit_cond: SeedExitCondition,
875 downloaded: u64,
876 uploaded: u64,
877 ) -> BtSeedManager {
878 use std::net::SocketAddr;
879 let provider = Arc::new(InMemoryPieceProvider::new(16384, 10));
880 let config = BtSeedingConfig::default();
881 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
882
883 let choking_config = ChokingConfig {
885 max_upload_slots: 2,
886 optimistic_unchoke_interval_secs: 1,
887 snubbed_timeout_secs: 1,
888 choke_rotation_interval_secs: 1,
889 };
890 let mut algo = ChokingAlgorithm::new(choking_config);
891 let addr: SocketAddr = "127.0.0.1:6881".parse().unwrap();
893 algo.add_peer(PeerStats::new([0u8; 20], addr));
894
895 let mut mgr = BtSeedManager::new_with_choking_algo(
896 conns,
897 provider,
898 config,
899 exit_cond,
900 downloaded,
901 Some(algo),
902 );
903 mgr.total_uploaded = uploaded;
904 mgr
905 }
906
907 #[test]
912 fn test_bt_seed_manager_without_choking_algo() {
913 let mut manager = make_test_manager(SeedExitCondition::infinite(), 1000, 500);
915 assert!(manager.choking_algo.is_none());
916 assert!(manager.choking_algo().is_none());
917 assert!(manager.choking_algo_mut().is_none());
918
919 assert_eq!(manager.num_total_peers(), 0);
921 assert_eq!(manager.num_alive_peers(), 0);
922 assert_eq!(manager.total_uploaded(), 500);
923
924 manager.sync_choking_algo_stats(); }
927
928 #[test]
929 fn test_bt_seed_manager_with_choking_algo() {
930 let manager = make_test_manager_with_choking_algo(SeedExitCondition::infinite(), 2000, 800);
932 assert!(manager.choking_algo.is_some());
933
934 let algo = manager.choking_algo().unwrap();
936 assert_eq!(algo.len(), 1);
937 assert!(!algo.is_empty());
938
939 assert_eq!(manager.num_total_peers(), 0); assert_eq!(manager.total_uploaded(), 800);
942 }
943
944 #[test]
945 fn test_bt_seed_manager_choking_algo_rotate_choke() {
946 let mut manager = make_test_manager_with_choking_algo(SeedExitCondition::infinite(), 0, 0);
948
949 if let Some(algo) = manager.choking_algo_mut() {
951 let actions = algo.rotate_choke();
952
953 let unchoke_count = actions
956 .iter()
957 .filter(|a| matches!(a, ChokeAction::Unchoke(_)))
958 .count();
959
960 assert_eq!(actions.len(), 1, "Should have one action for one peer");
961 assert_eq!(unchoke_count, 1, "Single peer should be unchoked (top-K)");
962 } else {
963 panic!("Expected choking_algo to be present");
964 }
965 }
966
967 #[test]
968 fn test_bt_seed_manager_new_with_none_algo() {
969 let provider = Arc::new(InMemoryPieceProvider::new(16384, 10));
971 let config = BtSeedingConfig::default();
972 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
973
974 let mgr = BtSeedManager::new(conns, provider, config, SeedExitCondition::infinite(), 0);
975 assert!(mgr.choking_algo.is_none());
976 }
977
978 #[test]
979 fn test_bt_seed_manager_new_with_some_algo() {
980 let provider = Arc::new(InMemoryPieceProvider::new(16384, 10));
982 let config = BtSeedingConfig::default();
983 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
984
985 let choking_config = ChokingConfig::default();
986 let algo = ChokingAlgorithm::new(choking_config);
987
988 let mgr = BtSeedManager::new_with_choking_algo(
989 conns,
990 provider,
991 config,
992 SeedExitCondition::infinite(),
993 0,
994 Some(algo),
995 );
996 assert!(mgr.choking_algo.is_some());
997 assert_eq!(mgr.choking_algo.unwrap().len(), 0); }
999
1000 #[test]
1005 fn test_seed_ratio_met_1_0() {
1006 assert!(!SeedExitCondition::check_seed_condition(500, 1000, 1.0));
1008 assert!(SeedExitCondition::check_seed_condition(1000, 1000, 1.0));
1009 assert!(SeedExitCondition::check_seed_condition(1500, 1000, 1.0));
1010 }
1011
1012 #[test]
1013 fn test_seed_ratio_infinite_never_stops() {
1014 assert!(!SeedExitCondition::check_seed_condition(999999, 1, 0.0));
1016 assert!(!SeedExitCondition::check_seed_condition(u64::MAX, 1, -1.0));
1017 }
1018
1019 #[test]
1020 fn test_seed_ratio_zero_downloaded() {
1021 assert!(!SeedExitCondition::check_seed_condition(1000, 0, 2.0));
1023 }
1024
1025 #[test]
1026 fn test_seed_time_met_stops() {
1027 let start = Instant::now();
1028 assert!(!SeedExitCondition::check_seed_time(start, 5, true));
1030 let past_start = Instant::now() - Duration::from_secs(10);
1032 assert!(SeedExitCondition::check_seed_time(past_start, 5, true));
1033 }
1034
1035 #[test]
1036 fn test_seed_time_infinite_zero() {
1037 let start = Instant::now();
1039 assert!(!SeedExitCondition::check_seed_time(start, 0, true));
1040 }
1041
1042 #[test]
1043 fn test_seed_time_not_pure_seeding() {
1044 let past_start = Instant::now() - Duration::from_secs(100);
1046 assert!(
1047 !SeedExitCondition::check_seed_time(past_start, 5, false),
1048 "Should not stop if not in pure seeding phase"
1049 );
1050 }
1051
1052 #[test]
1053 fn test_both_conditions_either_triggers_stop() {
1054 let past_start = Instant::now() - Duration::from_secs(100);
1056
1057 assert!(
1059 SeedExitCondition::check_seed_time(past_start, 5, true)
1060 || SeedExitCondition::check_seed_condition(500, 1000, 2.0),
1061 "Time condition should trigger stop"
1062 );
1063
1064 assert!(
1066 !SeedExitCondition::check_seed_time(Instant::now(), 100, true)
1067 && SeedExitCondition::check_seed_condition(2000, 1000, 1.5),
1068 "Ratio condition should trigger stop"
1069 );
1070
1071 assert!(
1073 !SeedExitCondition::check_seed_time(Instant::now(), 100, true)
1074 && !SeedExitCondition::check_seed_condition(500, 1000, 2.0),
1075 "Neither condition met, should continue seeding"
1076 );
1077 }
1078
1079 #[test]
1084 fn test_upload_session_creation() {
1085 let addr: SocketAddr = "192.168.1.1:6881".parse().unwrap();
1086 let session = UploadSession::new(addr);
1087 assert_eq!(session.peer_addr, addr);
1088 assert_eq!(session.uploaded_bytes, 0);
1089 assert_eq!(session.upload_speed, 0);
1090 assert!(session.is_active);
1091 }
1092
1093 #[test]
1094 fn test_upload_session_record_upload() {
1095 let addr: SocketAddr = "192.168.1.1:6881".parse().unwrap();
1096 let mut session = UploadSession::new(addr);
1097 session.record_upload(1024);
1098 assert_eq!(session.uploaded_bytes, 1024);
1099 session.record_upload(2048);
1100 assert_eq!(session.uploaded_bytes, 3072);
1101 }
1102
1103 #[test]
1104 fn test_upload_session_update_speed() {
1105 let addr: SocketAddr = "192.168.1.1:6881".parse().unwrap();
1106 let mut session = UploadSession::new(addr);
1107 session.update_speed(50000);
1108 assert_eq!(session.upload_speed, 50000);
1109 }
1110
1111 #[test]
1112 fn test_upload_session_deactivate() {
1113 let addr: SocketAddr = "192.168.1.1:6881".parse().unwrap();
1114 let mut session = UploadSession::new(addr);
1115 assert!(session.is_active);
1116 session.deactivate();
1117 assert!(!session.is_active);
1118 }
1119
1120 #[test]
1121 fn test_bt_seed_manager_get_upload_stats() {
1122 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 500);
1123 let (uploaded, speed) = manager.get_upload_stats();
1124 assert_eq!(uploaded, 0);
1126 assert_eq!(speed, 0);
1127 }
1128
1129 #[test]
1130 fn test_bt_seed_manager_update_upload_speed() {
1131 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 500);
1132 manager.update_upload_speed(100000);
1133 let (_, speed) = manager.get_upload_stats();
1134 assert_eq!(speed, 100000);
1135 }
1136
1137 #[test]
1138 fn test_bt_seed_manager_should_stop_seeding_ratio() {
1139 let manager = make_test_manager(SeedExitCondition::with_ratio(1.0), 1000, 0);
1140
1141 assert!(!manager.should_stop_seeding(1000));
1143
1144 manager.uploaded_bytes_atomic.store(500, Ordering::Relaxed);
1146 assert!(!manager.should_stop_seeding(1000));
1147
1148 manager.uploaded_bytes_atomic.store(1000, Ordering::Relaxed);
1150 assert!(manager.should_stop_seeding(1000));
1151
1152 manager.uploaded_bytes_atomic.store(1500, Ordering::Relaxed);
1154 assert!(manager.should_stop_seeding(1000));
1155 }
1156
1157 #[test]
1158 fn test_bt_seed_manager_should_stop_seeding_time() {
1159 let mut manager = make_test_manager(SeedExitCondition::with_time(1), 1000, 0);
1160
1161 assert!(!manager.should_stop_seeding(1000));
1163
1164 manager.seeding_start_time = Instant::now() - Duration::from_secs(2);
1166 assert!(manager.should_stop_seeding(1000));
1167 }
1168
1169 #[test]
1170 fn test_bt_seed_manager_should_stop_seeding_infinite() {
1171 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1172
1173 assert!(!manager.should_stop_seeding(1000));
1175 assert!(!manager.should_stop_seeding(0));
1176 }
1177
1178 #[test]
1179 fn test_bt_seed_manager_info_hash() {
1180 let mut manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1181
1182 assert_eq!(manager.info_hash(), &[0u8; 20]);
1184
1185 let custom_hash = [0x12u8; 20];
1187 manager.set_info_hash(custom_hash);
1188 assert_eq!(manager.info_hash(), &custom_hash);
1189 }
1190
1191 #[test]
1192 fn test_bt_seed_manager_new_with_info_hash() {
1193 let custom_hash = [0xABu8; 20];
1194 let provider = Arc::new(InMemoryPieceProvider::new(16384, 10));
1195 let config = BtSeedingConfig::default();
1196 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
1197
1198 let manager = BtSeedManager::new_with_info_hash(
1199 custom_hash,
1200 conns,
1201 provider,
1202 config,
1203 SeedExitCondition::infinite(),
1204 1000,
1205 );
1206
1207 assert_eq!(manager.info_hash(), &custom_hash);
1208 }
1209
1210 #[test]
1211 fn test_bt_seed_manager_max_uploads() {
1212 let mut manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1213
1214 assert_eq!(manager.max_uploads(), 4); manager.set_max_uploads(8);
1219 assert_eq!(manager.max_uploads(), 8);
1220 }
1221
1222 #[test]
1223 fn test_bt_seed_manager_seed_ratio_and_time() {
1224 let manager = make_test_manager(SeedExitCondition::with_time_and_ratio(60, 2.0), 1000, 0);
1225
1226 assert_eq!(manager.seed_ratio(), 2.0);
1227 assert_eq!(manager.seed_time(), Duration::from_secs(60));
1228 }
1229
1230 #[test]
1231 fn test_bt_seed_manager_total_downloaded() {
1232 let manager = make_test_manager(SeedExitCondition::infinite(), 5000, 0);
1233 assert_eq!(manager.total_downloaded(), 5000);
1234 }
1235
1236 #[test]
1237 fn test_bt_seed_manager_num_active_uploads() {
1238 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1239
1240 assert_eq!(manager.num_active_uploads(), 0);
1242 }
1243
1244 #[tokio::test]
1245 async fn test_bt_seed_manager_handle_piece_request() {
1246 let mut provider = InMemoryPieceProvider::new(16384, 10);
1247 provider.set_piece_data(0, vec![0xABu8; 16384]);
1249
1250 let provider_arc = Arc::new(provider);
1251 let config = BtSeedingConfig::default();
1252 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
1253
1254 let mut manager = BtSeedManager::new(
1255 conns,
1256 provider_arc,
1257 config,
1258 SeedExitCondition::infinite(),
1259 1000,
1260 );
1261
1262 let peer_addr: SocketAddr = "192.168.1.1:6881".parse().unwrap();
1263
1264 let result = manager.handle_piece_request(peer_addr, 0, 0, 1024).await;
1266 assert!(result.is_ok());
1267
1268 let data = result.unwrap();
1269 assert_eq!(data.len(), 1024);
1270 assert!(data.iter().all(|&b| b == 0xAB));
1271
1272 let (uploaded, _) = manager.get_upload_stats();
1274 assert_eq!(uploaded, 1024);
1275
1276 assert_eq!(manager.num_active_uploads(), 1);
1278 }
1279
1280 #[tokio::test]
1281 async fn test_bt_seed_manager_handle_piece_request_invalid_piece() {
1282 let provider = Arc::new(InMemoryPieceProvider::new(16384, 10));
1283 let config = BtSeedingConfig::default();
1284 let conns: Vec<aria2_protocol::bittorrent::peer::connection::PeerConnection> = vec![];
1285
1286 let mut manager =
1287 BtSeedManager::new(conns, provider, config, SeedExitCondition::infinite(), 1000);
1288
1289 let peer_addr: SocketAddr = "192.168.1.1:6881".parse().unwrap();
1290
1291 let result = manager.handle_piece_request(peer_addr, 0, 0, 1024).await;
1293 assert!(result.is_err());
1294 }
1295
1296 #[test]
1301 fn test_bt_seed_manager_max_upload_speed() {
1302 let mut manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1303
1304 assert!(manager.max_upload_speed().is_none());
1306
1307 manager.set_max_upload_speed(Some(100000));
1309 assert_eq!(manager.max_upload_speed(), Some(100000));
1310
1311 manager.set_max_upload_speed(Some(0));
1313 assert!(manager.max_upload_speed().is_none());
1314
1315 manager.set_max_upload_speed(None);
1317 assert!(manager.max_upload_speed().is_none());
1318 }
1319
1320 #[test]
1321 fn test_bt_seed_manager_current_upload_speed() {
1322 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1323
1324 assert_eq!(manager.current_upload_speed(), 0);
1326
1327 manager.update_upload_speed(50000);
1329 assert_eq!(manager.current_upload_speed(), 50000);
1330 }
1331
1332 #[tokio::test]
1333 async fn test_bt_seed_manager_throttle_upload_no_limit() {
1334 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1335
1336 let result = manager.throttle_upload(0, 10000).await;
1338 assert!(result.is_ok());
1339 }
1340
1341 #[tokio::test]
1342 async fn test_bt_seed_manager_throttle_upload_within_limit() {
1343 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1344
1345 let result = manager.throttle_upload(100000, 1000).await;
1347 assert!(result.is_ok());
1348
1349 assert_eq!(manager.throttle_window_bytes.load(Ordering::Relaxed), 1000);
1351 }
1352
1353 #[test]
1358 fn test_seed_stats_calculation() {
1359 let mut manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1360
1361 manager.uploaded_bytes_atomic.store(500, Ordering::Relaxed);
1363 manager.total_uploaded = 500;
1364
1365 let (uploaded, _) = manager.get_upload_stats();
1366 assert_eq!(uploaded, 500);
1367
1368 let ratio = uploaded as f64 / manager.total_downloaded as f64;
1370 assert!((ratio - 0.5).abs() < 0.001);
1371 }
1372
1373 #[test]
1374 fn test_seed_stats_with_elapsed_time() {
1375 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1376
1377 let duration = manager.seeding_duration();
1378 assert!(duration.as_secs() < 1);
1380 }
1381
1382 #[test]
1387 fn test_seed_exit_condition_zero_ratio_is_infinite() {
1388 let cond = SeedExitCondition::with_ratio(0.0);
1390 assert!(cond.seed_ratio.is_none());
1391 }
1392
1393 #[test]
1394 fn test_seed_exit_condition_zero_time_is_infinite() {
1395 let cond = SeedExitCondition::with_time(0);
1397 assert!(cond.seed_time.is_none());
1398 }
1399
1400 #[test]
1401 fn test_seed_exit_condition_negative_ratio() {
1402 let cond = SeedExitCondition::with_ratio(-1.0);
1404 assert!(cond.seed_ratio.is_none());
1405 }
1406
1407 #[test]
1408 fn test_should_stop_seeding_with_both_conditions() {
1409 let mut manager =
1411 make_test_manager(SeedExitCondition::with_time_and_ratio(60, 1.0), 1000, 0);
1412
1413 assert!(!manager.should_stop_seeding(1000));
1415
1416 manager.uploaded_bytes_atomic.store(1000, Ordering::Relaxed);
1418 assert!(manager.should_stop_seeding(1000));
1419
1420 manager.uploaded_bytes_atomic.store(0, Ordering::Relaxed);
1422 manager.seeding_start_time = Instant::now() - Duration::from_secs(120);
1423 assert!(manager.should_stop_seeding(1000));
1424 }
1425
1426 #[test]
1427 fn test_max_uploads_enforcement() {
1428 let mut manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1429
1430 assert_eq!(manager.max_uploads(), 4);
1432
1433 manager.set_max_uploads(10);
1435 assert_eq!(manager.max_uploads(), 10);
1436
1437 manager.set_max_uploads(2);
1439 assert_eq!(manager.max_uploads(), 2);
1440 }
1441
1442 #[test]
1443 fn test_active_uploads_tracking() {
1444 let manager = make_test_manager(SeedExitCondition::infinite(), 1000, 0);
1445
1446 assert_eq!(manager.num_active_uploads(), 0);
1448 assert!(manager.active_uploads().is_empty());
1449 }
1450}