use std::{
collections::HashMap,
cmp::Ordering
};
use chrono::{DateTime,Utc};
use serde::{Deserialize, Serialize};
#[derive(Default,Serialize, Deserialize)]
pub struct Meshviewer {
pub timestamp: Option<DateTime<Utc>>,
pub nodes: Vec<Node>,
pub links: Vec<Link>
}
#[derive(Default,Serialize, Deserialize)]
pub struct Node {
pub firstseen: DateTime<Utc>,
pub lastseen: DateTime<Utc>,
pub is_online: bool,
pub is_gateway: bool,
pub clients: u32,
pub clients_wifi24: u32,
pub clients_wifi5: u32,
pub clients_other: u32,
pub clients_owe: u32,
pub clients_owe24: u32,
pub clients_owe5: u32,
pub rootfs_usage: f64,
pub loadavg: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_usage: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uptime: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "String::is_empty")]
pub gateway_nexthop: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub gateway: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub gateway6: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_tq: Option<f64>,
pub node_id: String,
pub mac: String,
pub addresses: Vec<String>,
pub domain: String,
pub hostname: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub owner: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<Location>,
#[serde(skip_serializing_if = "Option::is_none")]
pub firmware: Option<Firmware>,
#[serde(skip_serializing_if = "Option::is_none")]
pub autoupdater: Option<Autoupdater>,
pub nproc: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>
}
#[derive(Default,Serialize, Deserialize)]
pub struct Firmware {
#[serde(skip_serializing_if = "String::is_empty")]
pub base: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub release: String
}
#[derive(Default,Serialize, Deserialize)]
pub struct Autoupdater {
pub enabled: bool,
#[serde(skip_serializing_if = "String::is_empty")]
pub branch: String
}
#[derive(Default,Serialize, Deserialize)]
pub struct Location {
#[serde(skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Link {
#[serde(rename="type")]
pub link_type: String,
pub source: String,
pub target: String,
pub source_tq: f64,
pub target_tq: f64,
pub source_addr: String,
pub target_addr: String,
}
pub struct MeshviewerBuilder {
nodes: HashMap<String,Node>,
links: HashMap<String,Link>
}
impl MeshviewerBuilder {
pub fn new() -> Self {
MeshviewerBuilder{
nodes: HashMap::new(),
links: HashMap::new()
}
}
pub fn build(self) -> Meshviewer {
let mut mv = Meshviewer::default();
mv.nodes = self.nodes.into_values().collect();
mv.links = self.links.into_values().collect();
mv
}
pub fn add_node(mut self, node: Node) -> Self {
let id = node.node_id.clone();
self.nodes.insert(id, node);
self
}
pub fn add_link(mut self, link: Link) -> Self {
let source_addr = link.source_addr.clone();
let target_addr = link.target_addr.clone();
let ordering = source_addr.cmp(&target_addr.clone());
let id = match ordering {
Ordering::Less => [source_addr, "-".to_string(), target_addr].concat(),
Ordering::Greater => [target_addr, "-".to_string(), source_addr].concat(),
Ordering::Equal => panic!("source and target id are equal"),
};
let current_tq = match self.links.get(&id.clone()) {
Some(stored_link) => match ordering {
Ordering::Less => stored_link.target_tq,
Ordering::Greater => stored_link.source_tq,
Ordering::Equal => panic!("source and target id are equal"),
},
None => 0.0
};
let ordered_link = match ordering {
Ordering::Less => Link{
link_type: link.link_type,
source: link.source,
target: link.target,
source_addr: link.source_addr,
target_addr: link.target_addr,
source_tq: link.source_tq,
target_tq: current_tq
},
Ordering::Greater => Link{
link_type: link.link_type,
source: link.target,
target: link.source,
source_addr: link.target_addr,
target_addr: link.source_addr,
source_tq: current_tq,
target_tq: link.source_tq
},
Ordering::Equal => panic!("source and target id are equal"),
};
self.links.insert(id, ordered_link);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn use_builder() {
let mv = MeshviewerBuilder::new()
.add_node(Node{
firstseen: DateTime::from_timestamp(61, 0).unwrap(),
lastseen: DateTime::from_timestamp(61, 0).unwrap(),
node_id: "test01".to_string(),
is_online: true,
..Default::default()
})
.add_link(Link{
link_type: "wifi5".to_string(),
source: "test01".to_string(),
target: "test02".to_string(),
source_tq: 0.1,
target_tq: -9.0,
source_addr: "test01-if01".to_string(),
target_addr: "test02-if01".to_string(),
})
.add_link(Link{
link_type: "vpn".to_string(),
source: "test02".to_string(),
target: "test01".to_string(),
source_tq: 0.3,
target_tq: -9.0,
source_addr: "test02-if01".to_string(),
target_addr: "test01-if01".to_string(),
})
.build();
assert_eq!(mv.nodes.len(), 1);
assert_eq!(mv.links.len(), 1);
assert_eq!(mv.links[0], Link{
link_type: "vpn".to_string(),
source: "test01".to_string(),
target: "test02".to_string(),
source_tq: 0.1,
target_tq: 0.3,
source_addr: "test01-if01".to_string(),
target_addr: "test02-if01".to_string(),
})
}
}