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