Skip to main content

bgpkit_parser/models/bgp/
elem.rs

1use crate::models::*;
2use itertools::Itertools;
3use std::cmp::Ordering;
4use std::fmt::{Display, Formatter};
5use std::net::IpAddr;
6use std::str::FromStr;
7
8// TODO(jmeggitt): BgpElem can be converted to an enum. Apply this change during performance PR.
9
10/// # ElemType
11///
12/// `ElemType` is an enumeration that represents the type of an element.
13/// It has two possible values:
14///
15/// - `ANNOUNCE`: Indicates an announcement/reachable prefix.
16/// - `WITHDRAW`: Indicates a withdrawn/unreachable prefix.
17///
18/// The enumeration derives the traits `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, and `Hash`.
19///
20/// It also has the following attributes:
21///
22/// - `#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]`
23///     - This attribute is conditionally applied when the `"serde"` feature is enabled. It allows
24///       the enumeration to be serialized and deserialized using serde.
25/// - `#[cfg_attr(feature = "serde", serde(rename = "lowercase"))]`
26///     - This attribute is conditionally applied when the `"serde"` feature is enabled. It specifies
27///       that the serialized form of the enumeration should be in lowercase.
28///
29/// Example usage:
30///
31/// ```
32/// use bgpkit_parser::models::ElemType;
33///
34/// let announce_type = ElemType::ANNOUNCE;
35/// let withdraw_type = ElemType::WITHDRAW;
36///
37/// assert_eq!(announce_type, ElemType::ANNOUNCE);
38/// assert_eq!(withdraw_type, ElemType::WITHDRAW);
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42#[cfg_attr(feature = "serde", serde(rename = "lowercase"))]
43pub enum ElemType {
44    ANNOUNCE,
45    WITHDRAW,
46}
47
48impl ElemType {
49    /// Checks if the `ElemType` is an announce.
50    ///
51    /// Returns `true` if `ElemType` is `ANNOUNCE`, and `false` if it is `WITHDRAW`.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use bgpkit_parser::models::ElemType;
57    ///
58    /// let elem = ElemType::ANNOUNCE;
59    /// assert_eq!(elem.is_announce(), true);
60    ///
61    /// let elem = ElemType::WITHDRAW;
62    /// assert_eq!(elem.is_announce(), false);
63    /// ```
64    pub fn is_announce(&self) -> bool {
65        match self {
66            ElemType::ANNOUNCE => true,
67            ElemType::WITHDRAW => false,
68        }
69    }
70}
71
72/// BgpElem represents a per-prefix BGP element.
73///
74/// This struct contains information about an announced/withdrawn prefix.
75///
76/// Fields:
77/// - `timestamp`: The time when the BGP element was received.
78/// - `elem_type`: The type of BGP element.
79/// - `peer_ip`: The IP address of the BGP peer.
80/// - `peer_asn`: The ASN of the BGP peer.
81/// - `prefix`: The network prefix.
82/// - `next_hop`: The next hop IP address.
83/// - `as_path`: The AS path.
84/// - `origin_asns`: The list of origin ASNs.
85/// - `origin`: The origin attribute, i.e. IGP, EGP, or INCOMPLETE.
86/// - `local_pref`: The local preference value.
87/// - `med`: The multi-exit discriminator value.
88/// - `communities`: The list of BGP communities.
89/// - `atomic`: Flag indicating if the announcement is atomic.
90/// - `aggr_asn`: The aggregated ASN.
91/// - `aggr_ip`: The aggregated IP address.
92/// - `only_to_customer`: The AS number to which the prefix is only announced.
93/// - `unknown`: Unknown attributes formatted as (TYPE, RAW_BYTES).
94/// - `deprecated`: Deprecated attributes formatted as (TYPE, RAW_BYTES).
95///
96/// Note: Constructing BGP elements consumes more memory due to duplicate information
97/// shared between multiple elements of one MRT record.
98#[derive(Debug, Clone, PartialEq)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub struct BgpElem {
101    /// The timestamp of the item in floating-point format.
102    pub timestamp: f64,
103    /// The element type of an item.
104    #[cfg_attr(feature = "serde", serde(rename = "type"))]
105    pub elem_type: ElemType,
106    /// The IP address of the peer associated with the item.
107    pub peer_ip: IpAddr,
108    /// The peer ASN (Autonomous System Number) of the item.
109    pub peer_asn: Asn,
110    /// The BGP Identifier (Router ID) of the peer, if available.
111    ///
112    /// Present in MRT TableDumpV2 records (from the PEER_INDEX_TABLE) and in BGP OPEN messages.
113    /// Not available when processing BGP UPDATE messages without an accompanying OPEN message
114    /// (e.g. Bgp4Mp records), in which case this field is `None`.
115    pub peer_bgp_id: Option<BgpIdentifier>,
116    /// The network prefix of the item.
117    pub prefix: NetworkPrefix,
118    /// The next hop IP address for the item, if available.
119    pub next_hop: Option<IpAddr>,
120    /// The optional path representation of the item.
121    ///
122    /// This field is of type `Option<AsPath>`, which means it can either contain
123    /// a value of type `AsPath` or be `None`.
124    pub as_path: Option<AsPath>,
125    /// The origin ASNs associated with the prefix, if available.
126    ///
127    /// # Remarks
128    /// An `Option` type is used to indicate that the `origin_asns` field may or may not have a value.
129    /// If it has a value, it will be a `Vec` (vector) of `Asn` objects representing the ASNs.
130    /// If it does not have a value, it will be `None`.
131    pub origin_asns: Option<Vec<Asn>>,
132    /// The origin of the item (IGP, EGP, INCOMPLETE), if known. Can be `None` if the origin is not available.
133    pub origin: Option<Origin>,
134    /// The local preference of the item, if available, represented as an option of unsigned 32-bit integer.
135    pub local_pref: Option<u32>,
136    /// The number of medical items in an option format.
137    pub med: Option<u32>,
138    /// A vector of optional `MetaCommunity` values.
139    ///
140    /// # Remarks
141    /// `MetaCommunity` represents a community metadata.
142    /// The `Option` type indicates that the vector can be empty or contain [MetaCommunity] values.
143    /// When the `Option` is `Some`, it means the vector is not empty and contains [MetaCommunity] values.
144    /// When the `Option` is `None`, it means the vector is empty.
145    pub communities: Option<Vec<MetaCommunity>>,
146    /// Indicates whether the item is atomic aggreagte or not.
147    pub atomic: bool,
148    /// The aggregated ASN of the item, represented as an optional [Asn] type.
149    pub aggr_asn: Option<Asn>,
150    /// The aggregated IP address of the item, represented as an optional [BgpIdentifier], i.e. `Ipv4Addr`.
151    pub aggr_ip: Option<BgpIdentifier>,
152    pub only_to_customer: Option<Asn>,
153    /// unknown attributes formatted as (TYPE, RAW_BYTES)
154    pub unknown: Option<Vec<AttrRaw>>,
155    /// deprecated attributes formatted as (TYPE, RAW_BYTES)
156    pub deprecated: Option<Vec<AttrRaw>>,
157}
158
159impl Eq for BgpElem {}
160
161impl PartialOrd<Self> for BgpElem {
162    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
163        Some(self.cmp(other))
164    }
165}
166
167impl Ord for BgpElem {
168    fn cmp(&self, other: &Self) -> Ordering {
169        self.timestamp
170            .partial_cmp(&other.timestamp)
171            .unwrap()
172            .then_with(|| self.peer_ip.cmp(&other.peer_ip))
173    }
174}
175
176impl Default for BgpElem {
177    fn default() -> Self {
178        BgpElem {
179            timestamp: 0.0,
180            elem_type: ElemType::ANNOUNCE,
181            peer_ip: IpAddr::from_str("0.0.0.0").unwrap(),
182            peer_asn: 0.into(),
183            peer_bgp_id: None,
184            prefix: NetworkPrefix::from_str("0.0.0.0/0").unwrap(),
185            next_hop: Some(IpAddr::from_str("0.0.0.0").unwrap()),
186            as_path: None,
187            origin_asns: None,
188            origin: None,
189            local_pref: None,
190            med: None,
191            communities: None,
192            atomic: false,
193            aggr_asn: None,
194            aggr_ip: None,
195            only_to_customer: None,
196            unknown: None,
197            deprecated: None,
198        }
199    }
200}
201
202/// `OptionToStr` is a helper struct that wraps an `Option` and provides a convenient
203/// way to convert its value to a string representation.
204///
205/// # Generic Parameters
206///
207/// - `'a`: The lifetime parameter that represents the lifetime of the wrapped `Option` value.
208///
209/// # Fields
210///
211/// - `0: &'a Option<T>`: The reference to the wrapped `Option` value.
212struct OptionToStr<'a, T>(&'a Option<T>);
213
214impl<T: Display> Display for OptionToStr<'_, T> {
215    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
216        match self.0 {
217            None => Ok(()),
218            Some(x) => write!(f, "{x}"),
219        }
220    }
221}
222
223/// Helper struct to convert Option<Vec<T>> to Vec<String>
224///
225/// This struct provides a convenient way to convert an `Option<Vec<T>>` into a `Vec<String>`.
226/// It is used for converting the `Option<Vec<MetaCommunity>>` and `Option<Vec<AttrRaw>>` fields
227/// of the `BgpElem` struct into a printable format.
228struct OptionToStrVec<'a, T>(&'a Option<Vec<T>>);
229
230impl<T: Display> Display for OptionToStrVec<'_, T> {
231    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
232        match self.0 {
233            None => Ok(()),
234            Some(v) => write!(
235                f,
236                "{}",
237                v.iter()
238                    .map(|e| e.to_string())
239                    .collect::<Vec<String>>()
240                    .join(" ")
241            ),
242        }
243    }
244}
245
246#[inline(always)]
247pub fn option_to_string_communities(o: &Option<Vec<MetaCommunity>>) -> String {
248    if let Some(v) = o {
249        v.iter().join(" ")
250    } else {
251        String::new()
252    }
253}
254
255impl Display for BgpElem {
256    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
257        let t = match self.elem_type {
258            ElemType::ANNOUNCE => "A",
259            ElemType::WITHDRAW => "W",
260        };
261        write!(
262            f,
263            "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
264            t,
265            &self.timestamp,
266            &self.peer_ip,
267            &self.peer_asn,
268            &self.prefix,
269            OptionToStr(&self.as_path),
270            OptionToStr(&self.origin),
271            OptionToStr(&self.next_hop),
272            OptionToStr(&self.local_pref),
273            OptionToStr(&self.med),
274            option_to_string_communities(&self.communities),
275            self.atomic,
276            OptionToStr(&self.aggr_asn),
277            OptionToStr(&self.aggr_ip),
278        )
279    }
280}
281
282impl BgpElem {
283    /// Returns true if the element is an announcement.
284    ///
285    /// Most of the time, users do not really need to get the type out, only needs to know if it is
286    /// an announcement or a withdrawal.
287    pub fn is_announcement(&self) -> bool {
288        self.elem_type == ElemType::ANNOUNCE
289    }
290
291    /// Returns the origin AS number as u32. Returns None if the origin AS number is not present or
292    /// it's a AS set.
293    pub fn get_origin_asn_opt(&self) -> Option<u32> {
294        let origin_asns = self.origin_asns.as_ref()?;
295        (origin_asns.len() == 1).then(|| origin_asns[0].into())
296    }
297
298    /// Returns the PSV header as a string.
299    ///
300    /// The PSV header is a pipe-separated string that represents the fields
301    /// present in PSV (Prefix Statement Format) records. PSV records are used
302    /// to describe BGP (Border Gateway Protocol) routing information.
303    ///
304    /// # Example
305    ///
306    /// ```
307    /// use bgpkit_parser::BgpElem;
308    ///
309    /// let header = BgpElem::get_psv_header();
310    /// assert_eq!(header, "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");
311    /// ```
312    pub fn get_psv_header() -> String {
313        let fields = [
314            "type",
315            "timestamp",
316            "peer_ip",
317            "peer_asn",
318            "prefix",
319            "as_path",
320            "origin_asns",
321            "origin",
322            "next_hop",
323            "local_pref",
324            "med",
325            "communities",
326            "atomic",
327            "aggr_asn",
328            "aggr_ip",
329            "only_to_customer",
330        ];
331        fields.join("|")
332    }
333
334    /// Converts the struct fields into a pipe-separated values (PSV) formatted string.
335    ///
336    /// # Returns
337    ///
338    /// Returns a `String` representing the struct fields in PSV format.
339    ///
340    /// # Example
341    ///
342    /// ```
343    /// use crate::bgpkit_parser::BgpElem;
344    ///
345    /// let psv_string = BgpElem::default().to_psv();
346    ///
347    /// println!("{}", psv_string);
348    /// ```
349    pub fn to_psv(&self) -> String {
350        let t = match self.elem_type {
351            ElemType::ANNOUNCE => "A",
352            ElemType::WITHDRAW => "W",
353        };
354        format!(
355            "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
356            t,
357            &self.timestamp,
358            &self.peer_ip,
359            &self.peer_asn,
360            &self.prefix,
361            OptionToStr(&self.as_path),
362            OptionToStrVec(&self.origin_asns),
363            OptionToStr(&self.origin),
364            OptionToStr(&self.next_hop),
365            OptionToStr(&self.local_pref),
366            OptionToStr(&self.med),
367            option_to_string_communities(&self.communities),
368            self.atomic,
369            OptionToStr(&self.aggr_asn),
370            OptionToStr(&self.aggr_ip),
371            OptionToStr(&self.only_to_customer),
372        )
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379    use std::default::Default;
380    use std::str::FromStr;
381
382    #[test]
383    #[cfg(feature = "serde")]
384    fn test_default() {
385        let elem = BgpElem {
386            timestamp: 0.0,
387            elem_type: ElemType::ANNOUNCE,
388            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
389            peer_asn: 0.into(),
390            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
391            ..Default::default()
392        };
393        println!("{}", serde_json::json!(elem));
394    }
395
396    #[test]
397    fn test_sorting() {
398        let elem1 = BgpElem {
399            timestamp: 1.1,
400            elem_type: ElemType::ANNOUNCE,
401            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
402            peer_asn: 0.into(),
403            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
404            ..Default::default()
405        };
406        let elem2 = BgpElem {
407            timestamp: 1.2,
408            elem_type: ElemType::ANNOUNCE,
409            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
410            peer_asn: 0.into(),
411            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
412            ..Default::default()
413        };
414        let elem3 = BgpElem {
415            timestamp: 1.2,
416            elem_type: ElemType::ANNOUNCE,
417            peer_ip: IpAddr::from_str("192.168.1.2").unwrap(),
418            peer_asn: 0.into(),
419            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
420            ..Default::default()
421        };
422
423        assert!(elem1 < elem2);
424        assert!(elem2 < elem3);
425    }
426
427    #[test]
428    fn test_psv() {
429        assert_eq!(
430            BgpElem::get_psv_header().as_str(),
431            "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"
432        );
433        let elem = BgpElem::default();
434        assert_eq!(
435            elem.to_psv().as_str(),
436            "A|0|0.0.0.0|0|0.0.0.0/0||||0.0.0.0||||false|||"
437        );
438    }
439
440    #[test]
441    fn test_option_to_str() {
442        let asn_opt: Option<u32> = Some(12);
443        assert_eq!(OptionToStr(&asn_opt).to_string(), "12");
444        let none_opt: Option<u32> = None;
445        assert_eq!(OptionToStr(&none_opt).to_string(), "");
446        let asns_opt = Some(vec![12, 34]);
447        assert_eq!(OptionToStrVec(&asns_opt).to_string(), "12 34");
448        assert_eq!(OptionToStrVec(&None::<Vec<u32>>).to_string(), "");
449    }
450}