1use super::{PeerId, PieceId};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum PieceSelectionStrategy {
13 #[default]
15 RarestFirst,
16
17 Sequential,
19
20 Random,
22
23 Adaptive,
25
26 Endgame,
28}
29
30#[derive(Debug, Clone)]
32pub struct SwarmStrategy {
33 pub piece_selection: PieceSelectionStrategy,
35
36 pub peer_selection: PeerSelectionPreferences,
38
39 pub request_timing: RequestTimingStrategy,
41
42 pub redundancy_management: RedundancyStrategy,
44
45 pub adaptation_config: AdaptationConfig,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct PeerSelectionPreferences {
52 pub prefer_fast_peers: bool,
54
55 pub prefer_low_latency: bool,
57
58 pub prefer_reliable_peers: bool,
60
61 pub load_balancing: LoadBalancingStrategy,
63
64 pub max_requests_per_peer: u32,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum LoadBalancingStrategy {
71 RoundRobin,
73
74 WeightedRandom,
76
77 LeastLoaded,
79
80 FastestFirst,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct RequestTimingStrategy {
87 pub pipeline_depth: u32,
89
90 pub request_timeout: std::time::Duration,
92
93 pub retry_strategy: RetryStrategy,
95
96 pub scheduling: RequestScheduling,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub enum RetryStrategy {
103 None,
105
106 Fixed { max_retries: u32 },
108
109 ExponentialBackoff {
111 max_retries: u32,
112 initial_delay: std::time::Duration,
113 max_delay: std::time::Duration,
114 },
115
116 Adaptive {
118 max_retries: u32,
119 success_rate_threshold: f64,
120 },
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub enum RequestScheduling {
126 FIFO,
128
129 Priority,
131
132 EarliestDeadlineFirst,
134
135 ShortestJobFirst,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RedundancyStrategy {
142 pub target_redundancy: f64,
144
145 pub duplicate_requests: DuplicateRequestStrategy,
147
148 pub repair_strategy: RepairStrategy,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub enum DuplicateRequestStrategy {
155 None,
157
158 CriticalPiecesOnly,
160
161 Endgame,
163
164 AdaptiveByRarity { rarity_threshold: u32 },
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub enum RepairStrategy {
171 ReRequest,
173
174 ErrorCorrection,
176
177 Hybrid,
179}
180
181#[derive(Debug, Clone)]
183pub struct AdaptationConfig {
184 pub monitoring_window: std::time::Duration,
186
187 pub switching_thresholds: SwitchingThresholds,
189
190 pub learning_rate: f64,
192
193 pub stability_period: std::time::Duration,
195}
196
197#[derive(Debug, Clone)]
199pub struct SwitchingThresholds {
200 pub min_download_speed: f64,
202
203 pub max_latency: std::time::Duration,
205
206 pub min_peer_availability: f64,
208
209 pub max_failure_rate: f64,
211}
212
213impl Default for SwarmStrategy {
214 fn default() -> Self {
215 Self {
216 piece_selection: PieceSelectionStrategy::RarestFirst,
217 peer_selection: PeerSelectionPreferences::default(),
218 request_timing: RequestTimingStrategy::default(),
219 redundancy_management: RedundancyStrategy::default(),
220 adaptation_config: AdaptationConfig::default(),
221 }
222 }
223}
224
225impl Default for PeerSelectionPreferences {
226 fn default() -> Self {
227 Self {
228 prefer_fast_peers: true,
229 prefer_low_latency: true,
230 prefer_reliable_peers: true,
231 load_balancing: LoadBalancingStrategy::WeightedRandom,
232 max_requests_per_peer: 4,
233 }
234 }
235}
236
237impl Default for RequestTimingStrategy {
238 fn default() -> Self {
239 Self {
240 pipeline_depth: 4,
241 request_timeout: std::time::Duration::from_secs(30),
242 retry_strategy: RetryStrategy::Fixed { max_retries: 3 },
243 scheduling: RequestScheduling::Priority,
244 }
245 }
246}
247
248impl Default for RedundancyStrategy {
249 fn default() -> Self {
250 Self {
251 target_redundancy: 1.5,
252 duplicate_requests: DuplicateRequestStrategy::CriticalPiecesOnly,
253 repair_strategy: RepairStrategy::Hybrid,
254 }
255 }
256}
257
258impl Default for AdaptationConfig {
259 fn default() -> Self {
260 Self {
261 monitoring_window: std::time::Duration::from_secs(60),
262 switching_thresholds: SwitchingThresholds::default(),
263 learning_rate: 0.1,
264 stability_period: std::time::Duration::from_secs(30),
265 }
266 }
267}
268
269impl Default for SwitchingThresholds {
270 fn default() -> Self {
271 Self {
272 min_download_speed: 100_000.0, max_latency: std::time::Duration::from_secs(5),
274 min_peer_availability: 0.3,
275 max_failure_rate: 0.2,
276 }
277 }
278}
279
280#[derive(Debug)]
282pub struct AdaptiveStrategyEngine {
283 current_strategy: PieceSelectionStrategy,
285
286 performance_history: Vec<StrategyPerformance>,
288
289 config: AdaptationConfig,
291
292 last_change: std::time::Instant,
294
295 strategy_scores: HashMap<PieceSelectionStrategy, f64>,
297}
298
299#[derive(Debug, Clone)]
301pub struct StrategyPerformance {
302 pub strategy: PieceSelectionStrategy,
304
305 pub time_period: std::time::Duration,
307
308 pub avg_download_speed: f64,
310
311 pub avg_latency: std::time::Duration,
313
314 pub success_rate: f64,
316
317 pub efficiency_score: f64,
319
320 pub timestamp: std::time::Instant,
322}
323
324#[derive(Debug)]
326pub struct PieceSelectionContext {
327 pub piece_redundancy: HashMap<PieceId, u32>,
329
330 pub active_peers: HashMap<PeerId, PeerInfo>,
332
333 pub transfer_progress: f64,
335
336 pub estimated_time_remaining: Option<std::time::Duration>,
338
339 pub current_performance: Option<StrategyPerformance>,
341}
342
343#[derive(Debug, Clone)]
345pub struct PeerInfo {
346 pub quality_score: f64,
348
349 pub active_requests: u32,
351
352 pub available_pieces: HashSet<PieceId>,
354
355 pub recent_response_time: std::time::Duration,
357
358 pub reliability: f64,
360}
361
362impl AdaptiveStrategyEngine {
363 pub fn new(config: AdaptationConfig) -> Self {
365 let mut strategy_scores = HashMap::new();
366 strategy_scores.insert(PieceSelectionStrategy::RarestFirst, 0.8);
367 strategy_scores.insert(PieceSelectionStrategy::Sequential, 0.6);
368 strategy_scores.insert(PieceSelectionStrategy::Random, 0.4);
369 strategy_scores.insert(PieceSelectionStrategy::Adaptive, 0.9);
370
371 Self {
372 current_strategy: PieceSelectionStrategy::RarestFirst,
373 performance_history: Vec::new(),
374 config,
375 last_change: std::time::Instant::now(),
376 strategy_scores,
377 }
378 }
379
380 pub fn select_strategy(&mut self, context: &PieceSelectionContext) -> PieceSelectionStrategy {
382 if self.last_change.elapsed() < self.config.stability_period {
384 return self.current_strategy;
385 }
386
387 let current_performance = self.analyze_current_performance(context);
389
390 if self.should_change_strategy(¤t_performance) {
392 let new_strategy = self.choose_best_strategy(context, ¤t_performance);
393 if new_strategy != self.current_strategy {
394 self.change_strategy(new_strategy);
395 }
396 }
397
398 self.current_strategy
399 }
400
401 pub fn record_performance(&mut self, performance: StrategyPerformance) {
403 self.performance_history.push(performance.clone());
404
405 let score = self.calculate_performance_score(&performance);
407 if let Some(current_score) = self.strategy_scores.get_mut(&performance.strategy) {
408 *current_score = (*current_score * (1.0 - self.config.learning_rate))
409 + (score * self.config.learning_rate);
410 }
411
412 if self.performance_history.len() > 100 {
414 self.performance_history.remove(0);
415 }
416 }
417
418 pub fn select_pieces(
420 &self,
421 context: &PieceSelectionContext,
422 max_pieces: usize,
423 ) -> Vec<PieceId> {
424 match self.current_strategy {
425 PieceSelectionStrategy::RarestFirst => self.select_rarest_first(context, max_pieces),
426 PieceSelectionStrategy::Sequential => self.select_sequential(context, max_pieces),
427 PieceSelectionStrategy::Random => self.select_random(context, max_pieces),
428 PieceSelectionStrategy::Adaptive => {
429 self.select_adaptive(context, max_pieces)
431 }
432 PieceSelectionStrategy::Endgame => self.select_endgame(context, max_pieces),
433 }
434 }
435
436 fn select_rarest_first(
438 &self,
439 context: &PieceSelectionContext,
440 max_pieces: usize,
441 ) -> Vec<PieceId> {
442 let mut pieces_by_rarity: Vec<(PieceId, u32)> = context
443 .piece_redundancy
444 .iter()
445 .map(|(piece_id, redundancy)| (*piece_id, *redundancy))
446 .collect();
447
448 pieces_by_rarity.sort_by_key(|(_, redundancy)| *redundancy);
450
451 pieces_by_rarity
452 .into_iter()
453 .take(max_pieces)
454 .map(|(piece_id, _)| piece_id)
455 .collect()
456 }
457
458 fn select_sequential(
460 &self,
461 context: &PieceSelectionContext,
462 max_pieces: usize,
463 ) -> Vec<PieceId> {
464 let mut available_pieces: Vec<PieceId> = context.piece_redundancy.keys().copied().collect();
465 available_pieces.sort_by_key(|piece_id| piece_id.as_u64());
466
467 available_pieces.into_iter().take(max_pieces).collect()
468 }
469
470 fn select_random(&self, context: &PieceSelectionContext, max_pieces: usize) -> Vec<PieceId> {
472 use std::collections::hash_map::DefaultHasher;
473 use std::hash::{Hash, Hasher};
474
475 let mut available_pieces: Vec<PieceId> = context.piece_redundancy.keys().copied().collect();
476
477 available_pieces.sort_by_key(|piece_id| {
479 let mut hasher = DefaultHasher::new();
480 piece_id.hash(&mut hasher);
481 hasher.finish()
482 });
483
484 available_pieces.into_iter().take(max_pieces).collect()
485 }
486
487 fn select_adaptive(&self, context: &PieceSelectionContext, max_pieces: usize) -> Vec<PieceId> {
489 let half = max_pieces / 2;
490
491 let mut selected = self.select_rarest_first(context, half);
493 let remaining = max_pieces - selected.len();
494
495 if remaining > 0 {
496 let sequential = self.select_sequential(context, remaining);
497 for piece_id in sequential {
498 if selected.len() >= max_pieces {
499 break;
500 }
501 if !selected.contains(&piece_id) {
502 selected.push(piece_id);
503 }
504 }
505 }
506
507 selected
508 }
509
510 fn select_endgame(&self, context: &PieceSelectionContext, max_pieces: usize) -> Vec<PieceId> {
512 context
514 .piece_redundancy
515 .keys()
516 .take(max_pieces)
517 .copied()
518 .collect()
519 }
520
521 fn analyze_current_performance(&self, context: &PieceSelectionContext) -> StrategyPerformance {
523 StrategyPerformance {
525 strategy: self.current_strategy,
526 time_period: self.config.monitoring_window,
527 avg_download_speed: context
528 .current_performance
529 .as_ref()
530 .map_or(1_000_000.0, |p| p.avg_download_speed),
531 avg_latency: context
532 .current_performance
533 .as_ref()
534 .map_or(std::time::Duration::from_millis(100), |p| p.avg_latency),
535 success_rate: context
536 .current_performance
537 .as_ref()
538 .map_or(0.9, |p| p.success_rate),
539 efficiency_score: context
540 .current_performance
541 .as_ref()
542 .map_or(0.8, |p| p.efficiency_score),
543 timestamp: std::time::Instant::now(),
544 }
545 }
546
547 fn should_change_strategy(&self, performance: &StrategyPerformance) -> bool {
549 let thresholds = &self.config.switching_thresholds;
550
551 performance.avg_download_speed < thresholds.min_download_speed
552 || performance.avg_latency > thresholds.max_latency
553 || performance.success_rate < (1.0 - thresholds.max_failure_rate)
554 }
555
556 fn choose_best_strategy(
558 &self,
559 context: &PieceSelectionContext,
560 _current_performance: &StrategyPerformance,
561 ) -> PieceSelectionStrategy {
562 if context.transfer_progress > 0.9 {
564 return PieceSelectionStrategy::Endgame;
565 }
566
567 self.strategy_scores
569 .iter()
570 .filter(|(strategy, _)| **strategy != PieceSelectionStrategy::Adaptive)
571 .max_by(|(_, score_a), (_, score_b)| {
572 score_a
573 .partial_cmp(score_b)
574 .unwrap_or(std::cmp::Ordering::Equal)
575 })
576 .map_or(PieceSelectionStrategy::RarestFirst, |(strategy, _)| {
577 *strategy
578 })
579 }
580
581 fn change_strategy(&mut self, new_strategy: PieceSelectionStrategy) {
583 self.current_strategy = new_strategy;
584 self.last_change = std::time::Instant::now();
585 }
586
587 fn calculate_performance_score(&self, performance: &StrategyPerformance) -> f64 {
589 let speed_score = (performance.avg_download_speed / 1_000_000.0).min(1.0);
590 let latency_score =
591 (1.0 / (performance.avg_latency.as_millis() as f64 / 1000.0 + 1.0)).min(1.0);
592 let success_score = performance.success_rate;
593 let efficiency_score = performance.efficiency_score;
594
595 (speed_score * 0.3 + latency_score * 0.2 + success_score * 0.3 + efficiency_score * 0.2)
596 .clamp(0.0, 1.0)
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603 use std::collections::HashMap;
604
605 fn create_test_context() -> PieceSelectionContext {
606 let mut piece_redundancy = HashMap::new();
607 piece_redundancy.insert(PieceId::new(0), 1); piece_redundancy.insert(PieceId::new(1), 3); piece_redundancy.insert(PieceId::new(2), 2); let mut active_peers = HashMap::new();
612 active_peers.insert(
613 PeerId::new("peer1"),
614 PeerInfo {
615 quality_score: 0.8,
616 active_requests: 2,
617 available_pieces: [PieceId::new(0), PieceId::new(1)].iter().copied().collect(),
618 recent_response_time: std::time::Duration::from_millis(100),
619 reliability: 0.9,
620 },
621 );
622
623 PieceSelectionContext {
624 piece_redundancy,
625 active_peers,
626 transfer_progress: 0.5,
627 estimated_time_remaining: Some(std::time::Duration::from_secs(300)),
628 current_performance: None,
629 }
630 }
631
632 #[test]
633 fn test_adaptive_strategy_engine_creation() {
634 let config = AdaptationConfig::default();
635 let engine = AdaptiveStrategyEngine::new(config);
636
637 assert_eq!(engine.current_strategy, PieceSelectionStrategy::RarestFirst);
638 assert!(!engine.strategy_scores.is_empty());
639 }
640
641 #[test]
642 fn test_rarest_first_selection() {
643 let config = AdaptationConfig::default();
644 let engine = AdaptiveStrategyEngine::new(config);
645 let context = create_test_context();
646
647 let selected = engine.select_rarest_first(&context, 2);
648 assert_eq!(selected.len(), 2);
649 assert_eq!(selected[0], PieceId::new(0));
651 }
652
653 #[test]
654 fn test_sequential_selection() {
655 let config = AdaptationConfig::default();
656 let engine = AdaptiveStrategyEngine::new(config);
657 let context = create_test_context();
658
659 let selected = engine.select_sequential(&context, 2);
660 assert_eq!(selected.len(), 2);
661 assert_eq!(selected[0], PieceId::new(0));
663 assert_eq!(selected[1], PieceId::new(1));
664 }
665
666 #[test]
667 fn test_strategy_scoring() {
668 let config = AdaptationConfig::default();
669 let engine = AdaptiveStrategyEngine::new(config);
670
671 let performance = StrategyPerformance {
672 strategy: PieceSelectionStrategy::RarestFirst,
673 time_period: std::time::Duration::from_secs(60),
674 avg_download_speed: 2_000_000.0,
675 avg_latency: std::time::Duration::from_millis(50),
676 success_rate: 0.95,
677 efficiency_score: 0.9,
678 timestamp: std::time::Instant::now(),
679 };
680
681 let score = engine.calculate_performance_score(&performance);
682 assert!(score > 0.5); assert!(score <= 1.0);
684 }
685
686 #[test]
687 fn test_endgame_detection() {
688 let config = AdaptationConfig::default();
689 let mut engine = AdaptiveStrategyEngine::new(config);
690
691 let mut context = create_test_context();
692 context.transfer_progress = 0.95; let _strategy = engine.select_strategy(&context);
695 }
698
699 #[test]
700 fn test_piece_selection_strategy_serialization() {
701 let strategy = PieceSelectionStrategy::RarestFirst;
702 let serialized = serde_json::to_string(&strategy).unwrap();
703 let deserialized: PieceSelectionStrategy = serde_json::from_str(&serialized).unwrap();
704 assert_eq!(strategy, deserialized);
705 }
706}