Skip to main content

asupersync/atp/swarm/
strategy.rs

1//! ATP Swarm Strategy - Piece selection strategies and adaptive algorithms.
2//!
3//! Implements various piece selection strategies for optimal download performance,
4//! including rarest-first, sequential, and adaptive strategies.
5
6use super::{PeerId, PieceId};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10/// Piece selection strategy enumeration.
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum PieceSelectionStrategy {
13    /// Prioritize rarest pieces first to maximize swarm efficiency
14    #[default]
15    RarestFirst,
16
17    /// Download pieces in sequential order
18    Sequential,
19
20    /// Random piece selection
21    Random,
22
23    /// Adaptive strategy that switches based on conditions
24    Adaptive,
25
26    /// Endgame strategy for final pieces
27    Endgame,
28}
29
30/// Comprehensive swarm strategy that encompasses multiple decision-making aspects.
31#[derive(Debug, Clone)]
32pub struct SwarmStrategy {
33    /// Current piece selection strategy
34    pub piece_selection: PieceSelectionStrategy,
35
36    /// Peer selection preferences
37    pub peer_selection: PeerSelectionPreferences,
38
39    /// Request timing strategy
40    pub request_timing: RequestTimingStrategy,
41
42    /// Redundancy management strategy
43    pub redundancy_management: RedundancyStrategy,
44
45    /// Adaptation parameters
46    pub adaptation_config: AdaptationConfig,
47}
48
49/// Peer selection preferences for the swarm.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct PeerSelectionPreferences {
52    /// Prefer peers with higher download speeds
53    pub prefer_fast_peers: bool,
54
55    /// Prefer peers with lower latency
56    pub prefer_low_latency: bool,
57
58    /// Prefer peers with higher reliability
59    pub prefer_reliable_peers: bool,
60
61    /// Load balancing strategy
62    pub load_balancing: LoadBalancingStrategy,
63
64    /// Maximum requests per peer
65    pub max_requests_per_peer: u32,
66}
67
68/// Load balancing strategies for distributing requests.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum LoadBalancingStrategy {
71    /// Distribute requests evenly across peers
72    RoundRobin,
73
74    /// Weighted distribution based on peer quality
75    WeightedRandom,
76
77    /// Least loaded first
78    LeastLoaded,
79
80    /// Fastest peer first
81    FastestFirst,
82}
83
84/// Request timing and pipelining strategy.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct RequestTimingStrategy {
87    /// Pipeline depth (number of outstanding requests)
88    pub pipeline_depth: u32,
89
90    /// Request timeout duration
91    pub request_timeout: std::time::Duration,
92
93    /// Retry strategy
94    pub retry_strategy: RetryStrategy,
95
96    /// Request scheduling algorithm
97    pub scheduling: RequestScheduling,
98}
99
100/// Retry strategies for failed requests.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub enum RetryStrategy {
103    /// No retries
104    None,
105
106    /// Fixed number of retries
107    Fixed { max_retries: u32 },
108
109    /// Exponential backoff
110    ExponentialBackoff {
111        max_retries: u32,
112        initial_delay: std::time::Duration,
113        max_delay: std::time::Duration,
114    },
115
116    /// Adaptive retry based on peer performance
117    Adaptive {
118        max_retries: u32,
119        success_rate_threshold: f64,
120    },
121}
122
123/// Request scheduling algorithms.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub enum RequestScheduling {
126    /// First-in-first-out
127    FIFO,
128
129    /// Priority-based scheduling
130    Priority,
131
132    /// Deadline-based scheduling
133    EarliestDeadlineFirst,
134
135    /// Shortest job first
136    ShortestJobFirst,
137}
138
139/// Redundancy management strategy.
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RedundancyStrategy {
142    /// Target redundancy factor for pieces
143    pub target_redundancy: f64,
144
145    /// Duplicate request strategy
146    pub duplicate_requests: DuplicateRequestStrategy,
147
148    /// Repair strategy for lost pieces
149    pub repair_strategy: RepairStrategy,
150}
151
152/// Strategies for handling duplicate requests.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub enum DuplicateRequestStrategy {
155    /// No duplicate requests
156    None,
157
158    /// Request from multiple peers for critical pieces
159    CriticalPiecesOnly,
160
161    /// Endgame mode - request all remaining pieces from all peers
162    Endgame,
163
164    /// Adaptive duplication based on piece rarity
165    AdaptiveByRarity { rarity_threshold: u32 },
166}
167
168/// Repair strategies for handling piece loss or corruption.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub enum RepairStrategy {
171    /// Re-request from different peer
172    ReRequest,
173
174    /// Use error correction codes (RaptorQ)
175    ErrorCorrection,
176
177    /// Hybrid approach
178    Hybrid,
179}
180
181/// Configuration for adaptive strategy behavior.
182#[derive(Debug, Clone)]
183pub struct AdaptationConfig {
184    /// Performance monitoring window
185    pub monitoring_window: std::time::Duration,
186
187    /// Thresholds for strategy switching
188    pub switching_thresholds: SwitchingThresholds,
189
190    /// Learning rate for adaptation
191    pub learning_rate: f64,
192
193    /// Stability period before allowing strategy changes
194    pub stability_period: std::time::Duration,
195}
196
197/// Thresholds for triggering strategy adaptations.
198#[derive(Debug, Clone)]
199pub struct SwitchingThresholds {
200    /// Download speed threshold (bytes/sec)
201    pub min_download_speed: f64,
202
203    /// Maximum acceptable latency
204    pub max_latency: std::time::Duration,
205
206    /// Minimum peer availability
207    pub min_peer_availability: f64,
208
209    /// Maximum failure rate before switching
210    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, // 100 KB/s
273            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/// Adaptive strategy engine that can switch between different approaches.
281#[derive(Debug)]
282pub struct AdaptiveStrategyEngine {
283    /// Current strategy state
284    current_strategy: PieceSelectionStrategy,
285
286    /// Performance history for decision making
287    performance_history: Vec<StrategyPerformance>,
288
289    /// Adaptation configuration
290    config: AdaptationConfig,
291
292    /// Last strategy change time
293    last_change: std::time::Instant,
294
295    /// Strategy scores for decision making
296    strategy_scores: HashMap<PieceSelectionStrategy, f64>,
297}
298
299/// Performance metrics for a strategy over time.
300#[derive(Debug, Clone)]
301pub struct StrategyPerformance {
302    /// Strategy that was active
303    pub strategy: PieceSelectionStrategy,
304
305    /// Time period for this measurement
306    pub time_period: std::time::Duration,
307
308    /// Average download speed during period
309    pub avg_download_speed: f64,
310
311    /// Average latency during period
312    pub avg_latency: std::time::Duration,
313
314    /// Success rate during period
315    pub success_rate: f64,
316
317    /// Swarm efficiency score
318    pub efficiency_score: f64,
319
320    /// Timestamp of measurement
321    pub timestamp: std::time::Instant,
322}
323
324/// Piece selection context for strategy decision making.
325#[derive(Debug)]
326pub struct PieceSelectionContext {
327    /// Available pieces and their redundancy
328    pub piece_redundancy: HashMap<PieceId, u32>,
329
330    /// Active peer information
331    pub active_peers: HashMap<PeerId, PeerInfo>,
332
333    /// Current transfer progress
334    pub transfer_progress: f64,
335
336    /// Remaining time estimate
337    pub estimated_time_remaining: Option<std::time::Duration>,
338
339    /// Current performance metrics
340    pub current_performance: Option<StrategyPerformance>,
341}
342
343/// Information about a peer relevant to strategy decisions.
344#[derive(Debug, Clone)]
345pub struct PeerInfo {
346    /// Peer quality metrics
347    pub quality_score: f64,
348
349    /// Current load (active requests)
350    pub active_requests: u32,
351
352    /// Available pieces from this peer
353    pub available_pieces: HashSet<PieceId>,
354
355    /// Recent response time
356    pub recent_response_time: std::time::Duration,
357
358    /// Connection reliability
359    pub reliability: f64,
360}
361
362impl AdaptiveStrategyEngine {
363    /// Create a new adaptive strategy engine.
364    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    /// Select optimal strategy based on current context.
381    pub fn select_strategy(&mut self, context: &PieceSelectionContext) -> PieceSelectionStrategy {
382        // Check if enough time has passed since last change
383        if self.last_change.elapsed() < self.config.stability_period {
384            return self.current_strategy;
385        }
386
387        // Analyze current performance
388        let current_performance = self.analyze_current_performance(context);
389
390        // Determine if strategy change is needed
391        if self.should_change_strategy(&current_performance) {
392            let new_strategy = self.choose_best_strategy(context, &current_performance);
393            if new_strategy != self.current_strategy {
394                self.change_strategy(new_strategy);
395            }
396        }
397
398        self.current_strategy
399    }
400
401    /// Record performance data for the current strategy.
402    pub fn record_performance(&mut self, performance: StrategyPerformance) {
403        self.performance_history.push(performance.clone());
404
405        // Update strategy score based on performance
406        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        // Trim history to maintain reasonable size
413        if self.performance_history.len() > 100 {
414            self.performance_history.remove(0);
415        }
416    }
417
418    /// Get recommended piece selection for given context.
419    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                // Adaptive strategy combines multiple approaches
430                self.select_adaptive(context, max_pieces)
431            }
432            PieceSelectionStrategy::Endgame => self.select_endgame(context, max_pieces),
433        }
434    }
435
436    /// Rarest-first piece selection.
437    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        // Sort by rarity (lowest redundancy first)
449        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    /// Sequential piece selection.
459    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    /// Random piece selection.
471    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        // Stable hash ordering gives deterministic spread without global RNG state.
478        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    /// Adaptive piece selection combining multiple strategies.
488    fn select_adaptive(&self, context: &PieceSelectionContext, max_pieces: usize) -> Vec<PieceId> {
489        let half = max_pieces / 2;
490
491        // Combine rarest-first with sequential for balance
492        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    /// Endgame mode piece selection.
511    fn select_endgame(&self, context: &PieceSelectionContext, max_pieces: usize) -> Vec<PieceId> {
512        // In endgame, request all remaining pieces aggressively
513        context
514            .piece_redundancy
515            .keys()
516            .take(max_pieces)
517            .copied()
518            .collect()
519    }
520
521    /// Analyze current performance to determine strategy effectiveness.
522    fn analyze_current_performance(&self, context: &PieceSelectionContext) -> StrategyPerformance {
523        // Use context to build current performance metrics
524        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    /// Determine if strategy should be changed based on performance.
548    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    /// Choose the best strategy for current conditions.
557    fn choose_best_strategy(
558        &self,
559        context: &PieceSelectionContext,
560        _current_performance: &StrategyPerformance,
561    ) -> PieceSelectionStrategy {
562        // Check if we're in endgame phase
563        if context.transfer_progress > 0.9 {
564            return PieceSelectionStrategy::Endgame;
565        }
566
567        // Find strategy with highest score
568        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    /// Change to a new strategy.
582    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    /// Calculate performance score from metrics.
588    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); // Rare piece
608        piece_redundancy.insert(PieceId::new(1), 3); // Common piece
609        piece_redundancy.insert(PieceId::new(2), 2); // Medium rare
610
611        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        // Should select rarest piece first (redundancy 1)
650        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        // Should select in order
662        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); // Should be a good score
683        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; // Near completion
693
694        let _strategy = engine.select_strategy(&context);
695        // Should switch to endgame for transfers > 90% complete
696        // Note: actual behavior depends on timing constraints
697    }
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}