use std::collections::HashMap;
const RTPS_MAGIC: [u8; 4] = [b'R', b'T', b'P', b'S'];
#[allow(dead_code)]
mod submsg {
pub const DATA: u8 = 0x15;
pub const DATA_FRAG: u8 = 0x16;
pub const HEARTBEAT: u8 = 0x07;
pub const ACKNACK: u8 = 0x06;
}
#[derive(Debug, Clone)]
pub struct RouteEntry {
pub source_id: u32,
pub topic_name: String,
pub last_seen: u64,
pub message_count: u64,
}
pub struct Router {
lora_nodes: HashMap<u8, Vec<String>>,
topic_to_lora: HashMap<String, Vec<u8>>,
known_topics: HashMap<String, RouteEntry>,
lora_to_wifi_filter: Option<Vec<String>>,
wifi_to_lora_filter: Option<Vec<String>>,
}
impl Router {
pub fn new() -> Self {
Self {
lora_nodes: HashMap::new(),
topic_to_lora: HashMap::new(),
known_topics: HashMap::new(),
lora_to_wifi_filter: None,
wifi_to_lora_filter: None,
}
}
pub fn set_lora_to_wifi_filter(&mut self, topics: Vec<String>) {
self.lora_to_wifi_filter = Some(topics);
}
pub fn set_wifi_to_lora_filter(&mut self, topics: Vec<String>) {
self.wifi_to_lora_filter = Some(topics);
}
pub fn clear_filters(&mut self) {
self.lora_to_wifi_filter = None;
self.wifi_to_lora_filter = None;
}
pub fn should_forward_to_wifi(&self, topic: Option<&str>) -> bool {
match (&self.lora_to_wifi_filter, topic) {
(None, _) => true,
(Some(_), None) => true,
(Some(filter), Some(t)) => filter.iter().any(|f| f == t),
}
}
pub fn should_forward_to_lora(&self, topic: Option<&str>) -> bool {
match (&self.wifi_to_lora_filter, topic) {
(None, _) => true,
(Some(_), None) => true,
(Some(filter), Some(t)) => filter.iter().any(|f| f == t),
}
}
pub fn register_lora_node(&mut self, node_id: u8, topic: &str) {
self.lora_nodes
.entry(node_id)
.or_default()
.push(topic.to_string());
self.topic_to_lora
.entry(topic.to_string())
.or_default()
.push(node_id);
}
pub fn get_lora_nodes_for_topic(&self, topic: &str) -> Vec<u8> {
self.topic_to_lora.get(topic).cloned().unwrap_or_default()
}
pub fn update_topic_stats(&mut self, topic: &str, source_id: u32) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.known_topics
.entry(topic.to_string())
.and_modify(|e| {
e.last_seen = now;
e.message_count += 1;
})
.or_insert(RouteEntry {
source_id,
topic_name: topic.to_string(),
last_seen: now,
message_count: 1,
});
}
pub fn known_topics(&self) -> Vec<&RouteEntry> {
self.known_topics.values().collect()
}
pub fn extract_topic_from_rtps(&self, data: &[u8]) -> Option<String> {
if data.len() < 20 || data[0..4] != RTPS_MAGIC {
return None;
}
let mut pos = 20;
while pos + 4 <= data.len() {
let submsg_id = data[pos];
let submsg_len = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize;
if submsg_id == submsg::DATA && pos + 4 + submsg_len <= data.len() {
let submsg_data = &data[pos + 4..pos + 4 + submsg_len];
if let Some(topic) = self.find_topic_string(submsg_data) {
return Some(topic);
}
}
pos += 4 + submsg_len;
pos = (pos + 3) & !3;
}
None
}
fn find_topic_string(&self, data: &[u8]) -> Option<String> {
let known_prefixes = [
"Temperature",
"Humidity",
"Pressure",
"GPS",
"Command",
"Config",
"Status",
"Sensor",
"Data",
];
for prefix in known_prefixes {
if let Some(pos) = data
.windows(prefix.len())
.position(|w| w == prefix.as_bytes())
{
let start = pos;
let mut end = start;
while end < data.len() && data[end] >= 0x20 && data[end] < 0x7F {
end += 1;
}
if end > start {
if let Ok(s) = std::str::from_utf8(&data[start..end]) {
return Some(s.to_string());
}
}
}
}
None
}
}
impl Default for Router {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_router_creation() {
let router = Router::new();
assert!(router.known_topics().is_empty());
}
#[test]
fn test_filter_lora_to_wifi() {
let mut router = Router::new();
assert!(router.should_forward_to_wifi(Some("Temperature")));
router.set_lora_to_wifi_filter(vec!["Temperature".to_string()]);
assert!(router.should_forward_to_wifi(Some("Temperature")));
assert!(!router.should_forward_to_wifi(Some("Humidity")));
assert!(router.should_forward_to_wifi(None));
}
#[test]
fn test_lora_node_registration() {
let mut router = Router::new();
router.register_lora_node(1, "Temperature");
router.register_lora_node(2, "Humidity");
let nodes = router.get_lora_nodes_for_topic("Temperature");
assert_eq!(nodes, vec![1]);
}
#[test]
fn test_topic_stats() {
let mut router = Router::new();
router.update_topic_stats("Temperature", 1);
router.update_topic_stats("Temperature", 1);
let topics = router.known_topics();
assert_eq!(topics.len(), 1);
let temp = topics
.iter()
.find(|e| e.topic_name == "Temperature")
.unwrap();
assert_eq!(temp.message_count, 2);
}
}