p2p_chat/sync/engine/discovery/
ranking.rs

1//! This module contains logic for ranking mailbox providers based on their
2//! perceived performance and reliability.
3use std::cmp::Ordering;
4
5use libp2p::PeerId;
6
7use super::super::SyncEngine;
8
9impl SyncEngine {
10    /// Ranks a given set of candidate mailbox providers.
11    ///
12    /// Providers are filtered by whether they can be attempted (not backed off)
13    /// and then sorted by their calculated score in descending order.
14    ///
15    /// # Arguments
16    ///
17    /// * `candidates` - An iterator over `PeerId`s of potential mailbox providers.
18    ///
19    /// # Returns
20    ///
21    /// A `Vec` of `PeerId`s, ranked from best to worst.
22    pub(super) fn rank_mailboxes<I>(&self, candidates: I) -> Vec<PeerId>
23    where
24        I: IntoIterator<Item = PeerId>,
25    {
26        let mut providers: Vec<_> = candidates
27            .into_iter()
28            .filter(|peer| self.backoff_manager.can_attempt(peer))
29            .collect();
30
31        providers.sort_by(|a, b| {
32            let score_a = self.calculate_mailbox_score(*a);
33            let score_b = self.calculate_mailbox_score(*b);
34            score_b.partial_cmp(&score_a).unwrap_or(Ordering::Equal)
35        });
36
37        providers
38    }
39
40    /// Calculates a performance score for a single mailbox provider.
41    ///
42    /// The score takes into account:
43    /// - Success rate (70% weight)
44    /// - Recency of last success (20% weight, with bonus for recent success)
45    /// - Average response time (10% weight, bonus for faster responses)
46    /// - Penalty for consecutive failures
47    /// - A significant penalty if the peer is currently in backoff.
48    ///
49    /// The score is clamped between 0.0 and 1.0.
50    ///
51    /// # Arguments
52    ///
53    /// * `peer_id` - The `PeerId` of the mailbox provider.
54    ///
55    /// # Returns
56    ///
57    /// A `f64` representing the performance score of the mailbox.
58    fn calculate_mailbox_score(&self, peer_id: PeerId) -> f64 {
59        let mut score = 0.5;
60
61        if let Some(perf) = self.mailbox_performance.get(&peer_id) {
62            let total_attempts = perf.success_count + perf.failure_count;
63
64            if total_attempts > 0 {
65                let success_rate = perf.success_count as f64 / total_attempts as f64;
66                score = success_rate * 0.7;
67
68                if let Some(last_success) = perf.last_success {
69                    let age_hours = last_success.elapsed().as_secs() as f64 / 3600.0;
70                    let recency_bonus = (1.0 / (1.0 + age_hours)).min(0.3);
71                    score += recency_bonus * 0.2;
72                }
73
74                let response_ms = perf.avg_response_time.as_millis() as f64;
75                let speed_score = (3000.0 - response_ms.min(3000.0)) / 3000.0;
76                score += speed_score * 0.1;
77
78                let failure_penalty = (perf.consecutive_failures as f64 * 0.1).min(0.3);
79                score -= failure_penalty;
80            }
81        }
82
83        if !self.backoff_manager.can_attempt(&peer_id) {
84            score *= 0.1;
85        }
86
87        score.clamp(0.0, 1.0)
88    }
89}