bgpkit_parser/parser/mrt/
mrt_elem.rs

1#![allow(unused)]
2//! This module handles converting MRT records into individual per-prefix BGP elements.
3//!
4//! Each MRT record may contain reachability information for multiple prefixes. This module breaks
5//! down MRT records into corresponding BGP elements, and thus allowing users to more conveniently
6//! process BGP information on a per-prefix basis.
7use crate::models::*;
8use crate::parser::bgp::messages::parse_bgp_update_message;
9use crate::ParserError;
10use crate::ParserError::ParseError;
11use itertools::Itertools;
12use log::{error, warn};
13use std::collections::HashMap;
14use std::fmt::{Display, Formatter};
15use std::net::{IpAddr, Ipv4Addr};
16
17#[derive(Default, Debug, Clone)]
18pub struct Elementor {
19    pub peer_table: Option<PeerIndexTable>,
20}
21
22// use macro_rules! <name of macro>{<Body>}
23macro_rules! get_attr_value {
24    ($a:tt, $b:expr) => {
25        if let Attribute::$a(x) = $b {
26            Some(x)
27        } else {
28            None
29        }
30    };
31}
32
33#[allow(clippy::type_complexity)]
34fn get_relevant_attributes(
35    attributes: Attributes,
36) -> (
37    Option<AsPath>,
38    Option<AsPath>,
39    Option<Origin>,
40    Option<IpAddr>,
41    Option<u32>,
42    Option<u32>,
43    Option<Vec<MetaCommunity>>,
44    bool,
45    Option<(Asn, BgpIdentifier)>,
46    Option<Nlri>,
47    Option<Nlri>,
48    Option<Asn>,
49    Option<Vec<AttrRaw>>,
50    Option<Vec<AttrRaw>>,
51) {
52    let mut as_path = None;
53    let mut as4_path = None;
54    let mut origin = None;
55    let mut next_hop = None;
56    let mut local_pref = Some(0);
57    let mut med = Some(0);
58    let mut atomic = false;
59    let mut aggregator = None;
60    let mut announced = None;
61    let mut withdrawn = None;
62    let mut otc = None;
63    let mut unknown = vec![];
64    let mut deprecated = vec![];
65
66    let mut communities_vec: Vec<MetaCommunity> = vec![];
67
68    for attr in attributes {
69        match attr {
70            AttributeValue::Origin(v) => origin = Some(v),
71            AttributeValue::AsPath {
72                path,
73                is_as4: false,
74            } => as_path = Some(path),
75            AttributeValue::AsPath { path, is_as4: true } => as4_path = Some(path),
76            AttributeValue::NextHop(v) => next_hop = Some(v),
77            AttributeValue::MultiExitDiscriminator(v) => med = Some(v),
78            AttributeValue::LocalPreference(v) => local_pref = Some(v),
79            AttributeValue::AtomicAggregate => atomic = true,
80            AttributeValue::Communities(v) => communities_vec.extend(
81                v.into_iter()
82                    .map(MetaCommunity::Plain)
83                    .collect::<Vec<MetaCommunity>>(),
84            ),
85            AttributeValue::ExtendedCommunities(v) => communities_vec.extend(
86                v.into_iter()
87                    .map(MetaCommunity::Extended)
88                    .collect::<Vec<MetaCommunity>>(),
89            ),
90            AttributeValue::Ipv6AddressSpecificExtendedCommunities(v) => communities_vec.extend(
91                v.into_iter()
92                    .map(MetaCommunity::Ipv6Extended)
93                    .collect::<Vec<MetaCommunity>>(),
94            ),
95            AttributeValue::LargeCommunities(v) => communities_vec.extend(
96                v.into_iter()
97                    .map(MetaCommunity::Large)
98                    .collect::<Vec<MetaCommunity>>(),
99            ),
100            AttributeValue::Aggregator { asn, id, .. } => aggregator = Some((asn, id)),
101            AttributeValue::MpReachNlri(nlri) => announced = Some(nlri),
102            AttributeValue::MpUnreachNlri(nlri) => withdrawn = Some(nlri),
103            AttributeValue::OnlyToCustomer(o) => otc = Some(o),
104
105            AttributeValue::Unknown(t) => {
106                unknown.push(t);
107            }
108            AttributeValue::Deprecated(t) => {
109                deprecated.push(t);
110            }
111
112            AttributeValue::OriginatorId(_)
113            | AttributeValue::Clusters(_)
114            | AttributeValue::Development(_)
115            | AttributeValue::LinkState(_)
116            | AttributeValue::TunnelEncapsulation(_) => {}
117        };
118    }
119
120    let communities = match !communities_vec.is_empty() {
121        true => Some(communities_vec),
122        false => None,
123    };
124
125    // If the next_hop is not set, we try to get it from the announced NLRI.
126    let next_hop = next_hop.or_else(|| {
127        announced.as_ref().and_then(|v| {
128            v.next_hop.as_ref().map(|h| match h {
129                NextHopAddress::Ipv4(v) => IpAddr::from(*v),
130                NextHopAddress::Ipv6(v) => IpAddr::from(*v),
131                NextHopAddress::Ipv6LinkLocal(v, _) => IpAddr::from(*v),
132                // RFC 8950: VPN next hops - return the IPv6 address part
133                NextHopAddress::VpnIpv6(_, v) => IpAddr::from(*v),
134                NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => IpAddr::from(*v),
135            })
136        })
137    });
138
139    (
140        as_path,
141        as4_path,
142        origin,
143        next_hop,
144        local_pref,
145        med,
146        communities,
147        atomic,
148        aggregator,
149        announced,
150        withdrawn,
151        otc,
152        if unknown.is_empty() {
153            None
154        } else {
155            Some(unknown)
156        },
157        if deprecated.is_empty() {
158            None
159        } else {
160            Some(deprecated)
161        },
162    )
163}
164
165impl Elementor {
166    pub fn new() -> Elementor {
167        Self::default()
168    }
169
170    /// Sets the peer index table for the elementor.
171    ///
172    /// This method takes an MRT record and extracts the peer index table from it if the record contains one.
173    /// The peer index table is required for processing TableDumpV2 records, as it contains the mapping between
174    /// peer indices and their corresponding IP addresses and ASNs.
175    ///
176    /// # Arguments
177    ///
178    /// * `record` - An MRT record that should contain a peer index table
179    ///
180    /// # Returns
181    ///
182    /// * `Ok(())` - If the peer table was successfully extracted and set
183    /// * `Err(ParserError)` - If the record does not contain a peer index table
184    ///
185    /// # Example
186    ///
187    /// ```no_run
188    /// use bgpkit_parser::{BgpkitParser, Elementor};
189    ///
190    /// let mut parser = BgpkitParser::new("rib.dump.bz2").unwrap();
191    /// let mut elementor = Elementor::new();
192    ///
193    /// // Get the first record which should be the peer index table
194    /// if let Ok(record) = parser.next_record() {
195    ///     elementor.set_peer_table(record).unwrap();
196    /// }
197    /// ```
198    pub fn set_peer_table(&mut self, record: MrtRecord) -> Result<(), ParserError> {
199        if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(p)) =
200            record.message
201        {
202            self.peer_table = Some(p);
203            Ok(())
204        } else {
205            Err(ParseError("peer_table is not a PeerIndexTable".to_string()))
206        }
207    }
208
209    /// Convert a [BgpMessage] to a vector of [BgpElem]s.
210    ///
211    /// A [BgpMessage] may include `Update`, `Open`, `Notification` or `KeepAlive` messages,
212    /// and only `Update` message contains [BgpElem]s.
213    pub fn bgp_to_elems(
214        msg: BgpMessage,
215        timestamp: f64,
216        peer_ip: &IpAddr,
217        peer_asn: &Asn,
218    ) -> Vec<BgpElem> {
219        match msg {
220            BgpMessage::Update(msg) => {
221                Elementor::bgp_update_to_elems(msg, timestamp, peer_ip, peer_asn)
222            }
223            BgpMessage::Open(_) | BgpMessage::Notification(_) | BgpMessage::KeepAlive => {
224                vec![]
225            }
226        }
227    }
228
229    /// Convert a [BgpUpdateMessage] to a vector of [BgpElem]s.
230    pub fn bgp_update_to_elems(
231        msg: BgpUpdateMessage,
232        timestamp: f64,
233        peer_ip: &IpAddr,
234        peer_asn: &Asn,
235    ) -> Vec<BgpElem> {
236        let mut elems = vec![];
237
238        let (
239            as_path,
240            as4_path, // Table dump v1 does not have 4-byte AS number
241            origin,
242            next_hop,
243            local_pref,
244            med,
245            communities,
246            atomic,
247            aggregator,
248            announced,
249            withdrawn,
250            only_to_customer,
251            unknown,
252            deprecated,
253        ) = get_relevant_attributes(msg.attributes);
254
255        let path = match (as_path, as4_path) {
256            (None, None) => None,
257            (Some(v), None) => Some(v),
258            (None, Some(v)) => Some(v),
259            (Some(v1), Some(v2)) => Some(AsPath::merge_aspath_as4path(&v1, &v2)),
260        };
261
262        let origin_asns = path
263            .as_ref()
264            .map(|as_path| as_path.iter_origins().collect());
265
266        elems.extend(msg.announced_prefixes.into_iter().map(|p| BgpElem {
267            timestamp,
268            elem_type: ElemType::ANNOUNCE,
269            peer_ip: *peer_ip,
270            peer_asn: *peer_asn,
271            prefix: p,
272            next_hop,
273            as_path: path.clone(),
274            origin_asns: origin_asns.clone(),
275            origin,
276            local_pref,
277            med,
278            communities: communities.clone(),
279            atomic,
280            aggr_asn: aggregator.as_ref().map(|v| v.0),
281            aggr_ip: aggregator.as_ref().map(|v| v.1),
282            only_to_customer,
283            unknown: unknown.clone(),
284            deprecated: deprecated.clone(),
285        }));
286
287        if let Some(nlri) = announced {
288            elems.extend(nlri.prefixes.into_iter().map(|p| BgpElem {
289                timestamp,
290                elem_type: ElemType::ANNOUNCE,
291                peer_ip: *peer_ip,
292                peer_asn: *peer_asn,
293                prefix: p,
294                next_hop,
295                as_path: path.clone(),
296                origin,
297                origin_asns: origin_asns.clone(),
298                local_pref,
299                med,
300                communities: communities.clone(),
301                atomic,
302                aggr_asn: aggregator.as_ref().map(|v| v.0),
303                aggr_ip: aggregator.as_ref().map(|v| v.1),
304                only_to_customer,
305                unknown: unknown.clone(),
306                deprecated: deprecated.clone(),
307            }));
308        }
309
310        elems.extend(msg.withdrawn_prefixes.into_iter().map(|p| BgpElem {
311            timestamp,
312            elem_type: ElemType::WITHDRAW,
313            peer_ip: *peer_ip,
314            peer_asn: *peer_asn,
315            prefix: p,
316            next_hop: None,
317            as_path: None,
318            origin: None,
319            origin_asns: None,
320            local_pref: None,
321            med: None,
322            communities: None,
323            atomic: false,
324            aggr_asn: None,
325            aggr_ip: None,
326            only_to_customer,
327            unknown: None,
328            deprecated: None,
329        }));
330        if let Some(nlri) = withdrawn {
331            elems.extend(nlri.prefixes.into_iter().map(|p| BgpElem {
332                timestamp,
333                elem_type: ElemType::WITHDRAW,
334                peer_ip: *peer_ip,
335                peer_asn: *peer_asn,
336                prefix: p,
337                next_hop: None,
338                as_path: None,
339                origin: None,
340                origin_asns: None,
341                local_pref: None,
342                med: None,
343                communities: None,
344                atomic: false,
345                aggr_asn: None,
346                aggr_ip: None,
347                only_to_customer,
348                unknown: None,
349                deprecated: None,
350            }));
351        };
352        elems
353    }
354
355    /// Convert a [MrtRecord] to a vector of [BgpElem]s.
356    pub fn record_to_elems(&mut self, record: MrtRecord) -> Vec<BgpElem> {
357        let mut elems = vec![];
358        let t = record.common_header.timestamp;
359        let timestamp: f64 = if let Some(micro) = &record.common_header.microsecond_timestamp {
360            let m = (*micro as f64) / 1000000.0;
361            t as f64 + m
362        } else {
363            f64::from(t)
364        };
365
366        match record.message {
367            MrtMessage::TableDumpMessage(msg) => {
368                let (
369                    as_path,
370                    _as4_path, // Table dump v1 does not have 4-byte AS number
371                    origin,
372                    next_hop,
373                    local_pref,
374                    med,
375                    communities,
376                    atomic,
377                    aggregator,
378                    _announced,
379                    _withdrawn,
380                    only_to_customer,
381                    unknown,
382                    deprecated,
383                ) = get_relevant_attributes(msg.attributes);
384
385                let origin_asns = as_path
386                    .as_ref()
387                    .map(|as_path| as_path.iter_origins().collect());
388
389                elems.push(BgpElem {
390                    timestamp,
391                    elem_type: ElemType::ANNOUNCE,
392                    peer_ip: msg.peer_ip,
393                    peer_asn: msg.peer_asn,
394                    prefix: msg.prefix,
395                    next_hop,
396                    as_path,
397                    origin,
398                    origin_asns,
399                    local_pref,
400                    med,
401                    communities,
402                    atomic,
403                    aggr_asn: aggregator.map(|v| v.0),
404                    aggr_ip: aggregator.map(|v| v.1),
405                    only_to_customer,
406                    unknown,
407                    deprecated,
408                });
409            }
410
411            MrtMessage::TableDumpV2Message(msg) => {
412                match msg {
413                    TableDumpV2Message::PeerIndexTable(p) => {
414                        self.peer_table = Some(p);
415                    }
416                    TableDumpV2Message::RibAfi(t) => {
417                        let prefix = t.prefix;
418                        for e in t.rib_entries {
419                            let pid = e.peer_index;
420                            let peer = match self.peer_table.as_ref() {
421                                None => {
422                                    error!("peer_table is None");
423                                    break;
424                                }
425                                Some(table) => match table.get_peer_by_id(&pid) {
426                                    None => {
427                                        error!("peer ID {} not found in peer_index table", pid);
428                                        break;
429                                    }
430                                    Some(peer) => peer,
431                                },
432                            };
433                            let (
434                                as_path,
435                                as4_path, // Table dump v1 does not have 4-byte AS number
436                                origin,
437                                next_hop,
438                                local_pref,
439                                med,
440                                communities,
441                                atomic,
442                                aggregator,
443                                announced,
444                                _withdrawn,
445                                only_to_customer,
446                                unknown,
447                                deprecated,
448                            ) = get_relevant_attributes(e.attributes);
449
450                            let path = match (as_path, as4_path) {
451                                (None, None) => None,
452                                (Some(v), None) => Some(v),
453                                (None, Some(v)) => Some(v),
454                                (Some(v1), Some(v2)) => {
455                                    Some(AsPath::merge_aspath_as4path(&v1, &v2))
456                                }
457                            };
458
459                            let next = match next_hop {
460                                None => {
461                                    if let Some(v) = announced {
462                                        if let Some(h) = v.next_hop {
463                                            match h {
464                                                NextHopAddress::Ipv4(v) => Some(IpAddr::from(v)),
465                                                NextHopAddress::Ipv6(v) => Some(IpAddr::from(v)),
466                                                NextHopAddress::Ipv6LinkLocal(v, _) => {
467                                                    Some(IpAddr::from(v))
468                                                }
469                                                // RFC 8950: VPN next hops - return the IPv6 address part
470                                                NextHopAddress::VpnIpv6(_, v) => {
471                                                    Some(IpAddr::from(v))
472                                                }
473                                                NextHopAddress::VpnIpv6LinkLocal(_, v, _, _) => {
474                                                    Some(IpAddr::from(v))
475                                                }
476                                            }
477                                        } else {
478                                            None
479                                        }
480                                    } else {
481                                        None
482                                    }
483                                }
484                                Some(v) => Some(v),
485                            };
486
487                            let origin_asns = path
488                                .as_ref()
489                                .map(|as_path| as_path.iter_origins().collect());
490
491                            elems.push(BgpElem {
492                                timestamp,
493                                elem_type: ElemType::ANNOUNCE,
494                                peer_ip: peer.peer_ip,
495                                peer_asn: peer.peer_asn,
496                                prefix,
497                                next_hop: next,
498                                as_path: path,
499                                origin,
500                                origin_asns,
501                                local_pref,
502                                med,
503                                communities,
504                                atomic,
505                                aggr_asn: aggregator.map(|v| v.0),
506                                aggr_ip: aggregator.map(|v| v.1),
507                                only_to_customer,
508                                unknown,
509                                deprecated,
510                            });
511                        }
512                    }
513                    TableDumpV2Message::RibGeneric(_t) => {
514                        warn!(
515                            "to_elem for TableDumpV2Message::RibGenericEntries not yet implemented"
516                        );
517                    }
518                    TableDumpV2Message::GeoPeerTable(_t) => {
519                        // GeoPeerTable doesn't generate BGP elements, it provides geo-location context
520                        // for other peer entries. No BGP elements are generated from this message type.
521                    }
522                }
523            }
524            MrtMessage::Bgp4Mp(msg) => match msg {
525                Bgp4MpEnum::StateChange(_) => {}
526                Bgp4MpEnum::Message(v) => {
527                    elems.extend(Elementor::bgp_to_elems(
528                        v.bgp_message,
529                        timestamp,
530                        &v.peer_ip,
531                        &v.peer_asn,
532                    ));
533                }
534            },
535        }
536        elems
537    }
538}
539
540#[inline(always)]
541pub fn option_to_string<T>(o: &Option<T>) -> String
542where
543    T: Display,
544{
545    if let Some(v) = o {
546        v.to_string()
547    } else {
548        String::new()
549    }
550}
551
552impl From<&BgpElem> for Attributes {
553    fn from(value: &BgpElem) -> Self {
554        let mut values = Vec::<AttributeValue>::new();
555        let mut attributes = Attributes::default();
556        let prefix = value.prefix;
557
558        if value.elem_type == ElemType::WITHDRAW {
559            values.push(AttributeValue::MpUnreachNlri(Nlri::new_unreachable(prefix)));
560            attributes.extend(values);
561            return attributes;
562        }
563
564        values.push(AttributeValue::MpReachNlri(Nlri::new_reachable(
565            prefix,
566            value.next_hop,
567        )));
568
569        if let Some(v) = value.next_hop {
570            values.push(AttributeValue::NextHop(v));
571        }
572
573        if let Some(v) = value.as_path.as_ref() {
574            let is_as4 = match v.get_origin_opt() {
575                None => true,
576                Some(asn) => asn.is_four_byte(),
577            };
578            values.push(AttributeValue::AsPath {
579                path: v.clone(),
580                is_as4,
581            });
582        }
583
584        if let Some(v) = value.origin {
585            values.push(AttributeValue::Origin(v));
586        }
587
588        if let Some(v) = value.local_pref {
589            values.push(AttributeValue::LocalPreference(v));
590        }
591
592        if let Some(v) = value.med {
593            values.push(AttributeValue::MultiExitDiscriminator(v));
594        }
595
596        if let Some(v) = value.communities.as_ref() {
597            let mut communites = vec![];
598            let mut extended_communities = vec![];
599            let mut ipv6_extended_communities = vec![];
600            let mut large_communities = vec![];
601            for c in v {
602                match c {
603                    MetaCommunity::Plain(v) => communites.push(*v),
604                    MetaCommunity::Extended(v) => extended_communities.push(*v),
605                    MetaCommunity::Large(v) => large_communities.push(*v),
606                    MetaCommunity::Ipv6Extended(v) => ipv6_extended_communities.push(*v),
607                }
608            }
609            if !communites.is_empty() {
610                values.push(AttributeValue::Communities(communites));
611            }
612            if !extended_communities.is_empty() {
613                values.push(AttributeValue::ExtendedCommunities(extended_communities));
614            }
615            if !large_communities.is_empty() {
616                values.push(AttributeValue::LargeCommunities(large_communities));
617            }
618            if !ipv6_extended_communities.is_empty() {
619                values.push(AttributeValue::Ipv6AddressSpecificExtendedCommunities(
620                    ipv6_extended_communities,
621                ));
622            }
623        }
624
625        if let Some(v) = value.aggr_asn {
626            let aggregator_id = match value.aggr_ip {
627                Some(v) => v,
628                None => Ipv4Addr::UNSPECIFIED,
629            };
630            values.push(AttributeValue::Aggregator {
631                asn: v,
632                id: aggregator_id,
633                is_as4: v.is_four_byte(),
634            });
635        }
636
637        if let Some(v) = value.only_to_customer {
638            values.push(AttributeValue::OnlyToCustomer(v));
639        }
640
641        if let Some(v) = value.unknown.as_ref() {
642            for t in v {
643                values.push(AttributeValue::Unknown(t.clone()));
644            }
645        }
646
647        if let Some(v) = value.deprecated.as_ref() {
648            for t in v {
649                values.push(AttributeValue::Deprecated(t.clone()));
650            }
651        }
652
653        attributes.extend(values);
654        attributes
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661    use crate::BgpkitParser;
662    use std::net::{Ipv4Addr, Ipv6Addr};
663    use std::str::FromStr;
664
665    #[test]
666    fn test_option_to_string() {
667        let o1 = Some(1);
668        let o2: Option<u32> = None;
669        assert_eq!(option_to_string(&o1), "1");
670        assert_eq!(option_to_string(&o2), "");
671    }
672
673    #[test]
674    fn test_record_to_elems() {
675        let url_table_dump_v1 = "https://data.ris.ripe.net/rrc00/2003.01/bview.20030101.0000.gz";
676        let url_table_dump_v2 = "https://data.ris.ripe.net/rrc00/2023.01/bview.20230101.0000.gz";
677        let url_bgp4mp = "https://data.ris.ripe.net/rrc00/2021.10/updates.20211001.0000.gz";
678
679        let mut elementor = Elementor::new();
680        let parser = BgpkitParser::new(url_table_dump_v1).unwrap();
681        let mut record_iter = parser.into_record_iter();
682        let record = record_iter.next().unwrap();
683        let elems = elementor.record_to_elems(record);
684        assert_eq!(elems.len(), 1);
685
686        let parser = BgpkitParser::new(url_table_dump_v2).unwrap();
687        let mut record_iter = parser.into_record_iter();
688        let peer_index_table = record_iter.next().unwrap();
689        let _elems = elementor.record_to_elems(peer_index_table);
690        let record = record_iter.next().unwrap();
691        let elems = elementor.record_to_elems(record);
692        assert!(!elems.is_empty());
693
694        let parser = BgpkitParser::new(url_bgp4mp).unwrap();
695        let mut record_iter = parser.into_record_iter();
696        let record = record_iter.next().unwrap();
697        let elems = elementor.record_to_elems(record);
698        assert!(!elems.is_empty());
699    }
700
701    #[test]
702    fn test_attributes_from_bgp_elem() {
703        let mut elem = BgpElem {
704            timestamp: 0.0,
705            elem_type: ElemType::ANNOUNCE,
706            peer_ip: IpAddr::from_str("10.0.0.1").unwrap(),
707            peer_asn: Asn::new_32bit(65000),
708            prefix: NetworkPrefix::from_str("10.0.1.0/24").unwrap(),
709            next_hop: Some(IpAddr::from_str("10.0.0.2").unwrap()),
710            as_path: Some(AsPath::from_sequence([65000, 65001, 65002])),
711            origin: Some(Origin::EGP),
712            origin_asns: Some(vec![Asn::new_32bit(65000)]),
713            local_pref: Some(100),
714            med: Some(200),
715            communities: Some(vec![
716                MetaCommunity::Plain(Community::NoAdvertise),
717                MetaCommunity::Extended(ExtendedCommunity::Raw([0, 0, 0, 0, 0, 0, 0, 0])),
718                MetaCommunity::Large(LargeCommunity {
719                    global_admin: 0,
720                    local_data: [0, 0],
721                }),
722                MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
723                    community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
724                    subtype: 0,
725                    global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
726                    local_admin: [0, 0],
727                }),
728            ]),
729            atomic: false,
730            aggr_asn: Some(Asn::new_32bit(65000)),
731            aggr_ip: Some(Ipv4Addr::from_str("10.2.0.0").unwrap()),
732            only_to_customer: Some(Asn::new_32bit(65000)),
733            unknown: Some(vec![AttrRaw {
734                attr_type: AttrType::RESERVED,
735                bytes: vec![],
736            }]),
737            deprecated: Some(vec![AttrRaw {
738                attr_type: AttrType::RESERVED,
739                bytes: vec![],
740            }]),
741        };
742
743        let _attributes = Attributes::from(&elem);
744        elem.elem_type = ElemType::WITHDRAW;
745        let _attributes = Attributes::from(&elem);
746    }
747
748    #[test]
749    fn test_get_relevant_attributes() {
750        let attributes = vec![
751            AttributeValue::Origin(Origin::IGP),
752            AttributeValue::AsPath {
753                path: AsPath::from_sequence([65000, 65001, 65002]),
754                is_as4: true,
755            },
756            AttributeValue::NextHop(IpAddr::from_str("10.0.0.1").unwrap()),
757            AttributeValue::MultiExitDiscriminator(100),
758            AttributeValue::LocalPreference(200),
759            AttributeValue::AtomicAggregate,
760            AttributeValue::Aggregator {
761                asn: Asn::new_32bit(65000),
762                id: Ipv4Addr::from_str("10.0.0.1").unwrap(),
763                is_as4: false,
764            },
765            AttributeValue::Communities(vec![Community::NoExport]),
766            AttributeValue::ExtendedCommunities(vec![ExtendedCommunity::Raw([
767                0, 0, 0, 0, 0, 0, 0, 0,
768            ])]),
769            AttributeValue::LargeCommunities(vec![LargeCommunity {
770                global_admin: 0,
771                local_data: [0, 0],
772            }]),
773            AttributeValue::Ipv6AddressSpecificExtendedCommunities(vec![Ipv6AddrExtCommunity {
774                community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
775                subtype: 0,
776                global_admin: Ipv6Addr::from_str("2001:db8::").unwrap(),
777                local_admin: [0, 0],
778            }]),
779            AttributeValue::MpReachNlri(Nlri::new_reachable(
780                NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
781                Some(IpAddr::from_str("10.0.0.1").unwrap()),
782            )),
783            AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
784                NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
785            )),
786            AttributeValue::OnlyToCustomer(Asn::new_32bit(65000)),
787            AttributeValue::Unknown(AttrRaw {
788                attr_type: AttrType::RESERVED,
789                bytes: vec![],
790            }),
791            AttributeValue::Deprecated(AttrRaw {
792                attr_type: AttrType::RESERVED,
793                bytes: vec![],
794            }),
795        ]
796        .into_iter()
797        .map(Attribute::from)
798        .collect::<Vec<Attribute>>();
799
800        let attributes = Attributes::from(attributes);
801
802        let (
803            _as_path,
804            _as4_path, // Table dump v1 does not have 4-byte AS number
805            _origin,
806            _next_hop,
807            _local_pref,
808            _med,
809            _communities,
810            _atomic,
811            _aggregator,
812            _announced,
813            _withdrawn,
814            _only_to_customer,
815            _unknown,
816            _deprecated,
817        ) = get_relevant_attributes(attributes);
818    }
819
820    #[test]
821    fn test_next_hop_from_nlri() {
822        let attributes = vec![AttributeValue::NextHop(
823            IpAddr::from_str("10.0.0.1").unwrap(),
824        )]
825        .into_iter()
826        .map(Attribute::from)
827        .collect::<Vec<Attribute>>();
828
829        let attributes = Attributes::from(attributes);
830
831        let (
832            _as_path,
833            _as4_path, // Table dump v1 does not have 4-byte AS number
834            _origin,
835            next_hop,
836            _local_pref,
837            _med,
838            _communities,
839            _atomic,
840            _aggregator,
841            _announced,
842            _withdrawn,
843            _only_to_customer,
844            _unknown,
845            _deprecated,
846        ) = get_relevant_attributes(attributes);
847
848        assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.1").unwrap()));
849
850        let attributes = vec![AttributeValue::MpReachNlri(Nlri::new_reachable(
851            NetworkPrefix::from_str("10.0.0.0/24").unwrap(),
852            Some(IpAddr::from_str("10.0.0.2").unwrap()),
853        ))]
854        .into_iter()
855        .map(Attribute::from)
856        .collect::<Vec<Attribute>>();
857
858        let attributes = Attributes::from(attributes);
859
860        let (
861            _as_path,
862            _as4_path, // Table dump v1 does not have 4-byte AS number
863            _origin,
864            next_hop,
865            _local_pref,
866            _med,
867            _communities,
868            _atomic,
869            _aggregator,
870            _announced,
871            _withdrawn,
872            _only_to_customer,
873            _unknown,
874            _deprecated,
875        ) = get_relevant_attributes(attributes);
876
877        assert_eq!(next_hop, Some(IpAddr::from_str("10.0.0.2").unwrap()));
878    }
879}