#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::{HierarchyLevel, NodeId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ConnectionState {
#[default]
Disconnected,
Connecting,
Connected,
Disconnecting,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeerRole {
Parent,
Child,
Peer,
}
#[derive(Debug, Clone)]
pub struct PeerInfo {
pub node_id: NodeId,
pub role: PeerRole,
pub state: ConnectionState,
pub hierarchy_level: HierarchyLevel,
pub rssi: Option<i8>,
pub connected_at: Option<u64>,
pub messages_received: u32,
pub messages_sent: u32,
pub failure_count: u8,
pub last_seen_ms: u64,
}
impl PeerInfo {
pub fn new(node_id: NodeId, role: PeerRole, hierarchy_level: HierarchyLevel) -> Self {
Self {
node_id,
role,
state: ConnectionState::Disconnected,
hierarchy_level,
rssi: None,
connected_at: None,
messages_received: 0,
messages_sent: 0,
failure_count: 0,
last_seen_ms: 0,
}
}
pub fn is_connected(&self) -> bool {
self.state == ConnectionState::Connected
}
pub fn update_rssi(&mut self, rssi: i8) {
self.rssi = Some(rssi);
}
pub fn record_failure(&mut self) {
self.failure_count = self.failure_count.saturating_add(1);
}
pub fn reset_failures(&mut self) {
self.failure_count = 0;
}
}
#[derive(Debug, Clone, Default)]
pub struct MeshTopology {
pub parent: Option<NodeId>,
pub children: Vec<NodeId>,
pub peers: Vec<NodeId>,
pub my_level: HierarchyLevel,
pub max_children: u8,
pub max_connections: u8,
}
impl MeshTopology {
pub fn new(my_level: HierarchyLevel, max_children: u8, max_connections: u8) -> Self {
Self {
parent: None,
children: Vec::new(),
peers: Vec::new(),
my_level,
max_children,
max_connections,
}
}
pub fn connection_count(&self) -> usize {
let parent_count = if self.parent.is_some() { 1 } else { 0 };
parent_count + self.children.len() + self.peers.len()
}
pub fn can_accept_connection(&self) -> bool {
self.connection_count() < self.max_connections as usize
}
pub fn can_accept_child(&self) -> bool {
self.children.len() < self.max_children as usize && self.can_accept_connection()
}
pub fn has_parent(&self) -> bool {
self.parent.is_some()
}
pub fn set_parent(&mut self, node_id: NodeId) -> bool {
if self.parent.is_some() {
return false;
}
if !self.can_accept_connection() {
return false;
}
self.parent = Some(node_id);
true
}
pub fn clear_parent(&mut self) -> Option<NodeId> {
self.parent.take()
}
pub fn add_child(&mut self, node_id: NodeId) -> bool {
if !self.can_accept_child() {
return false;
}
if self.children.contains(&node_id) {
return false;
}
self.children.push(node_id);
true
}
pub fn remove_child(&mut self, node_id: &NodeId) -> bool {
if let Some(pos) = self.children.iter().position(|n| n == node_id) {
self.children.remove(pos);
true
} else {
false
}
}
pub fn add_peer(&mut self, node_id: NodeId) -> bool {
if !self.can_accept_connection() {
return false;
}
if self.peers.contains(&node_id) {
return false;
}
self.peers.push(node_id);
true
}
pub fn remove_peer(&mut self, node_id: &NodeId) -> bool {
if let Some(pos) = self.peers.iter().position(|n| n == node_id) {
self.peers.remove(pos);
true
} else {
false
}
}
pub fn all_connected(&self) -> Vec<NodeId> {
let mut nodes = Vec::with_capacity(self.connection_count());
if let Some(ref parent) = self.parent {
nodes.push(*parent);
}
nodes.extend(self.children.iter().cloned());
nodes.extend(self.peers.iter().cloned());
nodes
}
pub fn is_connected(&self, node_id: &NodeId) -> bool {
self.parent.as_ref() == Some(node_id)
|| self.children.contains(node_id)
|| self.peers.contains(node_id)
}
pub fn get_role(&self, node_id: &NodeId) -> Option<PeerRole> {
if self.parent.as_ref() == Some(node_id) {
Some(PeerRole::Parent)
} else if self.children.contains(node_id) {
Some(PeerRole::Child)
} else if self.peers.contains(node_id) {
Some(PeerRole::Peer)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub enum TopologyEvent {
ParentConnected {
node_id: NodeId,
level: HierarchyLevel,
rssi: Option<i8>,
},
ParentDisconnected {
node_id: NodeId,
reason: DisconnectReason,
},
ChildConnected {
node_id: NodeId,
level: HierarchyLevel,
},
ChildDisconnected {
node_id: NodeId,
reason: DisconnectReason,
},
PeerConnected {
node_id: NodeId,
},
PeerDisconnected {
node_id: NodeId,
reason: DisconnectReason,
},
TopologyChanged {
child_count: usize,
peer_count: usize,
has_parent: bool,
},
ParentFailoverStarted {
old_parent: NodeId,
},
ParentFailoverCompleted {
old_parent: NodeId,
new_parent: Option<NodeId>,
},
ConnectionQualityChanged {
node_id: NodeId,
rssi: i8,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DisconnectReason {
Requested,
Timeout,
RemoteDisconnect,
SupervisionTimeout,
LinkLoss,
LocalError,
#[default]
Unknown,
}
#[derive(Debug, Clone)]
pub struct ParentCandidate {
pub node_id: NodeId,
pub level: HierarchyLevel,
pub rssi: i8,
pub age_ms: u64,
pub failure_count: u8,
}
impl ParentCandidate {
pub fn score(&self, my_level: HierarchyLevel) -> i32 {
let mut score = 0i32;
score += (self.rssi as i32 + 100) * 2;
if self.age_ms < 1000 {
score += 20;
} else if self.age_ms < 3000 {
score += 10;
} else if self.age_ms < 5000 {
score += 5;
}
score -= (self.failure_count as i32) * 15;
let ideal_level = match my_level {
HierarchyLevel::Platform => HierarchyLevel::Squad,
HierarchyLevel::Squad => HierarchyLevel::Platoon,
HierarchyLevel::Platoon => HierarchyLevel::Company,
HierarchyLevel::Company => HierarchyLevel::Company, };
if self.level == ideal_level {
score += 30;
} else if self.level > my_level {
score += 15;
}
score
}
}
#[derive(Debug, Clone)]
pub struct TopologyConfig {
pub max_children: u8,
pub max_connections: u8,
pub min_parent_rssi: i8,
pub max_beacon_age_ms: u64,
pub parent_timeout_ms: u64,
pub connect_timeout_ms: u64,
pub max_failures: u8,
pub failover_delay_ms: u64,
pub rssi_hysteresis: u8,
}
impl Default for TopologyConfig {
fn default() -> Self {
Self {
max_children: 3,
max_connections: 7,
min_parent_rssi: -85,
max_beacon_age_ms: 10_000,
parent_timeout_ms: 5_000,
connect_timeout_ms: 10_000,
max_failures: 3,
failover_delay_ms: 1_000,
rssi_hysteresis: 6,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_topology_new() {
let topology = MeshTopology::new(HierarchyLevel::Squad, 3, 7);
assert_eq!(topology.my_level, HierarchyLevel::Squad);
assert_eq!(topology.max_children, 3);
assert_eq!(topology.max_connections, 7);
assert!(topology.parent.is_none());
assert!(topology.children.is_empty());
}
#[test]
fn test_set_parent() {
let mut topology = MeshTopology::new(HierarchyLevel::Platform, 3, 7);
let parent_id = NodeId::new(0x1234);
assert!(topology.set_parent(parent_id));
assert_eq!(topology.parent, Some(parent_id));
assert_eq!(topology.connection_count(), 1);
assert!(!topology.set_parent(NodeId::new(0x5678)));
}
#[test]
fn test_add_children() {
let mut topology = MeshTopology::new(HierarchyLevel::Squad, 2, 7);
assert!(topology.add_child(NodeId::new(0x1111)));
assert!(topology.add_child(NodeId::new(0x2222)));
assert!(!topology.add_child(NodeId::new(0x3333)));
assert_eq!(topology.children.len(), 2);
}
#[test]
fn test_connection_limit() {
let mut topology = MeshTopology::new(HierarchyLevel::Squad, 5, 3);
assert!(topology.set_parent(NodeId::new(0x0001)));
assert!(topology.add_child(NodeId::new(0x0002)));
assert!(topology.add_peer(NodeId::new(0x0003)));
assert!(!topology.add_child(NodeId::new(0x0004)));
assert!(!topology.add_peer(NodeId::new(0x0005)));
assert_eq!(topology.connection_count(), 3);
}
#[test]
fn test_remove_child() {
let mut topology = MeshTopology::new(HierarchyLevel::Squad, 3, 7);
let child_id = NodeId::new(0x1111);
topology.add_child(child_id);
assert!(topology.remove_child(&child_id));
assert!(!topology.remove_child(&child_id)); assert!(topology.children.is_empty());
}
#[test]
fn test_get_role() {
let mut topology = MeshTopology::new(HierarchyLevel::Squad, 3, 7);
let parent_id = NodeId::new(0x0001);
let child_id = NodeId::new(0x0002);
let peer_id = NodeId::new(0x0003);
let unknown_id = NodeId::new(0x9999);
topology.set_parent(parent_id);
topology.add_child(child_id);
topology.add_peer(peer_id);
assert_eq!(topology.get_role(&parent_id), Some(PeerRole::Parent));
assert_eq!(topology.get_role(&child_id), Some(PeerRole::Child));
assert_eq!(topology.get_role(&peer_id), Some(PeerRole::Peer));
assert_eq!(topology.get_role(&unknown_id), None);
}
#[test]
fn test_all_connected() {
let mut topology = MeshTopology::new(HierarchyLevel::Squad, 3, 7);
topology.set_parent(NodeId::new(0x0001));
topology.add_child(NodeId::new(0x0002));
topology.add_peer(NodeId::new(0x0003));
let all = topology.all_connected();
assert_eq!(all.len(), 3);
}
#[test]
fn test_parent_candidate_score() {
let candidate = ParentCandidate {
node_id: NodeId::new(0x1234),
level: HierarchyLevel::Squad,
rssi: -50,
age_ms: 500,
failure_count: 0,
};
let score = candidate.score(HierarchyLevel::Platform);
assert_eq!(score, 150);
}
#[test]
fn test_parent_candidate_score_with_failures() {
let candidate = ParentCandidate {
node_id: NodeId::new(0x1234),
level: HierarchyLevel::Squad,
rssi: -50,
age_ms: 500,
failure_count: 2,
};
let score = candidate.score(HierarchyLevel::Platform);
assert_eq!(score, 120);
}
#[test]
fn test_peer_info() {
let mut peer = PeerInfo::new(
NodeId::new(0x1234),
PeerRole::Child,
HierarchyLevel::Platform,
);
assert!(!peer.is_connected());
assert_eq!(peer.failure_count, 0);
peer.record_failure();
peer.record_failure();
assert_eq!(peer.failure_count, 2);
peer.reset_failures();
assert_eq!(peer.failure_count, 0);
}
#[test]
fn test_topology_config_default() {
let config = TopologyConfig::default();
assert_eq!(config.max_children, 3);
assert_eq!(config.max_connections, 7);
assert_eq!(config.min_parent_rssi, -85);
}
}