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 network prefix of the item.
111    pub prefix: NetworkPrefix,
112    /// The next hop IP address for the item, if available.
113    pub next_hop: Option<IpAddr>,
114    /// The optional path representation of the item.
115    ///
116    /// This field is of type `Option<AsPath>`, which means it can either contain
117    /// a value of type `AsPath` or be `None`.
118    pub as_path: Option<AsPath>,
119    /// The origin ASNs associated with the prefix, if available.
120    ///
121    /// # Remarks
122    /// An `Option` type is used to indicate that the `origin_asns` field may or may not have a value.
123    /// If it has a value, it will be a `Vec` (vector) of `Asn` objects representing the ASNs.
124    /// If it does not have a value, it will be `None`.
125    pub origin_asns: Option<Vec<Asn>>,
126    /// The origin of the item (IGP, EGP, INCOMPLETE), if known. Can be `None` if the origin is not available.
127    pub origin: Option<Origin>,
128    /// The local preference of the item, if available, represented as an option of unsigned 32-bit integer.
129    pub local_pref: Option<u32>,
130    /// The number of medical items in an option format.
131    pub med: Option<u32>,
132    /// A vector of optional `MetaCommunity` values.
133    ///
134    /// # Remarks
135    /// `MetaCommunity` represents a community metadata.
136    /// The `Option` type indicates that the vector can be empty or contain [MetaCommunity] values.
137    /// When the `Option` is `Some`, it means the vector is not empty and contains [MetaCommunity] values.
138    /// When the `Option` is `None`, it means the vector is empty.
139    pub communities: Option<Vec<MetaCommunity>>,
140    /// Indicates whether the item is atomic aggreagte or not.
141    pub atomic: bool,
142    /// The aggregated ASN of the item, represented as an optional [Asn] type.
143    pub aggr_asn: Option<Asn>,
144    /// The aggregated IP address of the item, represented as an optional [BgpIdentifier], i.e. `Ipv4Addr`.
145    pub aggr_ip: Option<BgpIdentifier>,
146    pub only_to_customer: Option<Asn>,
147    /// unknown attributes formatted as (TYPE, RAW_BYTES)
148    pub unknown: Option<Vec<AttrRaw>>,
149    /// deprecated attributes formatted as (TYPE, RAW_BYTES)
150    pub deprecated: Option<Vec<AttrRaw>>,
151}
152
153impl Eq for BgpElem {}
154
155impl PartialOrd<Self> for BgpElem {
156    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
157        Some(self.cmp(other))
158    }
159}
160
161impl Ord for BgpElem {
162    fn cmp(&self, other: &Self) -> Ordering {
163        self.timestamp
164            .partial_cmp(&other.timestamp)
165            .unwrap()
166            .then_with(|| self.peer_ip.cmp(&other.peer_ip))
167    }
168}
169
170impl Default for BgpElem {
171    fn default() -> Self {
172        BgpElem {
173            timestamp: 0.0,
174            elem_type: ElemType::ANNOUNCE,
175            peer_ip: IpAddr::from_str("0.0.0.0").unwrap(),
176            peer_asn: 0.into(),
177            prefix: NetworkPrefix::from_str("0.0.0.0/0").unwrap(),
178            next_hop: Some(IpAddr::from_str("0.0.0.0").unwrap()),
179            as_path: None,
180            origin_asns: None,
181            origin: None,
182            local_pref: None,
183            med: None,
184            communities: None,
185            atomic: false,
186            aggr_asn: None,
187            aggr_ip: None,
188            only_to_customer: None,
189            unknown: None,
190            deprecated: None,
191        }
192    }
193}
194
195/// `OptionToStr` is a helper struct that wraps an `Option` and provides a convenient
196/// way to convert its value to a string representation.
197///
198/// # Generic Parameters
199///
200/// - `'a`: The lifetime parameter that represents the lifetime of the wrapped `Option` value.
201///
202/// # Fields
203///
204/// - `0: &'a Option<T>`: The reference to the wrapped `Option` value.
205struct OptionToStr<'a, T>(&'a Option<T>);
206
207impl<T: Display> Display for OptionToStr<'_, T> {
208    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
209        match self.0 {
210            None => Ok(()),
211            Some(x) => write!(f, "{x}"),
212        }
213    }
214}
215
216/// Helper struct to convert Option<Vec<T>> to Vec<String>
217///
218/// This struct provides a convenient way to convert an `Option<Vec<T>>` into a `Vec<String>`.
219/// It is used for converting the `Option<Vec<MetaCommunity>>` and `Option<Vec<AttrRaw>>` fields
220/// of the `BgpElem` struct into a printable format.
221struct OptionToStrVec<'a, T>(&'a Option<Vec<T>>);
222
223impl<T: Display> Display for OptionToStrVec<'_, T> {
224    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
225        match self.0 {
226            None => Ok(()),
227            Some(v) => write!(
228                f,
229                "{}",
230                v.iter()
231                    .map(|e| e.to_string())
232                    .collect::<Vec<String>>()
233                    .join(" ")
234            ),
235        }
236    }
237}
238
239#[inline(always)]
240pub fn option_to_string_communities(o: &Option<Vec<MetaCommunity>>) -> String {
241    if let Some(v) = o {
242        v.iter().join(" ")
243    } else {
244        String::new()
245    }
246}
247
248impl Display for BgpElem {
249    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250        let t = match self.elem_type {
251            ElemType::ANNOUNCE => "A",
252            ElemType::WITHDRAW => "W",
253        };
254        write!(
255            f,
256            "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
257            t,
258            &self.timestamp,
259            &self.peer_ip,
260            &self.peer_asn,
261            &self.prefix,
262            OptionToStr(&self.as_path),
263            OptionToStr(&self.origin),
264            OptionToStr(&self.next_hop),
265            OptionToStr(&self.local_pref),
266            OptionToStr(&self.med),
267            option_to_string_communities(&self.communities),
268            self.atomic,
269            OptionToStr(&self.aggr_asn),
270            OptionToStr(&self.aggr_ip),
271        )
272    }
273}
274
275impl BgpElem {
276    /// Returns true if the element is an announcement.
277    ///
278    /// Most of the time, users do not really need to get the type out, only needs to know if it is
279    /// an announcement or a withdrawal.
280    pub fn is_announcement(&self) -> bool {
281        self.elem_type == ElemType::ANNOUNCE
282    }
283
284    /// Returns the origin AS number as u32. Returns None if the origin AS number is not present or
285    /// it's a AS set.
286    pub fn get_origin_asn_opt(&self) -> Option<u32> {
287        let origin_asns = self.origin_asns.as_ref()?;
288        (origin_asns.len() == 1).then(|| origin_asns[0].into())
289    }
290
291    /// Returns the PSV header as a string.
292    ///
293    /// The PSV header is a pipe-separated string that represents the fields
294    /// present in PSV (Prefix Statement Format) records. PSV records are used
295    /// to describe BGP (Border Gateway Protocol) routing information.
296    ///
297    /// # Example
298    ///
299    /// ```
300    /// use bgpkit_parser::BgpElem;
301    ///
302    /// let header = BgpElem::get_psv_header();
303    /// 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");
304    /// ```
305    pub fn get_psv_header() -> String {
306        let fields = [
307            "type",
308            "timestamp",
309            "peer_ip",
310            "peer_asn",
311            "prefix",
312            "as_path",
313            "origin_asns",
314            "origin",
315            "next_hop",
316            "local_pref",
317            "med",
318            "communities",
319            "atomic",
320            "aggr_asn",
321            "aggr_ip",
322            "only_to_customer",
323        ];
324        fields.join("|")
325    }
326
327    /// Converts the struct fields into a pipe-separated values (PSV) formatted string.
328    ///
329    /// # Returns
330    ///
331    /// Returns a `String` representing the struct fields in PSV format.
332    ///
333    /// # Example
334    ///
335    /// ```
336    /// use crate::bgpkit_parser::BgpElem;
337    ///
338    /// let psv_string = BgpElem::default().to_psv();
339    ///
340    /// println!("{}", psv_string);
341    /// ```
342    pub fn to_psv(&self) -> String {
343        let t = match self.elem_type {
344            ElemType::ANNOUNCE => "A",
345            ElemType::WITHDRAW => "W",
346        };
347        format!(
348            "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
349            t,
350            &self.timestamp,
351            &self.peer_ip,
352            &self.peer_asn,
353            &self.prefix,
354            OptionToStr(&self.as_path),
355            OptionToStrVec(&self.origin_asns),
356            OptionToStr(&self.origin),
357            OptionToStr(&self.next_hop),
358            OptionToStr(&self.local_pref),
359            OptionToStr(&self.med),
360            option_to_string_communities(&self.communities),
361            self.atomic,
362            OptionToStr(&self.aggr_asn),
363            OptionToStr(&self.aggr_ip),
364            OptionToStr(&self.only_to_customer),
365        )
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use std::default::Default;
373    use std::str::FromStr;
374
375    #[test]
376    #[cfg(feature = "serde")]
377    fn test_default() {
378        let elem = BgpElem {
379            timestamp: 0.0,
380            elem_type: ElemType::ANNOUNCE,
381            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
382            peer_asn: 0.into(),
383            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
384            ..Default::default()
385        };
386        println!("{}", serde_json::json!(elem));
387    }
388
389    #[test]
390    fn test_sorting() {
391        let elem1 = BgpElem {
392            timestamp: 1.1,
393            elem_type: ElemType::ANNOUNCE,
394            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
395            peer_asn: 0.into(),
396            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
397            ..Default::default()
398        };
399        let elem2 = BgpElem {
400            timestamp: 1.2,
401            elem_type: ElemType::ANNOUNCE,
402            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
403            peer_asn: 0.into(),
404            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
405            ..Default::default()
406        };
407        let elem3 = BgpElem {
408            timestamp: 1.2,
409            elem_type: ElemType::ANNOUNCE,
410            peer_ip: IpAddr::from_str("192.168.1.2").unwrap(),
411            peer_asn: 0.into(),
412            prefix: NetworkPrefix::from_str("8.8.8.0/24").unwrap(),
413            ..Default::default()
414        };
415
416        assert!(elem1 < elem2);
417        assert!(elem2 < elem3);
418    }
419
420    #[test]
421    fn test_psv() {
422        assert_eq!(
423            BgpElem::get_psv_header().as_str(),
424            "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"
425        );
426        let elem = BgpElem::default();
427        assert_eq!(
428            elem.to_psv().as_str(),
429            "A|0|0.0.0.0|0|0.0.0.0/0||||0.0.0.0||||false|||"
430        );
431    }
432
433    #[test]
434    fn test_option_to_str() {
435        let asn_opt: Option<u32> = Some(12);
436        assert_eq!(OptionToStr(&asn_opt).to_string(), "12");
437        let none_opt: Option<u32> = None;
438        assert_eq!(OptionToStr(&none_opt).to_string(), "");
439        let asns_opt = Some(vec![12, 34]);
440        assert_eq!(OptionToStrVec(&asns_opt).to_string(), "12 34");
441        assert_eq!(OptionToStrVec(&None::<Vec<u32>>).to_string(), "");
442    }
443}