1#[cfg(not(feature = "std"))]
21use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
22#[cfg(feature = "std")]
23use std::collections::HashMap;
24
25use core::sync::atomic::{AtomicUsize, Ordering};
26
27#[cfg(feature = "std")]
28use std::sync::RwLock;
29
30use crate::discovery::HiveBeacon;
31use crate::error::{BleError, Result};
32use crate::{HierarchyLevel, NodeId};
33
34use super::topology::{
35 ConnectionState, DisconnectReason, MeshTopology, ParentCandidate, PeerInfo, PeerRole,
36 TopologyConfig, TopologyEvent,
37};
38
39pub type TopologyCallback = Box<dyn Fn(&TopologyEvent) + Send + Sync>;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub enum ManagerState {
45 #[default]
47 Stopped,
48 Starting,
50 Running,
52 Failover,
54 Stopping,
56}
57
58#[cfg(feature = "std")]
66pub struct MeshManager {
67 node_id: NodeId,
69 my_level: HierarchyLevel,
71 config: TopologyConfig,
73 topology: RwLock<MeshTopology>,
75 peers: RwLock<HashMap<NodeId, PeerInfo>>,
77 candidates: RwLock<Vec<ParentCandidate>>,
79 state: RwLock<ManagerState>,
81 callbacks: RwLock<Vec<TopologyCallback>>,
83 current_time_ms: AtomicUsize,
86}
87
88#[cfg(feature = "std")]
89impl MeshManager {
90 pub fn new(node_id: NodeId, my_level: HierarchyLevel, config: TopologyConfig) -> Self {
92 let topology = MeshTopology::new(my_level, config.max_children, config.max_connections);
93
94 Self {
95 node_id,
96 my_level,
97 config,
98 topology: RwLock::new(topology),
99 peers: RwLock::new(HashMap::new()),
100 candidates: RwLock::new(Vec::new()),
101 state: RwLock::new(ManagerState::Stopped),
102 callbacks: RwLock::new(Vec::new()),
103 current_time_ms: AtomicUsize::new(0),
104 }
105 }
106
107 pub fn node_id(&self) -> &NodeId {
109 &self.node_id
110 }
111
112 pub fn my_level(&self) -> HierarchyLevel {
114 self.my_level
115 }
116
117 pub fn state(&self) -> ManagerState {
119 *self.state.read().unwrap()
120 }
121
122 pub fn start(&self) -> Result<()> {
124 let mut state = self.state.write().unwrap();
125 match *state {
126 ManagerState::Stopped => {
127 *state = ManagerState::Running;
128 Ok(())
129 }
130 _ => Err(BleError::InvalidState("Already started".into())),
131 }
132 }
133
134 pub fn stop(&self) -> Result<()> {
136 let mut state = self.state.write().unwrap();
137 *state = ManagerState::Stopped;
138
139 let mut topology = self.topology.write().unwrap();
141 topology.parent = None;
142 topology.children.clear();
143 topology.peers.clear();
144
145 self.peers.write().unwrap().clear();
147
148 self.candidates.write().unwrap().clear();
150
151 Ok(())
152 }
153
154 pub fn on_topology_event(&self, callback: TopologyCallback) {
156 self.callbacks.write().unwrap().push(callback);
157 }
158
159 fn emit_event(&self, event: TopologyEvent) {
161 let callbacks = self.callbacks.read().unwrap();
162 for callback in callbacks.iter() {
163 callback(&event);
164 }
165 }
166
167 pub fn set_time_ms(&self, time_ms: u64) {
170 self.current_time_ms
171 .store(time_ms as usize, Ordering::SeqCst);
172 }
173
174 pub fn time_ms(&self) -> u64 {
177 self.current_time_ms.load(Ordering::SeqCst) as u64
178 }
179
180 pub fn topology(&self) -> MeshTopology {
182 self.topology.read().unwrap().clone()
183 }
184
185 pub fn has_parent(&self) -> bool {
187 self.topology.read().unwrap().has_parent()
188 }
189
190 pub fn parent(&self) -> Option<NodeId> {
192 self.topology.read().unwrap().parent
193 }
194
195 pub fn children(&self) -> Vec<NodeId> {
197 self.topology.read().unwrap().children.clone()
198 }
199
200 pub fn child_count(&self) -> usize {
202 self.topology.read().unwrap().children.len()
203 }
204
205 pub fn can_accept_child(&self) -> bool {
207 self.topology.read().unwrap().can_accept_child()
208 }
209
210 pub fn connected_peers(&self) -> Vec<NodeId> {
212 self.topology.read().unwrap().all_connected()
213 }
214
215 pub fn get_peer_info(&self, node_id: &NodeId) -> Option<PeerInfo> {
217 self.peers.read().unwrap().get(node_id).cloned()
218 }
219
220 pub fn process_beacon(&self, beacon: &HiveBeacon, rssi: i8) {
224 if beacon.hierarchy_level > self.my_level {
226 let candidate = ParentCandidate {
227 node_id: beacon.node_id,
228 level: beacon.hierarchy_level,
229 rssi,
230 age_ms: 0,
231 failure_count: self
232 .peers
233 .read()
234 .unwrap()
235 .get(&beacon.node_id)
236 .map(|p| p.failure_count)
237 .unwrap_or(0),
238 };
239
240 let mut candidates = self.candidates.write().unwrap();
241
242 if let Some(existing) = candidates.iter_mut().find(|c| c.node_id == beacon.node_id) {
244 existing.rssi = rssi;
245 existing.age_ms = 0;
246 existing.level = beacon.hierarchy_level;
247 } else {
248 candidates.push(candidate);
249 }
250 }
251 }
252
253 pub fn select_best_parent(&self) -> Option<ParentCandidate> {
257 let candidates = self.candidates.read().unwrap();
258
259 candidates
260 .iter()
261 .filter(|c| {
262 c.rssi >= self.config.min_parent_rssi
263 && c.age_ms <= self.config.max_beacon_age_ms
264 && c.failure_count < self.config.max_failures
265 })
266 .max_by_key(|c| c.score(self.my_level))
267 .cloned()
268 }
269
270 pub fn connect_parent(&self, node_id: NodeId, level: HierarchyLevel, rssi: i8) -> Result<()> {
272 let mut topology = self.topology.write().unwrap();
273
274 if topology.has_parent() {
275 return Err(BleError::InvalidState("Already have a parent".into()));
276 }
277
278 if !topology.set_parent(node_id) {
279 return Err(BleError::ConnectionFailed(
280 "Cannot accept connection".into(),
281 ));
282 }
283
284 let mut peer_info = PeerInfo::new(node_id, PeerRole::Parent, level);
286 peer_info.state = ConnectionState::Connected;
287 peer_info.rssi = Some(rssi);
288 peer_info.connected_at = Some(self.time_ms());
289 peer_info.last_seen_ms = self.time_ms();
290
291 self.peers.write().unwrap().insert(node_id, peer_info);
292
293 drop(topology); self.emit_event(TopologyEvent::ParentConnected {
296 node_id,
297 level,
298 rssi: Some(rssi),
299 });
300
301 self.emit_topology_changed();
302 Ok(())
303 }
304
305 pub fn disconnect_parent(&self, reason: DisconnectReason) -> Option<NodeId> {
307 let old_parent = {
308 let mut topology = self.topology.write().unwrap();
309 topology.clear_parent()
310 };
311
312 if let Some(ref parent_id) = old_parent {
313 self.peers.write().unwrap().remove(parent_id);
314
315 self.emit_event(TopologyEvent::ParentDisconnected {
316 node_id: *parent_id,
317 reason,
318 });
319 self.emit_topology_changed();
320 }
321
322 old_parent
323 }
324
325 pub fn accept_child(&self, node_id: NodeId, level: HierarchyLevel) -> Result<()> {
327 let mut topology = self.topology.write().unwrap();
328
329 if !topology.add_child(node_id) {
330 return Err(BleError::ConnectionFailed("Cannot accept child".into()));
331 }
332
333 let mut peer_info = PeerInfo::new(node_id, PeerRole::Child, level);
335 peer_info.state = ConnectionState::Connected;
336 peer_info.connected_at = Some(self.time_ms());
337 peer_info.last_seen_ms = self.time_ms();
338
339 self.peers.write().unwrap().insert(node_id, peer_info);
340
341 drop(topology);
343 self.emit_event(TopologyEvent::ChildConnected { node_id, level });
344
345 self.emit_topology_changed();
346 Ok(())
347 }
348
349 pub fn remove_child(&self, node_id: &NodeId, reason: DisconnectReason) -> bool {
351 let removed = {
352 let mut topology = self.topology.write().unwrap();
353 topology.remove_child(node_id)
354 };
355
356 if removed {
357 self.peers.write().unwrap().remove(node_id);
358
359 self.emit_event(TopologyEvent::ChildDisconnected {
360 node_id: *node_id,
361 reason,
362 });
363 self.emit_topology_changed();
364 }
365
366 removed
367 }
368
369 pub fn start_failover(&self) -> Result<()> {
371 let mut state = self.state.write().unwrap();
372 if *state != ManagerState::Running {
373 return Err(BleError::InvalidState("Not running".into()));
374 }
375
376 let old_parent = self.disconnect_parent(DisconnectReason::LinkLoss);
377
378 if let Some(old_parent_id) = old_parent {
379 *state = ManagerState::Failover;
380 drop(state);
381
382 self.emit_event(TopologyEvent::ParentFailoverStarted {
383 old_parent: old_parent_id,
384 });
385 }
386
387 Ok(())
388 }
389
390 pub fn complete_failover(
392 &self,
393 new_parent: Option<(NodeId, HierarchyLevel, i8)>,
394 ) -> Result<()> {
395 let old_parent = {
396 self.candidates
398 .read()
399 .unwrap()
400 .first()
401 .map(|c| c.node_id)
402 .unwrap_or_else(|| NodeId::new(0))
403 };
404
405 if let Some((node_id, level, rssi)) = new_parent {
406 self.connect_parent(node_id, level, rssi)?;
407
408 let mut state = self.state.write().unwrap();
409 *state = ManagerState::Running;
410 drop(state);
411
412 self.emit_event(TopologyEvent::ParentFailoverCompleted {
413 old_parent,
414 new_parent: Some(node_id),
415 });
416 } else {
417 let mut state = self.state.write().unwrap();
418 *state = ManagerState::Running;
419 drop(state);
420
421 self.emit_event(TopologyEvent::ParentFailoverCompleted {
422 old_parent,
423 new_parent: None,
424 });
425 }
426
427 Ok(())
428 }
429
430 pub fn update_rssi(&self, node_id: &NodeId, rssi: i8) {
432 let mut peers = self.peers.write().unwrap();
433 if let Some(peer) = peers.get_mut(node_id) {
434 peer.update_rssi(rssi);
435 peer.last_seen_ms = self.time_ms();
436 }
437 drop(peers);
438
439 self.emit_event(TopologyEvent::ConnectionQualityChanged {
440 node_id: *node_id,
441 rssi,
442 });
443 }
444
445 pub fn record_failure(&self, node_id: &NodeId) {
447 let mut peers = self.peers.write().unwrap();
448 if let Some(peer) = peers.get_mut(node_id) {
449 peer.record_failure();
450 }
451
452 let mut candidates = self.candidates.write().unwrap();
454 if let Some(candidate) = candidates.iter_mut().find(|c| &c.node_id == node_id) {
455 candidate.failure_count = candidate.failure_count.saturating_add(1);
456 }
457 }
458
459 pub fn age_candidates(&self, elapsed_ms: u64) {
461 let mut candidates = self.candidates.write().unwrap();
462 for candidate in candidates.iter_mut() {
463 candidate.age_ms = candidate.age_ms.saturating_add(elapsed_ms);
464 }
465
466 candidates.retain(|c| c.age_ms <= self.config.max_beacon_age_ms * 2);
468 }
469
470 pub fn should_switch_parent(&self) -> Option<ParentCandidate> {
472 let topology = self.topology.read().unwrap();
473 let current_parent = topology.parent?;
474 drop(topology);
475
476 let peers = self.peers.read().unwrap();
477 let current_rssi = peers.get(¤t_parent)?.rssi?;
478 drop(peers);
479
480 let best = self.select_best_parent()?;
482
483 if best.rssi > current_rssi + self.config.rssi_hysteresis as i8 {
485 Some(best)
486 } else {
487 None
488 }
489 }
490
491 fn emit_topology_changed(&self) {
493 let topology = self.topology.read().unwrap();
494 self.emit_event(TopologyEvent::TopologyChanged {
495 child_count: topology.children.len(),
496 peer_count: topology.peers.len(),
497 has_parent: topology.has_parent(),
498 });
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505
506 fn create_manager() -> MeshManager {
507 MeshManager::new(
508 NodeId::new(0x1234),
509 HierarchyLevel::Platform,
510 TopologyConfig::default(),
511 )
512 }
513
514 #[test]
515 fn test_manager_creation() {
516 let manager = create_manager();
517 assert_eq!(manager.node_id().as_u32(), 0x1234);
518 assert_eq!(manager.my_level(), HierarchyLevel::Platform);
519 assert_eq!(manager.state(), ManagerState::Stopped);
520 }
521
522 #[test]
523 fn test_start_stop() {
524 let manager = create_manager();
525
526 assert!(manager.start().is_ok());
527 assert_eq!(manager.state(), ManagerState::Running);
528
529 assert!(manager.stop().is_ok());
530 assert_eq!(manager.state(), ManagerState::Stopped);
531 }
532
533 #[test]
534 fn test_connect_parent() {
535 let manager = create_manager();
536 manager.start().unwrap();
537
538 let parent_id = NodeId::new(0x5678);
539 assert!(manager
540 .connect_parent(parent_id, HierarchyLevel::Squad, -50)
541 .is_ok());
542
543 assert!(manager.has_parent());
544 assert_eq!(manager.parent(), Some(parent_id));
545
546 assert!(manager
548 .connect_parent(NodeId::new(0x9999), HierarchyLevel::Squad, -50)
549 .is_err());
550 }
551
552 #[test]
553 fn test_disconnect_parent() {
554 let manager = create_manager();
555 manager.start().unwrap();
556
557 let parent_id = NodeId::new(0x5678);
558 manager
559 .connect_parent(parent_id, HierarchyLevel::Squad, -50)
560 .unwrap();
561
562 let old = manager.disconnect_parent(DisconnectReason::Requested);
563 assert_eq!(old, Some(parent_id));
564 assert!(!manager.has_parent());
565 }
566
567 #[test]
568 fn test_accept_child() {
569 let manager = MeshManager::new(
570 NodeId::new(0x1234),
571 HierarchyLevel::Squad,
572 TopologyConfig::default(),
573 );
574 manager.start().unwrap();
575
576 let child_id = NodeId::new(0x0001);
577 assert!(manager
578 .accept_child(child_id, HierarchyLevel::Platform)
579 .is_ok());
580
581 assert_eq!(manager.child_count(), 1);
582 assert_eq!(manager.children(), vec![child_id]);
583 }
584
585 #[test]
586 fn test_max_children() {
587 let config = TopologyConfig {
588 max_children: 2,
589 ..Default::default()
590 };
591
592 let manager = MeshManager::new(NodeId::new(0x1234), HierarchyLevel::Squad, config);
593 manager.start().unwrap();
594
595 assert!(manager
596 .accept_child(NodeId::new(0x0001), HierarchyLevel::Platform)
597 .is_ok());
598 assert!(manager
599 .accept_child(NodeId::new(0x0002), HierarchyLevel::Platform)
600 .is_ok());
601 assert!(manager
602 .accept_child(NodeId::new(0x0003), HierarchyLevel::Platform)
603 .is_err());
604 }
605
606 #[test]
607 fn test_process_beacon() {
608 let manager = create_manager();
609 manager.start().unwrap();
610
611 let beacon = HiveBeacon {
612 node_id: NodeId::new(0x5678),
613 hierarchy_level: HierarchyLevel::Squad,
614 version: 1,
615 seq_num: 1,
616 capabilities: 0,
617 battery_percent: 100,
618 geohash: 0,
619 };
620
621 manager.process_beacon(&beacon, -50);
622
623 let best = manager.select_best_parent();
624 assert!(best.is_some());
625 assert_eq!(best.unwrap().node_id.as_u32(), 0x5678);
626 }
627
628 #[test]
629 fn test_select_best_parent_rssi() {
630 let manager = create_manager();
631 manager.start().unwrap();
632
633 let beacon1 = HiveBeacon {
635 node_id: NodeId::new(0x1111),
636 hierarchy_level: HierarchyLevel::Squad,
637 version: 1,
638 seq_num: 1,
639 capabilities: 0,
640 battery_percent: 100,
641 geohash: 0,
642 };
643
644 let beacon2 = HiveBeacon {
645 node_id: NodeId::new(0x2222),
646 hierarchy_level: HierarchyLevel::Squad,
647 version: 1,
648 seq_num: 1,
649 capabilities: 0,
650 battery_percent: 100,
651 geohash: 0,
652 };
653
654 manager.process_beacon(&beacon1, -70);
655 manager.process_beacon(&beacon2, -50); let best = manager.select_best_parent().unwrap();
658 assert_eq!(best.node_id.as_u32(), 0x2222);
659 }
660
661 #[test]
662 fn test_failover() {
663 let manager = create_manager();
664 manager.start().unwrap();
665
666 let parent_id = NodeId::new(0x5678);
667 manager
668 .connect_parent(parent_id, HierarchyLevel::Squad, -50)
669 .unwrap();
670
671 assert!(manager.start_failover().is_ok());
673 assert_eq!(manager.state(), ManagerState::Failover);
674 assert!(!manager.has_parent());
675
676 assert!(manager.complete_failover(None).is_ok());
678 assert_eq!(manager.state(), ManagerState::Running);
679 }
680
681 #[test]
682 fn test_event_callback() {
683 use std::sync::atomic::{AtomicBool, Ordering};
684 use std::sync::Arc;
685
686 let manager = create_manager();
687 manager.start().unwrap();
688
689 let called = Arc::new(AtomicBool::new(false));
690 let called_clone = called.clone();
691
692 manager.on_topology_event(Box::new(move |event| {
693 if matches!(event, TopologyEvent::ParentConnected { .. }) {
694 called_clone.store(true, Ordering::SeqCst);
695 }
696 }));
697
698 manager
699 .connect_parent(NodeId::new(0x5678), HierarchyLevel::Squad, -50)
700 .unwrap();
701
702 assert!(called.load(Ordering::SeqCst));
703 }
704
705 #[test]
706 fn test_update_rssi() {
707 let manager = create_manager();
708 manager.start().unwrap();
709
710 let parent_id = NodeId::new(0x5678);
711 manager
712 .connect_parent(parent_id, HierarchyLevel::Squad, -50)
713 .unwrap();
714
715 manager.update_rssi(&parent_id, -60);
716
717 let info = manager.get_peer_info(&parent_id).unwrap();
718 assert_eq!(info.rssi, Some(-60));
719 }
720
721 #[test]
722 fn test_age_candidates() {
723 let manager = create_manager();
724 manager.start().unwrap();
725
726 let beacon = HiveBeacon {
727 node_id: NodeId::new(0x5678),
728 hierarchy_level: HierarchyLevel::Squad,
729 version: 1,
730 seq_num: 1,
731 capabilities: 0,
732 battery_percent: 100,
733 geohash: 0,
734 };
735
736 manager.process_beacon(&beacon, -50);
737
738 manager.age_candidates(25_000);
740
741 let best = manager.select_best_parent();
743 assert!(best.is_none());
744 }
745}