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
9It also contains the trait implementations that enable BgpkitParser to be used with
10Rust's iterator syntax.
11*/
12
13pub mod default;
14mod diagnostic;
15pub mod fallible;
16mod raw;
17mod recovery;
18mod route;
19mod update;
20
21// Re-export all iterator types for convenience
22pub use default::{ElemIterator, RecordIterator};
23pub use diagnostic::{DiagnosticEvent, DiagnosticIterator};
24pub use fallible::{FallibleElemIterator, FallibleRecordIterator};
25pub use raw::RawRecordIterator;
26pub use recovery::{
27    RecoveringElemIterator, RecoveringRecordIterator, RecoveryConfig, RecoveryError, RecoveryEvent,
28    RecoveryEvidence, RecoveryGap,
29};
30pub use route::{FallibleRouteIterator, RouteIterator};
31pub use update::{
32    Bgp4MpUpdate, FallibleUpdateIterator, LegacyBgpUpdate, MrtUpdate, TableDumpV2Entry,
33    UpdateIterator,
34};
35
36use crate::models::BgpElem;
37use crate::models::{MrtMessage, MrtRecord, TableDumpV2Message};
38use crate::parser::BgpkitParser;
39use crate::RawMrtRecord;
40use crate::{Elementor, Filter, Filterable};
41use std::io::Read;
42use std::path::Path;
43
44#[inline]
45pub(crate) fn record_matches_filters(
46    record: &MrtRecord,
47    filters: &[Filter],
48    elementor: &mut Elementor,
49) -> bool {
50    if filters.is_empty() {
51        return true;
52    }
53    if matches!(
54        &record.message,
55        MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_))
56    ) {
57        let _ = elementor.record_to_elems(record.clone());
58        return true;
59    }
60    elementor
61        .record_to_elems(record.clone())
62        .iter()
63        .any(|element| element.match_filters(filters))
64}
65
66pub(crate) fn write_mrt_core_dump(enabled: bool, bytes: Option<Vec<u8>>) {
67    write_mrt_core_dump_to_path(enabled, bytes, "mrt_core_dump");
68}
69
70pub(crate) fn write_mrt_core_dump_to_path<P: AsRef<Path>>(
71    enabled: bool,
72    bytes: Option<Vec<u8>>,
73    path: P,
74) {
75    if enabled {
76        if let Some(bytes) = bytes {
77            std::fs::write(path, bytes).expect("Unable to write to mrt_core_dump");
78        }
79    }
80}
81
82/// Use [ElemIterator] as the default iterator to return [BgpElem]s instead of [MrtRecord]s.
83impl<R: Read> IntoIterator for BgpkitParser<R> {
84    type Item = BgpElem;
85    type IntoIter = ElemIterator<R>;
86
87    fn into_iter(self) -> Self::IntoIter {
88        ElemIterator::new(self)
89    }
90}
91
92impl<R> BgpkitParser<R> {
93    pub fn into_record_iter(self) -> RecordIterator<R> {
94        RecordIterator::new(self)
95    }
96
97    pub fn into_elem_iter(self) -> ElemIterator<R> {
98        ElemIterator::new(self)
99    }
100
101    pub fn into_raw_record_iter(self) -> RawRecordIterator<R> {
102        RawRecordIterator::new(self)
103    }
104
105    /// Creates an opt-in iterator that reports skipped byte ranges while recovering MRT framing.
106    ///
107    /// Recovery never reconstructs a damaged record. It scans for a structurally valid boundary,
108    /// confirms a chain of records, emits [`RecoveryEvent::Gap`], and then resumes normal parsing.
109    /// Damage extending to the end of the stream is reported as a terminal gap. Offsets in
110    /// recovery events refer to the decompressed MRT byte stream.
111    pub fn into_recovering_record_iter(
112        self,
113        config: RecoveryConfig,
114    ) -> RecoveringRecordIterator<R> {
115        RecoveringRecordIterator::new(self, config)
116    }
117
118    /// Creates an opt-in iterator over BGP elements that reports skipped byte ranges while
119    /// recovering MRT framing.
120    ///
121    /// Behaves like [`into_recovering_record_iter`](Self::into_recovering_record_iter) but
122    /// converts each recovered record to [`BgpElem`]s, applying the parser's filters per
123    /// element.
124    pub fn into_recovering_elem_iter(self, config: RecoveryConfig) -> RecoveringElemIterator<R> {
125        RecoveringElemIterator::new(self, config)
126    }
127
128    /// Creates an iterator over BGP announcements from MRT data.
129    ///
130    /// This iterator yields `MrtUpdate` items from both UPDATES files (BGP4MP messages)
131    /// and RIB dump files (TableDump/TableDumpV2 messages). It's a middle ground
132    /// between `into_record_iter()` and `into_elem_iter()`:
133    ///
134    /// - More focused than `into_record_iter()` as it only returns BGP announcements
135    /// - More efficient than `into_elem_iter()` as it doesn't duplicate attributes per prefix
136    ///
137    /// The iterator returns an `MrtUpdate` enum with variants:
138    /// - `Bgp4MpUpdate`: BGP UPDATE messages from UPDATES files
139    /// - `LegacyBgpUpdate`: Deprecated MRT Type 5 BGP UPDATE messages
140    /// - `TableDumpV2Entry`: RIB entries from TableDumpV2 RIB dumps
141    /// - `TableDumpMessage`: Legacy TableDump v1 messages
142    ///
143    /// # Example
144    /// ```no_run
145    /// use bgpkit_parser::{BgpkitParser, MrtUpdate};
146    ///
147    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
148    /// for update in parser.into_update_iter() {
149    ///     match update {
150    ///         MrtUpdate::Bgp4MpUpdate(u) => {
151    ///             println!("Peer {} announced {} prefixes",
152    ///                 u.peer_ip,
153    ///                 u.message.announced_prefixes.len()
154    ///             );
155    ///         }
156    ///         MrtUpdate::LegacyBgpUpdate(u) => {
157    ///             println!("Legacy UPDATE from peer {}", u.peer_ip);
158    ///         }
159    ///         MrtUpdate::TableDumpV2Entry(e) => {
160    ///             println!("RIB entry for {} with {} peers",
161    ///                 e.prefix,
162    ///                 e.rib_entries.len()
163    ///             );
164    ///         }
165    ///         MrtUpdate::TableDumpMessage(m) => {
166    ///             println!("Legacy table dump for {}", m.prefix);
167    ///         }
168    ///     }
169    /// }
170    /// ```
171    pub fn into_update_iter(self) -> UpdateIterator<R> {
172        UpdateIterator::new(self)
173    }
174
175    /// Creates an iterator over lightweight route elements from MRT data.
176    ///
177    /// This iterator yields [`BgpRouteElem`](crate::models::BgpRouteElem)
178    /// values and only parses route identity, peer metadata, timestamp, and
179    /// AS path. Use [`into_elem_iter`](Self::into_elem_iter) when you need
180    /// the full [`BgpElem`] attribute set. Filters that only depend on route
181    /// fields are supported; `community` filters do not match route elements.
182    pub fn into_route_iter(self) -> RouteIterator<R> {
183        RouteIterator::new(self)
184    }
185
186    /// Creates a fallible iterator over MRT records that returns parsing errors.
187    ///
188    /// # Example
189    /// ```no_run
190    /// use bgpkit_parser::BgpkitParser;
191    ///
192    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
193    /// for result in parser.into_fallible_record_iter() {
194    ///     match result {
195    ///         Ok(record) => {
196    ///             // Process the record
197    ///         }
198    ///         Err(e) => {
199    ///             // Handle the error
200    ///             eprintln!("Error parsing record: {}", e);
201    ///         }
202    ///     }
203    /// }
204    /// ```
205    pub fn into_fallible_record_iter(self) -> FallibleRecordIterator<R> {
206        FallibleRecordIterator::new(self)
207    }
208
209    /// Creates a fallible iterator over BGP elements that returns parsing errors.
210    ///
211    /// # Example
212    /// ```no_run
213    /// use bgpkit_parser::BgpkitParser;
214    ///
215    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
216    /// for result in parser.into_fallible_elem_iter() {
217    ///     match result {
218    ///         Ok(elem) => {
219    ///             // Process the element
220    ///         }
221    ///         Err(e) => {
222    ///             // Handle the error
223    ///             eprintln!("Error parsing element: {}", e);
224    ///         }
225    ///     }
226    /// }
227    /// ```
228    pub fn into_fallible_elem_iter(self) -> FallibleElemIterator<R> {
229        FallibleElemIterator::new(self)
230    }
231
232    /// Creates a fallible iterator over BGP announcements that returns parsing errors.
233    ///
234    /// Unlike the default `into_update_iter()`, this iterator returns
235    /// `Result<MrtUpdate, ParserErrorWithBytes>` allowing users to handle parsing
236    /// errors explicitly instead of having them logged and skipped.
237    ///
238    /// # Example
239    /// ```no_run
240    /// use bgpkit_parser::{BgpkitParser, MrtUpdate};
241    ///
242    /// let parser = BgpkitParser::new("updates.mrt").unwrap();
243    /// for result in parser.into_fallible_update_iter() {
244    ///     match result {
245    ///         Ok(MrtUpdate::Bgp4MpUpdate(update)) => {
246    ///             println!("Peer {} announced {} prefixes",
247    ///                 update.peer_ip,
248    ///                 update.message.announced_prefixes.len()
249    ///             );
250    ///         }
251    ///         Ok(_) => { /* handle other variants */ }
252    ///         Err(e) => {
253    ///             eprintln!("Error parsing: {}", e);
254    ///         }
255    ///     }
256    /// }
257    /// ```
258    pub fn into_fallible_update_iter(self) -> FallibleUpdateIterator<R> {
259        FallibleUpdateIterator::new(self)
260    }
261
262    /// Creates a fallible iterator over lightweight route elements.
263    pub fn into_fallible_route_iter(self) -> FallibleRouteIterator<R> {
264        FallibleRouteIterator::new(self)
265    }
266
267    /// Creates an iterator that classifies each MRT record for malformed-data investigation.
268    ///
269    /// This iterator emits clean parsed records, recoverable RFC 7606 validation findings, and
270    /// fatal parse errors with available raw-byte context. It ignores parser filters so that
271    /// malformed records cannot be hidden by element-oriented matching. Text-dump parsers yield
272    /// no diagnostic events because they have no MRT record representation.
273    ///
274    /// # Example
275    /// ```no_run
276    /// use bgpkit_parser::{BgpkitParser, DiagnosticEvent};
277    ///
278    /// for event in BgpkitParser::new("updates.mrt")?.into_diagnostic_iter() {
279    ///     match event {
280    ///         DiagnosticEvent::Record(record) => println!("{record}"),
281    ///         DiagnosticEvent::Validation { warnings, raw_record, .. } => {
282    ///             eprintln!("validation findings: {warnings:?}");
283    ///             raw_record.write_raw_bytes("malformed-record.mrt")?;
284    ///         }
285    ///         DiagnosticEvent::ParseError { error, raw_bytes, .. } => {
286    ///             eprintln!("parse error: {error}");
287    ///             if let Some(raw_bytes) = raw_bytes {
288    ///                 std::fs::write("malformed-record.mrt", raw_bytes)?;
289    ///             }
290    ///         }
291    ///         _ => {}
292    ///     }
293    /// }
294    /// # Ok::<(), Box<dyn std::error::Error>>(())
295    /// ```
296    pub fn into_diagnostic_iter(self) -> DiagnosticIterator<R> {
297        DiagnosticIterator::new(self)
298    }
299
300    /// Creates an Elementor pre-initialized with PeerIndexTable and an iterator over raw records.
301    ///
302    /// This is useful for parallel processing where the Elementor needs to be shared across threads.
303    /// The Elementor is created with the PeerIndexTable from the first record if present,
304    /// otherwise a new Elementor is created.
305    ///
306    /// # Example
307    /// See the `parallel_records_to_elem` example for full usage.
308    /// ```ignore
309    /// use bgpkit_parser::BgpkitParser;
310    ///
311    /// let parser = BgpkitParser::new_cached(url, "/tmp")?;
312    /// let (elementor, records) = parser.into_elementor_and_raw_record_iter();
313    /// ```
314    ///
315    pub fn into_elementor_and_raw_record_iter(
316        self,
317    ) -> (Elementor, impl Iterator<Item = RawMrtRecord>)
318    where
319        R: Read,
320    {
321        let mut raw_iter = RawRecordIterator::new(self).peekable();
322        let elementor = match raw_iter.peek().cloned().and_then(|r| r.parse().ok()) {
323            Some(MrtRecord {
324                message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(pit)),
325                ..
326            }) => {
327                raw_iter.next();
328                Elementor::with_peer_table(pit)
329            }
330            _ => Elementor::new(),
331        };
332        (elementor, raw_iter)
333    }
334
335    /// Creates an Elementor pre-initialized with PeerIndexTable and an iterator over parsed records.
336    ///
337    /// This is useful for parallel processing where the Elementor needs to be shared across threads.
338    /// The Elementor is created with the PeerIndexTable from the first record if present,
339    /// otherwise a new Elementor is created.
340    ///
341    /// # Example
342    /// See the `parallel_records_to_elem` example for full usage.
343    pub fn into_elementor_and_record_iter(self) -> (Elementor, impl Iterator<Item = MrtRecord>)
344    where
345        R: Read,
346    {
347        let mut record_iter = RecordIterator::new(self).peekable();
348        let elementor = match record_iter.peek().cloned() {
349            Some(MrtRecord {
350                message: MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(pit)),
351                ..
352            }) => {
353                record_iter.next();
354                Elementor::with_peer_table(pit)
355            }
356            _ => Elementor::new(),
357        };
358        (elementor, record_iter)
359    }
360}