#[rustfmt::skip]
mod topos;
use geoutils::Location;
use ordered_float::NotNan;
pub use topos::*;
use std::collections::{HashMap, HashSet};
use thiserror::Error;
use xmltree::{Element, ParseError as XmlParseError};
use crate::{
event::EventQueue,
network::Network,
ospf::OspfImpl,
prelude::GlobalOspf,
types::{IndexType, NetworkError, Prefix, RouterId, ASN},
};
#[derive(Debug)]
pub struct TopologyZooParser {
xml: Element,
nodes: Vec<TopologyZooNode>,
edges: Vec<TopologyZooEdge>,
keys: Vec<TopologyZooKey>,
key_id_lut: HashMap<String, usize>,
key_name_lut: HashMap<String, usize>,
}
impl TopologyZooParser {
pub fn new(graphml_content: &str) -> Result<Self, TopologyZooError> {
let xml = Element::parse(graphml_content.as_bytes())?;
if xml.name != "graphml" {
return Err(TopologyZooError::MissingNode("/graphml"));
}
let mut this = Self {
xml,
keys: Default::default(),
key_id_lut: Default::default(),
key_name_lut: Default::default(),
nodes: Default::default(),
edges: Default::default(),
};
this.setup_keys()?;
let graph = this
.xml
.get_child("graph")
.ok_or(TopologyZooError::MissingNode("/graphml/graph"))?;
this.nodes = graph
.children
.iter()
.filter_map(|c| c.as_element())
.filter(|child| child.name == "node")
.map(|node| this.extract_node(node))
.collect::<Result<Vec<TopologyZooNode>, TopologyZooError>>()?;
this.edges = graph
.children
.iter()
.filter_map(|c| c.as_element())
.filter(|child| child.name == "edge")
.map(|node| this.extract_edge(node))
.collect::<Result<Vec<TopologyZooEdge>, TopologyZooError>>()?;
Ok(this)
}
pub fn get_network<P: Prefix, Q: EventQueue<P>, Ospf: OspfImpl>(
&self,
queue: Q,
internal_asn: ASN,
external_asn: Option<ASN>,
) -> Result<Network<P, Q, Ospf>, TopologyZooError> {
let mut net: Network<P, Q, GlobalOspf> = Network::new(queue);
let mut routers_to_ignore = HashSet::new();
let nodes_lut: HashMap<&str, RouterId> = self
.nodes
.iter()
.filter_map(|r| {
if r.internal {
Some((r.id.as_str(), net.add_router(r.name.clone(), internal_asn)))
} else if let Some(external_asn) = external_asn {
Some((
r.id.as_str(),
net.add_router(r.name.clone(), net.next_unused_asn(external_asn)),
))
} else {
routers_to_ignore.insert(r.id.as_str());
None
}
})
.enumerate()
.map(|(idx, (name, id))| {
if idx == id.index() {
Ok((name, id))
} else {
Err(TopologyZooError::NonContiguousNodeIndices)
}
})
.collect::<Result<HashMap<&str, RouterId>, TopologyZooError>>()?;
let mut links = Vec::new();
for TopologyZooEdge { source, target } in self.edges.iter() {
if routers_to_ignore.contains(source.as_str())
|| routers_to_ignore.contains(target.as_str())
{
continue;
}
let src = *nodes_lut
.get(source.as_str())
.ok_or_else(|| TopologyZooError::NodeNotFound(source.clone()))?;
let dst = *nodes_lut
.get(target.as_str())
.ok_or_else(|| TopologyZooError::NodeNotFound(target.clone()))?;
links.push((src, dst));
}
net.add_links_from(links)?;
Ok(Network::from_global_ospf(net).unwrap())
}
pub fn get_geo_location(&self) -> HashMap<RouterId, Location> {
self.nodes
.iter()
.enumerate()
.map(|(i, node)| {
(
(i as IndexType).into(),
Location::new(
node.latitude.as_ref().copied().unwrap_or_default(),
node.longitude.as_ref().copied().unwrap_or_default(),
),
)
})
.collect()
}
fn setup_keys(&mut self) -> Result<(), TopologyZooError> {
self.keys = self
.xml
.children
.iter()
.filter_map(|node| node.as_element())
.filter(|node| node.name == "key")
.map(Self::extract_key)
.collect::<Result<Vec<TopologyZooKey>, TopologyZooError>>()?;
self.key_id_lut = self
.keys
.iter()
.enumerate()
.map(|(i, k)| (k.id.clone(), i))
.collect();
self.key_name_lut = self
.keys
.iter()
.enumerate()
.map(|(i, k)| (k.name.clone(), i))
.collect();
Ok(())
}
fn extract_key(e: &Element) -> Result<TopologyZooKey, TopologyZooError> {
let name = e
.attributes
.get("attr.name")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/key",
"attr.name",
))?
.to_string();
let ty = e
.attributes
.get("attr.type")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/key",
"attr.type",
))?
.parse::<AttrType>()?;
let id = e
.attributes
.get("id")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/key",
"attr.name",
))?
.to_string();
Ok(TopologyZooKey { name, id, ty })
}
fn extract_node(&self, e: &Element) -> Result<TopologyZooNode, TopologyZooError> {
let id = e
.attributes
.get("id")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/graph/node",
"id",
))?
.to_string();
let data = get_data(e)?;
let mut internal: Option<bool> = None;
let mut name: Option<String> = None;
let mut latitude: Option<NotNan<f64>> = None;
let mut longitude: Option<NotNan<f64>> = None;
for (key, value) in data.into_iter() {
let idx = *self
.key_id_lut
.get(&key)
.ok_or(TopologyZooError::UnknownKey(key))?;
let key = &self.keys[idx];
if key.name == "Internal" {
if AttrType::Int != key.ty {
return Err(TopologyZooError::AttrInvalidType(AttrType::Int, key.ty));
}
let value = value
.parse::<isize>()
.map_err(|_| TopologyZooError::ValueParseError(value, AttrType::Int))?;
internal = Some(value == 1);
} else if &key.name == "label" {
if AttrType::String != key.ty {
return Err(TopologyZooError::AttrInvalidType(AttrType::String, key.ty));
}
name = Some(
value
.chars()
.filter(|c| !c.is_whitespace())
.filter(|c| *c != ',')
.collect(),
);
} else if &key.name == "Latitude" {
if AttrType::Float != key.ty {
return Err(TopologyZooError::AttrInvalidType(AttrType::String, key.ty));
}
latitude = value.parse().ok();
} else if &key.name == "Longitude" {
if AttrType::Float != key.ty {
return Err(TopologyZooError::AttrInvalidType(AttrType::String, key.ty));
}
longitude = value.parse().ok();
}
if name.is_some() && internal.is_some() {
break;
}
}
Ok(TopologyZooNode {
id,
name: name.ok_or(TopologyZooError::MissingKey("label", "/graphml/graph/node"))?,
internal: internal.ok_or(TopologyZooError::MissingKey(
"Internal",
"/graphml/graph/node",
))?,
latitude,
longitude,
})
}
fn extract_edge(&self, e: &Element) -> Result<TopologyZooEdge, TopologyZooError> {
let source = e
.attributes
.get("source")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/graph/edge",
"source",
))?
.to_string();
let target = e
.attributes
.get("target")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/graph/edge",
"target",
))?
.to_string();
Ok(TopologyZooEdge { source, target })
}
}
fn get_data(e: &Element) -> Result<Vec<(String, String)>, TopologyZooError> {
e.children
.iter()
.filter_map(|node| node.as_element())
.filter(|node| node.name == "data")
.map(|d| -> Result<(String, String), TopologyZooError> {
Ok((
d.attributes
.get("key")
.ok_or(TopologyZooError::MissingAttribute(
"/graphml/graph/node/data",
"key",
))?
.to_string(),
d.children
.iter()
.find_map(|node| node.as_text())
.ok_or(TopologyZooError::MissingNode(
"/graphml/graph/node/data/text",
))?
.to_string(),
))
})
.collect()
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct TopologyZooKey {
name: String,
id: String,
ty: AttrType,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct TopologyZooNode {
id: String,
internal: bool,
name: String,
latitude: Option<NotNan<f64>>,
longitude: Option<NotNan<f64>>,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct TopologyZooEdge {
source: String,
target: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrType {
Int,
String,
Float,
}
impl std::fmt::Display for AttrType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AttrType::Int => f.write_str("Int"),
AttrType::String => f.write_str("String"),
AttrType::Float => f.write_str("Double"),
}
}
}
impl std::str::FromStr for AttrType {
type Err = AttrTypeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
match s.as_str() {
"string" => Ok(Self::String),
"int" => Ok(Self::Int),
"double" => Ok(Self::Float),
_ => Err(AttrTypeParseError::UnrecognizedToken(s)),
}
}
}
#[derive(Debug, Error)]
pub enum TopologyZooError {
#[error("Cannot parse the XML: {0}")]
ParseError(#[from] XmlParseError),
#[error("Missing node: {0}")]
MissingNode(&'static str),
#[error("Expecting Element for {0}, but got something else!")]
ExpectedElement(&'static str),
#[error("Missing attribute {1} for element {0}")]
MissingAttribute(&'static str, &'static str),
#[error("{0}")]
AttrTypeParseError(#[from] AttrTypeParseError),
#[error("Attribute should have type {0}, but it has {1}.")]
AttrInvalidType(AttrType, AttrType),
#[error("Network occurred while generating it: {0}")]
NetworkError(#[from] NetworkError),
#[error("Missing key {0} for {1}")]
MissingKey(&'static str, &'static str),
#[error("Key with id {0} is not defined!")]
UnknownKey(String),
#[error("Cannot parse value {0} as {1}.")]
ValueParseError(String, AttrType),
#[error("Node {0} referenced by an edge is not defined!")]
NodeNotFound(String),
#[error("Node indices are not contiguous!")]
NonContiguousNodeIndices,
}
#[derive(Clone, Debug, Error)]
pub enum AttrTypeParseError {
#[error("Unrecognized Token as Attribute Type: {0}")]
UnrecognizedToken(String),
}