1use std::net::SocketAddr;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum SeedChokingAlgorithm {
13 #[default]
15 FastestUpload,
16 RoundRobin,
18 AntiLeech,
20}
21
22#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum ChokingAlgorithm {
26 #[default]
28 FixedSlots,
29 RateBased,
31}
32
33#[derive(Debug, Clone)]
39pub(crate) struct PeerInfo {
40 pub addr: SocketAddr,
41 pub download_rate: u64,
43 pub upload_rate: u64,
45 pub interested: bool,
47 pub upload_only: bool,
49 pub is_seed: bool,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
59pub(crate) struct ChokeDecision {
60 pub to_unchoke: Vec<SocketAddr>,
62 pub to_choke: Vec<SocketAddr>,
64}
65
66pub(crate) trait ChokerStrategy: Send + Sync {
72 fn decide(
74 &mut self,
75 peers: &[PeerInfo],
76 unchoke_slots: usize,
77 seed_mode: bool,
78 ) -> ChokeDecision;
79
80 fn rotate_optimistic(&mut self, peers: &[PeerInfo]);
82
83 fn observe_throughput(&mut self, _throughput: u64) {}
86
87 #[allow(dead_code)] fn dynamic_slots(&self) -> Option<usize> {
91 None
92 }
93}
94
95pub(crate) struct FixedSlotsStrategy {
104 optimistic_peer: Option<SocketAddr>,
105 seed_algorithm: SeedChokingAlgorithm,
106 round_robin_offset: usize,
107}
108
109impl FixedSlotsStrategy {
110 pub fn new(seed_algorithm: SeedChokingAlgorithm) -> Self {
111 Self {
112 optimistic_peer: None,
113 seed_algorithm,
114 round_robin_offset: 0,
115 }
116 }
117
118 fn select_optimistic(
123 &self,
124 interested: &[&PeerInfo],
125 regular_unchokes: &[SocketAddr],
126 ) -> Option<SocketAddr> {
127 if let Some(opt) = self.optimistic_peer {
129 let still_interested = interested.iter().any(|p| p.addr == opt && !p.upload_only);
130 let already_regular = regular_unchokes.contains(&opt);
131 if still_interested && !already_regular {
132 return Some(opt);
133 }
134 }
135
136 interested
138 .iter()
139 .find(|p| !regular_unchokes.contains(&p.addr) && !p.upload_only)
140 .map(|p| p.addr)
141 }
142
143 fn sort_seed_mode(&mut self, interested: &mut [&PeerInfo], unchoke_slots: usize) {
145 match self.seed_algorithm {
146 SeedChokingAlgorithm::FastestUpload => {
147 interested.sort_by(|a, b| b.upload_rate.cmp(&a.upload_rate));
148 }
149 SeedChokingAlgorithm::RoundRobin => {
150 interested.sort_by(|a, b| a.addr.cmp(&b.addr));
152 if !interested.is_empty() {
153 let offset = self.round_robin_offset % interested.len();
154 interested.rotate_left(offset);
155 self.round_robin_offset =
156 self.round_robin_offset.wrapping_add(unchoke_slots) % interested.len();
157 }
158 }
159 SeedChokingAlgorithm::AntiLeech => {
160 interested.sort_by(|a, b| match (a.is_seed, b.is_seed) {
162 (false, true) => std::cmp::Ordering::Less,
163 (true, false) => std::cmp::Ordering::Greater,
164 _ => b.upload_rate.cmp(&a.upload_rate),
165 });
166 }
167 }
168 }
169}
170
171impl ChokerStrategy for FixedSlotsStrategy {
172 fn decide(
173 &mut self,
174 peers: &[PeerInfo],
175 unchoke_slots: usize,
176 seed_mode: bool,
177 ) -> ChokeDecision {
178 let all_addrs: Vec<SocketAddr> = peers.iter().map(|p| p.addr).collect();
179
180 let mut interested: Vec<&PeerInfo> = peers.iter().filter(|p| p.interested).collect();
182 if seed_mode {
183 self.sort_seed_mode(&mut interested, unchoke_slots);
184 } else {
185 interested.sort_by(|a, b| b.download_rate.cmp(&a.download_rate));
187 }
188
189 let regular_count = unchoke_slots.min(interested.len());
191 let regular_unchokes: Vec<SocketAddr> =
192 interested[..regular_count].iter().map(|p| p.addr).collect();
193
194 let optimistic = self.select_optimistic(&interested, ®ular_unchokes);
196 self.optimistic_peer = optimistic;
197
198 let mut to_unchoke = regular_unchokes;
200 if let Some(opt) = optimistic
201 && !to_unchoke.contains(&opt)
202 {
203 to_unchoke.push(opt);
204 }
205
206 let to_choke: Vec<SocketAddr> = all_addrs
208 .into_iter()
209 .filter(|a| !to_unchoke.contains(a))
210 .collect();
211
212 ChokeDecision {
213 to_unchoke,
214 to_choke,
215 }
216 }
217
218 fn rotate_optimistic(&mut self, peers: &[PeerInfo]) {
219 let mut interested: Vec<&PeerInfo> = peers
220 .iter()
221 .filter(|p| p.interested && !p.upload_only)
222 .collect();
223 interested.sort_by(|a, b| a.download_rate.cmp(&b.download_rate));
225
226 self.optimistic_peer = interested
227 .iter()
228 .find(|p| Some(p.addr) != self.optimistic_peer)
229 .map(|p| p.addr);
230 }
231}
232
233pub(crate) struct RateBasedStrategy {
244 inner: FixedSlotsStrategy,
246 dynamic_slots: usize,
248 prev_throughput: u64,
250 upload_rate_limit: u64,
252 min_slots: usize,
254 max_slots: usize,
256}
257
258impl RateBasedStrategy {
259 pub fn new(
260 seed_algorithm: SeedChokingAlgorithm,
261 upload_rate_limit: u64,
262 min_slots: usize,
263 max_slots: usize,
264 ) -> Self {
265 Self {
266 inner: FixedSlotsStrategy::new(seed_algorithm),
267 dynamic_slots: min_slots.max(2),
268 prev_throughput: 0,
269 upload_rate_limit,
270 min_slots,
271 max_slots,
272 }
273 }
274
275 fn observe_throughput_inner(&mut self, throughput: u64) {
277 if self.prev_throughput == 0 && throughput > 0 {
279 self.prev_throughput = throughput;
280 return;
281 }
282
283 if throughput > self.prev_throughput {
284 let at_capacity =
287 self.upload_rate_limit > 0 && throughput > self.upload_rate_limit * 90 / 100;
288
289 if !at_capacity && self.dynamic_slots < self.max_slots {
290 self.dynamic_slots += 1;
291 }
292 } else if self.prev_throughput > 0 {
293 let threshold = self.prev_throughput * 90 / 100;
295 if throughput < threshold && self.dynamic_slots > self.min_slots {
296 self.dynamic_slots -= 1;
297 }
298 }
299
300 if self.upload_rate_limit > 0
302 && throughput < self.upload_rate_limit
303 && self.dynamic_slots > 0
304 {
305 let per_slot_avg = throughput / self.dynamic_slots as u64;
306 if per_slot_avg > 0 {
307 let headroom = self.upload_rate_limit - throughput;
308 if headroom > per_slot_avg && self.dynamic_slots < self.max_slots {
309 self.dynamic_slots += 1;
310 }
311 }
312 }
313
314 self.prev_throughput = throughput;
315 }
316
317 #[cfg(test)]
319 pub fn current_slots(&self) -> usize {
320 self.dynamic_slots
321 }
322}
323
324impl ChokerStrategy for RateBasedStrategy {
325 fn decide(
326 &mut self,
327 peers: &[PeerInfo],
328 _unchoke_slots: usize,
329 seed_mode: bool,
330 ) -> ChokeDecision {
331 self.inner.decide(peers, self.dynamic_slots, seed_mode)
333 }
334
335 fn rotate_optimistic(&mut self, peers: &[PeerInfo]) {
336 self.inner.rotate_optimistic(peers);
337 }
338
339 fn observe_throughput(&mut self, throughput: u64) {
340 self.observe_throughput_inner(throughput);
341 }
342
343 fn dynamic_slots(&self) -> Option<usize> {
344 Some(self.dynamic_slots)
345 }
346}
347
348pub(crate) struct Choker {
358 strategy: Box<dyn ChokerStrategy>,
359 unchoke_slots: usize,
360 seed_mode: bool,
361 #[allow(dead_code)] choking_algorithm: ChokingAlgorithm,
363}
364
365impl Choker {
366 #[cfg(test)]
371 pub fn new(unchoke_slots: usize) -> Self {
372 Self {
373 strategy: Box::new(FixedSlotsStrategy::new(SeedChokingAlgorithm::FastestUpload)),
374 unchoke_slots,
375 seed_mode: false,
376 choking_algorithm: ChokingAlgorithm::FixedSlots,
377 }
378 }
379
380 pub fn with_algorithms(
382 unchoke_slots: usize,
383 seed_algorithm: SeedChokingAlgorithm,
384 choking_algorithm: ChokingAlgorithm,
385 upload_rate_limit: u64,
386 min_slots: usize,
387 max_slots: usize,
388 ) -> Self {
389 let strategy: Box<dyn ChokerStrategy> = match choking_algorithm {
390 ChokingAlgorithm::FixedSlots => Box::new(FixedSlotsStrategy::new(seed_algorithm)),
391 ChokingAlgorithm::RateBased => Box::new(RateBasedStrategy::new(
392 seed_algorithm,
393 upload_rate_limit,
394 min_slots,
395 max_slots,
396 )),
397 };
398
399 Self {
400 strategy,
401 unchoke_slots,
402 seed_mode: false,
403 choking_algorithm,
404 }
405 }
406
407 pub fn set_seed_mode(&mut self, seed_mode: bool) {
408 self.seed_mode = seed_mode;
409 }
410
411 pub fn set_unchoke_slots(&mut self, n: usize) {
413 self.unchoke_slots = n;
414 }
415
416 #[allow(dead_code)] pub fn unchoke_slots(&self) -> usize {
419 self.unchoke_slots
420 }
421
422 pub fn observe_throughput(&mut self, throughput: u64) {
424 self.strategy.observe_throughput(throughput);
425 }
426
427 #[cfg(test)]
429 pub fn choking_algorithm(&self) -> ChokingAlgorithm {
430 self.choking_algorithm
431 }
432
433 pub fn decide(&mut self, peers: &[PeerInfo]) -> ChokeDecision {
435 self.strategy
436 .decide(peers, self.unchoke_slots, self.seed_mode)
437 }
438
439 pub fn rotate_optimistic(&mut self, peers: &[PeerInfo]) {
441 self.strategy.rotate_optimistic(peers);
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 fn addr(port: u16) -> SocketAddr {
450 format!("127.0.0.1:{port}").parse().unwrap()
451 }
452
453 fn peer(port: u16, download_rate: u64, interested: bool) -> PeerInfo {
454 PeerInfo {
455 addr: addr(port),
456 download_rate,
457 upload_rate: 0,
458 interested,
459 upload_only: false,
460 is_seed: false,
461 }
462 }
463
464 fn seed_peer(port: u16, upload_rate: u64, interested: bool) -> PeerInfo {
465 PeerInfo {
466 addr: addr(port),
467 download_rate: 0,
468 upload_rate,
469 interested,
470 upload_only: false,
471 is_seed: false,
472 }
473 }
474
475 #[test]
480 fn unchoke_top_n() {
481 let mut choker = Choker::new(4);
482 let peers = vec![
483 peer(6881, 100, true),
484 peer(6882, 500, true),
485 peer(6883, 300, true),
486 peer(6884, 200, true),
487 peer(6885, 400, true),
488 peer(6886, 50, true),
489 ];
490
491 let decision = choker.decide(&peers);
492
493 assert!(decision.to_unchoke.contains(&addr(6882)));
495 assert!(decision.to_unchoke.contains(&addr(6885)));
496 assert!(decision.to_unchoke.contains(&addr(6883)));
497 assert!(decision.to_unchoke.contains(&addr(6884)));
498
499 assert_eq!(decision.to_unchoke.len(), 5);
502
503 assert_eq!(decision.to_choke.len(), 1);
505 }
506
507 #[test]
508 fn optimistic_rotation() {
509 let mut choker = Choker::new(4);
510 let peers = vec![
511 peer(6881, 500, true),
512 peer(6882, 400, true),
513 peer(6883, 300, true),
514 peer(6884, 200, true),
515 peer(6885, 100, true),
516 peer(6886, 50, true),
517 ];
518
519 let decision = choker.decide(&peers);
520
521 assert_eq!(decision.to_unchoke.len(), 5);
524
525 let regular = [addr(6881), addr(6882), addr(6883), addr(6884)];
527 let opt: Vec<_> = decision
528 .to_unchoke
529 .iter()
530 .filter(|a| !regular.contains(a))
531 .copied()
532 .collect();
533 assert_eq!(opt.len(), 1);
534 let first_opt = opt[0];
535 assert!(first_opt == addr(6885) || first_opt == addr(6886));
536
537 choker.rotate_optimistic(&peers);
539 let decision2 = choker.decide(&peers);
541 let opt2: Vec<_> = decision2
542 .to_unchoke
543 .iter()
544 .filter(|a| !regular.contains(a))
545 .copied()
546 .collect();
547 assert_eq!(opt2.len(), 1);
548 assert_ne!(opt2[0], first_opt);
549 }
550
551 #[test]
552 fn fewer_peers_than_slots() {
553 let mut choker = Choker::new(4);
554 let peers = vec![peer(6881, 100, true), peer(6882, 200, true)];
555
556 let decision = choker.decide(&peers);
557
558 assert!(decision.to_unchoke.contains(&addr(6881)));
560 assert!(decision.to_unchoke.contains(&addr(6882)));
561 assert_eq!(decision.to_unchoke.len(), 2);
562 assert!(decision.to_choke.is_empty());
563 }
564
565 #[test]
566 fn no_interested_peers() {
567 let mut choker = Choker::new(4);
568 let peers = vec![
569 peer(6881, 100, false),
570 peer(6882, 200, false),
571 peer(6883, 300, false),
572 ];
573
574 let decision = choker.decide(&peers);
575
576 assert!(decision.to_unchoke.is_empty());
577 assert_eq!(decision.to_choke.len(), 3);
579 assert!(decision.to_choke.contains(&addr(6881)));
580 assert!(decision.to_choke.contains(&addr(6882)));
581 assert!(decision.to_choke.contains(&addr(6883)));
582 }
583
584 #[test]
585 fn choke_below_threshold() {
586 let mut choker = Choker::new(2);
587 let peers = vec![
588 peer(6881, 500, true),
589 peer(6882, 400, true),
590 peer(6883, 100, true),
591 peer(6884, 50, true),
592 peer(6885, 200, false), ];
594
595 let decision = choker.decide(&peers);
596
597 assert!(decision.to_unchoke.contains(&addr(6881)));
599 assert!(decision.to_unchoke.contains(&addr(6882)));
600
601 assert_eq!(decision.to_unchoke.len(), 3);
603
604 assert_eq!(decision.to_choke.len(), 2);
607 assert!(decision.to_choke.contains(&addr(6885)));
609 }
610
611 #[test]
612 fn set_unchoke_slots_changes_capacity() {
613 let mut choker = Choker::new(2);
614 let peers = vec![
615 peer(6881, 500, true),
616 peer(6882, 400, true),
617 peer(6883, 300, true),
618 peer(6884, 200, true),
619 peer(6885, 100, true),
620 ];
621
622 let decision = choker.decide(&peers);
623 assert_eq!(decision.to_unchoke.len(), 3);
625
626 choker.set_unchoke_slots(4);
628 let decision = choker.decide(&peers);
629 assert_eq!(decision.to_unchoke.len(), 5);
631 }
632
633 #[test]
634 fn seed_mode_unchokes_by_upload_rate() {
635 let mut choker = Choker::new(2);
636 choker.set_seed_mode(true);
637
638 let peers = vec![
640 seed_peer(6881, 100, true),
641 seed_peer(6882, 500, true),
642 seed_peer(6883, 300, true),
643 seed_peer(6884, 200, true),
644 ];
645
646 let decision = choker.decide(&peers);
647
648 assert!(decision.to_unchoke.contains(&addr(6882)));
650 assert!(decision.to_unchoke.contains(&addr(6883)));
651
652 assert_eq!(decision.to_unchoke.len(), 3);
654 }
655
656 #[test]
657 fn upload_only_excluded_from_optimistic() {
658 let mut choker = Choker::new(2);
659 let peers = vec![
660 peer(6881, 500, true),
661 peer(6882, 400, true),
662 PeerInfo {
664 addr: addr(6883),
665 download_rate: 100,
666 upload_rate: 0,
667 interested: true,
668 upload_only: true,
669 is_seed: false,
670 },
671 PeerInfo {
672 addr: addr(6884),
673 download_rate: 50,
674 upload_rate: 0,
675 interested: true,
676 upload_only: true,
677 is_seed: false,
678 },
679 ];
680
681 let decision = choker.decide(&peers);
682
683 assert_eq!(decision.to_unchoke.len(), 2);
686 assert!(decision.to_unchoke.contains(&addr(6881)));
687 assert!(decision.to_unchoke.contains(&addr(6882)));
688
689 assert!(decision.to_choke.contains(&addr(6883)));
691 assert!(decision.to_choke.contains(&addr(6884)));
692 }
693
694 #[test]
695 fn upload_only_still_regular_unchoked() {
696 let mut choker = Choker::new(2);
698 choker.set_seed_mode(true);
699
700 let peers = vec![
701 PeerInfo {
702 addr: addr(6881),
703 download_rate: 0,
704 upload_rate: 500,
705 interested: true,
706 upload_only: true, is_seed: false,
708 },
709 seed_peer(6882, 300, true),
710 seed_peer(6883, 100, true),
711 ];
712
713 let decision = choker.decide(&peers);
714
715 assert!(decision.to_unchoke.contains(&addr(6881)));
717 assert!(decision.to_unchoke.contains(&addr(6882)));
718 }
719
720 #[test]
725 fn seed_choking_algorithm_default() {
726 assert_eq!(
727 SeedChokingAlgorithm::default(),
728 SeedChokingAlgorithm::FastestUpload
729 );
730 }
731
732 #[test]
733 fn choking_algorithm_default() {
734 assert_eq!(ChokingAlgorithm::default(), ChokingAlgorithm::FixedSlots);
735 }
736
737 #[test]
738 fn seed_choking_algorithm_serde_round_trip() {
739 for variant in [
740 SeedChokingAlgorithm::FastestUpload,
741 SeedChokingAlgorithm::RoundRobin,
742 SeedChokingAlgorithm::AntiLeech,
743 ] {
744 let json = serde_json::to_string(&variant).unwrap();
745 let decoded: SeedChokingAlgorithm = serde_json::from_str(&json).unwrap();
746 assert_eq!(decoded, variant);
747 }
748 }
749
750 #[test]
751 fn choking_algorithm_serde_round_trip() {
752 for variant in [ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased] {
753 let json = serde_json::to_string(&variant).unwrap();
754 let decoded: ChokingAlgorithm = serde_json::from_str(&json).unwrap();
755 assert_eq!(decoded, variant);
756 }
757 }
758
759 #[test]
764 fn fixed_slots_strategy_leech_mode() {
765 let mut strategy = FixedSlotsStrategy::new(SeedChokingAlgorithm::FastestUpload);
766 let peers = vec![
767 peer(6881, 100, true),
768 peer(6882, 500, true),
769 peer(6883, 300, true),
770 peer(6884, 200, true),
771 peer(6885, 400, true),
772 peer(6886, 50, true),
773 ];
774
775 let decision = strategy.decide(&peers, 4, false);
776
777 assert!(decision.to_unchoke.contains(&addr(6882)));
779 assert!(decision.to_unchoke.contains(&addr(6885)));
780 assert!(decision.to_unchoke.contains(&addr(6883)));
781 assert!(decision.to_unchoke.contains(&addr(6884)));
782
783 assert_eq!(decision.to_unchoke.len(), 5);
785 assert_eq!(decision.to_choke.len(), 1);
786 }
787
788 #[test]
789 fn fixed_slots_round_robin_rotates() {
790 let mut strategy = FixedSlotsStrategy::new(SeedChokingAlgorithm::RoundRobin);
791 let peers = vec![
793 seed_peer(6881, 100, true),
794 seed_peer(6882, 200, true),
795 seed_peer(6883, 300, true),
796 seed_peer(6884, 400, true),
797 seed_peer(6885, 500, true),
798 ];
799
800 let d1 = strategy.decide(&peers, 2, true);
801 let d2 = strategy.decide(&peers, 2, true);
802
803 let set1: Vec<SocketAddr> = d1.to_unchoke.iter().copied().collect();
808 let set2: Vec<SocketAddr> = d2.to_unchoke.iter().copied().collect();
809 assert_ne!(set1, set2, "round-robin should rotate unchoke set");
810 }
811
812 #[test]
813 fn fixed_slots_anti_leech_prefers_non_seeds() {
814 let mut strategy = FixedSlotsStrategy::new(SeedChokingAlgorithm::AntiLeech);
815 let peers = vec![
816 PeerInfo {
818 addr: addr(6881),
819 download_rate: 0,
820 upload_rate: 500,
821 interested: true,
822 upload_only: false,
823 is_seed: true,
824 },
825 PeerInfo {
826 addr: addr(6882),
827 download_rate: 0,
828 upload_rate: 400,
829 interested: true,
830 upload_only: false,
831 is_seed: true,
832 },
833 PeerInfo {
835 addr: addr(6883),
836 download_rate: 0,
837 upload_rate: 100,
838 interested: true,
839 upload_only: false,
840 is_seed: false,
841 },
842 PeerInfo {
843 addr: addr(6884),
844 download_rate: 0,
845 upload_rate: 50,
846 interested: true,
847 upload_only: false,
848 is_seed: false,
849 },
850 ];
851
852 let decision = strategy.decide(&peers, 2, true);
854
855 assert!(
857 decision.to_unchoke.contains(&addr(6883)),
858 "non-seed peer 6883 should be unchoked"
859 );
860 assert!(
861 decision.to_unchoke.contains(&addr(6884)),
862 "non-seed peer 6884 should be unchoked"
863 );
864 }
865
866 #[test]
871 fn rate_based_starts_at_min_slots() {
872 let strategy = RateBasedStrategy::new(
873 SeedChokingAlgorithm::FastestUpload,
874 0, 3, 10, );
878 assert_eq!(strategy.current_slots(), 3);
880
881 let strategy2 = RateBasedStrategy::new(
883 SeedChokingAlgorithm::FastestUpload,
884 0,
885 1, 10,
887 );
888 assert_eq!(strategy2.current_slots(), 2);
889 }
890
891 #[test]
892 fn rate_based_increases_slots_on_throughput_increase() {
893 let mut strategy = RateBasedStrategy::new(
894 SeedChokingAlgorithm::FastestUpload,
895 0, 2, 10, );
899 assert_eq!(strategy.current_slots(), 2);
900
901 strategy.observe_throughput_inner(1000);
903 assert_eq!(strategy.current_slots(), 2);
904
905 strategy.observe_throughput_inner(1500);
907 assert_eq!(strategy.current_slots(), 3);
908 }
909
910 #[test]
911 fn rate_based_decreases_slots_on_throughput_drop() {
912 let mut strategy = RateBasedStrategy::new(
913 SeedChokingAlgorithm::FastestUpload,
914 0, 2, 10, );
918
919 strategy.observe_throughput_inner(1000);
921 strategy.observe_throughput_inner(2000);
922 assert_eq!(strategy.current_slots(), 3);
923 strategy.observe_throughput_inner(3000);
924 assert_eq!(strategy.current_slots(), 4);
925
926 strategy.observe_throughput_inner(2000);
928 assert_eq!(strategy.current_slots(), 3);
929 }
930
931 #[test]
932 fn rate_based_respects_min_max() {
933 let mut strategy = RateBasedStrategy::new(
934 SeedChokingAlgorithm::FastestUpload,
935 0, 2, 3, );
939
940 strategy.observe_throughput_inner(1000);
942 strategy.observe_throughput_inner(2000);
943 assert_eq!(strategy.current_slots(), 3);
944
945 strategy.observe_throughput_inner(5000);
947 assert_eq!(strategy.current_slots(), 3);
948
949 strategy.observe_throughput_inner(1000);
951 assert_eq!(strategy.current_slots(), 2);
952
953 strategy.observe_throughput_inner(100);
955 assert_eq!(strategy.current_slots(), 2);
956 }
957
958 #[test]
959 fn rate_based_does_not_add_at_capacity() {
960 let mut strategy = RateBasedStrategy::new(
961 SeedChokingAlgorithm::FastestUpload,
962 10_000, 2,
964 10,
965 );
966
967 strategy.observe_throughput_inner(5_000);
969 assert_eq!(strategy.current_slots(), 2);
970
971 strategy.observe_throughput_inner(9_500);
973 assert_eq!(strategy.current_slots(), 2);
978 }
979
980 #[test]
981 fn rate_based_ignores_external_unchoke_slots() {
982 let mut strategy = RateBasedStrategy::new(SeedChokingAlgorithm::FastestUpload, 0, 2, 10);
983
984 let peers = vec![
985 peer(6881, 500, true),
986 peer(6882, 400, true),
987 peer(6883, 300, true),
988 peer(6884, 200, true),
989 peer(6885, 100, true),
990 ];
991
992 let decision = strategy.decide(&peers, 10, false);
994
995 assert_eq!(decision.to_unchoke.len(), 3);
998 }
999
1000 #[test]
1005 fn choker_with_round_robin() {
1006 let mut choker = Choker::with_algorithms(
1007 2,
1008 SeedChokingAlgorithm::RoundRobin,
1009 ChokingAlgorithm::FixedSlots,
1010 0,
1011 2,
1012 10,
1013 );
1014 choker.set_seed_mode(true);
1015
1016 let peers = vec![
1017 seed_peer(6881, 100, true),
1018 seed_peer(6882, 200, true),
1019 seed_peer(6883, 300, true),
1020 seed_peer(6884, 400, true),
1021 seed_peer(6885, 500, true),
1022 ];
1023
1024 let d1 = choker.decide(&peers);
1025 let d2 = choker.decide(&peers);
1026
1027 let set1: Vec<SocketAddr> = d1.to_unchoke.iter().copied().collect();
1029 let set2: Vec<SocketAddr> = d2.to_unchoke.iter().copied().collect();
1030 assert_ne!(set1, set2, "round-robin dispatcher should rotate");
1031 }
1032
1033 #[test]
1034 fn choker_with_rate_based() {
1035 let mut choker = Choker::with_algorithms(
1036 4, SeedChokingAlgorithm::FastestUpload,
1038 ChokingAlgorithm::RateBased,
1039 0,
1040 2,
1041 10,
1042 );
1043 assert_eq!(choker.choking_algorithm(), ChokingAlgorithm::RateBased);
1044
1045 let peers = vec![
1046 peer(6881, 500, true),
1047 peer(6882, 400, true),
1048 peer(6883, 300, true),
1049 peer(6884, 200, true),
1050 peer(6885, 100, true),
1051 ];
1052
1053 let decision = choker.decide(&peers);
1055 assert_eq!(decision.to_unchoke.len(), 3);
1056
1057 choker.observe_throughput(1000);
1059 choker.observe_throughput(2000);
1060 let decision = choker.decide(&peers);
1062 assert_eq!(decision.to_unchoke.len(), 4); }
1064
1065 #[test]
1066 fn choker_unchoke_slots_getter() {
1067 let mut choker = Choker::new(4);
1068 assert_eq!(choker.unchoke_slots(), 4);
1069
1070 choker.set_unchoke_slots(7);
1071 assert_eq!(choker.unchoke_slots(), 7);
1072
1073 choker.set_unchoke_slots(0);
1074 assert_eq!(choker.unchoke_slots(), 0);
1075 }
1076
1077 #[test]
1078 fn choker_new_is_backward_compatible() {
1079 let mut choker = Choker::new(4);
1080 assert_eq!(choker.choking_algorithm(), ChokingAlgorithm::FixedSlots);
1081
1082 let peers = vec![
1083 peer(6881, 500, true),
1084 peer(6882, 400, true),
1085 peer(6883, 300, true),
1086 peer(6884, 200, true),
1087 peer(6885, 100, true),
1088 peer(6886, 50, true),
1089 ];
1090
1091 let decision = choker.decide(&peers);
1092
1093 assert_eq!(decision.to_unchoke.len(), 5);
1095 assert!(decision.to_unchoke.contains(&addr(6882)));
1096 assert!(decision.to_unchoke.contains(&addr(6885)));
1097 assert!(decision.to_unchoke.contains(&addr(6883)));
1098 assert!(decision.to_unchoke.contains(&addr(6884)));
1099
1100 choker.set_seed_mode(true);
1102 choker.set_unchoke_slots(2);
1103 let decision = choker.decide(&peers);
1104 assert_eq!(decision.to_unchoke.len(), 3); }
1106}