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}