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(), "");
}
}