diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dda36a8..9d21fb9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ All notable changes to this project will be documented in this file. ### Code improvements +* added fallible iterator implementations for explicit error handling + * implemented `FallibleRecordIterator` that returns `Result` + * implemented `FallibleElemIterator` that returns `Result` + * added `into_fallible_record_iter()` and `into_fallible_elem_iter()` methods on `BgpkitParser` + * allows users to handle parsing errors explicitly instead of having them silently skipped + * maintains full backward compatibility with existing error-skipping iterators + * reorganized iterator implementations into structured `iters` module with `default` and `fallible` submodules * implemented comprehensive BGP capabilities support for all IANA-defined capability codes with RFC support * added structured parsing and encoding for 7 major BGP capabilities (RFC2858, RFC2918, RFC4724, RFC6793, RFC7911, RFC8950, RFC9234) * implemented `MultiprotocolExtensionsCapability` for AFI/SAFI advertisement per RFC2858 diff --git a/examples/fallible_parsing.rs b/examples/fallible_parsing.rs new file mode 100644 index 00000000..d497d68f --- /dev/null +++ b/examples/fallible_parsing.rs @@ -0,0 +1,77 @@ +use bgpkit_parser::BgpkitParser; + +/// Example demonstrating how to use fallible iterators to handle parsing errors explicitly +fn main() { + // Example with fallible record iterator + println!("=== Fallible Record Iterator Example ==="); + + let parser = + BgpkitParser::new("https://data.ris.ripe.net/rrc00/2021.11/updates.20211101.0000.gz") + .unwrap() + .disable_warnings(); + + let mut success_count = 0; + let mut error_count = 0; + + for (idx, result) in parser.into_fallible_record_iter().enumerate() { + match result { + Ok(record) => { + success_count += 1; + if idx < 5 { + println!( + "Record {}: timestamp={}, type={:?}", + idx, record.common_header.timestamp, record.common_header.entry_type + ); + } + } + Err(e) => { + error_count += 1; + println!("Error parsing record {}: {}", idx, e); + } + } + + // Stop after processing first 100 records for demo + if idx >= 100 { + break; + } + } + + println!( + "\nProcessed {} records successfully, {} errors", + success_count, error_count + ); + + // Example with fallible element iterator + println!("\n=== Fallible Element Iterator Example ==="); + + let parser = + BgpkitParser::new("https://data.ris.ripe.net/rrc00/2021.11/updates.20211101.0000.gz") + .unwrap() + .disable_warnings(); + + let mut elem_count = 0; + let mut elem_errors = 0; + + for result in parser.into_fallible_elem_iter().take(100) { + match result { + Ok(elem) => { + elem_count += 1; + if elem_count <= 5 { + println!( + "Element {}: {} -> {} via {:?}", + elem_count, elem.peer_ip, elem.prefix, elem.next_hop + ); + } + } + Err(e) => { + elem_errors += 1; + println!("Error parsing element: {}", e); + } + } + } + + println!( + "\nProcessed {} elements successfully, {} errors", + elem_count, elem_errors + ); +} diff --git a/src/parser/iters.rs b/src/parser/iters/default.rs similarity index 90% rename from src/parser/iters.rs rename to src/parser/iters/default.rs index 64470b94..d9fd94d7 100644 --- a/src/parser/iters.rs +++ b/src/parser/iters/default.rs @@ -1,5 +1,5 @@ /*! -Provides parser iterator implementation. +Default iterator implementations that skip errors and return successfully parsed items. */ use crate::error::ParserError; use crate::models::*; @@ -8,25 +8,6 @@ use crate::{Elementor, Filterable}; use log::{error, warn}; use std::io::Read; -/// Use [ElemIterator] as the default iterator to return [BgpElem]s instead of [MrtRecord]s. -impl IntoIterator for BgpkitParser { - type Item = BgpElem; - type IntoIter = ElemIterator; - - fn into_iter(self) -> Self::IntoIter { - ElemIterator::new(self) - } -} - -impl BgpkitParser { - pub fn into_record_iter(self) -> RecordIterator { - RecordIterator::new(self) - } - pub fn into_elem_iter(self) -> ElemIterator { - ElemIterator::new(self) - } -} - /********* MrtRecord Iterator **********/ @@ -38,7 +19,7 @@ pub struct RecordIterator { } impl RecordIterator { - fn new(parser: BgpkitParser) -> Self { + pub(crate) fn new(parser: BgpkitParser) -> Self { RecordIterator { parser, count: 0, @@ -139,7 +120,7 @@ pub struct ElemIterator { } impl ElemIterator { - fn new(parser: BgpkitParser) -> Self { + pub(crate) fn new(parser: BgpkitParser) -> Self { ElemIterator { record_iter: RecordIterator::new(parser), count: 0, diff --git a/src/parser/iters/fallible.rs b/src/parser/iters/fallible.rs new file mode 100644 index 00000000..d5edb152 --- /dev/null +++ b/src/parser/iters/fallible.rs @@ -0,0 +1,200 @@ +/*! +Fallible iterator implementations that return Results, exposing parsing errors to users. + +These iterators complement the default iterators by returning `Result` +instead of silently skipping errors. This allows users to handle errors explicitly while +maintaining backward compatibility with existing code. +*/ +use crate::error::{ParserError, ParserErrorWithBytes}; +use crate::models::*; +use crate::parser::BgpkitParser; +use crate::{Elementor, Filterable}; +use std::io::Read; + +/// Fallible iterator over MRT records that returns parsing errors. +/// +/// Unlike the default `RecordIterator`, this iterator returns `Result` +/// allowing users to handle parsing errors explicitly instead of having them logged and skipped. +pub struct FallibleRecordIterator { + parser: BgpkitParser, + elementor: Elementor, +} + +impl FallibleRecordIterator { + pub(crate) fn new(parser: BgpkitParser) -> Self { + FallibleRecordIterator { + parser, + elementor: Elementor::new(), + } + } +} + +impl Iterator for FallibleRecordIterator { + type Item = Result; + + fn next(&mut self) -> Option { + loop { + match self.parser.next_record() { + Ok(record) => { + // Apply filters if any are set + let filters = &self.parser.filters; + if filters.is_empty() { + return Some(Ok(record)); + } + + // Special handling for PeerIndexTable - always pass through + if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_)) = + &record.message + { + let _ = self.elementor.record_to_elems(record.clone()); + return Some(Ok(record)); + } + + // Check if any elements from this record match the filters + let elems = self.elementor.record_to_elems(record.clone()); + if elems.iter().any(|e| e.match_filters(filters)) { + return Some(Ok(record)); + } + // Record doesn't match filters, continue to next + continue; + } + Err(e) if matches!(e.error, ParserError::EofExpected) => { + // Normal end of file + return None; + } + Err(e) => { + // Return the error to the user + return Some(Err(e)); + } + } + } + } +} + +/// Fallible iterator over BGP elements that returns parsing errors. +/// +/// Unlike the default `ElemIterator`, this iterator returns `Result` +/// for each successfully parsed element, and surfaces any parsing errors encountered. +pub struct FallibleElemIterator { + cache_elems: Vec, + record_iter: FallibleRecordIterator, + elementor: Elementor, +} + +impl FallibleElemIterator { + pub(crate) fn new(parser: BgpkitParser) -> Self { + FallibleElemIterator { + record_iter: FallibleRecordIterator::new(parser), + cache_elems: vec![], + elementor: Elementor::new(), + } + } +} + +impl Iterator for FallibleElemIterator { + type Item = Result; + + fn next(&mut self) -> Option { + loop { + // First check if we have cached elements + if !self.cache_elems.is_empty() { + if let Some(elem) = self.cache_elems.pop() { + if elem.match_filters(&self.record_iter.parser.filters) { + return Some(Ok(elem)); + } + // Element doesn't match filters, continue to next + continue; + } + } + + // Need to refill cache from next record + match self.record_iter.next() { + None => return None, + Some(Err(e)) => return Some(Err(e)), + Some(Ok(record)) => { + let mut elems = self.elementor.record_to_elems(record); + if elems.is_empty() { + // No elements from this record, try next + continue; + } + // Reverse to maintain order when popping + elems.reverse(); + self.cache_elems = elems; + continue; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + /// Create a test parser with mock data that will cause parsing errors + fn create_test_parser_with_errors() -> BgpkitParser>> { + // Create some invalid MRT data that will trigger parsing errors + let invalid_data = vec![ + // MRT header with invalid type + 0x00, 0x00, 0x00, 0x00, // timestamp + 0xFF, 0xFF, // invalid type + 0x00, 0x00, // subtype + 0x00, 0x00, 0x00, 0x04, // length + 0x00, 0x00, 0x00, 0x00, // dummy data + ]; + + let cursor = Cursor::new(invalid_data); + BgpkitParser::from_reader(cursor) + } + + /// Create a test parser with valid data + fn create_test_parser_with_valid_data() -> BgpkitParser>> { + // This would need actual valid MRT data - for now using empty data + // which will result in EOF + let cursor = Cursor::new(vec![]); + BgpkitParser::from_reader(cursor) + } + + #[test] + fn test_fallible_record_iterator_with_errors() { + let parser = create_test_parser_with_errors(); + let mut iter = parser.into_fallible_record_iter(); + + // First item should be an error + let result = iter.next(); + assert!(result.is_some()); + assert!(result.unwrap().is_err()); + } + + #[test] + fn test_fallible_record_iterator_eof() { + let parser = create_test_parser_with_valid_data(); + let mut iter = parser.into_fallible_record_iter(); + + // Should return None on EOF + let result = iter.next(); + assert!(result.is_none()); + } + + #[test] + fn test_fallible_elem_iterator_with_errors() { + let parser = create_test_parser_with_errors(); + let mut iter = parser.into_fallible_elem_iter(); + + // First item should be an error + let result = iter.next(); + assert!(result.is_some()); + assert!(result.unwrap().is_err()); + } + + #[test] + fn test_fallible_elem_iterator_eof() { + let parser = create_test_parser_with_valid_data(); + let mut iter = parser.into_fallible_elem_iter(); + + // Should return None on EOF + let result = iter.next(); + assert!(result.is_none()); + } +} diff --git a/src/parser/iters/mod.rs b/src/parser/iters/mod.rs new file mode 100644 index 00000000..bf4247bf --- /dev/null +++ b/src/parser/iters/mod.rs @@ -0,0 +1,87 @@ +/*! +Iterator implementations for bgpkit-parser. + +This module contains different iterator implementations for parsing BGP data: +- `default`: Standard iterators that skip errors (RecordIterator, ElemIterator) +- `fallible`: Fallible iterators that return Results (FallibleRecordIterator, FallibleElemIterator) + +It also contains the trait implementations that enable BgpkitParser to be used with +Rust's iterator syntax. +*/ + +pub mod default; +pub mod fallible; + +// Re-export all iterator types for convenience +pub use default::{ElemIterator, RecordIterator}; +pub use fallible::{FallibleElemIterator, FallibleRecordIterator}; + +use crate::models::BgpElem; +use crate::parser::BgpkitParser; +use std::io::Read; + +/// Use [ElemIterator] as the default iterator to return [BgpElem]s instead of [MrtRecord]s. +impl IntoIterator for BgpkitParser { + type Item = BgpElem; + type IntoIter = ElemIterator; + + fn into_iter(self) -> Self::IntoIter { + ElemIterator::new(self) + } +} + +impl BgpkitParser { + pub fn into_record_iter(self) -> RecordIterator { + RecordIterator::new(self) + } + + pub fn into_elem_iter(self) -> ElemIterator { + ElemIterator::new(self) + } + + /// Creates a fallible iterator over MRT records that returns parsing errors. + /// + /// # Example + /// ```no_run + /// use bgpkit_parser::BgpkitParser; + /// + /// let parser = BgpkitParser::new("updates.mrt").unwrap(); + /// for result in parser.into_fallible_record_iter() { + /// match result { + /// Ok(record) => { + /// // Process the record + /// } + /// Err(e) => { + /// // Handle the error + /// eprintln!("Error parsing record: {}", e); + /// } + /// } + /// } + /// ``` + pub fn into_fallible_record_iter(self) -> FallibleRecordIterator { + FallibleRecordIterator::new(self) + } + + /// Creates a fallible iterator over BGP elements that returns parsing errors. + /// + /// # Example + /// ```no_run + /// use bgpkit_parser::BgpkitParser; + /// + /// let parser = BgpkitParser::new("updates.mrt").unwrap(); + /// for result in parser.into_fallible_elem_iter() { + /// match result { + /// Ok(elem) => { + /// // Process the element + /// } + /// Err(e) => { + /// // Handle the error + /// eprintln!("Error parsing element: {}", e); + /// } + /// } + /// } + /// ``` + pub fn into_fallible_elem_iter(self) -> FallibleElemIterator { + FallibleElemIterator::new(self) + } +}