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(AttributeValue::AsPath(AsPath::from_sequence([64500, 64501])).into());
895        attributes
896            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
897
898        MrtRecord {
899            common_header: CommonHeader {
900                timestamp: 1_700_000_000,
901                microsecond_timestamp: None,
902                entry_type: EntryType::BGP4MP,
903                entry_subtype: Bgp4MpType::MessageAs4 as u16,
904                length: 0,
905            },
906            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
907                msg_type: Bgp4MpType::MessageAs4,
908                peer_asn: Asn::new_32bit(64496),
909                local_asn: Asn::new_32bit(64497),
910                interface_index: 0,
911                peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
912                local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
913                bgp_message: BgpMessage::Update(BgpUpdateMessage {
914                    withdrawn_prefixes: vec![NetworkPrefix::from_str("198.51.100.0/24").unwrap()],
915                    attributes,
916                    announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
917                }),
918            })),
919        }
920    }
921
922    fn route_attributes(as_path: impl AsRef<[u32]>) -> Attributes {
923        let mut attributes = Attributes::default();
924        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
925        attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence(as_path)).into());
926        attributes
927            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
928        attributes
929    }
930
931    fn bgp4mp_record(msg_type: Bgp4MpType, bgp_message: BgpMessage) -> MrtRecord {
932        let asn = if matches!(
933            msg_type,
934            Bgp4MpType::Message
935                | Bgp4MpType::MessageLocal
936                | Bgp4MpType::MessageAddpath
937                | Bgp4MpType::MessageLocalAddpath
938        ) {
939            Asn::new_16bit(64496)
940        } else {
941            Asn::new_32bit(64496)
942        };
943
944        MrtRecord {
945            common_header: CommonHeader {
946                timestamp: 1_700_000_000,
947                microsecond_timestamp: None,
948                entry_type: EntryType::BGP4MP,
949                entry_subtype: msg_type as u16,
950                length: 0,
951            },
952            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
953                msg_type,
954                peer_asn: asn,
955                local_asn: Asn::new_32bit(64497),
956                interface_index: 0,
957                peer_ip: IpAddr::from_str("192.0.2.1").unwrap(),
958                local_ip: IpAddr::from_str("192.0.2.2").unwrap(),
959                bgp_message,
960            })),
961        }
962    }
963
964    fn open_message() -> BgpMessage {
965        BgpMessage::Open(BgpOpenMessage {
966            version: 4,
967            asn: Asn::new_16bit(64496),
968            hold_time: 180,
969            bgp_identifier: Ipv4Addr::new(192, 0, 2, 1),
970            extended_length: false,
971            opt_params: vec![],
972        })
973    }
974
975    fn raw_bgp_message(length: u16, msg_type: BgpMessageType, payload: &[u8]) -> Bytes {
976        raw_bgp_message_with_marker([0xff; 16], length, msg_type, payload)
977    }
978
979    fn raw_bgp_message_with_marker(
980        marker: [u8; 16],
981        length: u16,
982        msg_type: BgpMessageType,
983        payload: &[u8],
984    ) -> Bytes {
985        let mut bytes = BytesMut::new();
986        bytes.put_slice(&marker);
987        bytes.put_u16(length);
988        bytes.put_u8(msg_type as u8);
989        bytes.put_slice(payload);
990        bytes.freeze()
991    }
992
993    fn table_dump_record() -> MrtRecord {
994        let mut attributes = Attributes::default();
995        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
996        attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([64500, 64501])).into());
997        attributes
998            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
999
1000        MrtRecord {
1001            common_header: CommonHeader {
1002                timestamp: 1_700_000_000,
1003                microsecond_timestamp: None,
1004                entry_type: EntryType::TABLE_DUMP,
1005                entry_subtype: 1,
1006                length: 0,
1007            },
1008            message: MrtMessage::TableDumpMessage(TableDumpMessage {
1009                view_number: 0,
1010                sequence_number: 1,
1011                prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1012                status: 1,
1013                originated_time: 1_699_999_998,
1014                peer_ip: IpAddr::from_str("192.0.2.20").unwrap(),
1015                peer_asn: Asn::new_16bit(64496),
1016                attributes,
1017            }),
1018        }
1019    }
1020
1021    fn table_dump_ipv6_record() -> MrtRecord {
1022        let mut attributes = Attributes::default();
1023        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
1024        attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([64500, 64501])).into());
1025
1026        MrtRecord {
1027            common_header: CommonHeader {
1028                timestamp: 1_700_000_000,
1029                microsecond_timestamp: None,
1030                entry_type: EntryType::TABLE_DUMP,
1031                entry_subtype: 2,
1032                length: 0,
1033            },
1034            message: MrtMessage::TableDumpMessage(TableDumpMessage {
1035                view_number: 0,
1036                sequence_number: 1,
1037                prefix: NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1038                status: 1,
1039                originated_time: 1_699_999_998,
1040                peer_ip: IpAddr::from_str("2001:db8::20").unwrap(),
1041                peer_asn: Asn::new_16bit(64496),
1042                attributes,
1043            }),
1044        }
1045    }
1046
1047    fn table_dump_v2_records_bytes() -> Vec<u8> {
1048        let peer = Peer::new(
1049            "192.0.2.10".parse().unwrap(),
1050            "192.0.2.11".parse().unwrap(),
1051            Asn::new_32bit(64496),
1052        );
1053        let mut peer_table = PeerIndexTable::default();
1054        let peer_index = peer_table.add_peer(peer).unwrap();
1055
1056        let mut attributes = Attributes::default();
1057        attributes.add_attr(AttributeValue::Origin(Origin::IGP).into());
1058        attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([64500, 64501])).into());
1059        attributes
1060            .add_attr(AttributeValue::NextHop(IpAddr::from_str("192.0.2.254").unwrap()).into());
1061
1062        let pit_record = MrtRecord {
1063            common_header: CommonHeader {
1064                timestamp: 1_700_000_000,
1065                microsecond_timestamp: None,
1066                entry_type: EntryType::TABLE_DUMP_V2,
1067                entry_subtype: TableDumpV2Type::PeerIndexTable as u16,
1068                length: 0,
1069            },
1070            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(peer_table)),
1071        };
1072        let rib_record = MrtRecord {
1073            common_header: CommonHeader {
1074                timestamp: 1_700_000_001,
1075                microsecond_timestamp: None,
1076                entry_type: EntryType::TABLE_DUMP_V2,
1077                entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
1078                length: 0,
1079            },
1080            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
1081                rib_type: TableDumpV2Type::RibIpv4Unicast,
1082                sequence_number: 1,
1083                prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1084                rib_entries: vec![RibEntry {
1085                    peer_index,
1086                    originated_time: 1_699_999_999,
1087                    path_id: None,
1088                    attributes,
1089                }],
1090            })),
1091        };
1092
1093        let mut bytes = pit_record.encode().unwrap().to_vec();
1094        bytes.extend_from_slice(&rib_record.encode().unwrap());
1095        bytes
1096    }
1097
1098    fn table_dump_v2_truncated_attribute_payload() -> (Vec<u8>, Bytes, PeerIndexTable) {
1099        let peer = Peer::new(
1100            "192.0.2.10".parse().unwrap(),
1101            "192.0.2.11".parse().unwrap(),
1102            Asn::new_32bit(64496),
1103        );
1104        let mut peer_table = PeerIndexTable::default();
1105        let peer_index = peer_table.add_peer(peer).unwrap();
1106
1107        let pit_record = MrtRecord {
1108            common_header: CommonHeader {
1109                timestamp: 1_700_000_000,
1110                microsecond_timestamp: None,
1111                entry_type: EntryType::TABLE_DUMP_V2,
1112                entry_subtype: TableDumpV2Type::PeerIndexTable as u16,
1113                length: 0,
1114            },
1115            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(
1116                peer_table.clone(),
1117            )),
1118        };
1119
1120        let first_entry = RibEntry {
1121            peer_index,
1122            originated_time: 1_699_999_999,
1123            path_id: None,
1124            attributes: route_attributes([64500, 64501]),
1125        };
1126
1127        let mut rib_body = BytesMut::new();
1128        rib_body.put_u32(1);
1129        rib_body.extend(NetworkPrefix::from_str("203.0.113.0/24").unwrap().encode());
1130        rib_body.put_u16(2);
1131        rib_body.extend(first_entry.encode().unwrap());
1132        rib_body.put_u16(peer_index);
1133        rib_body.put_u32(1_699_999_998);
1134        rib_body.put_u16(32);
1135        rib_body.put_u8(0);
1136
1137        let rib_body = rib_body.freeze();
1138        let rib_header = CommonHeader {
1139            timestamp: 1_700_000_001,
1140            microsecond_timestamp: None,
1141            entry_type: EntryType::TABLE_DUMP_V2,
1142            entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
1143            length: rib_body.len() as u32,
1144        };
1145
1146        let mut bytes = pit_record.encode().unwrap().to_vec();
1147        bytes.extend_from_slice(&rib_header.encode());
1148        bytes.extend_from_slice(&rib_body);
1149
1150        (bytes, rib_body, peer_table)
1151    }
1152
1153    fn assert_filtered_route_projection(bytes: Vec<u8>, filters: &[(&str, &str)]) {
1154        let elem_parser = filters.iter().fold(
1155            BgpkitParser::from_reader(Cursor::new(bytes.clone())),
1156            |parser, (filter_type, filter_value)| {
1157                parser.add_filter(filter_type, filter_value).unwrap()
1158            },
1159        );
1160        let route_parser = filters.iter().fold(
1161            BgpkitParser::from_reader(Cursor::new(bytes)),
1162            |parser, (filter_type, filter_value)| {
1163                parser.add_filter(filter_type, filter_value).unwrap()
1164            },
1165        );
1166
1167        let elem_projection = elem_parser
1168            .into_elem_iter()
1169            .map(route_projection)
1170            .collect::<Vec<_>>();
1171        let routes = route_parser.into_route_iter().collect::<Vec<_>>();
1172
1173        assert_eq!(routes, elem_projection, "filters: {filters:?}");
1174    }
1175
1176    fn assert_route_projection(bytes: Vec<u8>) -> Vec<BgpRouteElem> {
1177        let elem_projection = BgpkitParser::from_reader(Cursor::new(bytes.clone()))
1178            .into_elem_iter()
1179            .map(route_projection)
1180            .collect::<Vec<_>>();
1181        let routes = BgpkitParser::from_reader(Cursor::new(bytes))
1182            .into_route_iter()
1183            .collect::<Vec<_>>();
1184
1185        assert_eq!(routes, elem_projection);
1186        routes
1187    }
1188
1189    #[test]
1190    fn bgp4mp_routes_rejects_link_state_envelope_afi() {
1191        let mut data = BytesMut::new();
1192        data.put_u16(65000);
1193        data.put_u16(65001);
1194        data.put_u16(0);
1195        data.put_u16(Afi::LinkState as u16);
1196        data.put_slice(&BgpMessage::KeepAlive.encode(AsnLength::Bits16).unwrap());
1197
1198        let error =
1199            match parse_bgp4mp_routes(Bgp4MpType::Message as u16, data.freeze(), 1_700_000_000.0) {
1200                Err(error) => error,
1201                Ok(_) => panic!("unexpectedly parsed BGP4MP routes"),
1202            };
1203        assert!(matches!(
1204            error,
1205            ParserError::ParseError(message)
1206                if message == "Link-State AFI is invalid in a BGP4MP envelope"
1207        ));
1208    }
1209
1210    #[test]
1211    fn route_iterator_matches_elem_projection_for_update() {
1212        let bytes = update_record().encode().unwrap().to_vec();
1213        let routes = assert_route_projection(bytes);
1214        assert_eq!(routes.len(), 2);
1215        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1216        assert_eq!(routes[1].elem_type, ElemType::WITHDRAW);
1217        assert!(routes[1].as_path.is_none());
1218    }
1219
1220    #[test]
1221    fn route_iterator_shares_as_path_for_update_announcements() {
1222        let bytes = bgp4mp_record(
1223            Bgp4MpType::MessageAs4,
1224            BgpMessage::Update(BgpUpdateMessage {
1225                withdrawn_prefixes: vec![],
1226                attributes: route_attributes([64500, 64501]),
1227                announced_prefixes: vec![
1228                    NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1229                    NetworkPrefix::from_str("198.51.100.0/24").unwrap(),
1230                ],
1231            }),
1232        )
1233        .encode()
1234        .unwrap()
1235        .to_vec();
1236
1237        let routes = BgpkitParser::from_reader(Cursor::new(bytes))
1238            .into_route_iter()
1239            .collect::<Vec<_>>();
1240
1241        assert_eq!(routes.len(), 2);
1242        assert!(Arc::ptr_eq(
1243            routes[0].as_path.as_ref().unwrap(),
1244            routes[1].as_path.as_ref().unwrap()
1245        ));
1246    }
1247
1248    #[test]
1249    fn route_iterator_uses_microsecond_timestamps() {
1250        let timestamp = record_timestamp(&CommonHeader {
1251            timestamp: 1_700_000_000,
1252            microsecond_timestamp: Some(123_456),
1253            entry_type: EntryType::BGP4MP_ET,
1254            entry_subtype: Bgp4MpType::MessageAs4 as u16,
1255            length: 0,
1256        });
1257
1258        assert_eq!(timestamp, 1_700_000_000.123_456);
1259    }
1260
1261    #[test]
1262    fn route_iterator_matches_elem_projection_for_mp_update() {
1263        let mut attributes = route_attributes([64500, 64501]);
1264        attributes.add_attr(
1265            AttributeValue::MpReachNlri(Nlri::new_reachable(
1266                NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1267                Some(IpAddr::from_str("2001:db8::1").unwrap()),
1268            ))
1269            .into(),
1270        );
1271        attributes.add_attr(
1272            AttributeValue::MpUnreachNlri(Nlri::new_unreachable(
1273                NetworkPrefix::from_str("2001:db8:1::/48").unwrap(),
1274            ))
1275            .into(),
1276        );
1277
1278        let bytes = bgp4mp_record(
1279            Bgp4MpType::MessageAs4,
1280            BgpMessage::Update(BgpUpdateMessage {
1281                withdrawn_prefixes: vec![],
1282                attributes,
1283                announced_prefixes: vec![],
1284            }),
1285        )
1286        .encode()
1287        .unwrap()
1288        .to_vec();
1289
1290        let routes = assert_route_projection(bytes);
1291        assert_eq!(routes.len(), 2);
1292        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1293        assert_eq!(
1294            routes[0].prefix,
1295            NetworkPrefix::from_str("2001:db8::/32").unwrap()
1296        );
1297        assert_eq!(routes[1].elem_type, ElemType::WITHDRAW);
1298        assert_eq!(
1299            routes[1].prefix,
1300            NetworkPrefix::from_str("2001:db8:1::/48").unwrap()
1301        );
1302    }
1303
1304    #[test]
1305    fn route_iterator_matches_elem_projection_for_non_update_bgp4mp_messages() {
1306        let records = [
1307            bgp4mp_record(Bgp4MpType::Message, open_message()),
1308            bgp4mp_record(
1309                Bgp4MpType::MessageAs4,
1310                BgpMessage::Notification(BgpNotificationMessage {
1311                    error: BgpError::Unknown(1, 0),
1312                    data: vec![],
1313                }),
1314            ),
1315            bgp4mp_record(Bgp4MpType::MessageAddpath, BgpMessage::KeepAlive),
1316            bgp4mp_record(Bgp4MpType::MessageAs4Addpath, BgpMessage::KeepAlive),
1317        ];
1318        let mut bytes = Vec::new();
1319        for record in records {
1320            bytes.extend_from_slice(&record.encode().unwrap());
1321        }
1322
1323        assert!(assert_route_projection(bytes).is_empty());
1324    }
1325
1326    #[test]
1327    fn route_iterator_matches_elem_projection_for_bgp4mp_16bit_update() {
1328        let bytes = bgp4mp_record(
1329            Bgp4MpType::Message,
1330            BgpMessage::Update(BgpUpdateMessage {
1331                withdrawn_prefixes: vec![],
1332                attributes: route_attributes([64500, 64501]),
1333                announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
1334            }),
1335        )
1336        .encode()
1337        .unwrap()
1338        .to_vec();
1339
1340        let routes = assert_route_projection(bytes);
1341        assert_eq!(routes.len(), 1);
1342        assert_eq!(routes[0].peer_asn, Asn::new_16bit(64496));
1343    }
1344
1345    #[test]
1346    fn route_iterator_filters_match_elem_projection_for_update() {
1347        let bytes = update_record().encode().unwrap().to_vec();
1348        let cases: &[&[(&str, &str)]] = &[
1349            &[("peer_ip", "192.0.2.1")],
1350            &[("peer_ip", "192.0.2.99")],
1351            &[("peer_asn", "64496")],
1352            &[("type", "a")],
1353            &[("type", "w")],
1354            &[("type", "!w")],
1355            &[("prefix", "203.0.113.0/24")],
1356            &[("prefix", "198.51.100.0/24")],
1357            &[("prefix_super", "203.0.113.128/25")],
1358            &[("origin_asn", "64501")],
1359            &[("origin_asns", "64496,64501")],
1360            &[("as_path", "64500 64501$")],
1361            &[("ip_version", "4")],
1362            &[("ts_start", "1700000000"), ("ts_end", "1700000000")],
1363            &[("peer_ip", "192.0.2.1"), ("type", "a")],
1364        ];
1365
1366        for filters in cases {
1367            assert_filtered_route_projection(bytes.clone(), filters);
1368        }
1369    }
1370
1371    #[test]
1372    fn selective_attribute_parser_merges_as4_path() {
1373        let mut attributes = Attributes::default();
1374        attributes.add_attr(AttributeValue::AsPath(AsPath::from_sequence([23456, 64497])).into());
1375        attributes.add_attr(AttributeValue::As4Path(AsPath::from_sequence([65536, 64497])).into());
1376
1377        let attrs = parse_route_attributes(
1378            attributes.encode(AsnLength::Bits16).unwrap(),
1379            &AsnLength::Bits16,
1380            false,
1381            RouteAttributeContext {
1382                afi: None,
1383                safi: None,
1384                prefixes: None,
1385                is_announcement: Some(true),
1386                has_standard_nlri: true,
1387            },
1388        )
1389        .unwrap();
1390
1391        assert_eq!(
1392            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1393            vec![65536, 64497]
1394        );
1395    }
1396
1397    #[test]
1398    fn selective_attribute_parser_handles_as_path_without_as4_path() {
1399        let attrs = parse_route_attributes(
1400            route_attributes([64500, 64501])
1401                .encode(AsnLength::Bits16)
1402                .unwrap(),
1403            &AsnLength::Bits16,
1404            false,
1405            RouteAttributeContext {
1406                afi: None,
1407                safi: None,
1408                prefixes: None,
1409                is_announcement: Some(true),
1410                has_standard_nlri: true,
1411            },
1412        )
1413        .unwrap();
1414
1415        assert_eq!(
1416            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1417            vec![64500, 64501]
1418        );
1419    }
1420
1421    #[test]
1422    fn selective_attribute_parser_handles_as4_path_without_as_path() {
1423        let mut attributes = Attributes::default();
1424        attributes.add_attr(AttributeValue::As4Path(AsPath::from_sequence([65536, 64497])).into());
1425
1426        let attrs = parse_route_attributes(
1427            attributes.encode(AsnLength::Bits16).unwrap(),
1428            &AsnLength::Bits16,
1429            false,
1430            RouteAttributeContext {
1431                afi: None,
1432                safi: None,
1433                prefixes: None,
1434                is_announcement: Some(false),
1435                has_standard_nlri: false,
1436            },
1437        )
1438        .unwrap();
1439
1440        assert_eq!(
1441            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1442            vec![65536, 64497]
1443        );
1444    }
1445
1446    #[test]
1447    fn selective_attribute_parser_handles_no_as_path() {
1448        let attrs = parse_route_attributes(
1449            Bytes::new(),
1450            &AsnLength::Bits16,
1451            false,
1452            RouteAttributeContext {
1453                afi: None,
1454                safi: None,
1455                prefixes: None,
1456                is_announcement: Some(false),
1457                has_standard_nlri: false,
1458            },
1459        )
1460        .unwrap();
1461
1462        assert!(attrs.as_path.is_none());
1463    }
1464
1465    #[test]
1466    fn selective_attribute_parser_handles_extended_and_truncated_attributes() {
1467        let mut extended_as_path = BytesMut::new();
1468        extended_as_path.put_u8((AttrFlags::TRANSITIVE | AttrFlags::EXTENDED).bits());
1469        extended_as_path.put_u8(u8::from(AttrType::AS_PATH));
1470        extended_as_path.put_u16(4);
1471        extended_as_path.put_u8(2);
1472        extended_as_path.put_u8(1);
1473        extended_as_path.put_u16(64500);
1474
1475        let attrs = parse_route_attributes(
1476            extended_as_path.freeze(),
1477            &AsnLength::Bits16,
1478            false,
1479            RouteAttributeContext {
1480                afi: None,
1481                safi: None,
1482                prefixes: None,
1483                is_announcement: Some(false),
1484                has_standard_nlri: false,
1485            },
1486        )
1487        .unwrap();
1488        assert_eq!(
1489            attrs.as_path.unwrap().to_u32_vec_opt(false).unwrap(),
1490            vec![64500]
1491        );
1492
1493        let attrs = parse_route_attributes(
1494            Bytes::from_static(&[0x40, 2, 5, 0]),
1495            &AsnLength::Bits16,
1496            false,
1497            RouteAttributeContext {
1498                afi: None,
1499                safi: None,
1500                prefixes: None,
1501                is_announcement: Some(false),
1502                has_standard_nlri: false,
1503            },
1504        )
1505        .unwrap();
1506        assert!(attrs.as_path.is_none());
1507    }
1508
1509    #[test]
1510    fn selective_attribute_parser_discards_malformed_as_path() {
1511        let attrs = parse_route_attributes(
1512            Bytes::from_static(&[0x40, 2, 1, 0]),
1513            &AsnLength::Bits16,
1514            false,
1515            RouteAttributeContext {
1516                afi: None,
1517                safi: None,
1518                prefixes: None,
1519                is_announcement: Some(false),
1520                has_standard_nlri: false,
1521            },
1522        )
1523        .unwrap();
1524
1525        assert!(attrs.as_path.is_none());
1526    }
1527
1528    #[test]
1529    fn route_iterator_matches_elem_projection_for_table_dump() {
1530        let bytes = table_dump_record().encode().unwrap().to_vec();
1531        let routes = assert_route_projection(bytes);
1532        assert_eq!(routes.len(), 1);
1533        assert_eq!(routes[0].timestamp, 1_699_999_998.0);
1534        assert_eq!(routes[0].peer_asn, Asn::new_16bit(64496));
1535    }
1536
1537    #[test]
1538    fn route_iterator_matches_elem_projection_for_table_dump_ipv6() {
1539        let bytes = table_dump_ipv6_record().encode().unwrap().to_vec();
1540        let routes = assert_route_projection(bytes);
1541        assert_eq!(routes.len(), 1);
1542        assert_eq!(
1543            routes[0].prefix,
1544            NetworkPrefix::from_str("2001:db8::/32").unwrap()
1545        );
1546        assert_eq!(
1547            routes[0].peer_ip,
1548            IpAddr::from(Ipv6Addr::from_str("2001:db8::20").unwrap())
1549        );
1550    }
1551
1552    #[test]
1553    fn route_iterator_matches_elem_projection_for_table_dump_v2() {
1554        let bytes = table_dump_v2_records_bytes();
1555        let routes = assert_route_projection(bytes);
1556        assert_eq!(routes.len(), 1);
1557        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1558        assert_eq!(
1559            routes[0].as_path.as_ref().unwrap().to_u32_vec_opt(false),
1560            Some(vec![64500, 64501])
1561        );
1562    }
1563
1564    #[test]
1565    fn route_iterator_matches_elem_projection_for_table_dump_v2_ipv6_addpath() {
1566        let peer = Peer::new(
1567            "192.0.2.11".parse().unwrap(),
1568            "2001:db8::10".parse().unwrap(),
1569            Asn::new_32bit(64496),
1570        );
1571        let mut peer_table = PeerIndexTable::default();
1572        let peer_index = peer_table.add_peer(peer).unwrap();
1573
1574        let pit_record = MrtRecord {
1575            common_header: CommonHeader {
1576                timestamp: 1_700_000_000,
1577                microsecond_timestamp: None,
1578                entry_type: EntryType::TABLE_DUMP_V2,
1579                entry_subtype: TableDumpV2Type::PeerIndexTable as u16,
1580                length: 0,
1581            },
1582            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(peer_table)),
1583        };
1584        let rib_record = MrtRecord {
1585            common_header: CommonHeader {
1586                timestamp: 1_700_000_001,
1587                microsecond_timestamp: None,
1588                entry_type: EntryType::TABLE_DUMP_V2,
1589                entry_subtype: TableDumpV2Type::RibIpv6UnicastAddPath as u16,
1590                length: 0,
1591            },
1592            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
1593                rib_type: TableDumpV2Type::RibIpv6UnicastAddPath,
1594                sequence_number: 1,
1595                prefix: NetworkPrefix::from_str("2001:db8::/32").unwrap(),
1596                rib_entries: vec![RibEntry {
1597                    peer_index,
1598                    originated_time: 1_699_999_999,
1599                    path_id: Some(1234),
1600                    attributes: route_attributes([64500, 64501]),
1601                }],
1602            })),
1603        };
1604
1605        let mut bytes = pit_record.encode().unwrap().to_vec();
1606        bytes.extend_from_slice(&rib_record.encode().unwrap());
1607        let routes = assert_route_projection(bytes);
1608        assert_eq!(routes.len(), 1);
1609        assert_eq!(
1610            routes[0].prefix,
1611            NetworkPrefix::from_str("2001:db8::/32").unwrap()
1612        );
1613    }
1614
1615    #[test]
1616    fn route_iterator_matches_elem_projection_for_bgp4mp_ipv6_peer_update() {
1617        let record = MrtRecord {
1618            common_header: CommonHeader {
1619                timestamp: 1_700_000_000,
1620                microsecond_timestamp: None,
1621                entry_type: EntryType::BGP4MP,
1622                entry_subtype: Bgp4MpType::MessageAs4 as u16,
1623                length: 0,
1624            },
1625            message: MrtMessage::Bgp4Mp(Bgp4MpEnum::Message(Bgp4MpMessage {
1626                msg_type: Bgp4MpType::MessageAs4,
1627                peer_asn: Asn::new_32bit(64496),
1628                local_asn: Asn::new_32bit(64497),
1629                interface_index: 0,
1630                peer_ip: IpAddr::from_str("2001:db8::1").unwrap(),
1631                local_ip: IpAddr::from_str("2001:db8::2").unwrap(),
1632                bgp_message: BgpMessage::Update(BgpUpdateMessage {
1633                    withdrawn_prefixes: vec![],
1634                    attributes: route_attributes([64500, 64501]),
1635                    announced_prefixes: vec![NetworkPrefix::from_str("203.0.113.0/24").unwrap()],
1636                }),
1637            })),
1638        };
1639
1640        let routes = assert_route_projection(record.encode().unwrap().to_vec());
1641        assert_eq!(routes.len(), 1);
1642        assert_eq!(
1643            routes[0].peer_ip,
1644            IpAddr::from(Ipv6Addr::from_str("2001:db8::1").unwrap())
1645        );
1646    }
1647
1648    #[test]
1649    fn route_iterator_filters_match_elem_projection_for_table_dump_v2() {
1650        let bytes = table_dump_v2_records_bytes();
1651        let cases: &[&[(&str, &str)]] = &[
1652            &[("peer_ip", "192.0.2.10")],
1653            &[("peer_asn", "64496")],
1654            &[("type", "a")],
1655            &[("type", "w")],
1656            &[("prefix", "203.0.113.0/24")],
1657            &[("prefix_sub", "203.0.112.0/23")],
1658            &[("origin_asn", "64501")],
1659            &[("as_path", "64500 64501$")],
1660            &[("ts_start", "1699999999"), ("ts_end", "1699999999")],
1661            &[("peer_asn", "64496"), ("origin_asn", "64501")],
1662        ];
1663
1664        for filters in cases {
1665            assert_filtered_route_projection(bytes.clone(), filters);
1666        }
1667    }
1668
1669    #[test]
1670    fn route_parser_reports_bgp_message_shape_errors() {
1671        assert!(parse_bgp_message_routes(
1672            raw_bgp_message(18, BgpMessageType::KEEPALIVE, &[]),
1673            false,
1674            &AsnLength::Bits16,
1675            1_700_000_000.0,
1676            "192.0.2.1".parse().unwrap(),
1677            Asn::new_16bit(64496)
1678        )
1679        .is_err());
1680        assert!(parse_bgp_message_routes(
1681            raw_bgp_message(4097, BgpMessageType::OPEN, &[]),
1682            false,
1683            &AsnLength::Bits16,
1684            1_700_000_000.0,
1685            "192.0.2.1".parse().unwrap(),
1686            Asn::new_16bit(64496)
1687        )
1688        .is_err());
1689
1690        let routes = collect_route_record_iter(
1691            parse_bgp_message_routes(
1692                raw_bgp_message(30, BgpMessageType::KEEPALIVE, &[]),
1693                false,
1694                &AsnLength::Bits16,
1695                1_700_000_000.0,
1696                "192.0.2.1".parse().unwrap(),
1697                Asn::new_16bit(64496),
1698            )
1699            .unwrap(),
1700        )
1701        .unwrap();
1702        assert!(routes.is_empty());
1703
1704        let routes = collect_route_record_iter(
1705            parse_bgp_message_routes(
1706                raw_bgp_message(19, BgpMessageType::KEEPALIVE, &[0]),
1707                false,
1708                &AsnLength::Bits16,
1709                1_700_000_000.0,
1710                "192.0.2.1".parse().unwrap(),
1711                Asn::new_16bit(64496),
1712            )
1713            .unwrap(),
1714        )
1715        .unwrap();
1716        assert!(routes.is_empty());
1717
1718        let routes = collect_route_record_iter(
1719            parse_bgp_message_routes(
1720                raw_bgp_message_with_marker([0x00; 16], 19, 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            .unwrap(),
1728        )
1729        .unwrap();
1730        assert!(routes.is_empty());
1731    }
1732
1733    #[test]
1734    fn route_core_dump_write_respects_enabled_flag() {
1735        let dir = tempfile::tempdir().unwrap();
1736        let path = dir.path().join("mrt_core_dump");
1737
1738        write_mrt_core_dump_to_path(false, Some(vec![1, 2, 3]), &path);
1739        assert!(!path.exists());
1740
1741        write_mrt_core_dump_to_path(true, Some(vec![1, 2, 3]), &path);
1742        assert_eq!(std::fs::read(&path).unwrap(), vec![1, 2, 3]);
1743    }
1744
1745    #[test]
1746    fn route_parser_handles_table_dump_v2_error_edges() {
1747        let rib = RibAfiEntries {
1748            rib_type: TableDumpV2Type::RibIpv4Unicast,
1749            sequence_number: 1,
1750            prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1751            rib_entries: vec![RibEntry {
1752                peer_index: 99,
1753                originated_time: 1_699_999_999,
1754                path_id: None,
1755                attributes: route_attributes([64500, 64501]),
1756            }],
1757        };
1758        let mut no_peer_table = None;
1759        assert!(parse_table_dump_v2_routes(
1760            TableDumpV2Type::RibIpv4Unicast as u16,
1761            rib.encode().unwrap(),
1762            &mut no_peer_table,
1763        )
1764        .is_err());
1765
1766        let mut empty_peer_table = Some(RoutePeerTable::default());
1767        let routes = collect_route_record_iter(
1768            parse_table_dump_v2_routes(
1769                TableDumpV2Type::RibIpv4Unicast as u16,
1770                rib.encode().unwrap(),
1771                &mut empty_peer_table,
1772            )
1773            .unwrap(),
1774        )
1775        .unwrap();
1776        assert!(routes.is_empty());
1777
1778        let mut truncated = BytesMut::new();
1779        truncated.put_u32(1);
1780        truncated.extend(NetworkPrefix::from_str("203.0.113.0/24").unwrap().encode());
1781        truncated.put_u16(1);
1782        let mut empty_peer_table = Some(RoutePeerTable::default());
1783        let routes = collect_route_record_iter(
1784            parse_table_dump_v2_routes(
1785                TableDumpV2Type::RibIpv4Unicast as u16,
1786                truncated.freeze(),
1787                &mut empty_peer_table,
1788            )
1789            .unwrap(),
1790        )
1791        .unwrap();
1792        assert!(routes.is_empty());
1793
1794        let peer = Peer::new(
1795            "192.0.2.10".parse().unwrap(),
1796            "192.0.2.11".parse().unwrap(),
1797            Asn::new_32bit(64496),
1798        );
1799        let mut peer_table = PeerIndexTable::default();
1800        let peer_index = peer_table.add_peer(peer).unwrap();
1801
1802        let first_entry = RibEntry {
1803            peer_index,
1804            originated_time: 1_699_999_999,
1805            path_id: Some(1234),
1806            attributes: route_attributes([64500, 64501]),
1807        };
1808        let mut add_path_truncated = BytesMut::new();
1809        add_path_truncated.put_u32(1);
1810        add_path_truncated.extend(NetworkPrefix::from_str("203.0.113.0/24").unwrap().encode());
1811        add_path_truncated.put_u16(2);
1812        add_path_truncated.extend(first_entry.encode().unwrap());
1813        add_path_truncated.put_u16(peer_index);
1814        add_path_truncated.put_u32(1_699_999_998);
1815        add_path_truncated.put_u32(5678);
1816
1817        let mut peer_table = Some(route_peer_table_from_peer_index(peer_table));
1818        let routes = collect_route_record_iter(
1819            parse_table_dump_v2_routes(
1820                TableDumpV2Type::RibIpv4UnicastAddPath as u16,
1821                add_path_truncated.freeze(),
1822                &mut peer_table,
1823            )
1824            .unwrap(),
1825        )
1826        .unwrap();
1827        assert_eq!(routes.len(), 1);
1828        assert_eq!(
1829            routes[0].prefix,
1830            NetworkPrefix::from_str("203.0.113.0/24").unwrap()
1831        );
1832    }
1833
1834    #[test]
1835    fn route_parser_preserves_table_dump_v2_routes_before_truncated_attribute_payload() {
1836        let (_bytes, rib_body, peer_table) = table_dump_v2_truncated_attribute_payload();
1837        let mut peer_table = Some(route_peer_table_from_peer_index(peer_table));
1838
1839        let routes = collect_route_record_iter(
1840            parse_table_dump_v2_routes(
1841                TableDumpV2Type::RibIpv4Unicast as u16,
1842                rib_body,
1843                &mut peer_table,
1844            )
1845            .unwrap(),
1846        )
1847        .unwrap();
1848
1849        assert_eq!(routes.len(), 1);
1850        assert_eq!(
1851            routes[0].prefix,
1852            NetworkPrefix::from_str("203.0.113.0/24").unwrap()
1853        );
1854        assert_eq!(
1855            routes[0].as_path.as_ref().unwrap().to_u32_vec_opt(false),
1856            Some(vec![64500, 64501])
1857        );
1858    }
1859
1860    #[test]
1861    fn route_iterators_preserve_table_dump_v2_routes_before_truncated_attribute_payload() {
1862        let (bytes, _rib_body, _peer_table) = table_dump_v2_truncated_attribute_payload();
1863
1864        let routes = BgpkitParser::from_reader(Cursor::new(bytes.clone()))
1865            .into_route_iter()
1866            .collect::<Vec<_>>();
1867        assert_eq!(routes.len(), 1);
1868        assert_eq!(
1869            routes[0].prefix,
1870            NetworkPrefix::from_str("203.0.113.0/24").unwrap()
1871        );
1872
1873        let fallible_routes = BgpkitParser::from_reader(Cursor::new(bytes))
1874            .into_fallible_route_iter()
1875            .collect::<Result<Vec<_>, _>>()
1876            .unwrap();
1877        assert_eq!(fallible_routes, routes);
1878    }
1879
1880    fn table_dump_v2_rib_without_peer_table_record() -> MrtRecord {
1881        MrtRecord {
1882            common_header: CommonHeader {
1883                timestamp: 1_700_000_001,
1884                microsecond_timestamp: None,
1885                entry_type: EntryType::TABLE_DUMP_V2,
1886                entry_subtype: TableDumpV2Type::RibIpv4Unicast as u16,
1887                length: 0,
1888            },
1889            message: MrtMessage::TableDumpV2Message(TableDumpV2Message::RibAfi(RibAfiEntries {
1890                rib_type: TableDumpV2Type::RibIpv4Unicast,
1891                sequence_number: 1,
1892                prefix: NetworkPrefix::from_str("203.0.113.0/24").unwrap(),
1893                rib_entries: vec![RibEntry {
1894                    peer_index: 0,
1895                    originated_time: 1_699_999_999,
1896                    path_id: None,
1897                    attributes: route_attributes([64500, 64501]),
1898                }],
1899            })),
1900        }
1901    }
1902
1903    #[test]
1904    fn route_iterator_skips_route_parse_errors() {
1905        let routes = BgpkitParser::from_reader(Cursor::new(
1906            table_dump_v2_rib_without_peer_table_record()
1907                .encode()
1908                .unwrap()
1909                .to_vec(),
1910        ))
1911        .into_route_iter()
1912        .collect::<Vec<_>>();
1913
1914        assert!(routes.is_empty());
1915    }
1916
1917    #[test]
1918    fn fallible_route_iterator_applies_filters_to_cached_routes() {
1919        let routes =
1920            BgpkitParser::from_reader(Cursor::new(update_record().encode().unwrap().to_vec()))
1921                .add_filter("type", "w")
1922                .unwrap()
1923                .into_fallible_route_iter()
1924                .collect::<Result<Vec<_>, _>>()
1925                .unwrap();
1926
1927        assert_eq!(routes.len(), 1);
1928        assert_eq!(routes[0].elem_type, ElemType::WITHDRAW);
1929    }
1930
1931    #[test]
1932    fn fallible_route_iterator_returns_route_parse_errors() {
1933        let bytes = table_dump_v2_rib_without_peer_table_record()
1934            .encode()
1935            .unwrap()
1936            .to_vec();
1937        let mut iter =
1938            BgpkitParser::from_reader(Cursor::new(bytes.clone())).into_fallible_route_iter();
1939
1940        let error = iter.next().unwrap().unwrap_err();
1941        assert_eq!(error.bytes.as_deref(), Some(bytes.as_slice()));
1942    }
1943
1944    #[test]
1945    fn fallible_route_iterator_yields_routes() {
1946        let bytes = update_record().encode().unwrap().to_vec();
1947        let routes = BgpkitParser::from_reader(Cursor::new(bytes))
1948            .into_fallible_route_iter()
1949            .collect::<Result<Vec<_>, _>>()
1950            .unwrap();
1951
1952        assert_eq!(routes.len(), 2);
1953        assert_eq!(routes[0].elem_type, ElemType::ANNOUNCE);
1954        assert_eq!(routes[1].elem_type, ElemType::WITHDRAW);
1955    }
1956
1957    #[test]
1958    fn fallible_route_iterator_returns_parse_errors() {
1959        let invalid_data = vec![
1960            0x00, 0x00, 0x00, 0x00, // timestamp
1961            0xFF, 0xFF, // invalid type
1962            0x00, 0x00, // subtype
1963            0x00, 0x00, 0x00, 0x04, // length
1964            0x00, 0x00, 0x00, 0x00, // dummy data
1965        ];
1966
1967        let mut iter =
1968            BgpkitParser::from_reader(Cursor::new(invalid_data.clone())).into_fallible_route_iter();
1969
1970        let error = iter.next().unwrap().unwrap_err();
1971        assert_eq!(error.bytes.as_deref(), Some(&invalid_data[..12]));
1972    }
1973}