use std::collections::HashMap;
#[cfg(feature = "topology_zoo")]
use geoutils::Location;
use itertools::Itertools;
#[cfg(feature = "topology_zoo")]
use mapproj::{cylindrical::mer::Mer, LonLat, Projection};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[cfg(feature = "topology_zoo")]
use crate::topology_zoo::TopologyZoo;
use crate::{
config::{ConfigExpr, ConfigModifier, NetworkConfig},
event::{BasicEventQueue, Event, EventQueue},
network::Network,
ospf::{LocalOspf, OspfImpl},
policies::{FwPolicy, Policy, PolicyError},
types::{IntoIpv4Prefix, Ipv4Prefix, NetworkError, Prefix, RouterId, ASN},
};
const JSON_FIELD_NAME_NETWORK: &str = "net";
const JSON_FIELD_NAME_CONFIG: &str = "config_nodes_routes";
type ExportConfig<P> = Vec<ConfigExpr<P>>;
type ExportRouters = Vec<(RouterId, String, ASN)>;
type ExportLinks = Vec<(RouterId, RouterId)>;
type ExportTuple<P> = (ExportConfig<P>, ExportRouters, ExportLinks);
impl<P, Q, Ospf> Network<P, Q, Ospf>
where
P: Prefix,
Q: EventQueue<P> + Serialize,
Ospf: OspfImpl,
{
pub fn as_json_str(&self) -> String {
serde_json::to_string(&json!({
JSON_FIELD_NAME_NETWORK: serde_json::to_value(self).unwrap(),
JSON_FIELD_NAME_CONFIG: self.as_config_json_value(),
}))
.unwrap()
}
pub fn as_json_str_compact(&self) -> String {
serde_json::to_string(&json!({
JSON_FIELD_NAME_CONFIG: self.as_config_json_value()
}))
.unwrap()
}
fn as_config_json_value(&self) -> serde_json::Value {
serde_json::to_value(self.as_topo_config()).unwrap()
}
}
impl<P, Q, Ospf> Network<P, Q, Ospf>
where
P: Prefix,
Q: EventQueue<P>,
Ospf: OspfImpl,
{
fn as_topo_config(&self) -> ExportTuple<P> {
let config = Vec::from_iter(self.get_config().unwrap().iter().cloned());
let mut nodes: ExportRouters = self
.routers()
.map(|r| (r.router_id(), r.name().to_string(), r.asn()))
.collect();
nodes.sort_by_key(|(r, _, _)| *r);
let links: ExportLinks = self
.ospf
.edges()
.map(|e| (e.src(), e.dst()))
.map(|(a, b)| if a < b { (a, b) } else { (b, a) })
.unique()
.collect();
(config, nodes, links)
}
}
#[derive(Debug, Serialize)]
#[allow(clippy::type_complexity)]
pub struct WebExporter {
net: Option<Network<Ipv4Prefix, BasicEventQueue<Ipv4Prefix>, LocalOspf>>,
config_node_routes: ExportTuple<Ipv4Prefix>,
pos: Option<HashMap<RouterId, Point>>,
spec:
Option<HashMap<RouterId, Vec<(FwPolicy<Ipv4Prefix>, Result<(), PolicyError<Ipv4Prefix>>)>>>,
#[cfg(feature = "topology_zoo")]
topology_zoo: Option<TopologyZoo>,
replay: Option<Vec<(Event<Ipv4Prefix, ()>, Option<usize>)>>,
#[serde(skip)]
compact: bool,
}
impl WebExporter {
pub fn new<P: Prefix, Q: EventQueue<P> + Clone, Ospf: OspfImpl>(
net: &Network<P, Q, Ospf>,
) -> Self {
let mut net = net.clone();
let mut queue: BasicEventQueue<Ipv4Prefix> = Default::default();
while let Some(e) = net.queue.pop() {
queue.0.push_back(e.into_ipv4_prefix())
}
let net = net.into_global_ospf().unwrap().into_local_ospf().unwrap();
let Ok(net) = net.into_ipv4_prefix(queue) else {
unreachable!("Queue was emptied above")
};
let config_node_routes = net.as_topo_config();
Self {
net: Some(net),
config_node_routes,
pos: None,
spec: None,
#[cfg(feature = "topology_zoo")]
topology_zoo: None,
replay: None,
compact: false,
}
}
pub fn compact(mut self) -> Self {
self.compact = true;
self
}
pub fn spec<P: Prefix>(mut self, spec: Vec<FwPolicy<P>>) -> Self {
let Some(net) = self.net.as_ref() else {
self.spec = Some(
spec.into_iter()
.map(FwPolicy::into_ipv4_prefix)
.map(|s| (s.router().unwrap(), (s, Ok(()))))
.into_group_map(),
);
return self;
};
let mut fw_state = net.get_forwarding_state();
self.spec = Some(
spec.into_iter()
.map(FwPolicy::into_ipv4_prefix)
.map(|s| (s.router().unwrap(), (s.clone(), s.check(&mut fw_state))))
.into_group_map(),
);
self
}
#[cfg(feature = "topology_zoo")]
pub fn topology_zoo(mut self, topo: TopologyZoo) -> Self {
fn rad(x: Location) -> LonLat {
let mut lon = x.longitude();
let mut lat = x.latitude();
if lon < 0.0 {
lon += 360.0;
}
lon = lon * std::f64::consts::PI / 180.0;
lat = lat * std::f64::consts::PI / 180.0;
LonLat::new(lon, lat)
}
let mut geo = topo.geo_location();
geo.retain(|_, pos| pos.latitude() != 0.0 || pos.longitude() != 0.0);
if geo.is_empty() {
return self;
}
let mut pos = std::collections::HashMap::new();
let proj = Mer::new();
for (r, p) in geo {
let xy = proj.proj_lonlat(&rad(p)).unwrap();
pos.insert(
r,
Point {
x: xy.x(),
y: -xy.y(),
},
);
}
self.topology_zoo = Some(topo);
self.pos = Some(pos);
self
}
pub fn set_positions(mut self, pos: HashMap<RouterId, Point>) -> Self {
self.pos.get_or_insert(Default::default()).extend(pos);
self
}
pub fn replay<P: Prefix, T>(mut self, events: Vec<Event<P, T>>) -> Self {
self.replay = Some(
events
.into_iter()
.map(|x| (x.into_ipv4_prefix(), None))
.collect(),
);
self
}
pub fn replay_with_trigger<P: Prefix, T>(
mut self,
events: Vec<(Event<P, T>, Option<usize>)>,
) -> Self {
self.replay = Some(
events
.into_iter()
.map(|(x, id)| (x.into_ipv4_prefix(), id))
.collect(),
);
self
}
pub fn replay_only<P: Prefix, T>(events: Vec<Event<P, T>>) -> String {
let events: Vec<(Event<Ipv4Prefix, ()>, Option<usize>)> = events
.into_iter()
.map(|x| (x.into_ipv4_prefix(), None))
.collect();
serde_json::json!({
"replay": events
})
.to_string()
}
pub fn replay_only_with_trigger<P: Prefix, T>(
events: Vec<(Event<P, T>, Option<usize>)>,
) -> String {
let events: Vec<(Event<Ipv4Prefix, ()>, Option<usize>)> = events
.into_iter()
.map(|(x, id)| (x.into_ipv4_prefix(), id))
.collect();
serde_json::json!({
"replay": events
})
.to_string()
}
pub fn to_json(mut self) -> String {
if self.compact {
self.net = None;
}
serde_json::to_string(&self).unwrap()
}
}
#[derive(Debug, Serialize)]
pub struct Point {
pub x: f64,
pub y: f64,
}
impl<P, Q, Ospf> Network<P, Q, Ospf>
where
P: Prefix,
Q: EventQueue<P>,
Ospf: OspfImpl,
for<'a> Q: Deserialize<'a>,
{
pub fn from_json_str<F>(s: &str, default_queue: F) -> Result<Self, NetworkError>
where
F: FnOnce() -> Q,
{
let content: serde_json::Value = serde_json::from_str(s)?;
if let Some(net) = content
.get(JSON_FIELD_NAME_NETWORK)
.and_then(|v| serde_json::from_value(v.clone()).ok())
{
Ok(net)
} else {
match content
.get(JSON_FIELD_NAME_CONFIG)
.and_then(|v| v.as_array())
{
Some(v) if v.len() == 3 => Self::from_config_nodes_routes(
v[0].clone(),
v[1].clone(),
v[2].clone(),
default_queue,
),
_ => Err(serde_json::from_str::<ConfigNodeRoutes>(s).unwrap_err())?,
}
}
}
}
impl<P, Q, Ospf> Network<P, Q, Ospf>
where
P: Prefix,
Q: EventQueue<P>,
Ospf: OspfImpl,
{
fn from_config_nodes_routes<F>(
config: serde_json::Value,
nodes: serde_json::Value,
links: serde_json::Value,
default_queue: F,
) -> Result<Self, NetworkError>
where
F: FnOnce() -> Q,
{
let config: Vec<ConfigExpr<P>> = serde_json::from_value(config)?;
let nodes: ExportRouters = serde_json::from_value(nodes)?;
let links: ExportLinks = serde_json::from_value(links)?;
let mut nodes_lut: HashMap<RouterId, RouterId> = HashMap::new();
let mut net = Network::new(default_queue());
for (id, name, asn) in nodes.into_iter() {
let new_id = net.add_router(name, asn);
nodes_lut.insert(id, new_id);
}
let node = |id: RouterId| {
nodes_lut
.get(&id)
.copied()
.ok_or(NetworkError::DeviceNotFound(id))
};
let links = links
.into_iter()
.map(|(a, b)| Ok::<_, NetworkError>((node(a)?, node(b)?)))
.collect::<Result<Vec<_>, _>>()?;
net.add_links_from(links)?;
for expr in config.iter() {
let expr = match expr.clone() {
ConfigExpr::IgpLinkWeight {
source,
target,
weight,
} => ConfigExpr::IgpLinkWeight {
source: node(source)?,
target: node(target)?,
weight,
},
ConfigExpr::OspfArea {
source,
target,
area,
} => ConfigExpr::OspfArea {
source: node(source)?,
target: node(target)?,
area,
},
ConfigExpr::BgpSession {
source,
target,
target_is_client,
} => ConfigExpr::BgpSession {
source: node(source)?,
target: node(target)?,
target_is_client,
},
ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map,
} => ConfigExpr::BgpRouteMap {
router: node(router)?,
neighbor: node(neighbor)?,
direction,
map,
},
ConfigExpr::StaticRoute {
router,
prefix,
target,
} => ConfigExpr::StaticRoute {
router: node(router)?,
prefix,
target,
},
ConfigExpr::LoadBalancing { router } => ConfigExpr::LoadBalancing {
router: node(router)?,
},
ConfigExpr::AdvertiseRoute {
router,
prefix,
as_path,
med,
community,
} => ConfigExpr::AdvertiseRoute {
router: node(router)?,
prefix,
as_path,
med,
community,
},
};
net.apply_modifier(&ConfigModifier::Insert(expr))?;
}
Ok(net)
}
}
#[derive(Debug, Deserialize)]
struct ConfigNodeRoutes {
#[allow(dead_code)]
config_nodes_routes: (serde_json::Value, serde_json::Value, serde_json::Value),
}