hive_btle/mesh/
routing.rs

1//! Message routing for HIVE-BTLE mesh
2//!
3//! Handles routing messages through the mesh topology, including:
4//! - Upward routing to parent
5//! - Downward routing to children
6//! - Broadcast to all connected peers
7
8#[cfg(not(feature = "std"))]
9use alloc::{vec, vec::Vec};
10
11use crate::{HierarchyLevel, NodeId};
12
13use super::topology::MeshTopology;
14
15/// Message routing direction
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum RouteDirection {
18    /// Route upward toward the root (parent direction)
19    Upward,
20    /// Route downward toward leaves (children direction)
21    Downward,
22    /// Route to all connected peers
23    Broadcast,
24    /// Route to a specific node
25    Targeted(u32), // NodeId as u32 for Copy
26}
27
28/// A routing decision for a message
29#[derive(Debug, Clone)]
30pub struct RouteDecision {
31    /// Next hop(s) for the message
32    pub next_hops: Vec<NodeId>,
33    /// Whether to keep a local copy
34    pub local_copy: bool,
35    /// Whether routing succeeded
36    pub routed: bool,
37    /// Reason if routing failed
38    pub failure_reason: Option<RouteFailure>,
39}
40
41impl RouteDecision {
42    /// Create a successful route decision
43    pub fn success(next_hops: Vec<NodeId>, local_copy: bool) -> Self {
44        Self {
45            next_hops,
46            local_copy,
47            routed: true,
48            failure_reason: None,
49        }
50    }
51
52    /// Create a failed route decision
53    pub fn failed(reason: RouteFailure) -> Self {
54        Self {
55            next_hops: Vec::new(),
56            local_copy: false,
57            routed: false,
58            failure_reason: Some(reason),
59        }
60    }
61
62    /// Route for local processing only
63    pub fn local_only() -> Self {
64        Self {
65            next_hops: Vec::new(),
66            local_copy: true,
67            routed: true,
68            failure_reason: None,
69        }
70    }
71}
72
73/// Reason for routing failure
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum RouteFailure {
76    /// No parent available for upward routing
77    NoParent,
78    /// No children for downward routing
79    NoChildren,
80    /// Target node not found in topology
81    TargetNotFound,
82    /// No peers connected
83    NoPeers,
84    /// TTL expired
85    TtlExpired,
86    /// Message too large
87    MessageTooLarge,
88}
89
90/// Router for mesh messages
91///
92/// Determines where to send messages based on the current topology.
93#[derive(Debug, Clone)]
94pub struct MeshRouter {
95    /// Our node ID
96    pub node_id: NodeId,
97    /// Our hierarchy level
98    pub my_level: HierarchyLevel,
99}
100
101impl MeshRouter {
102    /// Create a new router
103    pub fn new(node_id: NodeId, my_level: HierarchyLevel) -> Self {
104        Self { node_id, my_level }
105    }
106
107    /// Route a message based on direction
108    pub fn route(&self, direction: RouteDirection, topology: &MeshTopology) -> RouteDecision {
109        match direction {
110            RouteDirection::Upward => self.route_upward(topology),
111            RouteDirection::Downward => self.route_downward(topology),
112            RouteDirection::Broadcast => self.route_broadcast(topology),
113            RouteDirection::Targeted(target_id) => {
114                self.route_targeted(&NodeId::new(target_id), topology)
115            }
116        }
117    }
118
119    /// Route upward to parent
120    fn route_upward(&self, topology: &MeshTopology) -> RouteDecision {
121        match &topology.parent {
122            Some(parent_id) => RouteDecision::success(vec![*parent_id], true),
123            None => RouteDecision::failed(RouteFailure::NoParent),
124        }
125    }
126
127    /// Route downward to children
128    fn route_downward(&self, topology: &MeshTopology) -> RouteDecision {
129        if topology.children.is_empty() {
130            RouteDecision::failed(RouteFailure::NoChildren)
131        } else {
132            RouteDecision::success(topology.children.clone(), true)
133        }
134    }
135
136    /// Route to all connected peers (broadcast)
137    fn route_broadcast(&self, topology: &MeshTopology) -> RouteDecision {
138        let all = topology.all_connected();
139        if all.is_empty() {
140            RouteDecision::failed(RouteFailure::NoPeers)
141        } else {
142            RouteDecision::success(all, true)
143        }
144    }
145
146    /// Route to a specific target node
147    fn route_targeted(&self, target: &NodeId, topology: &MeshTopology) -> RouteDecision {
148        // Check if target is directly connected
149        if topology.is_connected(target) {
150            return RouteDecision::success(vec![*target], false);
151        }
152
153        // Not directly connected - need to route through topology
154        // For now, use simple strategy:
155        // - If we have a parent, route upward (parent has broader view)
156        // - Otherwise, route to all children (flood downward)
157
158        if let Some(ref parent) = topology.parent {
159            // Route to parent, it will figure out where to send
160            RouteDecision::success(vec![*parent], false)
161        } else if !topology.children.is_empty() {
162            // Flood to all children
163            RouteDecision::success(topology.children.clone(), false)
164        } else {
165            RouteDecision::failed(RouteFailure::TargetNotFound)
166        }
167    }
168
169    /// Determine routing for a received message
170    ///
171    /// Given the source and destination, decide what to do with a message.
172    pub fn handle_received(
173        &self,
174        source: &NodeId,
175        destination: Option<&NodeId>,
176        direction: RouteDirection,
177        topology: &MeshTopology,
178    ) -> RouteDecision {
179        // Check if we are the destination
180        if let Some(dest) = destination {
181            if dest == &self.node_id {
182                return RouteDecision::local_only();
183            }
184        }
185
186        // Determine role of source (may be useful for advanced routing)
187        let _source_role = topology.get_role(source);
188
189        match direction {
190            RouteDirection::Upward => {
191                // Message going upward - forward to parent
192                self.route_upward(topology)
193            }
194            RouteDirection::Downward => {
195                // Message going downward - forward to children
196                // Exclude the source if it's one of our children
197                let mut children = topology.children.clone();
198                children.retain(|c| c != source);
199                if children.is_empty() {
200                    RouteDecision::local_only()
201                } else {
202                    RouteDecision::success(children, true)
203                }
204            }
205            RouteDirection::Broadcast => {
206                // Broadcast - forward to all except source
207                let mut all = topology.all_connected();
208                all.retain(|n| n != source);
209                RouteDecision::success(all, true)
210            }
211            RouteDirection::Targeted(target_id) => {
212                let target = NodeId::new(target_id);
213                if target == self.node_id {
214                    RouteDecision::local_only()
215                } else {
216                    self.route_targeted(&target, topology)
217                }
218            }
219        }
220    }
221
222    /// Get the best route for aggregation (data flowing upward)
223    ///
224    /// For HIVE-Lite nodes, this is always the parent.
225    pub fn aggregation_route(&self, topology: &MeshTopology) -> Option<NodeId> {
226        topology.parent
227    }
228
229    /// Get routes for dissemination (data flowing downward)
230    ///
231    /// Returns all children that should receive the data.
232    pub fn dissemination_routes(&self, topology: &MeshTopology) -> Vec<NodeId> {
233        topology.children.clone()
234    }
235}
236
237/// Message hop tracking for loop prevention
238#[derive(Debug, Clone)]
239pub struct HopTracker {
240    /// Maximum allowed hops
241    pub max_hops: u8,
242    /// Nodes this message has visited
243    pub visited: Vec<NodeId>,
244}
245
246impl HopTracker {
247    /// Create a new hop tracker
248    pub fn new(max_hops: u8) -> Self {
249        Self {
250            max_hops,
251            visited: Vec::new(),
252        }
253    }
254
255    /// Record visiting a node
256    pub fn visit(&mut self, node_id: NodeId) -> bool {
257        if self.visited.contains(&node_id) {
258            return false; // Loop detected
259        }
260        if self.visited.len() >= self.max_hops as usize {
261            return false; // TTL expired
262        }
263        self.visited.push(node_id);
264        true
265    }
266
267    /// Check if we've visited a node
268    pub fn has_visited(&self, node_id: &NodeId) -> bool {
269        self.visited.contains(node_id)
270    }
271
272    /// Get remaining hops
273    pub fn remaining_hops(&self) -> u8 {
274        self.max_hops.saturating_sub(self.visited.len() as u8)
275    }
276
277    /// Check if TTL is expired
278    pub fn is_expired(&self) -> bool {
279        self.visited.len() >= self.max_hops as usize
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    fn create_topology_with_parent() -> MeshTopology {
288        let mut topology = MeshTopology::new(HierarchyLevel::Platform, 3, 7);
289        topology.set_parent(NodeId::new(0x1000));
290        topology.add_child(NodeId::new(0x0001));
291        topology.add_child(NodeId::new(0x0002));
292        topology
293    }
294
295    #[test]
296    fn test_route_upward() {
297        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
298        let topology = create_topology_with_parent();
299
300        let decision = router.route(RouteDirection::Upward, &topology);
301        assert!(decision.routed);
302        assert_eq!(decision.next_hops.len(), 1);
303        assert_eq!(decision.next_hops[0].as_u32(), 0x1000);
304    }
305
306    #[test]
307    fn test_route_upward_no_parent() {
308        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
309        let topology = MeshTopology::new(HierarchyLevel::Platform, 3, 7);
310
311        let decision = router.route(RouteDirection::Upward, &topology);
312        assert!(!decision.routed);
313        assert_eq!(decision.failure_reason, Some(RouteFailure::NoParent));
314    }
315
316    #[test]
317    fn test_route_downward() {
318        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Squad);
319        let topology = create_topology_with_parent();
320
321        let decision = router.route(RouteDirection::Downward, &topology);
322        assert!(decision.routed);
323        assert_eq!(decision.next_hops.len(), 2);
324    }
325
326    #[test]
327    fn test_route_broadcast() {
328        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
329        let topology = create_topology_with_parent();
330
331        let decision = router.route(RouteDirection::Broadcast, &topology);
332        assert!(decision.routed);
333        assert_eq!(decision.next_hops.len(), 3); // Parent + 2 children
334    }
335
336    #[test]
337    fn test_route_targeted_direct() {
338        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
339        let topology = create_topology_with_parent();
340
341        // Target is a child (directly connected)
342        let decision = router.route(RouteDirection::Targeted(0x0001), &topology);
343        assert!(decision.routed);
344        assert_eq!(decision.next_hops.len(), 1);
345        assert_eq!(decision.next_hops[0].as_u32(), 0x0001);
346    }
347
348    #[test]
349    fn test_route_targeted_via_parent() {
350        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
351        let topology = create_topology_with_parent();
352
353        // Target is not directly connected - should route to parent
354        let decision = router.route(RouteDirection::Targeted(0x9999), &topology);
355        assert!(decision.routed);
356        assert_eq!(decision.next_hops.len(), 1);
357        assert_eq!(decision.next_hops[0].as_u32(), 0x1000); // Parent
358    }
359
360    #[test]
361    fn test_handle_received_for_us() {
362        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
363        let topology = create_topology_with_parent();
364
365        let decision = router.handle_received(
366            &NodeId::new(0x1000),
367            Some(&NodeId::new(0x1234)), // Destination is us
368            RouteDirection::Downward,
369            &topology,
370        );
371
372        assert!(decision.local_copy);
373        assert!(decision.next_hops.is_empty());
374    }
375
376    #[test]
377    fn test_hop_tracker() {
378        let mut tracker = HopTracker::new(3);
379
380        assert!(tracker.visit(NodeId::new(0x0001)));
381        assert!(tracker.visit(NodeId::new(0x0002)));
382        assert!(tracker.visit(NodeId::new(0x0003)));
383        assert!(!tracker.visit(NodeId::new(0x0004))); // TTL expired
384
385        assert!(tracker.has_visited(&NodeId::new(0x0001)));
386        assert!(!tracker.has_visited(&NodeId::new(0x0004)));
387    }
388
389    #[test]
390    fn test_hop_tracker_loop_detection() {
391        let mut tracker = HopTracker::new(10);
392
393        assert!(tracker.visit(NodeId::new(0x0001)));
394        assert!(tracker.visit(NodeId::new(0x0002)));
395        assert!(!tracker.visit(NodeId::new(0x0001))); // Loop detected
396    }
397
398    #[test]
399    fn test_aggregation_route() {
400        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Platform);
401        let topology = create_topology_with_parent();
402
403        let route = router.aggregation_route(&topology);
404        assert_eq!(route, Some(NodeId::new(0x1000)));
405    }
406
407    #[test]
408    fn test_dissemination_routes() {
409        let router = MeshRouter::new(NodeId::new(0x1234), HierarchyLevel::Squad);
410        let topology = create_topology_with_parent();
411
412        let routes = router.dissemination_routes(&topology);
413        assert_eq!(routes.len(), 2);
414    }
415}