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}