#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::{HierarchyLevel, NodeId};
use super::topology::MeshTopology;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteDirection {
Upward,
Downward,
Broadcast,
Targeted(u32), }
#[derive(Debug, Clone)]
pub struct RouteDecision {
pub next_hops: Vec<NodeId>,
pub local_copy: bool,
pub routed: bool,
pub failure_reason: Option<RouteFailure>,
}
impl RouteDecision {
pub fn success(next_hops: Vec<NodeId>, local_copy: bool) -> Self {
Self {
next_hops,
local_copy,
routed: true,
failure_reason: None,
}
}
pub fn failed(reason: RouteFailure) -> Self {
Self {
next_hops: Vec::new(),
local_copy: false,
routed: false,
failure_reason: Some(reason),
}
}
pub fn local_only() -> Self {
Self {
next_hops: Vec::new(),
local_copy: true,
routed: true,
failure_reason: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteFailure {
NoParent,
NoChildren,
TargetNotFound,
NoPeers,
TtlExpired,
MessageTooLarge,
}
#[derive(Debug, Clone)]
pub struct MeshRouter {
pub node_id: NodeId,
pub my_level: HierarchyLevel,
}
impl MeshRouter {
pub fn new(node_id: NodeId, my_level: HierarchyLevel) -> Self {
Self { node_id, my_level }
}
pub fn route(&self, direction: RouteDirection, topology: &MeshTopology) -> RouteDecision {
match direction {
RouteDirection::Upward => self.route_upward(topology),
RouteDirection::Downward => self.route_downward(topology),
RouteDirection::Broadcast => self.route_broadcast(topology),
RouteDirection::Targeted(target_id) => {
self.route_targeted(&NodeId::new(target_id), topology)
}
}
}
fn route_upward(&self, topology: &MeshTopology) -> RouteDecision {
match &topology.parent {
Some(parent_id) => RouteDecision::success(vec![*parent_id], true),
None => RouteDecision::failed(RouteFailure::NoParent),
}
}
fn route_downward(&self, topology: &MeshTopology) -> RouteDecision {
if topology.children.is_empty() {
RouteDecision::failed(RouteFailure::NoChildren)
} else {
RouteDecision::success(topology.children.clone(), true)
}
}
fn route_broadcast(&self, topology: &MeshTopology) -> RouteDecision {
let all = topology.all_connected();
if all.is_empty() {
RouteDecision::failed(RouteFailure::NoPeers)
} else {
RouteDecision::success(all, true)
}
}
fn route_targeted(&self, target: &NodeId, topology: &MeshTopology) -> RouteDecision {
if topology.is_connected(target) {
return RouteDecision::success(vec![*target], false);
}
if let Some(ref parent) = topology.parent {
RouteDecision::success(vec![*parent], false)
} else if !topology.children.is_empty() {
RouteDecision::success(topology.children.clone(), false)
} else {
RouteDecision::failed(RouteFailure::TargetNotFound)
}
}
pub fn handle_received(
&self,
source: &NodeId,
destination: Option<&NodeId>,
direction: RouteDirection,
topology: &MeshTopology,
) -> RouteDecision {
if let Some(dest) = destination {
if dest == &self.node_id {
return RouteDecision::local_only();
}
}
let _source_role = topology.get_role(source);
match direction {
RouteDirection::Upward => {
self.route_upward(topology)
}
RouteDirection::Downward => {
let mut children = topology.children.clone();
children.retain(|c| c != source);
if children.is_empty() {
RouteDecision::local_only()
} else {
RouteDecision::success(children, true)
}
}
RouteDirection::Broadcast => {
let mut all = topology.all_connected();
all.retain(|n| n != source);
RouteDecision::success(all, true)
}
RouteDirection::Targeted(target_id) => {
let target = NodeId::new(target_id);
if target == self.node_id {
RouteDecision::local_only()
} else {
self.route_targeted(&target, topology)
}
}
}
}
pub fn aggregation_route(&self, topology: &MeshTopology) -> Option<NodeId> {
topology.parent
}
pub fn dissemination_routes(&self, topology: &MeshTopology) -> Vec<NodeId> {
topology.children.clone()
}
}
#[derive(Debug, Clone)]
pub struct HopTracker {
pub max_hops: u8,
pub visited: Vec<NodeId>,
}
impl HopTracker {
pub fn new(max_hops: u8) -> Self {
Self {
max_hops,
visited: Vec::new(),
}
}
pub fn visit(&mut self, node_id: NodeId) -> bool {
if self.visited.contains(&node_id) {
return false; }
if self.visited.len() >= self.max_hops as usize {
return false; }
self.visited.push(node_id);
true
}
pub fn has_visited(&self, node_id: &NodeId) -> bool {
self.visited.contains(node_id)
}
pub fn remaining_hops(&self) -> u8 {
self.max_hops.saturating_sub(self.visited.len() as u8)
}
pub fn is_expired(&self) -> bool {
self.visited.len() >= self.max_hops as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_topology_with_parent() -> MeshTopology {
let mut topology = MeshTopology::new(HierarchyLevel::Platform, 3, 7);
topology.set_parent(NodeId::new(0x1000));
topology.add_child(NodeId::new(0x0001));
topology.add_child(NodeId::new(0x0002));
topology
}
#[test]
fn test_route_upward() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = create_topology_with_parent();
let decision = router.route(RouteDirection::Upward, &topology);
assert!(decision.routed);
assert_eq!(decision.next_hops.len(), 1);
assert_eq!(decision.next_hops[0].as_u32(), 0x1000);
}
#[test]
fn test_route_upward_no_parent() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = MeshTopology::new(HierarchyLevel::Platform, 3, 7);
let decision = router.route(RouteDirection::Upward, &topology);
assert!(!decision.routed);
assert_eq!(decision.failure_reason, Some(RouteFailure::NoParent));
}
#[test]
fn test_route_downward() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Squad);
let topology = create_topology_with_parent();
let decision = router.route(RouteDirection::Downward, &topology);
assert!(decision.routed);
assert_eq!(decision.next_hops.len(), 2);
}
#[test]
fn test_route_broadcast() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = create_topology_with_parent();
let decision = router.route(RouteDirection::Broadcast, &topology);
assert!(decision.routed);
assert_eq!(decision.next_hops.len(), 3); }
#[test]
fn test_route_targeted_direct() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = create_topology_with_parent();
let decision = router.route(RouteDirection::Targeted(0x0001), &topology);
assert!(decision.routed);
assert_eq!(decision.next_hops.len(), 1);
assert_eq!(decision.next_hops[0].as_u32(), 0x0001);
}
#[test]
fn test_route_targeted_via_parent() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = create_topology_with_parent();
let decision = router.route(RouteDirection::Targeted(0x9999), &topology);
assert!(decision.routed);
assert_eq!(decision.next_hops.len(), 1);
assert_eq!(decision.next_hops[0].as_u32(), 0x1000); }
#[test]
fn test_handle_received_for_us() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = create_topology_with_parent();
let decision = router.handle_received(
&NodeId::new(0x1000),
Some(&NodeId::new(0x1234)), RouteDirection::Downward,
&topology,
);
assert!(decision.local_copy);
assert!(decision.next_hops.is_empty());
}
#[test]
fn test_hop_tracker() {
let mut tracker = HopTracker::new(3);
assert!(tracker.visit(NodeId::new(0x0001)));
assert!(tracker.visit(NodeId::new(0x0002)));
assert!(tracker.visit(NodeId::new(0x0003)));
assert!(!tracker.visit(NodeId::new(0x0004)));
assert!(tracker.has_visited(&NodeId::new(0x0001)));
assert!(!tracker.has_visited(&NodeId::new(0x0004)));
}
#[test]
fn test_hop_tracker_loop_detection() {
let mut tracker = HopTracker::new(10);
assert!(tracker.visit(NodeId::new(0x0001)));
assert!(tracker.visit(NodeId::new(0x0002)));
assert!(!tracker.visit(NodeId::new(0x0001))); }
#[test]
fn test_aggregation_route() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
let topology = create_topology_with_parent();
let route = router.aggregation_route(&topology);
assert_eq!(route, Some(NodeId::new(0x1000)));
}
#[test]
fn test_dissemination_routes() {
let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Squad);
let topology = create_topology_with_parent();
let routes = router.dissemination_routes(&topology);
assert_eq!(routes.len(), 2);
}
}