use std::collections::HashSet;
use std::time::{SystemTime, UNIX_EPOCH};
use indexmap::IndexMap;
use mumu::parser::types::Value;
use super::iterator::{handle_from_engine, LldpEngine};
use super::options::{LldpMode, LldpOptions};
use super::proto::DiscoveryProtocol;
#[derive(Hash, Eq, PartialEq)]
struct SeenKey {
iface: String,
proto: DiscoveryProtocol,
chassis_id: String,
port_id: String,
}
impl SeenKey {
fn new(iface: &str, proto: DiscoveryProtocol, chassis_id: &str, port_id: &str) -> Self {
Self {
iface: iface.to_string(),
proto,
chassis_id: chassis_id.to_string(),
port_id: port_id.to_string(),
}
}
}
struct StreamingEngine {
_mode: LldpMode,
ifaces: Vec<String>,
protocols: Vec<DiscoveryProtocol>,
ttl: u16,
hostname: String,
port_id_override: Option<String>,
remain: Option<usize>,
idx: usize,
verbose: bool,
seen: HashSet<SeenKey>,
update_period: usize,
}
impl StreamingEngine {
fn new(opts: LldpOptions) -> Self {
let mut ifaces: Vec<String> = if !opts.ifaces.is_empty() {
opts.ifaces.clone()
} else if let Some(primary) = opts.iface.clone() {
vec![primary]
} else {
vec!["eth0".to_string()]
};
if let Some(primary) = &opts.iface {
if !ifaces.iter().any(|x| x == primary) {
ifaces.push(primary.clone());
}
}
let protocols: Vec<DiscoveryProtocol> = if opts.protocols.is_empty() {
vec![DiscoveryProtocol::LLDP]
} else {
opts.protocols.clone()
};
Self {
_mode: opts.mode,
ifaces,
protocols,
ttl: opts.ttl.max(1),
hostname: opts.hostname.unwrap_or_else(|| "stub-host".into()),
port_id_override: opts.port_id,
remain: opts.count, idx: 0,
verbose: opts.verbose,
seen: HashSet::new(),
update_period: 8, }
}
fn synth_values(&self, i: usize) -> (String, DiscoveryProtocol, String, String, String, String, String, Vec<String>) {
let iface = self.ifaces[i % self.ifaces.len()].clone();
let proto = self.protocols[(i / self.ifaces.len()) % self.protocols.len()];
let oct = 0x10u8.wrapping_add((i as u8) % 0xEF);
let chassis_id = format!("aa:bb:cc:dd:ee:{:02x}", oct);
let port_id = self
.port_id_override
.clone()
.unwrap_or_else(|| iface.clone());
let system_name = self.hostname.clone();
let system_desc = "MuMu-LLDP stub 0.1".to_string();
let management_ip = format!("198.51.100.{}", 10 + (i % 140));
let capabilities = vec!["bridge".into(), "router".into()];
(iface, proto, chassis_id, port_id, system_name, system_desc, management_ip, capabilities)
}
#[inline]
fn now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
}
impl LldpEngine for StreamingEngine {
fn next(&mut self) -> Result<Value, String> {
if let Some(left) = self.remain.as_mut() {
if *left == 0 {
return Err("NO_MORE_DATA".into());
}
}
let i = self.idx;
self.idx = self.idx.wrapping_add(1);
let (iface, proto, chassis_id, mut port_id, system_name, mut system_desc, management_ip, capabilities) =
self.synth_values(i);
let mut port_desc = format!("StubPort {}", iface);
let vlan = if (i % self.update_period) == 0 && i != 0 {
port_desc = format!("StubPort {} (u{})", iface, i);
system_desc = format!("MuMu-LLDP stub 0.1 | u{}", i);
if self.port_id_override.is_none() {
port_id = format!("{}:{}", port_id, (i % 4) + 1);
}
Some(((i % 4094) + 1).to_string())
} else {
None
};
let key = SeenKey::new(&iface, proto, &chassis_id, &port_id);
let is_new = self.seen.insert(key);
let mut out: IndexMap<String, Value> = IndexMap::new();
out.insert("ok".into(), Value::Bool(true));
out.insert(
"event".into(),
Value::SingleString(if is_new { "add".into() } else { "update".into() }),
);
out.insert("iface".into(), Value::SingleString(iface));
out.insert(
"protocol".into(),
Value::SingleString(proto.as_str().to_string()),
);
out.insert("chassis_id".into(), Value::SingleString(chassis_id));
out.insert("port_id".into(), Value::SingleString(port_id));
out.insert("system_name".into(), Value::SingleString(system_name));
out.insert("system_desc".into(), Value::SingleString(system_desc));
out.insert("port_desc".into(), Value::SingleString(port_desc));
if let Some(v) = vlan {
out.insert("vlan".into(), Value::SingleString(v));
}
out.insert(
"management_ip".into(),
Value::SingleString(management_ip),
);
out.insert("capabilities".into(), Value::StrArray(capabilities));
out.insert("ttl".into(), Value::Int(self.ttl as i32));
out.insert("timestamp_ms".into(), Value::Long(Self::now_ms() as i64));
if self.verbose {
if let Value::SingleString(ev) = out.get("event").cloned().unwrap_or(Value::SingleString("?".into())) {
if let Value::SingleString(proto_s) = out
.get("protocol")
.cloned()
.unwrap_or(Value::SingleString("?".into()))
{
if let Value::SingleString(ifname) = out
.get("iface")
.cloned()
.unwrap_or(Value::SingleString("?".into()))
{
eprintln!(
"[net:lldp][stub] emit event={} proto={} iface={}",
ev, proto_s, ifname
);
}
}
}
}
if let Some(left) = self.remain.as_mut() {
if *left > 0 {
*left -= 1;
}
}
Ok(Value::KeyedArray(out))
}
}
pub fn spawn_iterator(opts: LldpOptions) -> mumu::parser::types::IteratorHandle {
if opts.verbose {
eprintln!(
"[net:lldp] stub spawn: mode={:?} iface={:?} ifaces={:?} ttl={} count={:?} protos={:?} hostname={:?} port_id={:?}",
opts.mode,
opts.iface,
if opts.ifaces.is_empty() { None::<Vec<String>> } else { Some(opts.ifaces.clone()) },
opts.ttl,
opts.count,
if opts.protocols.is_empty() { vec![DiscoveryProtocol::LLDP] } else { opts.protocols.clone() },
opts.hostname,
opts.port_id,
);
}
let engine = StreamingEngine::new(opts);
handle_from_engine(Box::new(engine))
}