use serde::{Deserialize, Serialize};
pub const CRITICAL_RELAY_BATTERY_LEVEL: u8 = 15;
#[derive(Debug, Clone)]
pub struct RelayConfig {
pub min_battery_for_relay: u8,
pub allow_relay: bool,
pub relay_priority: RelayPriority,
}
impl Default for RelayConfig {
fn default() -> Self {
Self {
min_battery_for_relay: 30,
allow_relay: true,
relay_priority: RelayPriority::Auto,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RelayPriority {
Never,
Auto,
Always,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RelayRole {
Regular,
Relay,
}
#[derive(Debug, Clone)]
pub struct RelayInfo {
pub connection_count: usize,
pub battery_level: u8,
pub is_charging: bool,
pub role: RelayRole,
pub link_quality: u8,
pub queue_depth: usize,
pub congestion_level: f32,
}
pub struct RelayManager;
impl RelayManager {
pub fn new() -> Self {
Self
}
pub fn calculate_relay_score(&self, relay: &RelayInfo) -> f32 {
let mut score = 0.0;
let connection_score = (relay.connection_count as f32 / 10.0 * 30.0).min(30.0);
score += connection_score;
let battery_score = relay.battery_level as f32 / 100.0 * 20.0;
score += battery_score;
if relay.is_charging {
score += 20.0;
}
let link_score = relay.link_quality as f32 / 100.0 * 20.0;
score += link_score;
let congestion_penalty = relay.congestion_level * 15.0;
score -= congestion_penalty;
let queue_penalty = (relay.queue_depth as f32 / 50.0 * 15.0).min(15.0);
score -= queue_penalty;
score.max(0.0)
}
pub fn select_best_relays(
&self,
mut candidates: Vec<RelayInfo>,
count: usize,
max_congestion_level: f32,
) -> Vec<RelayInfo> {
candidates.retain(|relay| relay.congestion_level < max_congestion_level);
candidates.sort_by(|a, b| {
let score_a = self.calculate_relay_score(a);
let score_b = self.calculate_relay_score(b);
score_b
.partial_cmp(&score_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
candidates.into_iter().take(count).collect()
}
}
impl Default for RelayManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relay_scoring() {
let manager = RelayManager::new();
let good_relay = RelayInfo {
connection_count: 8,
battery_level: 80,
is_charging: true,
role: RelayRole::Relay,
link_quality: 90,
queue_depth: 5,
congestion_level: 0.1,
};
let poor_relay = RelayInfo {
connection_count: 3,
battery_level: 25,
is_charging: false,
role: RelayRole::Regular,
link_quality: 40,
queue_depth: 60,
congestion_level: 0.8,
};
let good_score = manager.calculate_relay_score(&good_relay);
let poor_score = manager.calculate_relay_score(&poor_relay);
assert!(good_score > poor_score);
assert!(good_score > 70.0); }
#[test]
fn test_select_best_relays() {
let manager = RelayManager::new();
let relays = vec![
RelayInfo {
connection_count: 8,
battery_level: 80,
is_charging: true,
role: RelayRole::Relay,
link_quality: 90,
queue_depth: 5,
congestion_level: 0.1,
},
RelayInfo {
connection_count: 5,
battery_level: 60,
is_charging: false,
role: RelayRole::Relay,
link_quality: 70,
queue_depth: 15,
congestion_level: 0.3,
},
RelayInfo {
connection_count: 3,
battery_level: 40,
is_charging: false,
role: RelayRole::Regular,
link_quality: 50,
queue_depth: 40,
congestion_level: 0.6,
},
];
let best = manager.select_best_relays(relays, 2, 0.7);
assert_eq!(best.len(), 2);
assert_eq!(best[0].connection_count, 8);
}
#[test]
fn test_select_best_relays_honors_congestion_threshold() {
let manager = RelayManager::new();
let candidate = |congestion_level: f32| RelayInfo {
connection_count: 5,
battery_level: 60,
is_charging: false,
role: RelayRole::Relay,
link_quality: 70,
queue_depth: 10,
congestion_level,
};
let best = manager.select_best_relays(vec![candidate(0.6)], 1, 0.7);
assert_eq!(best.len(), 1);
let best = manager.select_best_relays(vec![candidate(0.6)], 1, 0.5);
assert!(best.is_empty());
}
}