Skip to main content

bgpkit_parser/parser/iters/
route.rs

1use crate::error::{ParserError, ParserErrorWithBytes};
2use crate::models::*;
3use crate::parser::bgp::attributes::{parse_as_path, parse_nlri, AttributeValidationState};
4use crate::parser::bgp::messages::read_and_validate_bgp_marker;
5use crate::parser::iters::write_mrt_core_dump;
6use crate::parser::mrt::messages::bgp4mp::{bgp4mp_message_payload_len, is_short_zebra_open};
7use crate::parser::mrt::messages::legacy_bgp::{
8    BGP_KEEPALIVE, BGP_NOTIFY, BGP_OPEN, BGP_STATE_CHANGE, BGP_UPDATE,
9};
10use crate::parser::mrt::messages::table_dump_v2::rib_entry_min_len;
11use crate::parser::mrt::mrt_record::raw_record_uses_zebra_compat;
12use crate::parser::{
13    chunk_mrt_record, parse_legacy_bgp, parse_nlri_list, BgpkitParser, Filterable, ReadUtils,
14};
15use bytes::{Buf, Bytes};
16use ipnet::IpNet;
17use log::{error, warn};
18use std::io::Read;
19use std::net::{IpAddr, Ipv4Addr};
20use std::sync::Arc;
21
22#[derive(Default)]
23struct RouteAttributes {
24    as_path: Option<Arc<AsPath>>,
25    announced: Vec<NetworkPrefix>,
26    withdrawn: Vec<NetworkPrefix>,
27}
28
29struct RouteAttributeContext<'a> {
30    afi: Option<Afi>,
31    safi: Option<Safi>,
32    prefixes: Option<&'a [NetworkPrefix]>,
33    is_announcement: Option<bool>,
34    has_standard_nlri: bool,
35}
36
37fn merge_as_path(as_path: Option<AsPath>, as4_path: Option<AsPath>) -> Option<Arc<AsPath>> {
38    let path = match (as_path, as4_path) {
39        (None, None) => None,
40        (Some(path), None) | (None, Some(path)) => Some(path),
41        (Some(path), Some(as4_path)) => Some(AsPath::merge_aspath_as4path(&path, &as4_path)),
42    };
43    path.map(Arc::new)
44}
45
46fn parse_route_attributes(
47    mut data: Bytes,
48    asn_len: &AsnLength,
49    add_path: bool,
50    ctx: RouteAttributeContext<'_>,
51) -> Result<RouteAttributes, ParserError> {
52    let mut validation = AttributeValidationState::new();
53    let mut as_path = None;
54    let mut as4_path = None;
55    let mut announced = Vec::new();
56    let mut withdrawn = Vec::new();
57
58    while data.remaining() >= 3 {
59        let flags = AttrFlags::from_bits_retain(data.read_u8()?);
60        let raw_attr_type = data.read_u8()?;
61        let attr_length = if flags.contains(AttrFlags::EXTENDED) {
62            data.read_u16()? as usize
63        } else {
64            data.read_u8()? as usize
65        };
66        let attr_type = AttrType::from(raw_attr_type);
67        let partial = validation.observe_header(raw_attr_type, attr_type, flags, attr_length);
68
69        if data.remaining() < attr_length {
70            warn!(
71                "{:?} attribute encodes a length ({}) that is longer than the remaining attribute data ({}). Skipping remaining attribute data for BGP message",
72                attr_type,
73                attr_length,
74                data.remaining()
75            );
76            break;
77        }
78
79        let attr_data = data.split_to(attr_length);
80        let result = match attr_type {
81            AttrType::AS_PATH => parse_as_path(attr_data, asn_len).map(|path| {
82                as_path = Some(path);
83            }),
84            AttrType::AS4_PATH => parse_as_path(attr_data, &AsnLength::Bits32).map(|path| {
85                as4_path = Some(path);
86            }),
87            AttrType::MP_REACHABLE_NLRI => parse_nlri(
88                attr_data,
89                &ctx.afi,
90                &ctx.safi,
91                &ctx.prefixes,
92                true,
93                add_path,
94            )
95            .map(|attr| {
96                if let AttributeValue::MpReachNlri(nlri) = attr {
97                    announced = nlri.prefixes;
98                }
99            }),
100            AttrType::MP_UNREACHABLE_NLRI => parse_nlri(
101                attr_data,
102                &ctx.afi,
103                &ctx.safi,
104                &ctx.prefixes,
105                false,
106                add_path,
107            )
108            .map(|attr| {
109                if let AttributeValue::MpUnreachNlri(nlri) = attr {
110                    withdrawn = nlri.prefixes;
111                }
112            }),
113            _ => Ok(()),
114        };
115
116        if let Err(err) = result {
117            validation.observe_parse_error(attr_type, partial, &err);
118        }
119    }
120
121    let is_announcement = ctx
122        .is_announcement
123        .unwrap_or(ctx.has_standard_nlri || validation.has_attr(AttrType::MP_REACHABLE_NLRI));
124    validation.check_mandatory_attributes(is_announcement, ctx.has_standard_nlri);
125    let _warnings = validation.finish();
126    Ok(RouteAttributes {
127        as_path: merge_as_path(as_path, as4_path),
128        announced,
129        withdrawn,
130    })
131}
132
133fn record_timestamp(common_header: &CommonHeader) -> f64 {
134    match common_header.microsecond_timestamp {
135        Some(microseconds) => common_header.timestamp as f64 + microseconds as f64 / 1_000_000.0,
136        None => common_header.timestamp as f64,
137    }
138}
139
140struct RouteUpdateIter {
141    timestamp: f64,
142    peer_ip: IpAddr,
143    peer_asn: Asn,
144    as_path: Option<Arc<AsPath>>,
145    announced:
146        std::iter::Chain<std::vec::IntoIter<NetworkPrefix>, std::vec::IntoIter<NetworkPrefix>>,
147    withdrawn:
148        std::iter::Chain<std::vec::IntoIter<NetworkPrefix>, std::vec::IntoIter<NetworkPrefix>>,
149    in_withdrawn_phase: bool,
150}
151
152impl RouteUpdateIter {
153    fn next_route(&mut self) -> Option<BgpRouteElem> {
154        if !self.in_withdrawn_phase {
155            if let Some(prefix) = self.announced.next() {
156                return Some(BgpRouteElem {
157                    timestamp: self.timestamp,
158                    elem_type: ElemType::ANNOUNCE,
159                    peer_ip: self.peer_ip,
160                    peer_asn: self.peer_asn,
161                    prefix,
162                    as_path: self.as_path.clone(),
163                });
164            }
165            self.in_withdrawn_phase = true;
166        }
167
168        self.withdrawn.next().map(|prefix| BgpRouteElem {
169            timestamp: self.timestamp,
170            elem_type: ElemType::WITHDRAW,
171            peer_ip: self.peer_ip,
172            peer_asn: self.peer_asn,
173            prefix,
174            as_path: None,
175        })
176    }
177}
178
179#[derive(Clone, Default)]
180struct RoutePeerTable {
181    peers: Arc<[Peer]>,
182}
183
184impl RoutePeerTable {
185    fn get_peer_by_id(&self, peer_index: u16) -> Option<Peer> {
186        self.peers.get(peer_index as usize).copied()
187    }
188}
189
190fn parse_route_peer_table(mut data: Bytes) -> Result<RoutePeerTable, ParserError> {
191    let _collector_bgp_id = data.read_u32()?;
192    let view_name_length = data.read_u16()? as usize;
193    data.has_n_remaining(view_name_length)?;
194    data.advance(view_name_length);
195
196    let peer_count = data.read_u16()? as usize;
197    let mut peers = Vec::with_capacity(peer_count);
198    for _ in 0..peer_count {
199        let peer_type = PeerType::from_bits_retain(data.read_u8()?);
200        let afi = if peer_type.contains(PeerType::ADDRESS_FAMILY_IPV6) {
201            Afi::Ipv6
202        } else {
203            Afi::Ipv4
204        };
205        let asn_len = if peer_type.contains(PeerType::AS_SIZE_32BIT) {
206            AsnLength::Bits32
207        } else {
208            AsnLength::Bits16
209        };
210
211        let peer_bgp_id = data.read_ipv4_address()?;
212        let peer_ip = data.read_address(&afi)?;
213        let peer_asn = data.read_asn(asn_len)?;
214        peers.push(Peer {
215            peer_type,
216            peer_bgp_id,
217            peer_ip,
218            peer_asn,
219        });
220    }
221
222    Ok(RoutePeerTable {
223        peers: Arc::from(peers),
224    })
225}
226
227#[derive(Default)]
228enum RouteRecordIter {
229    #[default]
230    Empty,
231    Update(RouteUpdateIter),
232    TableDump(RouteTableDumpIter),
233    RibAfi(RouteRibAfiIter),
234}
235
236impl RouteRecordIter {
237    fn next_route(&mut self) -> Result<Option<BgpRouteElem>, ParserError> {
238        match self {
239            RouteRecordIter::Empty => Ok(None),
240            RouteRecordIter::Update(iter) => Ok(iter.next_route()),
241            RouteRecordIter::TableDump(iter) => iter.next_route(),
242            RouteRecordIter::RibAfi(iter) => iter.next_route(),
243        }
244    }
245}
246
247struct RouteTableDumpIter {
248    data: Bytes,
249    afi: Afi,
250}
251
252impl RouteTableDumpIter {
253    fn next_route(&mut self) -> Result<Option<BgpRouteElem>, ParserError> {
254        if self.data.is_empty() {
255            return Ok(None);
256        }
257
258        let prefix = match self.afi {
259            Afi::Ipv4 => self.data.read_ipv4_prefix().map(IpNet::V4),
260            Afi::Ipv6 => self.data.read_ipv6_prefix().map(IpNet::V6),
261            Afi::LinkState => unreachable!(),
262        }?;
263        let _status = self.data.read_u8()?;
264        let originated_time = self.data.read_u32()? as f64;
265        let peer_ip = self.data.read_address(&self.afi)?;
266        let peer_asn = Asn::new_16bit(self.data.read_u16()?);
267        let attribute_length = self.data.read_u16()? as usize;
268        self.data.has_n_remaining(attribute_length)?;
269        let attrs = parse_route_attributes(
270            self.data.split_to(attribute_length),
271            &AsnLength::Bits16,
272            false,
273            RouteAttributeContext {
274                afi: None,
275                safi: None,
276                prefixes: None,
277                is_announcement: Some(true),
278                has_standard_nlri: self.afi == Afi::Ipv4,
279            },
280        )?;
281
282        Ok(Some(BgpRouteElem {
283            timestamp: originated_time,
284            elem_type: ElemType::ANNOUNCE,
285            peer_ip,
286            peer_asn,
287            prefix: NetworkPrefix::new(prefix, None),
288            as_path: attrs.as_path,
289        }))
290    }
291}
292
293struct RouteRibAfiIter {
294    data: Bytes,
295    peer_table: RoutePeerTable,
296    afi: Afi,
297    safi: Safi,
298    is_add_path: bool,
299    prefix: NetworkPrefix,
300    remaining_entries: u16,
301}
302
303impl RouteRibAfiIter {
304    fn next_route(&mut self) -> Result<Option<BgpRouteElem>, ParserError> {
305        while self.remaining_entries > 0 {
306            if self.data.remaining() < rib_entry_min_len(self.is_add_path) {
307                warn!("early break due to truncated msg while parsing RIB AFI entries");
308                self.remaining_entries = 0;
309                return Ok(None);
310            }
311
312            self.remaining_entries -= 1;
313            let peer_index = self.data.read_u16()?;
314            let originated_time = self.data.read_u32()? as f64;
315            let _path_id = if self.is_add_path {
316                Some(self.data.read_u32()?)
317            } else {
318                None
319            };
320            let attribute_length = self.data.read_u16()? as usize;
321            if self.data.remaining() < attribute_length {
322                warn!(
323                    "early break due to truncated attribute payload while parsing RIB AFI entries: expected {} bytes, have {} bytes available",
324                    attribute_length,
325                    self.data.remaining()
326                );
327                self.remaining_entries = 0;
328                return Ok(None);
329            }
330
331            let prefixes = [self.prefix];
332            let attrs = parse_route_attributes(
333                self.data.split_to(attribute_length),
334                &AsnLength::Bits32,
335                self.is_add_path,
336                RouteAttributeContext {
337                    afi: Some(self.afi),
338                    safi: Some(self.safi),
339                    prefixes: Some(&prefixes),
340                    is_announcement: Some(true),
341                    has_standard_nlri: self.afi == Afi::Ipv4,
342                },
343            )?;
344            let Some(peer) = self.peer_table.get_peer_by_id(peer_index) else {
345                error!("peer ID {} not found in peer_index table", peer_index);
346                continue;
347            };
348
349            return Ok(Some(BgpRouteElem {
350                timestamp: originated_time,
351                elem_type: ElemType::ANNOUNCE,
352                peer_ip: peer.peer_ip,
353                peer_asn: peer.peer_asn,
354                prefix: self.prefix,
355                as_path: attrs.as_path,
356            }));
357        }
358
359        Ok(None)
360    }
361}
362
363fn parse_bgp_update_routes(
364    mut input: Bytes,
365    add_path: bool,
366    asn_len: &AsnLength,
367    timestamp: f64,
368    peer_ip: IpAddr,
369    peer_asn: Asn,
370) -> Result<RouteUpdateIter, ParserError> {
371    let withdrawn_len = input.read_u16()? as usize;
372    input.has_n_remaining(withdrawn_len)?;
373    let withdrawn_prefixes = parse_nlri_list(input.split_to(withdrawn_len), add_path, &Afi::Ipv4)?;
374
375    let attribute_length = input.read_u16()? as usize;
376    input.has_n_remaining(attribute_length)?;
377    let attribute_bytes = input.split_to(attribute_length);
378    let announced_prefixes = parse_nlri_list(input, add_path, &Afi::Ipv4)?;
379    let attributes = parse_route_attributes(
380        attribute_bytes,
381        asn_len,
382        add_path,
383        RouteAttributeContext {
384            afi: None,
385            safi: None,
386            prefixes: None,
387            is_announcement: None,
388            has_standard_nlri: !announced_prefixes.is_empty(),
389        },
390    )?;
391
392    Ok(RouteUpdateIter {
393        timestamp,
394        peer_ip,
395        peer_asn,
396        as_path: attributes.as_path,
397        announced: announced_prefixes.into_iter().chain(attributes.announced),
398        withdrawn: withdrawn_prefixes.into_iter().chain(attributes.withdrawn),
399        in_withdrawn_phase: false,
400    })
401}
402
403fn parse_bgp_message_routes(
404    mut data: Bytes,
405    add_path: bool,
406    asn_len: &AsnLength,
407    timestamp: f64,
408    peer_ip: IpAddr,
409    peer_asn: Asn,
410) -> Result<RouteRecordIter, ParserError> {
411    let total_size = data.len();
412    data.has_n_remaining(19)?;
413    read_and_validate_bgp_marker(&mut data)?;
414    let length = data.read_u16()?;
415    if !(19..=65_535).contains(&length) {
416        return Err(ParserError::ParseError(format!(
417            "invalid BGP message length {length}"
418        )));
419    }
420
421    let bgp_msg_length = if length as usize > total_size {
422        total_size - 19
423    } else {
424        length as usize - 19
425    };
426    let msg_type = BgpMessageType::try_from(data.read_u8()?)
427        .map_err(|_| ParserError::ParseError("Unknown BGP Message Type".to_string()))?;
428
429    if matches!(msg_type, BgpMessageType::OPEN | BgpMessageType::KEEPALIVE) && length > 4096 {
430        return Err(ParserError::ParseError(format!(
431            "BGP {msg_type:?} message length {length} exceeds maximum allowed 4096 bytes (RFC 8654)"
432        )));
433    }
434
435    if data.remaining() != bgp_msg_length {
436        warn!(
437            "BGP message length {} does not match the actual length {} (parsing BGP message)",
438            bgp_msg_length,
439            data.remaining()
440        );
441    }
442    data.has_n_remaining(bgp_msg_length)?;
443    let msg_data = data.split_to(bgp_msg_length);
444
445    match msg_type {
446        BgpMessageType::UPDATE => Ok(RouteRecordIter::Update(parse_bgp_update_routes(
447            msg_data, add_path, asn_len, timestamp, peer_ip, peer_asn,
448        )?)),
449        BgpMessageType::OPEN
450        | BgpMessageType::NOTIFICATION
451        | BgpMessageType::KEEPALIVE
452        | BgpMessageType::ROUTE_REFRESH => Ok(RouteRecordIter::Empty),
453    }
454}
455
456fn bgp4mp_asn_len_and_add_path(msg_type: Bgp4MpType) -> Option<(AsnLength, bool)> {
457    match msg_type {
458        Bgp4MpType::Message | Bgp4MpType::MessageLocal => Some((AsnLength::Bits16, false)),
459        Bgp4MpType::MessageAs4 | Bgp4MpType::MessageAs4Local => Some((AsnLength::Bits32, false)),
460        Bgp4MpType::MessageAddpath | Bgp4MpType::MessageLocalAddpath => {
461            Some((AsnLength::Bits16, true))
462        }
463        Bgp4MpType::MessageAs4Addpath | Bgp4MpType::MessageLocalAs4Addpath => {
464            Some((AsnLength::Bits32, true))
465        }
466        Bgp4MpType::StateChange | Bgp4MpType::StateChangeAs4 => None,
467    }
468}
469
470fn parse_bgp4mp_routes(
471    sub_type: u16,
472    mut data: Bytes,
473    timestamp: f64,
474) -> Result<RouteRecordIter, ParserError> {
475    let msg_type = Bgp4MpType::try_from(sub_type)?;
476    let Some((asn_len, add_path)) = bgp4mp_asn_len_and_add_path(msg_type) else {
477        return Ok(RouteRecordIter::Empty);
478    };
479
480    let total_size = data.len();
481    let is_short_zebra_open = is_short_zebra_open(&data, &asn_len);
482    let peer_asn = data.read_asn(asn_len)?;
483    let _local_asn = data.read_asn(asn_len)?;
484    if is_short_zebra_open {
485        return parse_bgp_message_routes(
486            data,
487            add_path,
488            &asn_len,
489            timestamp,
490            IpAddr::V4(Ipv4Addr::UNSPECIFIED),
491            peer_asn,
492        );
493    }
494    let _interface_index = data.read_u16()?;
495    let afi = data.read_afi()?;
496    let should_read = bgp4mp_message_payload_len(&afi, &asn_len, total_size)?;
497    let peer_ip = data.read_address(&afi)?;
498    let _local_ip = data.read_address(&afi)?;
499
500    if should_read != data.remaining() {
501        return Err(ParserError::TruncatedMsg(format!(
502            "truncated bgp4mp message: should read {} bytes, have {} bytes available",
503            should_read,
504            data.remaining()
505        )));
506    }
507
508    parse_bgp_message_routes(data, add_path, &asn_len, timestamp, peer_ip, peer_asn)
509}
510
511fn table_dump_v2_afi_safi(rib_type: TableDumpV2Type) -> Result<(Afi, Safi), ParserError> {
512    match rib_type {
513        TableDumpV2Type::RibIpv4Unicast | TableDumpV2Type::RibIpv4UnicastAddPath => {
514            Ok((Afi::Ipv4, Safi::Unicast))
515        }
516        TableDumpV2Type::RibIpv4Multicast | TableDumpV2Type::RibIpv4MulticastAddPath => {
517            Ok((Afi::Ipv4, Safi::Multicast))
518        }
519        TableDumpV2Type::RibIpv6Unicast | TableDumpV2Type::RibIpv6UnicastAddPath => {
520            Ok((Afi::Ipv6, Safi::Unicast))
521        }
522        TableDumpV2Type::RibIpv6Multicast | TableDumpV2Type::RibIpv6MulticastAddPath => {
523            Ok((Afi::Ipv6, Safi::Multicast))
524        }
525        _ => Err(ParserError::ParseError(format!(
526            "wrong RIB type for parsing: {rib_type:?}"
527        ))),
528    }
529}
530
531fn is_add_path_rib_type(rib_type: TableDumpV2Type) -> bool {
532    matches!(
533        rib_type,
534        TableDumpV2Type::RibIpv4UnicastAddPath
535            | TableDumpV2Type::RibIpv4MulticastAddPath
536            | TableDumpV2Type::RibIpv6UnicastAddPath
537            | TableDumpV2Type::RibIpv6MulticastAddPath
538    )
539}
540
541fn parse_table_dump_routes(sub_type: u16, mut data: Bytes) -> Result<RouteRecordIter, ParserError> {
542    let afi = match sub_type {
543        1 => Afi::Ipv4,
544        2 => Afi::Ipv6,
545        _ => {
546            return Err(ParserError::ParseError(format!(
547                "Invalid subtype found for TABLE_DUMP (V1) message: {sub_type}"
548            )))
549        }
550    };
551
552    let _view_number = data.read_u16()?;
553    let _sequence_number = data.read_u16()?;
554    if data.is_empty() {
555        return Err(ParserError::TruncatedMsg(
556            "TABLE_DUMP record contains no entries".to_string(),
557        ));
558    }
559    Ok(RouteRecordIter::TableDump(RouteTableDumpIter { data, afi }))
560}
561
562fn parse_legacy_bgp_routes(
563    sub_type: u16,
564    mut data: Bytes,
565    timestamp: f64,
566) -> Result<RouteRecordIter, ParserError> {
567    match sub_type {
568        BGP_UPDATE => {
569            let peer_asn = Asn::new_16bit(data.read_u16()?);
570            let peer_ip = IpAddr::V4(data.read_ipv4_address()?);
571            let _local_asn = data.read_u16()?;
572            let _local_ip = data.read_ipv4_address()?;
573            Ok(RouteRecordIter::Update(parse_bgp_update_routes(
574                data,
575                false,
576                &AsnLength::Bits16,
577                timestamp,
578                peer_ip,
579                peer_asn,
580            )?))
581        }
582        BGP_STATE_CHANGE | BGP_OPEN | BGP_NOTIFY | BGP_KEEPALIVE => {
583            parse_legacy_bgp(sub_type, data)?;
584            Ok(RouteRecordIter::Empty)
585        }
586        _ => Err(ParserError::Unsupported(format!(
587            "unsupported legacy BGP subtype: {sub_type}"
588        ))),
589    }
590}
591
592fn parse_table_dump_v2_routes(
593    sub_type: u16,
594    mut data: Bytes,
595    peer_table: &mut Option<RoutePeerTable>,
596) -> Result<RouteRecordIter, ParserError> {
597    let v2_type = TableDumpV2Type::try_from(sub_type)?;
598    match v2_type {
599        TableDumpV2Type::PeerIndexTable => {
600            *peer_table = Some(parse_route_peer_table(data)?);
601            Ok(RouteRecordIter::Empty)
602        }
603        TableDumpV2Type::GeoPeerTable => Ok(RouteRecordIter::Empty),
604        TableDumpV2Type::RibGeneric | TableDumpV2Type::RibGenericAddPath => Err(
605            ParserError::Unsupported("TableDumpV2 RibGeneric is not currently supported".into()),
606        ),
607        rib_type => {
608            let (afi, safi) = table_dump_v2_afi_safi(rib_type)?;
609            let is_add_path = is_add_path_rib_type(rib_type);
610            let _sequence_number = data.read_u32()?;
611            let prefix = data.read_nlri_prefix(&afi, false)?;
612            let entry_count = data.read_u16()?;
613            let Some(peer_table) = peer_table.clone() else {
614                return Err(ParserError::ParseError(
615                    "peer table not set for TableDumpV2 RIB entries".to_string(),
616                ));
617            };
618
619            Ok(RouteRecordIter::RibAfi(RouteRibAfiIter {
620                data,
621                peer_table,
622                afi,
623                safi,
624                is_add_path,
625                prefix,
626                remaining_entries: entry_count,
627            }))
628        }
629    }
630}
631
632fn parse_raw_record_route_iter(
633    raw_record: crate::RawMrtRecord,
634    peer_table: &mut Option<RoutePeerTable>,
635) -> Result<RouteRecordIter, ParserError> {
636    let timestamp = record_timestamp(&raw_record.common_header);
637    match raw_record.common_header.entry_type {
638        EntryType::TABLE_DUMP => parse_table_dump_routes(
639            raw_record.common_header.entry_subtype,
640            raw_record.message_bytes,
641        ),
642        EntryType::TABLE_DUMP_V2 => parse_table_dump_v2_routes(
643            raw_record.common_header.entry_subtype,
644            raw_record.message_bytes,
645            peer_table,
646        ),
647        EntryType::BGP4MP | EntryType::BGP4MP_ET => parse_bgp4mp_routes(
648            raw_record.common_header.entry_subtype,
649            raw_record.message_bytes,
650            timestamp,
651        ),
652        EntryType::BGP => parse_legacy_bgp_routes(
653            raw_record.common_header.entry_subtype,
654            raw_record.message_bytes,
655            timestamp,
656        ),
657        v => Err(ParserError::Unsupported(format!(
658            "unsupported MRT type: {v:?}"
659        ))),
660    }
661}
662
663pub struct RouteIterator<R> {
664    parser: BgpkitParser<R>,
665    pending_routes: RouteRecordIter,
666    peer_table: Option<RoutePeerTable>,
667    pending_raw_bytes: Option<Vec<u8>>,
668}
669
670impl<R> RouteIterator<R> {
671    pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
672        Self {
673            parser,
674            pending_routes: RouteRecordIter::Empty,
675            peer_table: None,
676            pending_raw_bytes: None,
677        }
678    }
679}
680
681impl<R: Read> Iterator for RouteIterator<R> {
682    type Item = BgpRouteElem;
683
684    fn next(&mut self) -> Option<Self::Item> {
685        // Text-dump parsers have no MRT-record representation; short-circuit.
686        if self.parser.text_dump_iter.is_some() {
687            return None;
688        }
689        loop {
690            match self.pending_routes.next_route() {
691                Ok(Some(route)) => {
692                    if route.match_filters(&self.parser.filters) {
693                        return Some(route);
694                    }
695                    continue;
696                }
697                Ok(None) => {
698                    self.pending_raw_bytes = None;
699                }
700                Err(err) => {
701                    error!("parser error: {}", err);
702                    self.pending_routes = RouteRecordIter::Empty;
703                    write_mrt_core_dump(self.parser.core_dump, self.pending_raw_bytes.take());
704                    if self.parser.core_dump {
705                        return None;
706                    }
707                    continue;
708                }
709            }
710
711            let raw_record = match chunk_mrt_record(&mut self.parser.reader) {
712                Ok(raw_record) => raw_record,
713                Err(e) => match e.error {
714                    ParserError::TruncatedMsg(err_str) | ParserError::Unsupported(err_str) => {
715                        if self.parser.options.show_warnings {
716                            warn!("parser warn: {}", err_str);
717                        }
718                        write_mrt_core_dump(self.parser.core_dump, e.bytes);
719                        continue;
720                    }
721                    ParserError::ParseError(err_str) => {
722                        error!("parser error: {}", err_str);
723                        write_mrt_core_dump(self.parser.core_dump, e.bytes);
724                        if self.parser.core_dump {
725                            return None;
726                        }
727                        continue;
728                    }
729                    ParserError::EofExpected => return None,
730                    ParserError::IoError(err) | ParserError::EofError(err) => {
731                        error!("{:?}", err);
732                        write_mrt_core_dump(self.parser.core_dump, e.bytes);
733                        return None;
734                    }
735                    #[cfg(feature = "oneio")]
736                    ParserError::OneIoError(_) => return None,
737                    ParserError::FilterError(_) => return None,
738                    ParserError::InvalidLabeledNlriLength
739                    | ParserError::TruncatedLabeledNlri
740                    | ParserError::TruncatedPrefix
741                    | ParserError::MaxLabelStackDepthExceeded
742                    | ParserError::PeerMaxLabelsExceeded
743                    | ParserError::InvalidPrefix => {
744                        if self.parser.options.show_warnings {
745                            warn!("parser warn: labeled NLRI parsing error: {:?}", e.error);
746                        }
747                        continue;
748                    }
749                },
750            };
751
752            let used_zebra_compat = raw_record_uses_zebra_compat(&raw_record);
753            let raw_bytes = raw_record.raw_bytes().to_vec();
754            match parse_raw_record_route_iter(raw_record, &mut self.peer_table) {
755                Ok(routes) => {
756                    if used_zebra_compat {
757                        self.parser.warn_zebra_compat_once();
758                    }
759                    self.pending_routes = routes;
760                    self.pending_raw_bytes = Some(raw_bytes);
761                }
762                Err(err) => {
763                    error!("parser error: {}", err);
764                    write_mrt_core_dump(self.parser.core_dump, Some(raw_bytes));
765                    if self.parser.core_dump {
766                        return None;
767                    }
768                    continue;
769                }
770            }
771        }
772    }
773}
774
775pub struct FallibleRouteIterator<R> {
776    parser: BgpkitParser<R>,
777    pending_routes: RouteRecordIter,
778    peer_table: Option<RoutePeerTable>,
779    pending_raw_bytes: Option<Vec<u8>>,
780}
781
782impl<R> FallibleRouteIterator<R> {
783    pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
784        Self {
785            parser,
786            pending_routes: RouteRecordIter::Empty,
787            peer_table: None,
788            pending_raw_bytes: None,
789        }
790    }
791}
792
793impl<R: Read> Iterator for FallibleRouteIterator<R> {
794    type Item = Result<BgpRouteElem, ParserErrorWithBytes>;
795
796    fn next(&mut self) -> Option<Self::Item> {
797        // Text-dump parsers have no MRT-record representation; short-circuit.
798        if self.parser.text_dump_iter.is_some() {
799            return None;
800        }
801        loop {
802            match self.pending_routes.next_route() {
803                Ok(Some(route)) => {
804                    if route.match_filters(&self.parser.filters) {
805                        return Some(Ok(route));
806                    }
807                    continue;
808                }
809                Ok(None) => {
810                    self.pending_raw_bytes = None;
811                }
812                Err(error) => {
813                    self.pending_routes = RouteRecordIter::Empty;
814                    return Some(Err(ParserErrorWithBytes {
815                        error,
816                        bytes: self.pending_raw_bytes.take(),
817                    }));
818                }
819            }
820
821            let raw_record = match chunk_mrt_record(&mut self.parser.reader) {
822                Ok(raw_record) => raw_record,
823                Err(e) if matches!(e.error, ParserError::EofExpected) => return None,
824                Err(e) => return Some(Err(e)),
825            };
826
827            let used_zebra_compat = raw_record_uses_zebra_compat(&raw_record);
828            let raw_bytes = raw_record.raw_bytes().to_vec();
829            match parse_raw_record_route_iter(raw_record, &mut self.peer_table) {
830                Ok(routes) => {
831                    if used_zebra_compat {
832                        self.parser.warn_zebra_compat_once();
833                    }
834                    self.pending_routes = routes;
835                    self.pending_raw_bytes = Some(raw_bytes);
836                }
837                Err(error) => {
838                    return Some(Err(ParserErrorWithBytes {
839                        error,
840                        bytes: Some(raw_bytes),
841                    }))
842                }
843            }
844        }
845    }
846}
847
848#[cfg(test)]
849mod tests {
850    use super::*;
851    use crate::parser::iters::write_mrt_core_dump_to_path;
852    use bytes::{BufMut, BytesMut};
853    use std::io::Cursor;
854    use std::net::{Ipv4Addr, Ipv6Addr};
855    use std::str::FromStr;
856
857    fn route_projection(elem: BgpElem) -> BgpRouteElem {
858        BgpRouteElem {
859            timestamp: elem.timestamp,
860            elem_type: elem.elem_type,
861            peer_ip: elem.peer_ip,
862            peer_asn: elem.peer_asn,
863            prefix: elem.prefix,
864            as_path: elem.as_path.map(Arc::new),
865        }
866    }
867
868    fn collect_route_record_iter(
869        mut iter: RouteRecordIter,
870    ) -> Result<Vec<BgpRouteElem>, ParserError> {
871        let mut routes = Vec::new();
872        while let Some(route) = iter.next_route()? {
873            routes.push(route);
874        }
875        Ok(routes)
876    }
877
878    fn route_peer_table_from_peer_index(peer_table: PeerIndexTable) -> RoutePeerTable {
879        let mut peer_ids = peer_table.id_peer_map.keys().copied().collect::<Vec<_>>();
880        peer_ids.sort_unstable();
881        let peers = peer_ids
882            .into_iter()
883            .map(|peer_id| peer_table.id_peer_map[&peer_id])
884            .collect::<Vec<_>>();
885
886        RoutePeerTable {
887            peers: Arc::from(peers),
888        }
889    }
890
891    fn update_record() -> MrtRecord {
892        let mut attributes = Attributes::default();
893        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
894        attributes.add_attr(
895            AttributeValue::AsPath {
896                path: AsPath::from_sequence([64500, 64501]),
897                is_as4: false,
898            }
899            .into(),
900        );
901        attributes
902            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
903
904        MrtRecord {
905            common_header: CommonHeader {
906                timestamp: 1_700_000_000,
907                microsecond_timestamp: None,
908                entry_type: EntryType::BGP4MP,
909                entry_subtype: Bgp4MpType::MessageAs4 as u16,
910                length: 0,
911            },
912            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
913                msg_type: Bgp4MpType::MessageAs4,
914                peer_asn: Asn::new_32bit(64496),
915                local_asn: Asn::new_32bit(64497),
916                interface_index: 0,
917                peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
918                local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
919                bgp_message: BgpMessage::Update(BgpUpdateMessage {
920                    withdrawn_prefixes: vec![NetworkPrefix::from_str("198.51.100.0/24").unwrap()],
921                    attributes,
922                    announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
923                }),
924            })),
925        }
926    }
927
928    fn route_attributes(as_path: impl AsRef<[u32]>) -> Attributes {
929        let mut attributes = Attributes::default();
930        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
931        attributes.add_attr(
932            AttributeValue::AsPath {
933                path: AsPath::from_sequence(as_path),
934                is_as4: false,
935            }
936            .into(),
937        );
938        attributes
939            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
940        attributes
941    }
942
943    fn bgp4mp_record(msg_type: Bgp4MpType, bgp_message: BgpMessage) -> MrtRecord {
944        let asn = if matches!(
945            msg_type,
946            Bgp4MpType::Message
947                | Bgp4MpType::MessageLocal
948                | Bgp4MpType::MessageAddpath
949                | Bgp4MpType::MessageLocalAddpath
950        ) {
951            Asn::new_16bit(64496)
952        } else {
953            Asn::new_32bit(64496)
954        };
955
956        MrtRecord {
957            common_header: CommonHeader {
958                timestamp: 1_700_000_000,
959                microsecond_timestamp: None,
960                entry_type: EntryType::BGP4MP,
961                entry_subtype: msg_type as u16,
962                length: 0,
963            },
964            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
965                msg_type,
966                peer_asn: asn,
967                local_asn: Asn::new_32bit(64497),
968                interface_index: 0,
969                peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
970                local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
971                bgp_message,
972            })),
973        }
974    }
975
976    fn open_message() -> BgpMessage {
977        BgpMessage::Open(BgpOpenMessage {
978            version: 4,
979            asn: Asn::new_16bit(64496),
980            hold_time: 180,
981            bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
982            extended_length: false,
983            opt_params: vec![],
984        })
985    }
986
987    fn raw_bgp_message(length: u16, msg_type: BgpMessageType, payload: &[u8]) -> Bytes {
988        raw_bgp_message_with_marker([0xff; 16], length, msg_type, payload)
989    }
990
991    fn raw_bgp_message_with_marker(
992        marker: [u8; 16],
993        length: u16,
994        msg_type: BgpMessageType,
995        payload: &[u8],
996    ) -> Bytes {
997        let mut bytes = BytesMut::new();
998        bytes.put_slice(&marker);
999        bytes.put_u16(length);
1000        bytes.put_u8(msg_type as u8);
1001        bytes.put_slice(payload);
1002        bytes.freeze()
1003    }
1004
1005    fn table_dump_record() -> MrtRecord {
1006        let mut attributes = Attributes::default();
1007        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
1008        attributes.add_attr(
1009            AttributeValue::AsPath {
1010                path: AsPath::from_sequence([64500, 64501]),
1011                is_as4: false,
1012            }
1013            .into(),
1014        );
1015        attributes
1016            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
1017
1018        MrtRecord {
1019            common_header: CommonHeader {
1020                timestamp: 1_700_000_000,
1021                microsecond_timestamp: None,
1022                entry_type: EntryType::TABLE_DUMP,
1023                entry_subtype: 1,
1024                length: 0,
1025            },
1026            message: MrtMessage::TableDumpMessage(TableDumpMessage {
1027                view_number: 0,
1028                sequence_number: 1,
1029                prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1030                status: 1,
1031                originated_time: 1_699_999_998,
1032                peer_ip: IpAddr::from_str("192.0.2.20").unwrap(),
1033                peer_asn: Asn::new_16bit(64496),
1034                attributes,
1035            }),
1036        }
1037    }
1038
1039    fn table_dump_ipv6_record() -> MrtRecord {
1040        let mut attributes = Attributes::default();
1041        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
1042        attributes.add_attr(
1043            AttributeValue::AsPath {
1044                path: AsPath::from_sequence([64500, 64501]),
1045                is_as4: false,
1046            }
1047            .into(),
1048        );
1049
1050        MrtRecord {
1051            common_header: CommonHeader {
1052                timestamp: 1_700_000_000,
1053                microsecond_timestamp: None,
1054                entry_type: EntryType::TABLE_DUMP,
1055                entry_subtype: 2,
1056                length: 0,
1057            },
1058            message: MrtMessage::TableDumpMessage(TableDumpMessage {
1059                view_number: 0,
1060                sequence_number: 1,
1061                prefix: NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1062                status: 1,
1063                originated_time: 1_699_999_998,
1064                peer_ip: IpAddr::from_str("2001:db8::20").unwrap(),
1065                peer_asn: Asn::new_16bit(64496),
1066                attributes,
1067            }),
1068        }
1069    }
1070
1071    fn table_dump_v2_records_bytes() -> Vec<u8> {
1072        let peer = Peer::new(
1073            "192.0.2.10".parse().unwrap(),
1074            "192.0.2.11".parse().unwrap(),
1075            Asn::new_32bit(64496),
1076        );
1077        let mut peer_table = PeerIndexTable::default();
1078        let peer_index = peer_table.add_peer(peer).unwrap();
1079
1080        let mut attributes = Attributes::default();
1081        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
1082        attributes.add_attr(
1083            AttributeValue::AsPath {
1084                path: AsPath::from_sequence([64500, 64501]),
1085                is_as4: false,
1086            }
1087            .into(),
1088        );
1089        attributes
1090            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
1091
1092        let pit_record = MrtRecord {
1093            common_header: CommonHeader {
1094                timestamp: 1_700_000_000,
1095                microsecond_timestamp: None,
1096                entry_type: EntryType::TABLE_DUMP_V2,
1097                entry_subtype: TableDumpV2Type::PeerIndexTable as u16,
1098                length: 0,
1099            },
1100            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(peer_table)),
1101        };
1102        let rib_record = MrtRecord {
1103            common_header: CommonHeader {
1104                timestamp: 1_700_000_001,
1105                microsecond_timestamp: None,
1106                entry_type: EntryType::TABLE_DUMP_V2,
1107                entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
1108                length: 0,
1109            },
1110            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
1111                rib_type: TableDumpV2Type::RibIpv4Unicast,
1112                sequence_number: 1,
1113                prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1114                rib_entries: vec![RibEntry {
1115                    peer_index,
1116                    originated_time: 1_699_999_999,
1117                    path_id: None,
1118                    attributes,
1119                }],
1120            })),
1121        };
1122
1123        let mut bytes = pit_record.encode().unwrap().to_vec();
1124        bytes.extend_from_slice(&rib_record.encode().unwrap());
1125        bytes
1126    }
1127
1128    fn table_dump_v2_truncated_attribute_payload() -> (Vec<u8>, Bytes, PeerIndexTable) {
1129        let peer = Peer::new(
1130            "192.0.2.10".parse().unwrap(),
1131            "192.0.2.11".parse().unwrap(),
1132            Asn::new_32bit(64496),
1133        );
1134        let mut peer_table = PeerIndexTable::default();
1135        let peer_index = peer_table.add_peer(peer).unwrap();
1136
1137        let pit_record = MrtRecord {
1138            common_header: CommonHeader {
1139                timestamp: 1_700_000_000,
1140                microsecond_timestamp: None,
1141                entry_type: EntryType::TABLE_DUMP_V2,
1142                entry_subtype: TableDumpV2Type::PeerIndexTable as u16,
1143                length: 0,
1144            },
1145            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(
1146                peer_table.clone(),
1147            )),
1148        };
1149
1150        let first_entry = RibEntry {
1151            peer_index,
1152            originated_time: 1_699_999_999,
1153            path_id: None,
1154            attributes: route_attributes([64500, 64501]),
1155        };
1156
1157        let mut rib_body = BytesMut::new();
1158        rib_body.put_u32(1);
1159        rib_body.extend(NetworkPrefix::from_str("203.0.113.0/24").unwrap().encode());
1160        rib_body.put_u16(2);
1161        rib_body.extend(first_entry.encode().unwrap());
1162        rib_body.put_u16(peer_index);
1163        rib_body.put_u32(1_699_999_998);
1164        rib_body.put_u16(32);
1165        rib_body.put_u8(0);
1166
1167        let rib_body = rib_body.freeze();
1168        let rib_header = CommonHeader {
1169            timestamp: 1_700_000_001,
1170            microsecond_timestamp: None,
1171            entry_type: EntryType::TABLE_DUMP_V2,
1172            entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
1173            length: rib_body.len() as u32,
1174        };
1175
1176        let mut bytes = pit_record.encode().unwrap().to_vec();
1177        bytes.extend_from_slice(&rib_header.encode());
1178        bytes.extend_from_slice(&rib_body);
1179
1180        (bytes, rib_body, peer_table)
1181    }
1182
1183    fn assert_filtered_route_projection(bytes: Vec<u8>, filters: &[(&str, &str)]) {
1184        let elem_parser = filters.iter().fold(
1185            BgpkitParser::from_reader(Cursor::new(bytes.clone())),
1186            |parser, (filter_type, filter_value)| {
1187                parser.add_filter(filter_type, filter_value).unwrap()
1188            },
1189        );
1190        let route_parser = filters.iter().fold(
1191            BgpkitParser::from_reader(Cursor::new(bytes)),
1192            |parser, (filter_type, filter_value)| {
1193                parser.add_filter(filter_type, filter_value).unwrap()
1194            },
1195        );
1196
1197        let elem_projection = elem_parser
1198            .into_elem_iter()
1199            .map(route_projection)
1200            .collect::<Vec<_>>();
1201        let routes = route_parser.into_route_iter().collect::<Vec<_>>();
1202
1203        assert_eq!(routes, elem_projection, "filters: {filters:?}");
1204    }
1205
1206    fn assert_route_projection(bytes: Vec<u8>) -> Vec<BgpRouteElem> {
1207        let elem_projection = BgpkitParser::from_reader(Cursor::new(bytes.clone()))
1208            .into_elem_iter()
1209            .map(route_projection)
1210            .collect::<Vec<_>>();
1211        let routes = BgpkitParser::from_reader(Cursor::new(bytes))
1212            .into_route_iter()
1213            .collect::<Vec<_>>();
1214
1215        assert_eq!(routes, elem_projection);
1216        routes
1217    }
1218
1219    #[test]
1220    fn bgp4mp_routes_rejects_link_state_envelope_afi() {
1221        let mut data = BytesMut::new();
1222        data.put_u16(65000);
1223        data.put_u16(65001);
1224        data.put_u16(0);
1225        data.put_u16(Afi::LinkState as u16);
1226        data.put_slice(&BgpMessage::KeepAlive.encode(AsnLength::Bits16).unwrap());
1227
1228        let error =
1229            match parse_bgp4mp_routes(Bgp4MpType::Message as u16, data.freeze(), 1_700_000_000.0) {
1230                Err(error) => error,
1231                Ok(_) => panic!("unexpectedly parsed BGP4MP routes"),
1232            };
1233        assert!(matches!(
1234            error,
1235            ParserError::ParseError(message)
1236                if message == "Link-State AFI is invalid in a BGP4MP envelope"
1237        ));
1238    }
1239
1240    #[test]
1241    fn route_iterator_matches_elem_projection_for_update() {
1242        let bytes = update_record().encode().unwrap().to_vec();
1243        let routes = assert_route_projection(bytes);
1244        assert_eq!(routes.len(), 2);
1245        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1246        assert_eq!(routes[1].elem_type, ElemType::WITHDRAW);
1247        assert!(routes[1].as_path.is_none());
1248    }
1249
1250    #[test]
1251    fn route_iterator_shares_as_path_for_update_announcements() {
1252        let bytes = bgp4mp_record(
1253            Bgp4MpType::MessageAs4,
1254            BgpMessage::Update(BgpUpdateMessage {
1255                withdrawn_prefixes: vec![],
1256                attributes: route_attributes([64500, 64501]),
1257                announced_prefixes: vec![
1258                    NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1259                    NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
1260                ],
1261            }),
1262        )
1263        .encode()
1264        .unwrap()
1265        .to_vec();
1266
1267        let routes = BgpkitParser::from_reader(Cursor::new(bytes))
1268            .into_route_iter()
1269            .collect::<Vec<_>>();
1270
1271        assert_eq!(routes.len(), 2);
1272        assert!(Arc::ptr_eq(
1273            routes[0].as_path.as_ref().unwrap(),
1274            routes[1].as_path.as_ref().unwrap()
1275        ));
1276    }
1277
1278    #[test]
1279    fn route_iterator_uses_microsecond_timestamps() {
1280        let timestamp = record_timestamp(&CommonHeader {
1281            timestamp: 1_700_000_000,
1282            microsecond_timestamp: Some(123_456),
1283            entry_type: EntryType::BGP4MP_ET,
1284            entry_subtype: Bgp4MpType::MessageAs4 as u16,
1285            length: 0,
1286        });
1287
1288        assert_eq!(timestamp, 1_700_000_000.123_456);
1289    }
1290
1291    #[test]
1292    fn route_iterator_matches_elem_projection_for_mp_update() {
1293        let mut attributes = route_attributes([64500, 64501]);
1294        attributes.add_attr(
1295            AttributeValue::MpReachNlri(Nlri::new_reachable(
1296                NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1297                Some(IpAddr::from_str("2001:db8::1").unwrap()),
1298            ))
1299            .into(),
1300        );
1301        attributes.add_attr(
1302            AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1303                NetworkPrefix::from_str("2001:db8:1::/48").unwrap(),
1304            ))
1305            .into(),
1306        );
1307
1308        let bytes = bgp4mp_record(
1309            Bgp4MpType::MessageAs4,
1310            BgpMessage::Update(BgpUpdateMessage {
1311                withdrawn_prefixes: vec![],
1312                attributes,
1313                announced_prefixes: vec![],
1314            }),
1315        )
1316        .encode()
1317        .unwrap()
1318        .to_vec();
1319
1320        let routes = assert_route_projection(bytes);
1321        assert_eq!(routes.len(), 2);
1322        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1323        assert_eq!(
1324            routes[0].prefix,
1325            NetworkPrefix::from_str("2001:db8::/32").unwrap()
1326        );
1327        assert_eq!(routes[1].elem_type, ElemType::WITHDRAW);
1328        assert_eq!(
1329            routes[1].prefix,
1330            NetworkPrefix::from_str("2001:db8:1::/48").unwrap()
1331        );
1332    }
1333
1334    #[test]
1335    fn route_iterator_matches_elem_projection_for_non_update_bgp4mp_messages() {
1336        let records = [
1337            bgp4mp_record(Bgp4MpType::Message, open_message()),
1338            bgp4mp_record(
1339                Bgp4MpType::MessageAs4,
1340                BgpMessage::Notification(BgpNotificationMessage {
1341                    error: BgpError::Unknown(1, 0),
1342                    data: vec![],
1343                }),
1344            ),
1345            bgp4mp_record(Bgp4MpType::MessageAddpath, BgpMessage::KeepAlive),
1346            bgp4mp_record(Bgp4MpType::MessageAs4Addpath, BgpMessage::KeepAlive),
1347        ];
1348        let mut bytes = Vec::new();
1349        for record in records {
1350            bytes.extend_from_slice(&record.encode().unwrap());
1351        }
1352
1353        assert!(assert_route_projection(bytes).is_empty());
1354    }
1355
1356    #[test]
1357    fn route_iterator_matches_elem_projection_for_bgp4mp_16bit_update() {
1358        let bytes = bgp4mp_record(
1359            Bgp4MpType::Message,
1360            BgpMessage::Update(BgpUpdateMessage {
1361                withdrawn_prefixes: vec![],
1362                attributes: route_attributes([64500, 64501]),
1363                announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
1364            }),
1365        )
1366        .encode()
1367        .unwrap()
1368        .to_vec();
1369
1370        let routes = assert_route_projection(bytes);
1371        assert_eq!(routes.len(), 1);
1372        assert_eq!(routes[0].peer_asn, Asn::new_16bit(64496));
1373    }
1374
1375    #[test]
1376    fn route_iterator_filters_match_elem_projection_for_update() {
1377        let bytes = update_record().encode().unwrap().to_vec();
1378        let cases: &[&[(&str, &str)]] = &[
1379            &[("peer_ip", "192.0.2.1")],
1380            &[("peer_ip", "192.0.2.99")],
1381            &[("peer_asn", "64496")],
1382            &[("type", "a")],
1383            &[("type", "w")],
1384            &[("type", "!w")],
1385            &[("prefix", "203.0.113.0/24")],
1386            &[("prefix", "198.51.100.0/24")],
1387            &[("prefix_super", "203.0.113.128/25")],
1388            &[("origin_asn", "64501")],
1389            &[("origin_asns", "64496,64501")],
1390            &[("as_path", "64500 64501$")],
1391            &[("ip_version", "4")],
1392            &[("ts_start", "1700000000"), ("ts_end", "1700000000")],
1393            &[("peer_ip", "192.0.2.1"), ("type", "a")],
1394        ];
1395
1396        for filters in cases {
1397            assert_filtered_route_projection(bytes.clone(), filters);
1398        }
1399    }
1400
1401    #[test]
1402    fn selective_attribute_parser_merges_as4_path() {
1403        let mut attributes = Attributes::default();
1404        attributes.add_attr(
1405            AttributeValue::AsPath {
1406                path: AsPath::from_sequence([23456, 64497]),
1407                is_as4: false,
1408            }
1409            .into(),
1410        );
1411        attributes.add_attr(
1412            AttributeValue::AsPath {
1413                path: AsPath::from_sequence([65536, 64497]),
1414                is_as4: true,
1415            }
1416            .into(),
1417        );
1418
1419        let attrs = parse_route_attributes(
1420            attributes.encode(AsnLength::Bits16).unwrap(),
1421            &AsnLength::Bits16,
1422            false,
1423            RouteAttributeContext {
1424                afi: None,
1425                safi: None,
1426                prefixes: None,
1427                is_announcement: Some(true),
1428                has_standard_nlri: true,
1429            },
1430        )
1431        .unwrap();
1432
1433        assert_eq!(
1434            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1435            vec![65536, 64497]
1436        );
1437    }
1438
1439    #[test]
1440    fn selective_attribute_parser_handles_as_path_without_as4_path() {
1441        let attrs = parse_route_attributes(
1442            route_attributes([64500, 64501])
1443                .encode(AsnLength::Bits16)
1444                .unwrap(),
1445            &AsnLength::Bits16,
1446            false,
1447            RouteAttributeContext {
1448                afi: None,
1449                safi: None,
1450                prefixes: None,
1451                is_announcement: Some(true),
1452                has_standard_nlri: true,
1453            },
1454        )
1455        .unwrap();
1456
1457        assert_eq!(
1458            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1459            vec![64500, 64501]
1460        );
1461    }
1462
1463    #[test]
1464    fn selective_attribute_parser_handles_as4_path_without_as_path() {
1465        let mut attributes = Attributes::default();
1466        attributes.add_attr(
1467            AttributeValue::AsPath {
1468                path: AsPath::from_sequence([65536, 64497]),
1469                is_as4: true,
1470            }
1471            .into(),
1472        );
1473
1474        let attrs = parse_route_attributes(
1475            attributes.encode(AsnLength::Bits16).unwrap(),
1476            &AsnLength::Bits16,
1477            false,
1478            RouteAttributeContext {
1479                afi: None,
1480                safi: None,
1481                prefixes: None,
1482                is_announcement: Some(false),
1483                has_standard_nlri: false,
1484            },
1485        )
1486        .unwrap();
1487
1488        assert_eq!(
1489            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1490            vec![65536, 64497]
1491        );
1492    }
1493
1494    #[test]
1495    fn selective_attribute_parser_handles_no_as_path() {
1496        let attrs = parse_route_attributes(
1497            Bytes::new(),
1498            &AsnLength::Bits16,
1499            false,
1500            RouteAttributeContext {
1501                afi: None,
1502                safi: None,
1503                prefixes: None,
1504                is_announcement: Some(false),
1505                has_standard_nlri: false,
1506            },
1507        )
1508        .unwrap();
1509
1510        assert!(attrs.as_path.is_none());
1511    }
1512
1513    #[test]
1514    fn selective_attribute_parser_handles_extended_and_truncated_attributes() {
1515        let mut extended_as_path = BytesMut::new();
1516        extended_as_path.put_u8((AttrFlags::TRANSITIVE | AttrFlags::EXTENDED).bits());
1517        extended_as_path.put_u8(u8::from(AttrType::AS_PATH));
1518        extended_as_path.put_u16(4);
1519        extended_as_path.put_u8(2);
1520        extended_as_path.put_u8(1);
1521        extended_as_path.put_u16(64500);
1522
1523        let attrs = parse_route_attributes(
1524            extended_as_path.freeze(),
1525            &AsnLength::Bits16,
1526            false,
1527            RouteAttributeContext {
1528                afi: None,
1529                safi: None,
1530                prefixes: None,
1531                is_announcement: Some(false),
1532                has_standard_nlri: false,
1533            },
1534        )
1535        .unwrap();
1536        assert_eq!(
1537            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1538            vec![64500]
1539        );
1540
1541        let attrs = parse_route_attributes(
1542            Bytes::from_static(&[0x40, 2, 5, 0]),
1543            &AsnLength::Bits16,
1544            false,
1545            RouteAttributeContext {
1546                afi: None,
1547                safi: None,
1548                prefixes: None,
1549                is_announcement: Some(false),
1550                has_standard_nlri: false,
1551            },
1552        )
1553        .unwrap();
1554        assert!(attrs.as_path.is_none());
1555    }
1556
1557    #[test]
1558    fn selective_attribute_parser_discards_malformed_as_path() {
1559        let attrs = parse_route_attributes(
1560            Bytes::from_static(&[0x40, 2, 1, 0]),
1561            &AsnLength::Bits16,
1562            false,
1563            RouteAttributeContext {
1564                afi: None,
1565                safi: None,
1566                prefixes: None,
1567                is_announcement: Some(false),
1568                has_standard_nlri: false,
1569            },
1570        )
1571        .unwrap();
1572
1573        assert!(attrs.as_path.is_none());
1574    }
1575
1576    #[test]
1577    fn route_iterator_matches_elem_projection_for_table_dump() {
1578        let bytes = table_dump_record().encode().unwrap().to_vec();
1579        let routes = assert_route_projection(bytes);
1580        assert_eq!(routes.len(), 1);
1581        assert_eq!(routes[0].timestamp, 1_699_999_998.0);
1582        assert_eq!(routes[0].peer_asn, Asn::new_16bit(64496));
1583    }
1584
1585    #[test]
1586    fn route_iterator_matches_elem_projection_for_table_dump_ipv6() {
1587        let bytes = table_dump_ipv6_record().encode().unwrap().to_vec();
1588        let routes = assert_route_projection(bytes);
1589        assert_eq!(routes.len(), 1);
1590        assert_eq!(
1591            routes[0].prefix,
1592            NetworkPrefix::from_str("2001:db8::/32").unwrap()
1593        );
1594        assert_eq!(
1595            routes[0].peer_ip,
1596            IpAddr::from(Ipv6Addr::from_str("2001:db8::20").unwrap())
1597        );
1598    }
1599
1600    #[test]
1601    fn route_iterator_matches_elem_projection_for_table_dump_v2() {
1602        let bytes = table_dump_v2_records_bytes();
1603        let routes = assert_route_projection(bytes);
1604        assert_eq!(routes.len(), 1);
1605        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1606        assert_eq!(
1607            routes[0].as_path.as_ref().unwrap().to_u32_vec_opt(false),
1608            Some(vec![64500, 64501])
1609        );
1610    }
1611
1612    #[test]
1613    fn route_iterator_matches_elem_projection_for_table_dump_v2_ipv6_addpath() {
1614        let peer = Peer::new(
1615            "192.0.2.11".parse().unwrap(),
1616            "2001:db8::10".parse().unwrap(),
1617            Asn::new_32bit(64496),
1618        );
1619        let mut peer_table = PeerIndexTable::default();
1620        let peer_index = peer_table.add_peer(peer).unwrap();
1621
1622        let pit_record = MrtRecord {
1623            common_header: CommonHeader {
1624                timestamp: 1_700_000_000,
1625                microsecond_timestamp: None,
1626                entry_type: EntryType::TABLE_DUMP_V2,
1627                entry_subtype: TableDumpV2Type::PeerIndexTable as u16,
1628                length: 0,
1629            },
1630            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(peer_table)),
1631        };
1632        let rib_record = MrtRecord {
1633            common_header: CommonHeader {
1634                timestamp: 1_700_000_001,
1635                microsecond_timestamp: None,
1636                entry_type: EntryType::TABLE_DUMP_V2,
1637                entry_subtype: TableDumpV2Type::RibIpv6UnicastAddPath as u16,
1638                length: 0,
1639            },
1640            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
1641                rib_type: TableDumpV2Type::RibIpv6UnicastAddPath,
1642                sequence_number: 1,
1643                prefix: NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1644                rib_entries: vec![RibEntry {
1645                    peer_index,
1646                    originated_time: 1_699_999_999,
1647                    path_id: Some(1234),
1648                    attributes: route_attributes([64500, 64501]),
1649                }],
1650            })),
1651        };
1652
1653        let mut bytes = pit_record.encode().unwrap().to_vec();
1654        bytes.extend_from_slice(&rib_record.encode().unwrap());
1655        let routes = assert_route_projection(bytes);
1656        assert_eq!(routes.len(), 1);
1657        assert_eq!(
1658            routes[0].prefix,
1659            NetworkPrefix::from_str("2001:db8::/32").unwrap()
1660        );
1661    }
1662
1663    #[test]
1664    fn route_iterator_matches_elem_projection_for_bgp4mp_ipv6_peer_update() {
1665        let record = MrtRecord {
1666            common_header: CommonHeader {
1667                timestamp: 1_700_000_000,
1668                microsecond_timestamp: None,
1669                entry_type: EntryType::BGP4MP,
1670                entry_subtype: Bgp4MpType::MessageAs4 as u16,
1671                length: 0,
1672            },
1673            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
1674                msg_type: Bgp4MpType::MessageAs4,
1675                peer_asn: Asn::new_32bit(64496),
1676                local_asn: Asn::new_32bit(64497),
1677                interface_index: 0,
1678                peer_ip: IpAddr::from_str("2001:db8::1").unwrap(),
1679                local_ip: IpAddr::from_str("2001:db8::2").unwrap(),
1680                bgp_message: BgpMessage::Update(BgpUpdateMessage {
1681                    withdrawn_prefixes: vec![],
1682                    attributes: route_attributes([64500, 64501]),
1683                    announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
1684                }),
1685            })),
1686        };
1687
1688        let routes = assert_route_projection(record.encode().unwrap().to_vec());
1689        assert_eq!(routes.len(), 1);
1690        assert_eq!(
1691            routes[0].peer_ip,
1692            IpAddr::from(Ipv6Addr::from_str("2001:db8::1").unwrap())
1693        );
1694    }
1695
1696    #[test]
1697    fn route_iterator_filters_match_elem_projection_for_table_dump_v2() {
1698        let bytes = table_dump_v2_records_bytes();
1699        let cases: &[&[(&str, &str)]] = &[
1700            &[("peer_ip", "192.0.2.10")],
1701            &[("peer_asn", "64496")],
1702            &[("type", "a")],
1703            &[("type", "w")],
1704            &[("prefix", "203.0.113.0/24")],
1705            &[("prefix_sub", "203.0.112.0/23")],
1706            &[("origin_asn", "64501")],
1707            &[("as_path", "64500 64501$")],
1708            &[("ts_start", "1699999999"), ("ts_end", "1699999999")],
1709            &[("peer_asn", "64496"), ("origin_asn", "64501")],
1710        ];
1711
1712        for filters in cases {
1713            assert_filtered_route_projection(bytes.clone(), filters);
1714        }
1715    }
1716
1717    #[test]
1718    fn route_parser_reports_bgp_message_shape_errors() {
1719        assert!(parse_bgp_message_routes(
1720            raw_bgp_message(18, BgpMessageType::KEEPALIVE, &[]),
1721            false,
1722            &AsnLength::Bits16,
1723            1_700_000_000.0,
1724            "192.0.2.1".parse().unwrap(),
1725            Asn::new_16bit(64496)
1726        )
1727        .is_err());
1728        assert!(parse_bgp_message_routes(
1729            raw_bgp_message(4097, BgpMessageType::OPEN, &[]),
1730            false,
1731            &AsnLength::Bits16,
1732            1_700_000_000.0,
1733            "192.0.2.1".parse().unwrap(),
1734            Asn::new_16bit(64496)
1735        )
1736        .is_err());
1737
1738        let routes = collect_route_record_iter(
1739            parse_bgp_message_routes(
1740                raw_bgp_message(30, BgpMessageType::KEEPALIVE, &[]),
1741                false,
1742                &AsnLength::Bits16,
1743                1_700_000_000.0,
1744                "192.0.2.1".parse().unwrap(),
1745                Asn::new_16bit(64496),
1746            )
1747            .unwrap(),
1748        )
1749        .unwrap();
1750        assert!(routes.is_empty());
1751
1752        let routes = collect_route_record_iter(
1753            parse_bgp_message_routes(
1754                raw_bgp_message(19, BgpMessageType::KEEPALIVE, &[0]),
1755                false,
1756                &AsnLength::Bits16,
1757                1_700_000_000.0,
1758                "192.0.2.1".parse().unwrap(),
1759                Asn::new_16bit(64496),
1760            )
1761            .unwrap(),
1762        )
1763        .unwrap();
1764        assert!(routes.is_empty());
1765
1766        let routes = collect_route_record_iter(
1767            parse_bgp_message_routes(
1768                raw_bgp_message_with_marker([0x00; 16], 19, BgpMessageType::KEEPALIVE, &[]),
1769                false,
1770                &AsnLength::Bits16,
1771                1_700_000_000.0,
1772                "192.0.2.1".parse().unwrap(),
1773                Asn::new_16bit(64496),
1774            )
1775            .unwrap(),
1776        )
1777        .unwrap();
1778        assert!(routes.is_empty());
1779    }
1780
1781    #[test]
1782    fn route_core_dump_write_respects_enabled_flag() {
1783        let dir = tempfile::tempdir().unwrap();
1784        let path = dir.path().join("mrt_core_dump");
1785
1786        write_mrt_core_dump_to_path(false, Some(vec![1, 2, 3]), &path);
1787        assert!(!path.exists());
1788
1789        write_mrt_core_dump_to_path(true, Some(vec![1, 2, 3]), &path);
1790        assert_eq!(std::fs::read(&path).unwrap(), vec![1, 2, 3]);
1791    }
1792
1793    #[test]
1794    fn route_parser_handles_table_dump_v2_error_edges() {
1795        let rib = RibAfiEntries {
1796            rib_type: TableDumpV2Type::RibIpv4Unicast,
1797            sequence_number: 1,
1798            prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1799            rib_entries: vec![RibEntry {
1800                peer_index: 99,
1801                originated_time: 1_699_999_999,
1802                path_id: None,
1803                attributes: route_attributes([64500, 64501]),
1804            }],
1805        };
1806        let mut no_peer_table = None;
1807        assert!(parse_table_dump_v2_routes(
1808            TableDumpV2Type::RibIpv4Unicast as u16,
1809            rib.encode().unwrap(),
1810            &mut no_peer_table,
1811        )
1812        .is_err());
1813
1814        let mut empty_peer_table = Some(RoutePeerTable::default());
1815        let routes = collect_route_record_iter(
1816            parse_table_dump_v2_routes(
1817                TableDumpV2Type::RibIpv4Unicast as u16,
1818                rib.encode().unwrap(),
1819                &mut empty_peer_table,
1820            )
1821            .unwrap(),
1822        )
1823        .unwrap();
1824        assert!(routes.is_empty());
1825
1826        let mut truncated = BytesMut::new();
1827        truncated.put_u32(1);
1828        truncated.extend(NetworkPrefix::from_str("203.0.113.0/24").unwrap().encode());
1829        truncated.put_u16(1);
1830        let mut empty_peer_table = Some(RoutePeerTable::default());
1831        let routes = collect_route_record_iter(
1832            parse_table_dump_v2_routes(
1833                TableDumpV2Type::RibIpv4Unicast as u16,
1834                truncated.freeze(),
1835                &mut empty_peer_table,
1836            )
1837            .unwrap(),
1838        )
1839        .unwrap();
1840        assert!(routes.is_empty());
1841
1842        let peer = Peer::new(
1843            "192.0.2.10".parse().unwrap(),
1844            "192.0.2.11".parse().unwrap(),
1845            Asn::new_32bit(64496),
1846        );
1847        let mut peer_table = PeerIndexTable::default();
1848        let peer_index = peer_table.add_peer(peer).unwrap();
1849
1850        let first_entry = RibEntry {
1851            peer_index,
1852            originated_time: 1_699_999_999,
1853            path_id: Some(1234),
1854            attributes: route_attributes([64500, 64501]),
1855        };
1856        let mut add_path_truncated = BytesMut::new();
1857        add_path_truncated.put_u32(1);
1858        add_path_truncated.extend(NetworkPrefix::from_str("203.0.113.0/24").unwrap().encode());
1859        add_path_truncated.put_u16(2);
1860        add_path_truncated.extend(first_entry.encode().unwrap());
1861        add_path_truncated.put_u16(peer_index);
1862        add_path_truncated.put_u32(1_699_999_998);
1863        add_path_truncated.put_u32(5678);
1864
1865        let mut peer_table = Some(route_peer_table_from_peer_index(peer_table));
1866        let routes = collect_route_record_iter(
1867            parse_table_dump_v2_routes(
1868                TableDumpV2Type::RibIpv4UnicastAddPath as u16,
1869                add_path_truncated.freeze(),
1870                &mut peer_table,
1871            )
1872            .unwrap(),
1873        )
1874        .unwrap();
1875        assert_eq!(routes.len(), 1);
1876        assert_eq!(
1877            routes[0].prefix,
1878            NetworkPrefix::from_str("203.0.113.0/24").unwrap()
1879        );
1880    }
1881
1882    #[test]
1883    fn route_parser_preserves_table_dump_v2_routes_before_truncated_attribute_payload() {
1884        let (_bytes, rib_body, peer_table) = table_dump_v2_truncated_attribute_payload();
1885        let mut peer_table = Some(route_peer_table_from_peer_index(peer_table));
1886
1887        let routes = collect_route_record_iter(
1888            parse_table_dump_v2_routes(
1889                TableDumpV2Type::RibIpv4Unicast as u16,
1890                rib_body,
1891                &mut peer_table,
1892            )
1893            .unwrap(),
1894        )
1895        .unwrap();
1896
1897        assert_eq!(routes.len(), 1);
1898        assert_eq!(
1899            routes[0].prefix,
1900            NetworkPrefix::from_str("203.0.113.0/24").unwrap()
1901        );
1902        assert_eq!(
1903            routes[0].as_path.as_ref().unwrap().to_u32_vec_opt(false),
1904            Some(vec![64500, 64501])
1905        );
1906    }
1907
1908    #[test]
1909    fn route_iterators_preserve_table_dump_v2_routes_before_truncated_attribute_payload() {
1910        let (bytes, _rib_body, _peer_table) = table_dump_v2_truncated_attribute_payload();
1911
1912        let routes = BgpkitParser::from_reader(Cursor::new(bytes.clone()))
1913            .into_route_iter()
1914            .collect::<Vec<_>>();
1915        assert_eq!(routes.len(), 1);
1916        assert_eq!(
1917            routes[0].prefix,
1918            NetworkPrefix::from_str("203.0.113.0/24").unwrap()
1919        );
1920
1921        let fallible_routes = BgpkitParser::from_reader(Cursor::new(bytes))
1922            .into_fallible_route_iter()
1923            .collect::<Result<Vec<_>, _>>()
1924            .unwrap();
1925        assert_eq!(fallible_routes, routes);
1926    }
1927
1928    fn table_dump_v2_rib_without_peer_table_record() -> MrtRecord {
1929        MrtRecord {
1930            common_header: CommonHeader {
1931                timestamp: 1_700_000_001,
1932                microsecond_timestamp: None,
1933                entry_type: EntryType::TABLE_DUMP_V2,
1934                entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
1935                length: 0,
1936            },
1937            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
1938                rib_type: TableDumpV2Type::RibIpv4Unicast,
1939                sequence_number: 1,
1940                prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1941                rib_entries: vec![RibEntry {
1942                    peer_index: 0,
1943                    originated_time: 1_699_999_999,
1944                    path_id: None,
1945                    attributes: route_attributes([64500, 64501]),
1946                }],
1947            })),
1948        }
1949    }
1950
1951    #[test]
1952    fn route_iterator_skips_route_parse_errors() {
1953        let routes = BgpkitParser::from_reader(Cursor::new(
1954            table_dump_v2_rib_without_peer_table_record()
1955                .encode()
1956                .unwrap()
1957                .to_vec(),
1958        ))
1959        .into_route_iter()
1960        .collect::<Vec<_>>();
1961
1962        assert!(routes.is_empty());
1963    }
1964
1965    #[test]
1966    fn fallible_route_iterator_applies_filters_to_cached_routes() {
1967        let routes =
1968            BgpkitParser::from_reader(Cursor::new(update_record().encode().unwrap().to_vec()))
1969                .add_filter("type", "w")
1970                .unwrap()
1971                .into_fallible_route_iter()
1972                .collect::<Result<Vec<_>, _>>()
1973                .unwrap();
1974
1975        assert_eq!(routes.len(), 1);
1976        assert_eq!(routes[0].elem_type, ElemType::WITHDRAW);
1977    }
1978
1979    #[test]
1980    fn fallible_route_iterator_returns_route_parse_errors() {
1981        let bytes = table_dump_v2_rib_without_peer_table_record()
1982            .encode()
1983            .unwrap()
1984            .to_vec();
1985        let mut iter =
1986            BgpkitParser::from_reader(Cursor::new(bytes.clone())).into_fallible_route_iter();
1987
1988        let error = iter.next().unwrap().unwrap_err();
1989        assert_eq!(error.bytes.as_deref(), Some(bytes.as_slice()));
1990    }
1991
1992    #[test]
1993    fn fallible_route_iterator_yields_routes() {
1994        let bytes = update_record().encode().unwrap().to_vec();
1995        let routes = BgpkitParser::from_reader(Cursor::new(bytes))
1996            .into_fallible_route_iter()
1997            .collect::<Result<Vec<_>, _>>()
1998            .unwrap();
1999
2000        assert_eq!(routes.len(), 2);
2001        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
2002        assert_eq!(routes[1].elem_type, ElemType::WITHDRAW);
2003    }
2004
2005    #[test]
2006    fn fallible_route_iterator_returns_parse_errors() {
2007        let invalid_data = vec![
2008            0x00, 0x00, 0x00, 0x00, // timestamp
2009            0xFF, 0xFF, // invalid type
2010            0x00, 0x00, // subtype
2011            0x00, 0x00, 0x00, 0x04, // length
2012            0x00, 0x00, 0x00, 0x00, // dummy data
2013        ];
2014
2015        let mut iter =
2016            BgpkitParser::from_reader(Cursor::new(invalid_data.clone())).into_fallible_route_iter();
2017
2018        let error = iter.next().unwrap().unwrap_err();
2019        assert_eq!(error.bytes.as_deref(), Some(&invalid_data[..12]));
2020    }
2021}