bgp_models/bgp/
elem.rs

1use crate::bgp::attributes::{AsPath, AtomicAggregate, Origin};
2use crate::bgp::community::*;
3use crate::network::{Asn, NetworkPrefix};
4use itertools::Itertools;
5use serde::{Serialize, Serializer};
6use std::cmp::Ordering;
7use std::fmt::{Display, Formatter};
8use std::net::IpAddr;
9use std::str::FromStr;
10
11/// Element type.
12///
13/// - ANNOUNCE: announcement/reachable prefix
14/// - WITHDRAW: withdrawn/unreachable prefix
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ElemType {
17    ANNOUNCE,
18    WITHDRAW,
19}
20
21impl Serialize for ElemType {
22    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
23    where
24        S: Serializer,
25    {
26        serializer.serialize_str(match self {
27            ElemType::ANNOUNCE => "announce",
28            ElemType::WITHDRAW => "withdraw",
29        })
30    }
31}
32
33impl ElemType {
34    pub fn is_announce(&self) -> bool {
35        match self {
36            ElemType::ANNOUNCE => true,
37            ElemType::WITHDRAW => false,
38        }
39    }
40}
41
42/// BgpElem represents per-prefix BGP element.
43///
44/// The information is for per announced/withdrawn prefix.
45///
46/// Note: it consumes more memory to construct BGP elements due to duplicate information
47/// shared between multiple elements of one MRT record.
48#[derive(Debug, Clone, Serialize, PartialEq)]
49pub struct BgpElem {
50    pub timestamp: f64,
51    #[serde(rename = "type")]
52    pub elem_type: ElemType,
53    pub peer_ip: IpAddr,
54    pub peer_asn: Asn,
55    pub prefix: NetworkPrefix,
56    pub next_hop: Option<IpAddr>,
57    pub as_path: Option<AsPath>,
58    pub origin_asns: Option<Vec<Asn>>,
59    pub origin: Option<Origin>,
60    pub local_pref: Option<u32>,
61    pub med: Option<u32>,
62    pub communities: Option<Vec<MetaCommunity>>,
63    pub atomic: Option<AtomicAggregate>,
64    pub aggr_asn: Option<Asn>,
65    pub aggr_ip: Option<IpAddr>,
66    pub only_to_customer: Option<u32>,
67}
68
69impl Eq for BgpElem {}
70
71impl PartialOrd<Self> for BgpElem {
72    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
73        Some(self.cmp(other))
74    }
75}
76
77impl Ord for BgpElem {
78    fn cmp(&self, other: &Self) -> Ordering {
79        self.timestamp
80            .partial_cmp(&other.timestamp)
81            .unwrap()
82            .then_with(|| self.peer_ip.cmp(&other.peer_ip))
83    }
84}
85
86/// Reference version of the [BgpElem] struct.
87#[derive(Debug, Clone, Serialize)]
88pub struct BgpElemRef<'a> {
89    pub timestamp: &'a f64,
90    pub elem_type: &'a ElemType,
91    pub peer_ip: &'a IpAddr,
92    pub peer_asn: &'a Asn,
93    pub prefix: &'a NetworkPrefix,
94    pub next_hop: &'a Option<IpAddr>,
95    pub as_path: &'a Option<AsPath>,
96    pub origin_asns: &'a Option<Vec<Asn>>,
97    pub origin: &'a Option<Origin>,
98    pub local_pref: &'a Option<u32>,
99    pub med: &'a Option<u32>,
100    pub communities: &'a Option<Vec<MetaCommunity>>,
101    pub atomic: &'a Option<AtomicAggregate>,
102    pub aggr_asn: &'a Option<Asn>,
103    pub aggr_ip: &'a Option<IpAddr>,
104}
105
106impl Default for BgpElem {
107    fn default() -> Self {
108        BgpElem {
109            timestamp: 0.0,
110            elem_type: ElemType::ANNOUNCE,
111            peer_ip: IpAddr::from_str("0.0.0.0").unwrap(),
112            peer_asn: 0.into(),
113            prefix: NetworkPrefix::from_str("0.0.0.0/0").unwrap(),
114            next_hop: None,
115            as_path: None,
116            origin_asns: None,
117            origin: None,
118            local_pref: None,
119            med: None,
120            communities: None,
121            atomic: None,
122            aggr_asn: None,
123            aggr_ip: None,
124            only_to_customer: None,
125        }
126    }
127}
128
129macro_rules! option_to_string {
130    ($a:expr) => {
131        if let Some(v) = $a {
132            v.to_string()
133        } else {
134            String::new()
135        }
136    };
137}
138
139#[inline(always)]
140pub fn option_to_string_communities(o: &Option<Vec<MetaCommunity>>) -> String {
141    if let Some(v) = o {
142        v.iter().join(" ")
143    } else {
144        String::new()
145    }
146}
147
148impl Display for BgpElem {
149    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150        let t = match self.elem_type {
151            ElemType::ANNOUNCE => "A",
152            ElemType::WITHDRAW => "W",
153        };
154        let format = format!(
155            "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
156            t,
157            &self.timestamp,
158            &self.peer_ip,
159            &self.peer_asn,
160            &self.prefix,
161            option_to_string!(&self.as_path),
162            option_to_string!(&self.origin),
163            option_to_string!(&self.next_hop),
164            option_to_string!(&self.local_pref),
165            option_to_string!(&self.med),
166            option_to_string_communities(&self.communities),
167            option_to_string!(&self.atomic),
168            option_to_string!(&self.aggr_asn),
169            option_to_string!(&self.aggr_ip),
170        );
171        write!(f, "{}", format)
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use std::default::Default;
179    use std::str::FromStr;
180
181    #[test]
182    fn test_default() {
183        let elem = BgpElem {
184            timestamp: 0.0,
185            elem_type: ElemType::ANNOUNCE,
186            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
187            peer_asn: 0.into(),
188            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
189            ..Default::default()
190        };
191        println!("{}", serde_json::json!(elem).to_string());
192    }
193
194    #[test]
195    fn test_sorting() {
196        let elem1 = BgpElem {
197            timestamp: 1.1,
198            elem_type: ElemType::ANNOUNCE,
199            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
200            peer_asn: 0.into(),
201            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
202            ..Default::default()
203        };
204        let elem2 = BgpElem {
205            timestamp: 1.2,
206            elem_type: ElemType::ANNOUNCE,
207            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
208            peer_asn: 0.into(),
209            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
210            ..Default::default()
211        };
212        let elem3 = BgpElem {
213            timestamp: 1.2,
214            elem_type: ElemType::ANNOUNCE,
215            peer_ip: IpAddr::from_str("192.168.1.2").unwrap(),
216            peer_asn: 0.into(),
217            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
218            ..Default::default()
219        };
220
221        assert_eq!(elem1 < elem2, true);
222        assert_eq!(elem2 < elem3, true);
223    }
224}