use super::header::MeshHeader;
use super::neighbor::NeighborTable;
use super::seen::SeenCache;
use super::{MeshStats, DEFAULT_TTL, MAX_TTL};
#[derive(Debug, Clone, Copy)]
pub struct MeshConfig {
pub default_ttl: u8,
pub relay_enabled: bool,
pub relay_broadcast: bool,
pub min_relay_rssi: i16,
pub neighbor_lifetime: u32,
pub seen_lifetime: u32,
}
impl Default for MeshConfig {
fn default() -> Self {
Self {
default_ttl: DEFAULT_TTL,
relay_enabled: true,
relay_broadcast: true,
min_relay_rssi: -100, neighbor_lifetime: 300, seen_lifetime: 60, }
}
}
impl MeshConfig {
pub fn relay_node() -> Self {
Self {
relay_enabled: true,
relay_broadcast: true,
..Default::default()
}
}
pub fn endpoint() -> Self {
Self {
relay_enabled: false,
relay_broadcast: false,
..Default::default()
}
}
pub fn gateway() -> Self {
Self {
default_ttl: MAX_TTL,
relay_enabled: true,
relay_broadcast: true,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub enum RelayDecision {
Deliver,
Relay(MeshHeader),
Drop,
}
pub struct MeshRouter<const NEIGHBORS: usize, const SEEN: usize> {
node_id: u8,
config: MeshConfig,
neighbors: NeighborTable<NEIGHBORS>,
seen: SeenCache<SEEN>,
stats: MeshStats,
}
impl<const NEIGHBORS: usize, const SEEN: usize> MeshRouter<NEIGHBORS, SEEN> {
pub fn new(node_id: u8, config: MeshConfig) -> Self {
Self {
node_id,
config,
neighbors: NeighborTable::new(config.neighbor_lifetime),
seen: SeenCache::new(config.seen_lifetime),
stats: MeshStats::default(),
}
}
pub fn config(&self) -> &MeshConfig {
&self.config
}
pub fn neighbors(&self) -> &NeighborTable<NEIGHBORS> {
&self.neighbors
}
pub fn stats(&self) -> MeshStats {
self.stats
}
pub fn tick(&mut self) {
self.neighbors.tick();
self.seen.tick();
self.neighbors.expire_old();
}
pub fn process_received(&mut self, header: &MeshHeader, rssi: Option<i16>) -> RelayDecision {
if let Some(rssi) = rssi {
self.neighbors.update(header.src, rssi);
}
let msg_id = header.message_id();
if self.seen.check_and_mark(msg_id) {
self.stats.rx_duplicate += 1;
return RelayDecision::Drop;
}
let is_for_us = header.dst == self.node_id || header.dst == 0xFF;
let should_relay = self.should_relay(header, rssi);
if should_relay {
if let Some(relay_header) = header.for_relay() {
self.stats.tx_relayed += 1;
if is_for_us {
self.stats.rx_delivered += 1;
}
return RelayDecision::Relay(relay_header);
}
self.stats.rx_ttl_expired += 1;
if is_for_us {
self.stats.rx_delivered += 1;
return RelayDecision::Deliver;
}
return RelayDecision::Drop;
}
if is_for_us {
self.stats.rx_delivered += 1;
RelayDecision::Deliver
} else {
RelayDecision::Drop
}
}
fn should_relay(&self, header: &MeshHeader, rssi: Option<i16>) -> bool {
if header.src == self.node_id {
return false;
}
if header.dst == self.node_id {
return false;
}
if !self.config.relay_enabled {
return false;
}
if header.is_broadcast() && !self.config.relay_broadcast {
return false;
}
if let Some(rssi) = rssi {
if rssi < self.config.min_relay_rssi {
return false;
}
}
if header.ttl == 0 {
return false;
}
true
}
pub fn record_originated(&mut self, header: &MeshHeader) {
self.seen.mark_seen(header.message_id());
self.stats.tx_originated += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_router_creation() {
let router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::default());
assert_eq!(router.config().default_ttl, DEFAULT_TTL);
}
#[test]
fn test_relay_decision_deliver() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::endpoint());
let header = MeshHeader::new(2, 1, 100, 3);
let decision = router.process_received(&header, Some(-70));
match decision {
RelayDecision::Deliver => {}
_ => panic!("Expected Deliver"),
}
}
#[test]
fn test_relay_decision_broadcast() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::relay_node());
let header = MeshHeader::broadcast(2, 100, 3);
let decision = router.process_received(&header, Some(-70));
match decision {
RelayDecision::Relay(new_header) => {
assert_eq!(new_header.ttl, 2); assert_eq!(new_header.hop_count, 1); }
_ => panic!("Expected Relay"),
}
}
#[test]
fn test_relay_decision_duplicate() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::relay_node());
let header = MeshHeader::broadcast(2, 100, 3);
let decision1 = router.process_received(&header, Some(-70));
assert!(matches!(decision1, RelayDecision::Relay(_)));
let decision2 = router.process_received(&header, Some(-70));
assert!(matches!(decision2, RelayDecision::Drop));
assert_eq!(router.stats().rx_duplicate, 1);
}
#[test]
fn test_relay_decision_ttl_expired() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::relay_node());
let header = MeshHeader::new(2, 3, 100, 0);
let decision = router.process_received(&header, Some(-70));
assert!(matches!(decision, RelayDecision::Drop));
}
#[test]
fn test_relay_disabled() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::endpoint());
let header = MeshHeader::broadcast(2, 100, 3);
let decision = router.process_received(&header, Some(-70));
match decision {
RelayDecision::Deliver => {}
_ => panic!("Expected Deliver (not Relay)"),
}
}
#[test]
fn test_own_message_not_relayed() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::relay_node());
let header = MeshHeader::broadcast(1, 100, 3);
let decision = router.process_received(&header, Some(-70));
match decision {
RelayDecision::Deliver => {}
_ => panic!("Expected Deliver"),
}
}
#[test]
fn test_rssi_threshold() {
let config = MeshConfig {
min_relay_rssi: -80,
relay_enabled: true,
relay_broadcast: true,
..Default::default()
};
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, config);
let header1 = MeshHeader::broadcast(2, 100, 3);
let decision1 = router.process_received(&header1, Some(-70));
assert!(matches!(decision1, RelayDecision::Relay(_)));
let header2 = MeshHeader::broadcast(3, 101, 3);
let decision2 = router.process_received(&header2, Some(-90));
assert!(matches!(decision2, RelayDecision::Deliver));
}
#[test]
fn test_neighbor_tracking() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::relay_node());
let h1 = MeshHeader::broadcast(2, 100, 3);
let h2 = MeshHeader::broadcast(3, 101, 3);
let h3 = MeshHeader::broadcast(4, 102, 3);
router.process_received(&h1, Some(-70));
router.process_received(&h2, Some(-80));
router.process_received(&h3, Some(-60));
assert_eq!(router.neighbors().len(), 3);
let best = router.neighbors().best_neighbor().unwrap();
assert_eq!(best.node_id, 4); }
#[test]
fn test_stats_tracking() {
let mut router: MeshRouter<8, 32> = MeshRouter::new(1, MeshConfig::relay_node());
let h1 = MeshHeader::broadcast(2, 100, 3);
router.process_received(&h1, Some(-70));
router.process_received(&h1, Some(-70));
let h2 = MeshHeader::new(3, 1, 101, 3);
router.process_received(&h2, Some(-70));
let stats = router.stats();
assert_eq!(stats.tx_relayed, 1);
assert_eq!(stats.rx_duplicate, 1);
assert_eq!(stats.rx_delivered, 2); }
#[test]
fn test_config_presets() {
let relay = MeshConfig::relay_node();
assert!(relay.relay_enabled);
assert!(relay.relay_broadcast);
let endpoint = MeshConfig::endpoint();
assert!(!endpoint.relay_enabled);
let gateway = MeshConfig::gateway();
assert_eq!(gateway.default_ttl, MAX_TTL);
}
}