1use serde::{Deserialize, Serialize};
4
5pub const CRITICAL_RELAY_BATTERY_LEVEL: u8 = 15;
15
16#[derive(Debug, Clone)]
18pub struct RelayConfig {
19 pub min_battery_for_relay: u8,
21
22 pub allow_relay: bool,
24
25 pub relay_priority: RelayPriority,
27}
28
29impl Default for RelayConfig {
30 fn default() -> Self {
31 Self {
32 min_battery_for_relay: 30,
33 allow_relay: true,
34 relay_priority: RelayPriority::Auto,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum RelayPriority {
43 Never,
45 Auto,
47 Always,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "lowercase")]
54pub enum RelayRole {
55 Regular,
57 Relay,
59}
60
61#[derive(Debug, Clone)]
63pub struct RelayInfo {
64 pub connection_count: usize,
66
67 pub battery_level: u8,
69
70 pub is_charging: bool,
72
73 pub role: RelayRole,
75
76 pub link_quality: u8,
78
79 pub queue_depth: usize,
81
82 pub congestion_level: f32,
84}
85
86pub struct RelayManager;
93
94impl RelayManager {
95 pub fn new() -> Self {
97 Self
98 }
99
100 pub fn calculate_relay_score(&self, relay: &RelayInfo) -> f32 {
104 let mut score = 0.0;
105
106 let connection_score = (relay.connection_count as f32 / 10.0 * 30.0).min(30.0);
108 score += connection_score;
109
110 let battery_score = relay.battery_level as f32 / 100.0 * 20.0;
112 score += battery_score;
113
114 if relay.is_charging {
116 score += 20.0;
117 }
118
119 let link_score = relay.link_quality as f32 / 100.0 * 20.0;
121 score += link_score;
122
123 let congestion_penalty = relay.congestion_level * 15.0;
125 score -= congestion_penalty;
126
127 let queue_penalty = (relay.queue_depth as f32 / 50.0 * 15.0).min(15.0);
129 score -= queue_penalty;
130
131 score.max(0.0)
132 }
133
134 pub fn select_best_relays(
148 &self,
149 mut candidates: Vec<RelayInfo>,
150 count: usize,
151 max_congestion_level: f32,
152 ) -> Vec<RelayInfo> {
153 candidates.retain(|relay| relay.congestion_level < max_congestion_level);
155
156 candidates.sort_by(|a, b| {
158 let score_a = self.calculate_relay_score(a);
159 let score_b = self.calculate_relay_score(b);
160 score_b
161 .partial_cmp(&score_a)
162 .unwrap_or(std::cmp::Ordering::Equal)
163 });
164
165 candidates.into_iter().take(count).collect()
167 }
168}
169
170impl Default for RelayManager {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_relay_scoring() {
182 let manager = RelayManager::new();
183
184 let good_relay = RelayInfo {
185 connection_count: 8,
186 battery_level: 80,
187 is_charging: true,
188 role: RelayRole::Relay,
189 link_quality: 90,
190 queue_depth: 5,
191 congestion_level: 0.1,
192 };
193
194 let poor_relay = RelayInfo {
195 connection_count: 3,
196 battery_level: 25,
197 is_charging: false,
198 role: RelayRole::Regular,
199 link_quality: 40,
200 queue_depth: 60,
201 congestion_level: 0.8,
202 };
203
204 let good_score = manager.calculate_relay_score(&good_relay);
205 let poor_score = manager.calculate_relay_score(&poor_relay);
206
207 assert!(good_score > poor_score);
208 assert!(good_score > 70.0); }
210
211 #[test]
212 fn test_select_best_relays() {
213 let manager = RelayManager::new();
214
215 let relays = vec![
216 RelayInfo {
217 connection_count: 8,
218 battery_level: 80,
219 is_charging: true,
220 role: RelayRole::Relay,
221 link_quality: 90,
222 queue_depth: 5,
223 congestion_level: 0.1,
224 },
225 RelayInfo {
226 connection_count: 5,
227 battery_level: 60,
228 is_charging: false,
229 role: RelayRole::Relay,
230 link_quality: 70,
231 queue_depth: 15,
232 congestion_level: 0.3,
233 },
234 RelayInfo {
235 connection_count: 3,
236 battery_level: 40,
237 is_charging: false,
238 role: RelayRole::Regular,
239 link_quality: 50,
240 queue_depth: 40,
241 congestion_level: 0.6,
242 },
243 ];
244
245 let best = manager.select_best_relays(relays, 2, 0.7);
246 assert_eq!(best.len(), 2);
247
248 assert_eq!(best[0].connection_count, 8);
250 }
251
252 #[test]
253 fn test_select_best_relays_honors_congestion_threshold() {
254 let manager = RelayManager::new();
255 let candidate = |congestion_level: f32| RelayInfo {
256 connection_count: 5,
257 battery_level: 60,
258 is_charging: false,
259 role: RelayRole::Relay,
260 link_quality: 70,
261 queue_depth: 10,
262 congestion_level,
263 };
264
265 let best = manager.select_best_relays(vec![candidate(0.6)], 1, 0.7);
268 assert_eq!(best.len(), 1);
269
270 let best = manager.select_best_relays(vec![candidate(0.6)], 1, 0.5);
271 assert!(best.is_empty());
272 }
273}