1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum NodeMobility {
6 Static,
8 Mobile,
10 SemiMobile,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct NodeResources {
17 pub cpu_cores: u8,
19
20 pub memory_mb: u32,
22
23 pub bandwidth_mbps: u32,
25
26 #[serde(default)]
28 pub cpu_usage_percent: u8,
29
30 #[serde(default)]
32 pub memory_usage_percent: u8,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub battery_percent: Option<u8>,
37}
38
39impl NodeResources {
40 pub fn can_parent(&self, config: &ParentingRequirements) -> bool {
42 self.cpu_cores >= config.min_cpu_cores
43 && self.memory_mb >= config.min_memory_mb
44 && self.bandwidth_mbps >= config.min_bandwidth_mbps
45 && self.cpu_usage_percent < config.max_cpu_usage_percent
46 && self.memory_usage_percent < config.max_memory_usage_percent
47 && self.has_sufficient_battery(config.min_battery_percent)
48 }
49
50 fn has_sufficient_battery(&self, min_battery: Option<u8>) -> bool {
51 match (self.battery_percent, min_battery) {
52 (Some(battery), Some(min)) => battery >= min,
53 (None, _) => true, (Some(_), None) => true, }
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ParentingRequirements {
62 pub min_cpu_cores: u8,
63 pub min_memory_mb: u32,
64 pub min_bandwidth_mbps: u32,
65 pub max_cpu_usage_percent: u8,
66 pub max_memory_usage_percent: u8,
67 pub min_battery_percent: Option<u8>,
68}
69
70impl Default for ParentingRequirements {
71 fn default() -> Self {
72 Self {
73 min_cpu_cores: 2,
74 min_memory_mb: 512,
75 min_bandwidth_mbps: 10,
76 max_cpu_usage_percent: 80,
77 max_memory_usage_percent: 80,
78 min_battery_percent: Some(20),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct NodeProfile {
86 pub mobility: NodeMobility,
88
89 pub resources: NodeResources,
91
92 pub can_parent: bool,
94
95 pub prefer_leaf: bool,
97
98 pub parent_priority: u8,
100}
101
102impl NodeProfile {
103 pub fn static_node(resources: NodeResources) -> Self {
105 Self {
106 mobility: NodeMobility::Static,
107 resources,
108 can_parent: true,
109 prefer_leaf: false,
110 parent_priority: 255, }
112 }
113
114 pub fn mobile_node(resources: NodeResources) -> Self {
116 Self {
117 mobility: NodeMobility::Mobile,
118 resources,
119 can_parent: false, prefer_leaf: true,
121 parent_priority: 0, }
123 }
124
125 pub fn semi_mobile_node(resources: NodeResources) -> Self {
127 Self {
128 mobility: NodeMobility::SemiMobile,
129 resources,
130 can_parent: true, prefer_leaf: false,
132 parent_priority: 128, }
134 }
135
136 pub fn is_parent_candidate(&self, requirements: &ParentingRequirements) -> bool {
138 if self.prefer_leaf || !self.can_parent {
139 return false;
140 }
141
142 if self.mobility == NodeMobility::Static {
144 return self.resources.can_parent(requirements);
145 }
146
147 if self.mobility == NodeMobility::Mobile {
149 return false;
150 }
151
152 self.resources.can_parent(requirements)
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct BeaconConfig {
160 pub geohash_precision: usize,
163
164 pub track_all: bool,
166
167 pub max_distance_meters: Option<f64>,
169
170 pub broadcast_interval_secs: u64,
172
173 pub ttl_secs: u64,
175
176 pub cleanup_interval_secs: u64,
178}
179
180impl Default for BeaconConfig {
181 fn default() -> Self {
182 Self {
183 geohash_precision: 7,
184 track_all: false,
185 max_distance_meters: Some(5000.0), broadcast_interval_secs: 5,
187 ttl_secs: 30,
188 cleanup_interval_secs: 5,
189 }
190 }
191}
192
193impl BeaconConfig {
194 pub fn tactical() -> Self {
196 Self {
197 geohash_precision: 8, track_all: false,
199 max_distance_meters: Some(2000.0), broadcast_interval_secs: 3,
201 ttl_secs: 15,
202 cleanup_interval_secs: 3,
203 }
204 }
205
206 pub fn distributed() -> Self {
208 Self {
209 geohash_precision: 5, track_all: true,
211 max_distance_meters: None,
212 broadcast_interval_secs: 10,
213 ttl_secs: 60,
214 cleanup_interval_secs: 10,
215 }
216 }
217
218 pub fn hybrid() -> Self {
220 Self {
221 geohash_precision: 6, track_all: false,
223 max_distance_meters: Some(10000.0), broadcast_interval_secs: 5,
225 ttl_secs: 30,
226 cleanup_interval_secs: 5,
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_node_mobility_types() {
237 let static_resources = NodeResources {
238 cpu_cores: 4,
239 memory_mb: 2048,
240 bandwidth_mbps: 100,
241 cpu_usage_percent: 30,
242 memory_usage_percent: 40,
243 battery_percent: None, };
245
246 let mobile_resources = NodeResources {
247 cpu_cores: 2,
248 memory_mb: 512,
249 bandwidth_mbps: 20,
250 cpu_usage_percent: 50,
251 memory_usage_percent: 60,
252 battery_percent: Some(75),
253 };
254
255 let static_profile = NodeProfile::static_node(static_resources);
256 let mobile_profile = NodeProfile::mobile_node(mobile_resources);
257
258 let requirements = ParentingRequirements::default();
259
260 assert!(static_profile.is_parent_candidate(&requirements));
262 assert_eq!(static_profile.parent_priority, 255);
263
264 assert!(!mobile_profile.is_parent_candidate(&requirements));
266 assert_eq!(mobile_profile.parent_priority, 0);
267 }
268
269 #[test]
270 fn test_resource_based_parenting() {
271 let low_resources = NodeResources {
272 cpu_cores: 1,
273 memory_mb: 256,
274 bandwidth_mbps: 5,
275 cpu_usage_percent: 90,
276 memory_usage_percent: 85,
277 battery_percent: Some(15),
278 };
279
280 let high_resources = NodeResources {
281 cpu_cores: 8,
282 memory_mb: 4096,
283 bandwidth_mbps: 1000,
284 cpu_usage_percent: 20,
285 memory_usage_percent: 30,
286 battery_percent: None,
287 };
288
289 let requirements = ParentingRequirements::default();
290
291 assert!(!low_resources.can_parent(&requirements));
292 assert!(high_resources.can_parent(&requirements));
293 }
294
295 #[test]
296 fn test_semi_mobile_profile() {
297 let resources = NodeResources {
298 cpu_cores: 4,
299 memory_mb: 1024,
300 bandwidth_mbps: 50,
301 cpu_usage_percent: 40,
302 memory_usage_percent: 50,
303 battery_percent: Some(80),
304 };
305
306 let profile = NodeProfile::semi_mobile_node(resources);
307 let requirements = ParentingRequirements::default();
308
309 assert!(profile.is_parent_candidate(&requirements));
311 assert_eq!(profile.parent_priority, 128);
312 assert_eq!(profile.mobility, NodeMobility::SemiMobile);
313 }
314
315 #[test]
316 fn test_beacon_config_presets() {
317 let tactical = BeaconConfig::tactical();
318 assert_eq!(tactical.geohash_precision, 8);
319 assert_eq!(tactical.max_distance_meters, Some(2000.0));
320
321 let distributed = BeaconConfig::distributed();
322 assert!(distributed.track_all);
323 assert_eq!(distributed.max_distance_meters, None);
324
325 let hybrid = BeaconConfig::hybrid();
326 assert_eq!(hybrid.geohash_precision, 6);
327 assert_eq!(hybrid.max_distance_meters, Some(10000.0));
328 }
329}