bgpkit_parser/models/bgp/
elem.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
use crate::models::*;
use itertools::Itertools;
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::net::IpAddr;
use std::str::FromStr;

// TODO(jmeggitt): BgpElem can be converted to an enum. Apply this change during performance PR.

/// # ElemType
///
/// `ElemType` is an enumeration that represents the type of an element.
/// It has two possible values:
///
/// - `ANNOUNCE`: Indicates an announcement/reachable prefix.
/// - `WITHDRAW`: Indicates a withdrawn/unreachable prefix.
///
/// The enumeration derives the traits `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, and `Hash`.
///
/// It also has the following attributes:
///
/// - `#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]`
///     - This attribute is conditionally applied when the `"serde"` feature is enabled. It allows
///       the enumeration to be serialized and deserialized using serde.
/// - `#[cfg_attr(feature = "serde", serde(rename = "lowercase"))]`
///     - This attribute is conditionally applied when the `"serde"` feature is enabled. It specifies
///       that the serialized form of the enumeration should be in lowercase.
///
/// Example usage:
///
/// ```
/// use bgpkit_parser::models::ElemType;
///
/// let announce_type = ElemType::ANNOUNCE;
/// let withdraw_type = ElemType::WITHDRAW;
///
/// assert_eq!(announce_type, ElemType::ANNOUNCE);
/// assert_eq!(withdraw_type, ElemType::WITHDRAW);
/// ```
#[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 {
    /// Checks if the `ElemType` is an announce.
    ///
    /// Returns `true` if `ElemType` is `ANNOUNCE`, and `false` if it is `WITHDRAW`.
    ///
    /// # Examples
    ///
    /// ```
    /// use bgpkit_parser::models::ElemType;
    ///
    /// let elem = ElemType::ANNOUNCE;
    /// assert_eq!(elem.is_announce(), true);
    ///
    /// let elem = ElemType::WITHDRAW;
    /// assert_eq!(elem.is_announce(), false);
    /// ```
    pub fn is_announce(&self) -> bool {
        match self {
            ElemType::ANNOUNCE => true,
            ElemType::WITHDRAW => false,
        }
    }
}

/// BgpElem represents a per-prefix BGP element.
///
/// This struct contains information about an announced/withdrawn prefix.
///
/// Fields:
/// - `timestamp`: The time when the BGP element was received.
/// - `elem_type`: The type of BGP element.
/// - `peer_ip`: The IP address of the BGP peer.
/// - `peer_asn`: The ASN of the BGP peer.
/// - `prefix`: The network prefix.
/// - `next_hop`: The next hop IP address.
/// - `as_path`: The AS path.
/// - `origin_asns`: The list of origin ASNs.
/// - `origin`: The origin attribute, i.e. IGP, EGP, or INCOMPLETE.
/// - `local_pref`: The local preference value.
/// - `med`: The multi-exit discriminator value.
/// - `communities`: The list of BGP communities.
/// - `atomic`: Flag indicating if the announcement is atomic.
/// - `aggr_asn`: The aggregated ASN.
/// - `aggr_ip`: The aggregated IP address.
/// - `only_to_customer`: The AS number to which the prefix is only announced.
/// - `unknown`: Unknown attributes formatted as (TYPE, RAW_BYTES).
/// - `deprecated`: Deprecated attributes formatted as (TYPE, RAW_BYTES).
///
/// Note: Constructing BGP elements consumes more memory due to duplicate information
/// shared between multiple elements of one MRT record.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BgpElem {
    /// The timestamp of the item in floating-point format.
    pub timestamp: f64,
    /// The element type of an item.
    #[cfg_attr(feature = "serde", serde(rename = "type"))]
    pub elem_type: ElemType,
    /// The IP address of the peer associated with the item.
    pub peer_ip: IpAddr,
    /// The peer ASN (Autonomous System Number) of the item.
    pub peer_asn: Asn,
    /// The network prefix of the item.
    pub prefix: NetworkPrefix,
    /// The next hop IP address for the item, if available.
    pub next_hop: Option<IpAddr>,
    /// The optional path representation of the item.
    ///
    /// This field is of type `Option<AsPath>`, which means it can either contain
    /// a value of type `AsPath` or be `None`.
    pub as_path: Option<AsPath>,
    /// The origin ASNs associated with the prefix, if available.
    ///
    /// # Remarks
    /// An `Option` type is used to indicate that the `origin_asns` field may or may not have a value.
    /// If it has a value, it will be a `Vec` (vector) of `Asn` objects representing the ASNs.
    /// If it does not have a value, it will be `None`.
    pub origin_asns: Option<Vec<Asn>>,
    /// The origin of the item (IGP, EGP, INCOMPLETE), if known. Can be `None` if the origin is not available.
    pub origin: Option<Origin>,
    /// The local preference of the item, if available, represented as an option of unsigned 32-bit integer.
    pub local_pref: Option<u32>,
    /// The number of medical items in an option format.
    pub med: Option<u32>,
    /// A vector of optional `MetaCommunity` values.
    ///
    /// # Remarks
    /// `MetaCommunity` represents a community metadata.
    /// The `Option` type indicates that the vector can be empty or contain [MetaCommunity] values.
    /// When the `Option` is `Some`, it means the vector is not empty and contains [MetaCommunity] values.
    /// When the `Option` is `None`, it means the vector is empty.
    pub communities: Option<Vec<MetaCommunity>>,
    /// Indicates whether the item is atomic aggreagte or not.
    pub atomic: bool,
    /// The aggregated ASN of the item, represented as an optional [Asn] type.
    pub aggr_asn: Option<Asn>,
    /// The aggregated IP address of the item, represented as an optional [BgpIdentifier], i.e. `Ipv4Addr`.
    pub aggr_ip: Option<BgpIdentifier>,
    pub only_to_customer: Option<Asn>,
    /// unknown attributes formatted as (TYPE, RAW_BYTES)
    pub unknown: Option<Vec<AttrRaw>>,
    /// deprecated attributes formatted as (TYPE, RAW_BYTES)
    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(),
            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,
        }
    }
}

