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}