use itertools::Itertools;
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
use crate::formatter::NetworkFormatter;
use crate::network::Network;
use crate::ospf::{LinkWeight, OspfImpl};
use crate::types::{Prefix, RouterId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum LsaType {
Router,
Summary,
External,
}
impl std::fmt::Display for LsaType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
LsaType::Router => "Router",
LsaType::Summary => "Summary",
LsaType::External => "External",
})
}
}
pub const MAX_AGE: u16 = u16::MAX;
pub const MAX_SEQ: u32 = u32::MAX;
impl LsaType {
#[inline(always)]
pub fn is_router(&self) -> bool {
matches!(self, LsaType::Router)
}
#[inline(always)]
pub fn is_summary(&self) -> bool {
matches!(self, LsaType::Summary)
}
#[inline(always)]
pub fn is_external(&self) -> bool {
matches!(self, LsaType::External)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LsaHeader {
pub lsa_type: LsaType,
pub router: RouterId,
pub target: Option<RouterId>,
pub seq: u32,
pub age: u16,
}
impl LsaHeader {
#[inline(always)]
pub fn is_router(&self) -> bool {
self.lsa_type.is_router()
}
#[inline(always)]
pub fn is_summary(&self) -> bool {
self.lsa_type.is_summary()
}
#[inline(always)]
pub fn is_external(&self) -> bool {
self.lsa_type.is_external()
}
pub fn compare(&self, other: &Self) -> LsaOrd {
match self.seq.cmp(&other.seq) {
std::cmp::Ordering::Less => LsaOrd::Older,
std::cmp::Ordering::Equal => {
let self_dead = self.age == MAX_AGE;
let other_dead = other.age == MAX_AGE;
match (self_dead, other_dead) {
(true, false) => LsaOrd::Newer,
(false, true) => LsaOrd::Older,
(true, true) | (false, false) => LsaOrd::Same,
}
}
std::cmp::Ordering::Greater => LsaOrd::Newer,
}
}
pub fn target(&self) -> RouterId {
self.target.unwrap_or(self.router)
}
#[inline]
pub fn key(&self) -> LsaKey {
LsaKey {
lsa_type: self.lsa_type,
router: self.router,
target: self.target,
}
}
pub fn is_max_age(&self) -> bool {
self.age == MAX_AGE
}
pub(crate) fn is_max_seq(&self) -> bool {
self.seq == MAX_SEQ
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LsaOrd {
Same,
Newer,
Older,
}
impl LsaOrd {
pub fn is_newer(&self) -> bool {
matches!(self, Self::Newer)
}
pub fn is_older(&self) -> bool {
matches!(self, Self::Older)
}
pub fn is_same(&self) -> bool {
matches!(self, Self::Same)
}
}
impl From<std::cmp::Ordering> for LsaOrd {
fn from(value: std::cmp::Ordering) -> Self {
match value {
std::cmp::Ordering::Less => Self::Older,
std::cmp::Ordering::Equal => Self::Same,
std::cmp::Ordering::Greater => Self::Newer,
}
}
}
impl From<LsaOrd> for std::cmp::Ordering {
fn from(value: LsaOrd) -> Self {
match value {
LsaOrd::Older => Self::Less,
LsaOrd::Same => Self::Equal,
LsaOrd::Newer => Self::Greater,
}
}
}
impl PartialOrd for LsaHeader {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.key() == other.key() {
Some(self.compare(other).into())
} else {
None
}
}
}
impl PartialOrd for Lsa {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.key() == other.key() {
Some(self.compare(other).into())
} else {
None
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Lsa {
pub header: LsaHeader,
pub data: LsaData,
}
impl Lsa {
#[inline]
pub fn key(&self) -> LsaKey {
self.header.key()
}
pub fn is_max_age(&self) -> bool {
self.header.is_max_age()
}
pub(crate) fn is_max_seq(&self) -> bool {
self.header.is_max_seq()
}
pub fn compare(&self, other: &Self) -> LsaOrd {
self.header.compare(&other.header)
}
#[inline(always)]
pub fn is_router(&self) -> bool {
self.header.is_router()
}
#[inline(always)]
pub fn is_summary(&self) -> bool {
self.header.is_summary()
}
#[inline(always)]
pub fn is_external(&self) -> bool {
self.header.is_external()
}
pub fn target(&self) -> RouterId {
self.header.target.unwrap_or(self.header.router)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LsaData {
Router(Vec<RouterLsaLink>),
Summary(NotNan<LinkWeight>),
External(NotNan<LinkWeight>),
}
impl LsaData {
pub fn router(&self) -> Option<&Vec<RouterLsaLink>> {
match self {
Self::Router(links) => Some(links),
_ => None,
}
}
pub fn summary(&self) -> Option<NotNan<LinkWeight>> {
match self {
Self::Summary(w) => Some(*w),
_ => None,
}
}
pub fn external(&self) -> Option<NotNan<LinkWeight>> {
match self {
Self::External(w) => Some(*w),
_ => None,
}
}
pub fn summary_external(&self) -> Option<NotNan<LinkWeight>> {
match self {
Self::Summary(w) | Self::External(w) => Some(*w),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LinkType {
PointToPoint = 1,
Virtual = 4,
}
impl LinkType {
#[inline(always)]
pub fn is_p2p(&self) -> bool {
matches!(self, Self::PointToPoint)
}
#[inline(always)]
pub fn is_virtual(&self) -> bool {
matches!(self, Self::Virtual)
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Copy, Eq, Hash)]
pub struct RouterLsaLink {
pub link_type: LinkType,
pub target: RouterId,
pub weight: NotNan<LinkWeight>,
}
impl RouterLsaLink {
#[inline(always)]
pub fn is_p2p(&self) -> bool {
self.link_type.is_p2p()
}
#[inline(always)]
pub fn is_virtual(&self) -> bool {
self.link_type.is_virtual()
}
}
impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for LsaHeader {
fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
let max_age = if self.is_max_age() { " MaxAge" } else { "" };
match self.lsa_type {
LsaType::Router => format!(
"RouterLSA({} [seq={}]{})",
self.router.fmt(net),
self.seq,
max_age
),
LsaType::Summary => format!(
"SummaryLSA({} --> {} [seq={}]{})",
self.router.fmt(net),
self.target.unwrap().fmt(net),
self.seq,
max_age
),
LsaType::External => format!(
"ExternalLSA({} --> {} [seq={}]{})",
self.router.fmt(net),
self.target.unwrap().fmt(net),
self.seq,
max_age
),
}
}
}
impl std::fmt::Debug for LsaHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let max_age = if self.is_max_age() { " MaxAge" } else { "" };
match self.lsa_type {
LsaType::Router => write!(
f,
"RouterLSA({} [seq={}]{})",
self.router.index(),
self.seq,
max_age
),
LsaType::Summary => write!(
f,
"SummaryLSA({} --> {} [seq={}]{})",
self.router.index(),
self.target.unwrap().index(),
self.seq,
max_age
),
LsaType::External => write!(
f,
"ExternalLSA({} --> {} [seq={}]{})",
self.router.index(),
self.target.unwrap().index(),
self.seq,
max_age
),
}
}
}
impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for LsaData {
fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
match self {
LsaData::Router(x) => format!("{{{}}}", x.iter().map(|x| x.fmt(net)).join(", ")),
LsaData::Summary(weight) => format!("{weight}"),
LsaData::External(weight) => format!("{weight}"),
}
}
}
impl std::fmt::Debug for LsaData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LsaData::Router(x) => {
write!(f, "{{{}}}", x.iter().map(|x| format!("{x:?}")).join(", "))
}
LsaData::Summary(weight) => write!(f, "{weight}"),
LsaData::External(weight) => write!(f, "{weight}"),
}
}
}
impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for Lsa {
fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
format!("{} => {}", self.header.fmt(net), self.data.fmt(net))
}
}
impl std::fmt::Debug for Lsa {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?} => {:?}", self.header, self.data)
}
}
impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for RouterLsaLink {
fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
let ty = match self.link_type {
LinkType::PointToPoint => "",
LinkType::Virtual => " [v]",
};
format!("{}: {}{}", self.target.fmt(net), self.weight, ty)
}
}
impl std::fmt::Debug for RouterLsaLink {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ty = match self.link_type {
LinkType::PointToPoint => "",
LinkType::Virtual => " [v]",
};
write!(f, "{}: {}{}", self.target.index(), self.weight, ty)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct LsaKey {
pub lsa_type: LsaType,
pub router: RouterId,
pub target: Option<RouterId>,
}
impl LsaKey {
pub fn router(router: RouterId) -> Self {
Self {
lsa_type: LsaType::Router,
router,
target: None,
}
}
pub fn summary(router: RouterId, target: RouterId) -> Self {
Self {
lsa_type: LsaType::Summary,
router,
target: Some(target),
}
}
pub fn external(router: RouterId, target: RouterId) -> Self {
Self {
lsa_type: LsaType::External,
router,
target: Some(target),
}
}
#[inline(always)]
pub fn is_router(&self) -> bool {
self.lsa_type.is_router()
}
#[inline(always)]
pub fn is_summary(&self) -> bool {
self.lsa_type.is_summary()
}
#[inline(always)]
pub fn is_external(&self) -> bool {
self.lsa_type.is_external()
}
pub fn target(&self) -> RouterId {
self.target.unwrap_or(self.router)
}
}
impl std::hash::Hash for LsaKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.router.hash(state);
self.target.hash(state);
}
}
impl From<LsaHeader> for LsaKey {
fn from(value: LsaHeader) -> Self {
value.key()
}
}
impl From<&LsaHeader> for LsaKey {
fn from(value: &LsaHeader) -> Self {
value.key()
}
}
impl From<Lsa> for LsaKey {
fn from(value: Lsa) -> Self {
value.key()
}
}
impl From<&Lsa> for LsaKey {
fn from(value: &Lsa) -> Self {
value.key()
}
}
impl<'n, P: Prefix, Q, Ospf: OspfImpl> NetworkFormatter<'n, P, Q, Ospf> for LsaKey {
fn fmt(&self, net: &'n Network<P, Q, Ospf>) -> String {
match self.lsa_type {
LsaType::Router => format!("RouterLSA({})", self.router.fmt(net),),
LsaType::Summary => format!(
"SummaryLSA({} --> {})",
self.router.fmt(net),
self.target.unwrap().fmt(net),
),
LsaType::External => format!(
"ExternalLSA({} --> {})",
self.router.fmt(net),
self.target.unwrap().fmt(net),
),
}
}
}
impl std::fmt::Debug for LsaKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.lsa_type {
LsaType::Router => write!(f, "RouterLSA({})", self.router.index(),),
LsaType::Summary => write!(
f,
"SummaryLSA({} --> {})",
self.router.index(),
self.target.unwrap().index(),
),
LsaType::External => write!(
f,
"ExternalLSA({} --> {})",
self.router.index(),
self.target.unwrap().index(),
),
}
}
}