/// `OptionToStr` is a helper struct that wraps an `Option` and provides a convenient
/// way to convert its value to a string representation.
///
/// # Generic Parameters
///
/// - `'a`: The lifetime parameter that represents the lifetime of the wrapped `Option` value.
///
/// # Fields
///
/// - `0: &'a Option<T>`: The reference to the wrapped `Option` value.
struct OptionToStr<'a, T>(&'a Option<T>);

impl<'a, T: Display> Display for OptionToStr<'a, T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self.0 {
            None => Ok(()),
            Some(x) => write!(f, "{}", x),
        }
    }
}

/// Helper struct to convert Option<Vec<T>> to Vec<String>
///
/// This struct provides a convenient way to convert an `Option<Vec<T>>` into a `Vec<String>`.
/// It is used for converting the `Option<Vec<MetaCommunity>>` and `Option<Vec<AttrRaw>>` fields
/// of the `BgpElem` struct into a printable format.
struct OptionToStrVec<'a, T>(&'a Option<Vec<T>>);

impl<'a, T: Display> Display for OptionToStrVec<'a, 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 {
    /// Returns true if the element is an announcement.
    ///
    /// Most of the time, users do not really need to get the type out, only needs to know if it is
    /// an announcement or a withdrawal.
    pub fn is_announcement(&self) -> bool {
        self.elem_type == ElemType::ANNOUNCE
    }

    /// Returns the origin AS number as u32. Returns None if the origin AS number is not present or
    /// it's a AS set.
    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())
    }

    /// Returns the PSV header as a string.
    ///
    /// The PSV header is a pipe-separated string that represents the fields
    /// present in PSV (Prefix Statement Format) records. PSV records are used
    /// to describe BGP (Border Gateway Protocol) routing information.
    ///
    /// # Example
    ///
    /// ```
    /// use bgpkit_parser::BgpElem;
    ///
    /// let header = BgpElem::get_psv_header();
    /// 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");
    /// ```
    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("|")
    }

    /// Converts the struct fields into a pipe-separated values (PSV) formatted string.
    ///
    /// # Returns
    ///
    /// Returns a `String` representing the struct fields in PSV format.
    ///
    /// # Example
    ///
    /// ```
    /// use crate::bgpkit_parser::BgpElem;
    ///
    /// let psv_string = BgpElem::default().to_psv();
    ///
    /// println!("{}", psv_string);
    /// ```
    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(), "");
    }
}