Skip to main content

hdds_micro/transport/mesh/
mod.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! Mesh Multi-hop Transport for HDDS Micro
5//!
6//! Implements a simple flooding-based mesh protocol that allows messages
7//! to be relayed across multiple hops to extend range.
8//!
9//! ## Architecture
10//!
11//! ```text
12//! +--------------+   LoRa    +--------------+   LoRa    +--------------+
13//! |  Node A      | ~~~~~~~~~ |  Node B      | ~~~~~~~~~ |  Gateway     |
14//! |  (Sensor)    |  Hop 1    |  (Relay)     |  Hop 2    |              |
15//! +--------------+           +--------------+           +--------------+
16//! ```
17//!
18//! ## Protocol
19//!
20//! - Simple controlled flooding with TTL
21//! - Duplicate detection via sequence cache
22//! - Optional RSSI-based neighbor tracking
23//! - Configurable relay behavior
24
25#![allow(dead_code)]
26
27mod header;
28mod neighbor;
29mod router;
30mod seen;
31
32pub use header::{MeshFlags, MeshHeader, MESH_HEADER_SIZE};
33pub use neighbor::{Neighbor, NeighborTable};
34pub use router::{MeshConfig, MeshRouter, RelayDecision};
35pub use seen::SeenCache;
36
37use crate::error::{Error, Result};
38use crate::transport::Transport;
39
40/// Maximum mesh hops (TTL)
41pub const MAX_TTL: u8 = 7;
42
43/// Default TTL for new messages
44pub const DEFAULT_TTL: u8 = 3;
45
46/// Mesh transport wrapper
47///
48/// Wraps an underlying transport (LoRa, HC-12) and adds mesh routing.
49pub struct MeshTransport<T: Transport, const NEIGHBORS: usize, const SEEN: usize> {
50    /// Underlying transport
51    inner: T,
52    /// Mesh router
53    router: MeshRouter<NEIGHBORS, SEEN>,
54    /// Our node ID
55    node_id: u8,
56    /// Sequence number for outgoing messages
57    seq: u16,
58    /// Receive buffer for mesh header + payload
59    rx_buf: [u8; 256],
60}
61
62impl<T: Transport, const NEIGHBORS: usize, const SEEN: usize> MeshTransport<T, NEIGHBORS, SEEN> {
63    /// Create a new mesh transport
64    pub fn new(inner: T, node_id: u8, config: MeshConfig) -> Self {
65        Self {
66            inner,
67            router: MeshRouter::new(node_id, config),
68            node_id,
69            seq: 0,
70            rx_buf: [0u8; 256],
71        }
72    }
73
74    /// Get our node ID
75    pub fn node_id(&self) -> u8 {
76        self.node_id
77    }
78
79    /// Get next sequence number
80    fn next_seq(&mut self) -> u16 {
81        let seq = self.seq;
82        self.seq = self.seq.wrapping_add(1);
83        seq
84    }
85
86    /// Send a message (originates from this node)
87    pub fn send_mesh(&mut self, payload: &[u8]) -> Result<()> {
88        if payload.len() > 255 - MESH_HEADER_SIZE {
89            return Err(Error::BufferTooSmall);
90        }
91
92        let header = MeshHeader {
93            src: self.node_id,
94            dst: 0xFF, // Broadcast
95            seq: self.next_seq(),
96            ttl: self.router.config().default_ttl,
97            flags: MeshFlags::empty(),
98            hop_count: 0,
99        };
100
101        self.send_with_header(&header, payload)
102    }
103
104    /// Send to a specific destination
105    pub fn send_to(&mut self, dest: u8, payload: &[u8]) -> Result<()> {
106        if payload.len() > 255 - MESH_HEADER_SIZE {
107            return Err(Error::BufferTooSmall);
108        }
109
110        let header = MeshHeader {
111            src: self.node_id,
112            dst: dest,
113            seq: self.next_seq(),
114            ttl: self.router.config().default_ttl,
115            flags: MeshFlags::empty(),
116            hop_count: 0,
117        };
118
119        self.send_with_header(&header, payload)
120    }
121
122    /// Send with a specific header
123    fn send_with_header(&mut self, header: &MeshHeader, payload: &[u8]) -> Result<()> {
124        let mut buf = [0u8; 256];
125        let header_len = header.encode(&mut buf)?;
126
127        let total_len = header_len + payload.len();
128        if total_len > buf.len() {
129            return Err(Error::BufferTooSmall);
130        }
131
132        buf[header_len..total_len].copy_from_slice(payload);
133
134        // Use broadcast locator for mesh
135        let locator = crate::rtps::Locator::udpv4([255, 255, 255, 255], 0);
136        self.inner.send(&buf[..total_len], &locator)?;
137        Ok(())
138    }
139
140    /// Receive a message (may be for us or needs relay)
141    ///
142    /// Returns `Some((src_node, payload))` if message is for us.
143    /// Automatically relays messages that need forwarding.
144    pub fn recv_mesh(&mut self, buf: &mut [u8]) -> Result<Option<(u8, usize)>> {
145        // Try to receive from underlying transport
146        let (len, _locator) = match self.inner.try_recv(&mut self.rx_buf) {
147            Ok(result) => result,
148            Err(Error::ResourceExhausted) => return Ok(None),
149            Err(e) => return Err(e),
150        };
151
152        if len < MESH_HEADER_SIZE {
153            return Ok(None); // Too short for mesh header
154        }
155
156        // Decode mesh header
157        let header = match MeshHeader::decode(&self.rx_buf[..len]) {
158            Ok(h) => h,
159            Err(_) => return Ok(None), // Invalid header
160        };
161
162        // Get RSSI if available (for neighbor tracking)
163        let rssi = self.inner.last_rssi();
164
165        // Let router decide what to do
166        let decision = self.router.process_received(&header, rssi);
167
168        match decision {
169            RelayDecision::Deliver => {
170                // Message is for us, copy payload to output buffer
171                let payload_start = MESH_HEADER_SIZE;
172                let payload_len = len - payload_start;
173
174                if payload_len > buf.len() {
175                    return Err(Error::BufferTooSmall);
176                }
177
178                buf[..payload_len].copy_from_slice(&self.rx_buf[payload_start..len]);
179                Ok(Some((header.src, payload_len)))
180            }
181            RelayDecision::Relay(new_header) => {
182                // Need to relay this message
183                let payload_start = MESH_HEADER_SIZE;
184                let payload_len = len - payload_start;
185
186                // Copy payload first to avoid borrow conflict
187                let is_for_us = header.dst == 0xFF || header.dst == self.node_id;
188                let should_deliver = is_for_us && payload_len <= buf.len();
189
190                if should_deliver {
191                    buf[..payload_len].copy_from_slice(&self.rx_buf[payload_start..len]);
192                }
193
194                // Now we can borrow rx_buf again for relay
195                let mut relay_buf = [0u8; 256];
196                relay_buf[..payload_len].copy_from_slice(&self.rx_buf[payload_start..len]);
197
198                // Send with updated header (decremented TTL, incremented hop)
199                let _ = self.send_with_header(&new_header, &relay_buf[..payload_len]);
200
201                if should_deliver {
202                    Ok(Some((header.src, payload_len)))
203                } else {
204                    Ok(None)
205                }
206            }
207            RelayDecision::Drop => {
208                // Duplicate or TTL expired
209                Ok(None)
210            }
211        }
212    }
213
214    /// Get neighbor table reference
215    pub fn neighbors(&self) -> &NeighborTable<NEIGHBORS> {
216        self.router.neighbors()
217    }
218
219    /// Get router statistics
220    pub fn stats(&self) -> MeshStats {
221        self.router.stats()
222    }
223
224    /// Get mutable reference to underlying transport
225    pub fn inner_mut(&mut self) -> &mut T {
226        &mut self.inner
227    }
228
229    /// Get reference to underlying transport
230    pub fn inner(&self) -> &T {
231        &self.inner
232    }
233}
234
235/// Mesh statistics
236#[derive(Debug, Clone, Copy, Default)]
237pub struct MeshStats {
238    /// Messages originated by us
239    pub tx_originated: u32,
240    /// Messages relayed
241    pub tx_relayed: u32,
242    /// Messages received for us
243    pub rx_delivered: u32,
244    /// Messages dropped (duplicate)
245    pub rx_duplicate: u32,
246    /// Messages dropped (TTL expired)
247    pub rx_ttl_expired: u32,
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::transport::NullTransport;
254
255    #[test]
256    fn test_mesh_transport_creation() {
257        let inner = NullTransport::default();
258        let config = MeshConfig::default();
259        let mesh: MeshTransport<_, 8, 32> = MeshTransport::new(inner, 1, config);
260
261        assert_eq!(mesh.node_id(), 1);
262    }
263
264    #[test]
265    fn test_mesh_sequence_increment() {
266        let inner = NullTransport::default();
267        let config = MeshConfig::default();
268        let mut mesh: MeshTransport<_, 8, 32> = MeshTransport::new(inner, 1, config);
269
270        assert_eq!(mesh.next_seq(), 0);
271        assert_eq!(mesh.next_seq(), 1);
272        assert_eq!(mesh.next_seq(), 2);
273    }
274}