Skip to main content

bgpkit_parser/parser/iters/
mod.rs

1/*!
2Iterator implementations for bgpkit-parser.
3
4This module contains different iterator implementations for parsing BGP data:
5- `default`: Standard iterators that skip errors (RecordIterator, ElemIterator)
6- `fallible`: Fallible iterators that return Results (FallibleRecordIterator, FallibleElemIterator)
7- `update`: Iterators for BGP UPDATE messages (UpdateIterator, FallibleUpdateIterator)
8- `route`: Route-level iterators (RouteIterator, FallibleRouteIterator)
9- `recovery`: Iterators that survive damaged MRT framing (RecoveringRecordIterator,
10  RecoveringElemIterator, RecoveryEvent)
11- `diagnostic`: Per-record dissection and validation reporting (DiagnosticIterator,
12  DissectingDiagnosticIterator)
13- `raw`: Raw MRT records with their original bytes (RawRecordIterator,
14  FilteredRawRecordIterator)
15
16It also contains the trait implementations that enable BgpkitParser to be used with
17Rust's iterator syntax.
18*/
19
20pub mod default;
21mod diagnostic;
22pub mod fallible;
23mod raw;
24mod recovery;
25mod route;
26mod update;
27
28// Re-export all iterator types for convenience
29pub use default::{ElemIterator, RecordIterator};
30pub use diagnostic::{
31    record_validation_warnings, span_record_warnings, DiagnosticEvent, DiagnosticIterator,
32    DissectedDiagnosticEvent, DissectingDiagnosticIterator,
33};
34pub use fallible::{FallibleElemIterator, FallibleRecordIterator};
35pub use raw::{FilteredRawRecordIterator, RawRecordIterator};
36pub use recovery::{
37    RecoveringElemIterator, RecoveringRecordIterator, RecoveryConfig, RecoveryError, RecoveryEvent,
38    RecoveryEvidence, RecoveryGap,
39};
40pub use route::{FallibleRouteIterator, RouteIterator};
41pub use update::{
42    Bgp4MpUpdate, FallibleUpdateIterator, LegacyBgpUpdate, MrtUpdate, TableDumpV2Entry,
43    UpdateIterator,
44};
45
46use crate::error::{ParserError, ParserErrorWithBytes};
47use crate::models::BgpElem;
48use crate::models::{MrtMessage, MrtRecord, TableDumpV2Message};
49use crate::parser::mrt::mrt_record::parse_chunked_record_with_zebra_compat;
50use crate::parser::{chunk_mrt_record, BgpkitParser};
51use crate::RawMrtRecord;
52use crate::{Elementor, Filter, Filterable};
53use log::{debug, error, warn};
54use std::io::Read;
55use std::path::Path;
56
57// Classify stream failures while framing: body parsing can also return
58// skippable IoErrors, but a failed decompressor must not be polled again.
59fn next_fallible_raw_record(
60    reader: &mut impl Read,
61    finished: &mut bool,
62) -> Option<Result<RawMrtRecord, ParserErrorWithBytes>> {
63    if *finished {
64        return None;
65    }
66    match chunk_mrt_record(reader) {
67        Ok(record) => Some(Ok(record)),
68        Err(error) if matches!(error.error, ParserError::EofExpected) => {
69            *finished = true;
70            None
71        }
72        Err(error) => {
73            match &error.error {
74                ParserError::IoError(io_error) | ParserError::EofError(io_error) => {
75                    // A resumed pull starts a fresh header, so retrying is only safe
76                    // when the failed read consumed nothing; otherwise the partial
77                    // framing bytes are gone and the stream stays misaligned.
78                    let nothing_consumed = error.bytes.as_ref().is_none_or(Vec::is_empty);
79                    *finished = !(nothing_consumed
80                        && matches!(
81                            io_error.kind(),
82                            std::io::ErrorKind::Interrupted | std::io::ErrorKind::WouldBlock
83                        ));
84                }
85                ParserError::OversizedRecord { .. } => {
86                    // no record boundary to resume from
87                    *finished = true;
88                }
89                _ => {}
90            }
91            Some(Err(error))
92        }
93    }
94}
95
96fn next_fallible_record<R: Read>(
97    parser: &mut BgpkitParser<R>,
98    finished: &mut bool,
99) -> Option<Result<MrtRecord, ParserErrorWithBytes>> {
100    let raw_record = match next_fallible_raw_record(&mut parser.reader, finished)? {
101        Ok(record) => record,
102        Err(error) => return Some(Err(error)),
103    };
104    Some(match parse_chunked_record_with_zebra_compat(raw_record) {
105        Ok((record, used_zebra_compat)) => {
106            if used_zebra_compat {
107                parser.warn_zebra_compat_once();
108            }
109            Ok(record)
110        }
111        Err(error) => Err(error),
112    })
113}
114
115#[inline]
116pub(crate) fn record_matches_filters(
117    record: &MrtRecord,
118    filters: &[Filter],
119    elementor: &mut Elementor,
120) -> bool {
121    if filters.is_empty() {
122        return true;
123    }
124    if matches!(
125        &record.message,
126        MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_))
127    ) {
128        let _ = elementor.record_to_elems(record.clone());
129        return true;
130    }
131    // Filters match on the elem projection. Records that produce no elems
132    // (KEEPALIVE, OPEN, NOTIFICATION, state changes) can therefore never
133    // match and are dropped from record iteration while filters are active.
134    let elems = elementor.record_to_elems(record.clone());
135    if elems.is_empty() {
136        debug!(
137            "filters active: record of type {:?} yields no elems and is dropped",
138            record.common_header.entry_type
139        );
140        return false;
141    }
142    elems.iter().any(|element| element.match_filters(filters))
143}
144
145/// Shared body-parse error policy for record-producing iterators.
146///
147/// Mirrors the historical `RecordIterator` behavior: warnings honor
148/// `disable_warnings()`, core dumps are written for recoverable classes,
149/// and a fatal `ParseError` with core dumps enabled stops the iterator so
150/// a later failure cannot overwrite the dump. Returns `true` to continue
151/// iterating, `false` to stop.
152pub(crate) fn handle_record_parse_error<R>(
153    parser: &mut crate::parser::BgpkitParser<R>,
154    error: ParserError,
155    bytes: Option<Vec<u8>>,
156) -> bool {
157    match error {
158        ParserError::TruncatedMsg(err_str) | ParserError::Unsupported(err_str) => {
159            if parser.options.show_warnings {
160                warn!("parser warn: {}", err_str);
161            }
162            write_mrt_core_dump(parser.core_dump, bytes);
163            true
164        }
165        ParserError::ParseError(err_str) => {
166            parser.log_parse_error_once(&err_str);
167            write_mrt_core_dump(parser.core_dump, bytes);
168            // stop after writing the dump so later failures don't overwrite it
169            !parser.core_dump
170        }
171        ParserError::EofExpected => {
172            // normal end of file
173            false
174        }
175        ParserError::OversizedRecord { length } => {
176            // no record boundary to resume from, stop iterating
177            error!(
178                "parser error: MRT message too large: {} bytes; stream position is unreliable, stopping",
179                length
180            );
181            write_mrt_core_dump(parser.core_dump, bytes);
182            false
183        }
184        ParserError::IoError(err) | ParserError::EofError(err) => {
185            // when reaching IO error, stop iterating
186            error!("{:?}", err);
187            write_mrt_core_dump(parser.core_dump, bytes);
188            false
189        }
190        #[cfg(feature = "oneio")]
191        ParserError::OneIoError(_) => false,
192        ParserError::FilterError(_) => {
193            // this should not happen at this stage
194            false
195        }
196        // Labeled NLRI parsing errors - treat as malformed and skip
197        ParserError::InvalidLabeledNlriLength
198        | ParserError::TruncatedLabeledNlri
199        | ParserError::TruncatedPrefix
200        | ParserError::MaxLabelStackDepthExceeded
201        | ParserError::PeerMaxLabelsExceeded
202        | ParserError::InvalidPrefix => {
203            if parser.options.show_warnings {
204                warn!("parser warn: labeled NLRI parsing error: {:?}", error);
205            }
206            true
207        }
208    }
209}
210
211pub(crate) fn write_mrt_core_dump(enabled: bool, bytes: Option<Vec<u8>>) {
212    write_mrt_core_dump_to_path(enabled, bytes, "mrt_core_dump");
213}
214
215pub(crate) fn write_mrt_core_dump_to_path<P: AsRef<Path>>(
216    enabled: bool,
217    bytes: Option<Vec<u8>>,
218    path: P,
219) {
220    if enabled {
221        if let Some(bytes) = bytes {
222            std::fs::write(path, bytes).expect("Unable to write to mrt_core_dump");
223        }
224    }
225}
226
227/// Use [ElemIterator] as the default iterator to return [BgpElem]s instead of [MrtRecord]s.
228impl<R: Read> IntoIterator for BgpkitParser<R> {
229    type Item = BgpElem;
230    type IntoIter = ElemIterator<R>;
231
232    fn into_iter(self) -> Self::IntoIter {
233        ElemIterator::new(self)
234    }
235}
236
237impl<R> BgpkitParser<R> {
238    pub fn into_record_iter(self) -> RecordIterator<R> {
239        RecordIterator::new(self)
240    }
241
242    pub fn into_elem_iter(self) -> ElemIterator<R> {
243        ElemIterator::new(self)
244    }
245
246    pub fn into_raw_record_iter(self) -> RawRecordIterator<R> {
247        RawRecordIterator::new(self)
248    }
249
250    /// Creates an iterator over raw MRT records with record-level filter
251    /// semantics applied.
252    ///
253    /// Like [`into_raw_record_iter`](Self::into_raw_record_iter), but only
254    /// records passing the parser's filters are yielded (same semantics as
255    /// [`into_record_iter`](Self::into_record_iter): filters match on the
256    /// elem projection, so no-elem records such as KEEPALIVEs are dropped
257    /// while filters are active, and the `PeerIndexTable` always passes).
258    /// The yielded records carry their original wire bytes — no
259    /// re-encoding — which is what byte-exact consumers (hex output,
260    /// re-dissection) need. Every record body is parsed once inside the
261    /// iterator and the parsed record is yielded alongside, so consumers
262    /// do not parse the bytes twice; parse failures follow the same
263    /// variant-aware diagnostics as the record iterator.
264    ///
265    /// # Example
266    /// ```no_run
267    /// use bgpkit_parser::BgpkitParser;
268    ///
269    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
270    /// for (raw, _) in parser.into_filtered_raw_record_iter() {
271    ///     println!("{}", raw.raw_bytes().len());
272    /// }
273    /// ```
274    pub fn into_filtered_raw_record_iter(self) -> FilteredRawRecordIterator<R> {
275        FilteredRawRecordIterator::new(self)
276    }
277
278    /// Creates an opt-in iterator that reports skipped byte ranges while recovering MRT framing.
279    ///
280    /// Recovery never reconstructs a damaged record. It scans for a structurally valid boundary,
281    /// confirms a chain of records, emits [`RecoveryEvent::Gap`], and then resumes normal parsing.
282    /// Damage extending to the end of the stream is reported as a terminal gap. Offsets in
283    /// recovery events refer to the decompressed MRT byte stream.
284    pub fn into_recovering_record_iter(
285        self,
286        config: RecoveryConfig,
287    ) -> RecoveringRecordIterator<R> {
288        RecoveringRecordIterator::new(self, config)
289    }
290
291    /// Creates an opt-in iterator over BGP elements that reports skipped byte ranges while
292    /// recovering MRT framing.
293    ///
294    /// Behaves like [`into_recovering_record_iter`](Self::into_recovering_record_iter) but
295    /// converts each recovered record to [`BgpElem`]s, applying the parser's filters per
296    /// element.
297    pub fn into_recovering_elem_iter(self, config: RecoveryConfig) -> RecoveringElemIterator<R> {
298        RecoveringElemIterator::new(self, config)
299    }
300
301    /// Creates an iterator over BGP announcements from MRT data.
302    ///
303    /// This iterator yields `MrtUpdate` items from both UPDATES files (BGP4MP messages)
304    /// and RIB dump files (TableDump/TableDumpV2 messages). It's a middle ground
305    /// between `into_record_iter()` and `into_elem_iter()`:
306    ///
307    /// - More focused than `into_record_iter()` as it only returns BGP announcements
308    /// - More efficient than `into_elem_iter()` as it doesn't duplicate attributes per prefix
309    ///
310    /// The iterator returns an `MrtUpdate` enum with variants:
311    /// - `Bgp4MpUpdate`: BGP UPDATE messages from UPDATES files
312    /// - `LegacyBgpUpdate`: Deprecated MRT Type 5 BGP UPDATE messages
313    /// - `TableDumpV2Entry`: RIB entries from TableDumpV2 RIB dumps
314    /// - `TableDumpMessage`: Legacy TableDump v1 messages
315    ///
316    /// # Example
317    /// ```no_run
318    /// use bgpkit_parser::{BgpkitParser, MrtUpdate};
319    ///
320    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
321    /// for update in parser.into_update_iter() {
322    ///     match update {
323    ///         MrtUpdate::Bgp4MpUpdate(u) => {
324    ///             println!("Peer {} announced {} prefixes",
325    ///                 u.peer_ip,
326    ///                 u.message.announced_prefixes.len()
327    ///             );
328    ///         }
329    ///         MrtUpdate::LegacyBgpUpdate(u) => {
330    ///             println!("Legacy UPDATE from peer {}", u.peer_ip);
331    ///         }
332    ///         MrtUpdate::TableDumpV2Entry(e) => {
333    ///             println!("RIB entry for {} with {} peers",
334    ///                 e.prefix,
335    ///                 e.rib_entries.len()
336    ///             );
337    ///         }
338    ///         MrtUpdate::TableDumpMessage(m) => {
339    ///             println!("Legacy table dump for {}", m.prefix);
340    ///         }
341    ///     }
342    /// }
343    /// ```
344    pub fn into_update_iter(self) -> UpdateIterator<R> {
345        UpdateIterator::new(self)
346    }
347
348    /// Creates an iterator over lightweight route elements from MRT data.
349    ///
350    /// This iterator yields [`BgpRouteElem`](crate::models::BgpRouteElem)
351    /// values and only parses route identity, peer metadata, timestamp, and
352    /// AS path. Use [`into_elem_iter`](Self::into_elem_iter) when you need
353    /// the full [`BgpElem`] attribute set. Filters that only depend on route
354    /// fields are supported; `community` filters do not match route elements.
355    pub fn into_route_iter(self) -> RouteIterator<R> {
356        RouteIterator::new(self)
357    }
358
359    /// Creates a fallible iterator over MRT records that returns parsing errors.
360    ///
361    /// Malformed records can be skipped. A framing I/O or decompression error
362    /// is yielded once and then ends iteration permanently, except for an
363    /// `Interrupted` or `WouldBlock` read that consumed no framing bytes, which
364    /// leaves the stream retryable. Normal EOF also ends iteration permanently.
365    ///
366    /// # Example
367    /// ```no_run
368    /// use bgpkit_parser::BgpkitParser;
369    ///
370    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
371    /// for result in parser.into_fallible_record_iter() {
372    ///     match result {
373    ///         Ok(record) => {
374    ///             // Process the record
375    ///         }
376    ///         Err(e) => {
377    ///             // Handle the error
378    ///             eprintln!("Error parsing record: {}", e);
379    ///         }
380    ///     }
381    /// }
382    /// ```
383    pub fn into_fallible_record_iter(self) -> FallibleRecordIterator<R> {
384        FallibleRecordIterator::new(self)
385    }
386
387    /// Creates a fallible iterator over BGP elements that returns parsing errors.
388    ///
389    /// Uses the same stream-error termination policy as
390    /// [`into_fallible_record_iter`](Self::into_fallible_record_iter).
391    ///
392    /// # Example
393    /// ```no_run
394    /// use bgpkit_parser::BgpkitParser;
395    ///
396    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
397    /// for result in parser.into_fallible_elem_iter() {
398    ///     match result {
399    ///         Ok(elem) => {
400    ///             // Process the element
401    ///         }
402    ///         Err(e) => {
403    ///             // Handle the error
404    ///             eprintln!("Error parsing element: {}", e);
405    ///         }
406    ///     }
407    /// }
408    /// ```
409    pub fn into_fallible_elem_iter(self) -> FallibleElemIterator<R> {
410        FallibleElemIterator::new(self)
411    }
412
413    /// Creates a fallible iterator over BGP announcements that returns parsing errors.
414    ///
415    /// Unlike the default `into_update_iter()`, this iterator returns
416    /// `Result<MrtUpdate, ParserErrorWithBytes>` allowing users to handle parsing
417    /// errors explicitly instead of having them logged and skipped. Uses the same
418    /// stream-error termination policy as
419    /// [`into_fallible_record_iter`](Self::into_fallible_record_iter).
420    ///
421    /// # Example
422    /// ```no_run
423    /// use bgpkit_parser::{BgpkitParser, MrtUpdate};
424    ///
425    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
426    /// for result in parser.into_fallible_update_iter() {
427    ///     match result {
428    ///         Ok(MrtUpdate::Bgp4MpUpdate(update)) => {
429    ///             println!("Peer {} announced {} prefixes",
430    ///                 update.peer_ip,
431    ///                 update.message.announced_prefixes.len()
432    ///             );
433    ///         }
434    ///         Ok(_) => { /* handle other variants */ }
435    ///         Err(e) => {
436    ///             eprintln!("Error parsing: {}", e);
437    ///         }
438    ///     }
439    /// }
440    /// ```
441    pub fn into_fallible_update_iter(self) -> FallibleUpdateIterator<R> {
442        FallibleUpdateIterator::new(self)
443    }
444
445    /// Creates a fallible iterator over lightweight route elements.
446    ///
447    /// Uses the same stream-error termination policy as
448    /// [`into_fallible_record_iter`](Self::into_fallible_record_iter).
449    pub fn into_fallible_route_iter(self) -> FallibleRouteIterator<R> {
450        FallibleRouteIterator::new(self)
451    }
452
453    /// Creates an iterator that classifies each MRT record for malformed-data investigation.
454    ///
455    /// This iterator emits every record with its raw bytes attached: clean
456    /// records have empty warning lists, records with recoverable RFC 7606
457    /// validation findings carry them in `warnings`, and fatal parse errors
458    /// retain the consumed bytes plus a best-effort partial dissection tree
459    /// showing where parsing stopped. It ignores parser filters so that
460    /// malformed records cannot be hidden by element-oriented matching. Text-dump parsers yield
461    /// no diagnostic events because they have no MRT record representation.
462    ///
463    /// # Example
464    /// ```no_run
465    /// use bgpkit_parser::{BgpkitParser, DiagnosticEvent};
466    ///
467    /// for event in BgpkitParser::new("updates.mrt")?.into_diagnostic_iter() {
468    ///     match event {
469    ///         DiagnosticEvent::Record { record, raw, warnings } => {
470    ///             if warnings.is_empty() {
471    ///                 println!("{record}");
472    ///             } else {
473    ///                 eprintln!("validation findings: {warnings:?}");
474    ///                 raw.write_raw_bytes("malformed-record.mrt")?;
475    ///             }
476    ///         }
477    ///         DiagnosticEvent::ParseError { error, raw_bytes, .. } => {
478    ///             eprintln!("parse error: {error}");
479    ///             if let Some(raw_bytes) = raw_bytes {
480    ///                 std::fs::write("malformed-record.mrt", raw_bytes)?;
481    ///             }
482    ///         }
483    ///         _ => {}
484    ///     }
485    /// }
486    /// # Ok::<(), Box<dyn std::error::Error>>(())
487    /// ```
488    pub fn into_diagnostic_iter(self) -> DiagnosticIterator<R> {
489        DiagnosticIterator::new(self)
490    }
491
492    /// Creates an Elementor pre-initialized with PeerIndexTable and an iterator over raw records.
493    ///
494    /// This is useful for parallel processing where the Elementor needs to be shared across threads.
495    /// The Elementor is created with the PeerIndexTable from the first record if present,
496    /// otherwise a new Elementor is created.
497    ///
498    /// # Example
499    /// See the `parallel_records_to_elem` example for full usage.
500    /// ```ignore
501    /// use bgpkit_parser::BgpkitParser;
502    ///
503    /// let parser = BgpkitParser::new_cached(url, "/tmp")?;
504    /// let (elementor, records) = parser.into_elementor_and_raw_record_iter();
505    /// ```
506    ///
507    pub fn into_elementor_and_raw_record_iter(
508        self,
509    ) -> (Elementor, impl Iterator<Item = RawMrtRecord>)
510    where
511        R: Read,
512    {
513        let mut raw_iter = RawRecordIterator::new(self).peekable();
514        let elementor = match raw_iter.peek().cloned().and_then(|r| r.parse().ok()) {
515            Some(MrtRecord {
516                message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(pit)),
517                ..
518            }) => {
519                raw_iter.next();
520                Elementor::with_peer_table(pit)
521            }
522            _ => Elementor::new(),
523        };
524        (elementor, raw_iter)
525    }
526
527    /// Creates an Elementor pre-initialized with PeerIndexTable and an iterator over parsed records.
528    ///
529    /// This is useful for parallel processing where the Elementor needs to be shared across threads.
530    /// The Elementor is created with the PeerIndexTable from the first record if present,
531    /// otherwise a new Elementor is created.
532    ///
533    /// # Example
534    /// See the `parallel_records_to_elem` example for full usage.
535    pub fn into_elementor_and_record_iter(self) -> (Elementor, impl Iterator<Item = MrtRecord>)
536    where
537        R: Read,
538    {
539        let mut record_iter = RecordIterator::new(self).peekable();
540        let elementor = match record_iter.peek().cloned() {
541            Some(MrtRecord {
542                message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(pit)),
543                ..
544            }) => {
545                record_iter.next();
546                Elementor::with_peer_table(pit)
547            }
548            _ => Elementor::new(),
549        };
550        (elementor, record_iter)
551    }
552}