use crate::models::*;
use itertools::Itertools;
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::net::IpAddr;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename = "lowercase"))]
pub enum ElemType {
ANNOUNCE,
WITHDRAW,
}
impl ElemType {
pub fn is_announce(&self) -> bool {
match self {
ElemType::ANNOUNCE => true,
ElemType::WITHDRAW => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BgpElem {
pub timestamp: f64,
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub elem_type: ElemType,
pub peer_ip: IpAddr,
pub peer_asn: Asn,
pub peer_bgp_id: Option<BgpIdentifier>,
pub prefix: NetworkPrefix,
pub next_hop: Option<IpAddr>,
pub as_path: Option<AsPath>,
pub origin_asns: Option<Vec<Asn>>,
pub origin: Option<Origin>,
pub local_pref: Option<u32>,
pub med: Option<u32>,
pub communities: Option<Vec<MetaCommunity>>,
pub atomic: bool,
pub aggr_asn: Option<Asn>,
pub aggr_ip: Option<BgpIdentifier>,
pub only_to_customer: Option<Asn>,
pub unknown: Option<Vec<AttrRaw>>,
pub deprecated: Option<Vec<AttrRaw>>,
}
impl Eq for BgpElem {}
impl PartialOrd<Self> for BgpElem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BgpElem {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp
.partial_cmp(&other.timestamp)
.unwrap()
.then_with(|| self.peer_ip.cmp(&other.peer_ip))
}
}
impl Default for BgpElem {
fn default() -> Self {
BgpElem {
timestamp: 0.0,
elem_type: ElemType::ANNOUNCE,
peer_ip: IpAddr::from_str("0.0.0.0").unwrap(),
peer_asn: 0.into(),
peer_bgp_id: None,
prefix: NetworkPrefix::from_str("0.0.0.0/0").unwrap(),
next_hop: Some(IpAddr::from_str("0.0.0.0").unwrap()),
as_path: None,
origin_asns: None,
origin: None,
local_pref: None,
med: None,
communities: None,
atomic: false,
aggr_asn: None,
aggr_ip: None,
only_to_customer: None,
unknown: None,
deprecated: None,
}
}
}
struct OptionToStr<'a, T>(&'a Option<T>);
impl<T: Display> Display for OptionToStr<'_, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.0 {
None => Ok(()),
Some(x) => write!(f, "{x}"),
}
}
}
struct OptionToStrVec<'a, T>(&'a Option<Vec<T>>);
impl<T: Display> Display for OptionToStrVec<'_, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.0 {
None => Ok(()),
Some(v) => write!(
f,
"{}",
v.iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join(" ")
),
}
}
}
#[inline(always)]
pub fn option_to_string_communities(o: &Option<Vec<MetaCommunity>>) -> String {
if let Some(v) = o {
v.iter().join(" ")
} else {
String::new()
}
}
impl Display for BgpElem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let t = match self.elem_type {
ElemType::ANNOUNCE => "A",
ElemType::WITHDRAW => "W",
};
write!(
f,
"{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
t,
&self.timestamp,
&self.peer_ip,
&self.peer_asn,
&self.prefix,
OptionToStr(&self.as_path),
OptionToStr(&self.origin),
OptionToStr(&self.next_hop),
OptionToStr(&self.local_pref),
OptionToStr(&self.med),
option_to_string_communities(&self.communities),
self.atomic,
OptionToStr(&self.aggr_asn),
OptionToStr(&self.aggr_ip),
)
}
}
impl BgpElem {
pub fn is_announcement(&self) -> bool {
self.elem_type == ElemType::ANNOUNCE
}
pub fn get_origin_asn_opt(&self) -> Option<u32> {
let origin_asns = self.origin_asns.as_ref()?;
(origin_asns.len() == 1).then(|| origin_asns[0].into())
}
pub fn get_psv_header() -> String {
let fields = [
"type",
"timestamp",
"peer_ip",
"peer_asn",
"prefix",
"as_path",
"origin_asns",
"origin",
"next_hop",
"local_pref",
"med",
"communities",
"atomic",
"aggr_asn",
"aggr_ip",
"only_to_customer",
];
fields.join("|")
}
pub fn to_psv(&self) -> String {
let t = match self.elem_type {
ElemType::ANNOUNCE => "A",
ElemType::WITHDRAW => "W",
};
format!(
"{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
t,
&self.timestamp,
&self.peer_ip,
&self.peer_asn,
&self.prefix,
OptionToStr(&self.as_path),
OptionToStrVec(&self.origin_asns),
OptionToStr(&self.origin),
OptionToStr(&self.next_hop),
OptionToStr(&self.local_pref),
OptionToStr(&self.med),
option_to_string_communities(&self.communities),
self.atomic,
OptionToStr(&self.aggr_asn),
OptionToStr(&self.aggr_ip),
OptionToStr(&self.only_to_customer),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::default::Default;
use std::str::FromStr;
#[test]
#[cfg(feature = "serde")]
fn test_default() {
let elem = BgpElem {
timestamp: 0.0,
elem_type: ElemType::ANNOUNCE,
peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
peer_asn: 0.into(),
prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
..Default::default()
};
println!("{}", serde_json::json!(elem));
}
#[test]
fn test_sorting() {
let elem1 = BgpElem {
timestamp: 1.1,
elem_type: ElemType::ANNOUNCE,
peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
peer_asn: 0.into(),
prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
..Default::default()
};
let elem2 = BgpElem {
timestamp: 1.2,
elem_type: ElemType::ANNOUNCE,
peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
peer_asn: 0.into(),
prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
..Default::default()
};
let elem3 = BgpElem {
timestamp: 1.2,
elem_type: ElemType::ANNOUNCE,
peer_ip: IpAddr::from_str("192.168.1.2").unwrap(),
peer_asn: 0.into(),
prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
..Default::default()
};
assert!(elem1 < elem2);
assert!(elem2 < elem3);
}
#[test]
fn test_psv() {
assert_eq!(
BgpElem::get_psv_header().as_str(),
"type|timestamp|peer_ip|peer_asn|prefix|as_path|origin_asns|origin|next_hop|local_pref|med|communities|atomic|aggr_asn|aggr_ip|only_to_customer"
);
let elem = BgpElem::default();
assert_eq!(
elem.to_psv().as_str(),
"A|0|0.0.0.0|0|0.0.0.0/0||||0.0.0.0||||false|||"
);
}
#[test]
fn test_option_to_str() {
let asn_opt: Option<u32> = Some(12);
assert_eq!(OptionToStr(&asn_opt).to_string(), "12");
let none_opt: Option<u32> = None;
assert_eq!(OptionToStr(&none_opt).to_string(), "");
let asns_opt = Some(vec![12, 34]);
assert_eq!(OptionToStrVec(&asns_opt).to_string(), "12 34");
assert_eq!(OptionToStrVec(&None::<Vec<u32>>).to_string(), "");
}
}