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_key(|p| std::cmp::Reverse(p.upload_rate));
148 }
149 SeedChokingAlgorithm::RoundRobin => {
150 interested.sort_by_key(|p| p.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_key(|p| std::cmp::Reverse(p.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_key(|p| p.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]
638 fn set_unchoke_slots_caps_regular_set_at_m224_value() {
639 let peers: Vec<PeerInfo> = (0u16..10)
642 .map(|i| peer(6881 + i, 1_000 - u64::from(i) * 100, true))
643 .collect();
644
645 let mut choker = Choker::new(1);
647 let decision = choker.decide(&peers);
648 assert_eq!(
649 decision.to_unchoke.len(),
650 2,
651 "cap=1 must yield 1 regular + 1 optimistic"
652 );
653
654 choker.set_unchoke_slots(3);
656 let decision = choker.decide(&peers);
657 assert_eq!(
658 decision.to_unchoke.len(),
659 4,
660 "cap=3 must yield 3 regular + 1 optimistic"
661 );
662
663 choker.set_unchoke_slots(8);
665 let decision = choker.decide(&peers);
666 assert_eq!(
667 decision.to_unchoke.len(),
668 9,
669 "cap=8 must yield 8 regular + 1 optimistic"
670 );
671 }
672
673 #[test]
674 fn seed_mode_unchokes_by_upload_rate() {
675 let mut choker = Choker::new(2);
676 choker.set_seed_mode(true);
677
678 let peers = vec![
680 seed_peer(6881, 100, true),
681 seed_peer(6882, 500, true),
682 seed_peer(6883, 300, true),
683 seed_peer(6884, 200, true),
684 ];
685
686 let decision = choker.decide(&peers);
687
688 assert!(decision.to_unchoke.contains(&addr(6882)));
690 assert!(decision.to_unchoke.contains(&addr(6883)));
691
692 assert_eq!(decision.to_unchoke.len(), 3);
694 }
695
696 #[test]
697 fn upload_only_excluded_from_optimistic() {
698 let mut choker = Choker::new(2);
699 let peers = vec![
700 peer(6881, 500, true),
701 peer(6882, 400, true),
702 PeerInfo {
704 addr: addr(6883),
705 download_rate: 100,
706 upload_rate: 0,
707 interested: true,
708 upload_only: true,
709 is_seed: false,
710 },
711 PeerInfo {
712 addr: addr(6884),
713 download_rate: 50,
714 upload_rate: 0,
715 interested: true,
716 upload_only: true,
717 is_seed: false,
718 },
719 ];
720
721 let decision = choker.decide(&peers);
722
723 assert_eq!(decision.to_unchoke.len(), 2);
726 assert!(decision.to_unchoke.contains(&addr(6881)));
727 assert!(decision.to_unchoke.contains(&addr(6882)));
728
729 assert!(decision.to_choke.contains(&addr(6883)));
731 assert!(decision.to_choke.contains(&addr(6884)));
732 }
733
734 #[test]
735 fn upload_only_still_regular_unchoked() {
736 let mut choker = Choker::new(2);
738 choker.set_seed_mode(true);
739
740 let peers = vec![
741 PeerInfo {
742 addr: addr(6881),
743 download_rate: 0,
744 upload_rate: 500,
745 interested: true,
746 upload_only: true, is_seed: false,
748 },
749 seed_peer(6882, 300, true),
750 seed_peer(6883, 100, true),
751 ];
752
753 let decision = choker.decide(&peers);
754
755 assert!(decision.to_unchoke.contains(&addr(6881)));
757 assert!(decision.to_unchoke.contains(&addr(6882)));
758 }
759
760 #[test]
765 fn seed_choking_algorithm_default() {
766 assert_eq!(
767 SeedChokingAlgorithm::default(),
768 SeedChokingAlgorithm::FastestUpload
769 );
770 }
771
772 #[test]
773 fn choking_algorithm_default() {
774 assert_eq!(ChokingAlgorithm::default(), ChokingAlgorithm::FixedSlots);
775 }
776
777 #[test]
778 fn seed_choking_algorithm_serde_round_trip() {
779 for variant in [
780 SeedChokingAlgorithm::FastestUpload,
781 SeedChokingAlgorithm::RoundRobin,
782 SeedChokingAlgorithm::AntiLeech,
783 ] {
784 let json = serde_json::to_string(&variant).unwrap();
785 let decoded: SeedChokingAlgorithm = serde_json::from_str(&json).unwrap();
786 assert_eq!(decoded, variant);
787 }
788 }
789
790 #[test]
791 fn choking_algorithm_serde_round_trip() {
792 for variant in [ChokingAlgorithm::FixedSlots, ChokingAlgorithm::RateBased] {
793 let json = serde_json::to_string(&variant).unwrap();
794 let decoded: ChokingAlgorithm = serde_json::from_str(&json).unwrap();
795 assert_eq!(decoded, variant);
796 }
797 }
798
799 #[test]
804 fn fixed_slots_strategy_leech_mode() {
805 let mut strategy = FixedSlotsStrategy::new(SeedChokingAlgorithm::FastestUpload);
806 let peers = vec![
807 peer(6881, 100, true),
808 peer(6882, 500, true),
809 peer(6883, 300, true),
810 peer(6884, 200, true),
811 peer(6885, 400, true),
812 peer(6886, 50, true),
813 ];
814
815 let decision = strategy.decide(&peers, 4, false);
816
817 assert!(decision.to_unchoke.contains(&addr(6882)));
819 assert!(decision.to_unchoke.contains(&addr(6885)));
820 assert!(decision.to_unchoke.contains(&addr(6883)));
821 assert!(decision.to_unchoke.contains(&addr(6884)));
822
823 assert_eq!(decision.to_unchoke.len(), 5);
825 assert_eq!(decision.to_choke.len(), 1);
826 }
827
828 #[test]
829 fn fixed_slots_round_robin_rotates() {
830 let mut strategy = FixedSlotsStrategy::new(SeedChokingAlgorithm::RoundRobin);
831 let peers = vec![
833 seed_peer(6881, 100, true),
834 seed_peer(6882, 200, true),
835 seed_peer(6883, 300, true),
836 seed_peer(6884, 400, true),
837 seed_peer(6885, 500, true),
838 ];
839
840 let d1 = strategy.decide(&peers, 2, true);
841 let d2 = strategy.decide(&peers, 2, true);
842
843 let set1 = d1.to_unchoke;
848 let set2 = d2.to_unchoke;
849 assert_ne!(set1, set2, "round-robin should rotate unchoke set");
850 }
851
852 #[test]
853 fn fixed_slots_anti_leech_prefers_non_seeds() {
854 let mut strategy = FixedSlotsStrategy::new(SeedChokingAlgorithm::AntiLeech);
855 let peers = vec![
856 PeerInfo {
858 addr: addr(6881),
859 download_rate: 0,
860 upload_rate: 500,
861 interested: true,
862 upload_only: false,
863 is_seed: true,
864 },
865 PeerInfo {
866 addr: addr(6882),
867 download_rate: 0,
868 upload_rate: 400,
869 interested: true,
870 upload_only: false,
871 is_seed: true,
872 },
873 PeerInfo {
875 addr: addr(6883),
876 download_rate: 0,
877 upload_rate: 100,
878 interested: true,
879 upload_only: false,
880 is_seed: false,
881 },
882 PeerInfo {
883 addr: addr(6884),
884 download_rate: 0,
885 upload_rate: 50,
886 interested: true,
887 upload_only: false,
888 is_seed: false,
889 },
890 ];
891
892 let decision = strategy.decide(&peers, 2, true);
894
895 assert!(
897 decision.to_unchoke.contains(&addr(6883)),
898 "non-seed peer 6883 should be unchoked"
899 );
900 assert!(
901 decision.to_unchoke.contains(&addr(6884)),
902 "non-seed peer 6884 should be unchoked"
903 );
904 }
905
906 #[test]
911 fn rate_based_starts_at_min_slots() {
912 let strategy = RateBasedStrategy::new(
913 SeedChokingAlgorithm::FastestUpload,
914 0, 3, 10, );
918 assert_eq!(strategy.current_slots(), 3);
920
921 let strategy2 = RateBasedStrategy::new(
923 SeedChokingAlgorithm::FastestUpload,
924 0,
925 1, 10,
927 );
928 assert_eq!(strategy2.current_slots(), 2);
929 }
930
931 #[test]
932 fn rate_based_increases_slots_on_throughput_increase() {
933 let mut strategy = RateBasedStrategy::new(
934 SeedChokingAlgorithm::FastestUpload,
935 0, 2, 10, );
939 assert_eq!(strategy.current_slots(), 2);
940
941 strategy.observe_throughput_inner(1000);
943 assert_eq!(strategy.current_slots(), 2);
944
945 strategy.observe_throughput_inner(1500);
947 assert_eq!(strategy.current_slots(), 3);
948 }
949
950 #[test]
951 fn rate_based_decreases_slots_on_throughput_drop() {
952 let mut strategy = RateBasedStrategy::new(
953 SeedChokingAlgorithm::FastestUpload,
954 0, 2, 10, );
958
959 strategy.observe_throughput_inner(1000);
961 strategy.observe_throughput_inner(2000);
962 assert_eq!(strategy.current_slots(), 3);
963 strategy.observe_throughput_inner(3000);
964 assert_eq!(strategy.current_slots(), 4);
965
966 strategy.observe_throughput_inner(2000);
968 assert_eq!(strategy.current_slots(), 3);
969 }
970
971 #[test]
972 fn rate_based_respects_min_max() {
973 let mut strategy = RateBasedStrategy::new(
974 SeedChokingAlgorithm::FastestUpload,
975 0, 2, 3, );
979
980 strategy.observe_throughput_inner(1000);
982 strategy.observe_throughput_inner(2000);
983 assert_eq!(strategy.current_slots(), 3);
984
985 strategy.observe_throughput_inner(5000);
987 assert_eq!(strategy.current_slots(), 3);
988
989 strategy.observe_throughput_inner(1000);
991 assert_eq!(strategy.current_slots(), 2);
992
993 strategy.observe_throughput_inner(100);
995 assert_eq!(strategy.current_slots(), 2);
996 }
997
998 #[test]
999 fn rate_based_does_not_add_at_capacity() {
1000 let mut strategy = RateBasedStrategy::new(
1001 SeedChokingAlgorithm::FastestUpload,
1002 10_000, 2,
1004 10,
1005 );
1006
1007 strategy.observe_throughput_inner(5_000);
1009 assert_eq!(strategy.current_slots(), 2);
1010
1011 strategy.observe_throughput_inner(9_500);
1013 assert_eq!(strategy.current_slots(), 2);
1018 }
1019
1020 #[test]
1021 fn rate_based_ignores_external_unchoke_slots() {
1022 let mut strategy = RateBasedStrategy::new(SeedChokingAlgorithm::FastestUpload, 0, 2, 10);
1023
1024 let peers = vec![
1025 peer(6881, 500, true),
1026 peer(6882, 400, true),
1027 peer(6883, 300, true),
1028 peer(6884, 200, true),
1029 peer(6885, 100, true),
1030 ];
1031
1032 let decision = strategy.decide(&peers, 10, false);
1034
1035 assert_eq!(decision.to_unchoke.len(), 3);
1038 }
1039
1040 #[test]
1045 fn choker_with_round_robin() {
1046 let mut choker = Choker::with_algorithms(
1047 2,
1048 SeedChokingAlgorithm::RoundRobin,
1049 ChokingAlgorithm::FixedSlots,
1050 0,
1051 2,
1052 10,
1053 );
1054 choker.set_seed_mode(true);
1055
1056 let peers = vec![
1057 seed_peer(6881, 100, true),
1058 seed_peer(6882, 200, true),
1059 seed_peer(6883, 300, true),
1060 seed_peer(6884, 400, true),
1061 seed_peer(6885, 500, true),
1062 ];
1063
1064 let d1 = choker.decide(&peers);
1065 let d2 = choker.decide(&peers);
1066
1067 let set1 = d1.to_unchoke;
1069 let set2 = d2.to_unchoke;
1070 assert_ne!(set1, set2, "round-robin dispatcher should rotate");
1071 }
1072
1073 #[test]
1074 fn choker_with_rate_based() {
1075 let mut choker = Choker::with_algorithms(
1076 4, SeedChokingAlgorithm::FastestUpload,
1078 ChokingAlgorithm::RateBased,
1079 0,
1080 2,
1081 10,
1082 );
1083 assert_eq!(choker.choking_algorithm(), ChokingAlgorithm::RateBased);
1084
1085 let peers = vec![
1086 peer(6881, 500, true),
1087 peer(6882, 400, true),
1088 peer(6883, 300, true),
1089 peer(6884, 200, true),
1090 peer(6885, 100, true),
1091 ];
1092
1093 let decision = choker.decide(&peers);
1095 assert_eq!(decision.to_unchoke.len(), 3);
1096
1097 choker.observe_throughput(1000);
1099 choker.observe_throughput(2000);
1100 let decision = choker.decide(&peers);
1102 assert_eq!(decision.to_unchoke.len(), 4); }
1104
1105 #[test]
1106 fn choker_unchoke_slots_getter() {
1107 let mut choker = Choker::new(4);
1108 assert_eq!(choker.unchoke_slots(), 4);
1109
1110 choker.set_unchoke_slots(7);
1111 assert_eq!(choker.unchoke_slots(), 7);
1112
1113 choker.set_unchoke_slots(0);
1114 assert_eq!(choker.unchoke_slots(), 0);
1115 }
1116
1117 #[test]
1118 fn choker_new_is_backward_compatible() {
1119 let mut choker = Choker::new(4);
1120 assert_eq!(choker.choking_algorithm(), ChokingAlgorithm::FixedSlots);
1121
1122 let peers = vec![
1123 peer(6881, 500, true),
1124 peer(6882, 400, true),
1125 peer(6883, 300, true),
1126 peer(6884, 200, true),
1127 peer(6885, 100, true),
1128 peer(6886, 50, true),
1129 ];
1130
1131 let decision = choker.decide(&peers);
1132
1133 assert_eq!(decision.to_unchoke.len(), 5);
1135 assert!(decision.to_unchoke.contains(&addr(6882)));
1136 assert!(decision.to_unchoke.contains(&addr(6885)));
1137 assert!(decision.to_unchoke.contains(&addr(6883)));
1138 assert!(decision.to_unchoke.contains(&addr(6884)));
1139
1140 choker.set_seed_mode(true);
1142 choker.set_unchoke_slots(2);
1143 let decision = choker.decide(&peers);
1144 assert_eq!(decision.to_unchoke.len(), 3); }
1146}