use crate::formatter::NetworkFormatter;
use crate::ospf::local::LsaKey;
use crate::ospf::OspfImpl;
use crate::{bgp::BgpSessionType, network::Network};
use itertools::Itertools;
use petgraph::prelude::*;
use serde::{Deserialize, Serialize};
use thiserror::Error;
mod prefix;
pub(crate) use prefix::IntoIpv4Prefix;
pub use prefix::{
Ipv4Prefix, NonOverlappingPrefix, Prefix, PrefixMap, PrefixSet, SimplePrefix, SinglePrefix,
SinglePrefixMap, SinglePrefixSet,
};
pub(crate) type IndexType = u32;
pub type RouterId = NodeIndex<IndexType>;
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ASN(pub u32);
impl std::fmt::Display for ASN {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AS{}", self.0)
}
}
impl From<u32> for ASN {
fn from(x: u32) -> Self {
Self(x)
}
}
impl From<u64> for ASN {
fn from(x: u64) -> Self {
Self(x as u32)
}
}
impl From<usize> for ASN {
fn from(x: usize) -> Self {
Self(x as u32)
}
}
impl From<i32> for ASN {
fn from(x: i32) -> Self {
Self(x as u32)
}
}
impl From<i64> for ASN {
fn from(x: i64) -> Self {
Self(x as u32)
}
}
impl From<isize> for ASN {
fn from(x: isize) -> Self {
Self(x as u32)
}
}
impl<T> From<&T> for ASN
where
T: Into<ASN> + Copy,
{
fn from(x: &T) -> Self {
(*x).into()
}
}
pub type PhysicalNetwork = StableGraph<(), (), Undirected, IndexType>;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StepUpdate<P> {
Unchanged,
Single(FwDelta<P>),
Multiple,
}
impl<P> Default for StepUpdate<P> {
fn default() -> Self {
Self::Unchanged
}
}
impl<P> From<FwDelta<P>> for StepUpdate<P> {
fn from(value: FwDelta<P>) -> Self {
Self::Single(value)
}
}
impl<P> From<Option<FwDelta<P>>> for StepUpdate<P> {
fn from(value: Option<FwDelta<P>>) -> Self {
match value {
Some(v) => Self::from(v),
None => Self::Unchanged,
}
}
}
impl<P> StepUpdate<P> {
pub fn new(prefix: P, old: Vec<RouterId>, new: Vec<RouterId>) -> Self {
FwDelta::new(prefix, old, new).into()
}
pub fn changed(&self) -> bool {
!matches!(self, Self::Unchanged)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FwDelta<P> {
pub prefix: P,
pub old: Vec<RouterId>,
pub new: Vec<RouterId>,
}
impl<P> FwDelta<P> {
pub fn new(prefix: P, old: Vec<RouterId>, new: Vec<RouterId>) -> Option<Self> {
if old == new {
None
} else {
Some(Self { prefix, old, new })
}
}
}
impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for FwDelta<P> {
fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
format!(
"{}: {} --> {}",
self.prefix,
if self.old.is_empty() {
"X".to_string()
} else {
self.old
.iter()
.map(|r| net.get_router(*r).map(|x| x.name()).unwrap_or("?"))
.join("|")
},
if self.new.is_empty() {
"X".to_string()
} else {
self.new
.iter()
.map(|r| net.get_router(*r).map(|x| x.name()).unwrap_or("?"))
.join("|")
},
)
}
}
impl<P: Prefix> StepUpdate<P> {
pub fn fmt<Q, Ospf: OspfImpl>(&self, net: &Network<P, Q, Ospf>, router: RouterId) -> String {
match self {
StepUpdate::Unchanged => String::from("Unchanged"),
StepUpdate::Single(delta) => format!("{} => {}", router.fmt(net), delta.fmt(net)),
StepUpdate::Multiple => {
format!("{}: multiple FW changes (due to OSPF)", router.fmt(net),)
}
}
}
}
#[derive(Error, Debug, PartialEq, Serialize, Deserialize)]
pub enum ConfigError {
#[error("The new ConfigExpr `{old:?}` would overwrite the existing `{new:?}`!")]
ConfigExprOverload {
old: Box<crate::config::ConfigExpr<Ipv4Prefix>>,
new: Box<crate::config::ConfigExpr<Ipv4Prefix>>,
},
#[error("The ConfigModifier `{0:?}` cannot be applied.")]
ConfigModifier(Box<crate::config::ConfigModifier<Ipv4Prefix>>),
}
#[derive(Error, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceError {
#[error("Event with destination {1:?} was triggered on router {0:?}!")]
WrongRouter(RouterId, RouterId),
#[error("BGP Session with {0:?} is not yet created!")]
NoBgpSession(RouterId),
#[error("Router {0:?} is not known in the IGP forwarding table")]
RouterNotFound(RouterId),
#[error("Routers {0:?} and {1:?} are already OSPF neighbors.")]
AlreadyOspfNeighbors(RouterId, RouterId),
#[error("Routers {0:?} and {1:?} are not OSPF neighbors.")]
NotAnOspfNeighbor(RouterId, RouterId),
}
#[derive(Error, Debug)]
pub enum NetworkError {
#[error("Device Error: {0}")]
DeviceError(#[from] DeviceError),
#[error("Configuration Error: {0}")]
ConfigError(#[from] ConfigError),
#[error("The given AS does not exist in the network: {0:?}")]
UnknownAS(ASN),
#[error("Network device was not found in topology: {0:?}")]
DeviceNotFound(RouterId),
#[error("Network device name was not found in topology: {0}")]
DeviceNameNotFound(String),
#[error("Link does not exist: {0:?} -- {1:?}")]
LinkNotFound(RouterId, RouterId),
#[error("Forwarding Loop occurred! path: {to_loop:?}, {first_loop:?}")]
ForwardingLoop {
to_loop: Vec<RouterId>,
first_loop: Vec<RouterId>,
},
#[error("Black hole occurred! path: {0:?}")]
ForwardingBlackHole(Vec<RouterId>),
#[error("Invalid Session type: source: {0:?}, target: {1:?}, type: {2:?}")]
InvalidBgpSessionType(RouterId, RouterId, BgpSessionType),
#[error(
"Inconsistent BGP Session: both source {0:?} and target: {1:?} treat the other as client."
)]
InconsistentBgpSession(RouterId, RouterId),
#[error("Network cannot converge in the given time!")]
NoConvergence,
#[error("Invalid BGP table for router {0:?}")]
InvalidBgpTable(RouterId),
#[error("The OSPF distributed OSPF state is inconsistent for the LSA {0:?}")]
InconsistentOspfState(LsaKey),
#[error("{0}")]
JsonError(Box<serde_json::Error>),
}
impl From<serde_json::Error> for NetworkError {
fn from(value: serde_json::Error) -> Self {
Self::JsonError(Box::new(value))
}
}
impl PartialEq for NetworkError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::DeviceError(l0), Self::DeviceError(r0)) => l0 == r0,
(Self::ConfigError(l0), Self::ConfigError(r0)) => l0 == r0,
(Self::DeviceNotFound(l0), Self::DeviceNotFound(r0)) => l0 == r0,
(Self::DeviceNameNotFound(l0), Self::DeviceNameNotFound(r0)) => l0 == r0,
(Self::LinkNotFound(l0, l1), Self::LinkNotFound(r0, r1)) => l0 == r0 && l1 == r1,
(
Self::ForwardingLoop {
to_loop: l0,
first_loop: l1,
},
Self::ForwardingLoop {
to_loop: r0,
first_loop: r1,
},
) => l0 == r0 && l1 == r1,
(Self::ForwardingBlackHole(l0), Self::ForwardingBlackHole(r0)) => l0 == r0,
(Self::InvalidBgpSessionType(l0, l1, l2), Self::InvalidBgpSessionType(r0, r1, r2)) => {
l0 == r0 && l1 == r1 && l2 == r2
}
(Self::InconsistentBgpSession(l0, l1), Self::InconsistentBgpSession(r0, r1)) => {
l0 == r0 && l1 == r1
}
(Self::InvalidBgpTable(l0), Self::InvalidBgpTable(r0)) => l0 == r0,
(Self::JsonError(l), Self::JsonError(r)) => l.to_string() == r.to_string(),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
pub trait NetworkErrorOption<T> {
fn or_router_not_found(self, router: RouterId) -> Result<T, NetworkError>;
fn or_link_not_found(self, a: RouterId, b: RouterId) -> Result<T, NetworkError>;
}
impl<T> NetworkErrorOption<T> for Option<T> {
fn or_router_not_found(self, router: RouterId) -> Result<T, NetworkError> {
self.ok_or(NetworkError::DeviceNotFound(router))
}
fn or_link_not_found(self, a: RouterId, b: RouterId) -> Result<T, NetworkError> {
self.ok_or(NetworkError::LinkNotFound(a, b))
}
}