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}