Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<MrtRecord, ParserErrorWithBytes>`
* implemented `FallibleElemIterator` that returns `Result<BgpElem, ParserErrorWithBytes>`
* 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
Expand Down
77 changes: 77 additions & 0 deletions examples/fallible_parsing.rs
Original file line number Diff line number Diff line change
@@ -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
);
}
25 changes: 3 additions & 22 deletions src/parser/iters.rs → src/parser/iters/default.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -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<R: Read> IntoIterator for BgpkitParser<R> {
type Item = BgpElem;
type IntoIter = ElemIterator<R>;

fn into_iter(self) -> Self::IntoIter {
ElemIterator::new(self)
}
}

impl<R> BgpkitParser<R> {
pub fn into_record_iter(self) -> RecordIterator<R> {
RecordIterator::new(self)
}
pub fn into_elem_iter(self) -> ElemIterator<R> {
ElemIterator::new(self)
}
}

/*********
MrtRecord Iterator
**********/
Expand All @@ -38,7 +19,7 @@ pub struct RecordIterator<R> {
}

impl<R> RecordIterator<R> {
fn new(parser: BgpkitParser<R>) -> Self {
pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
RecordIterator {
parser,
count: 0,
Expand Down Expand Up @@ -139,7 +120,7 @@ pub struct ElemIterator<R> {
}

impl<R> ElemIterator<R> {
fn new(parser: BgpkitParser<R>) -> Self {
pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
ElemIterator {
record_iter: RecordIterator::new(parser),
count: 0,
Expand Down
200 changes: 200 additions & 0 deletions src/parser/iters/fallible.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*!
Fallible iterator implementations that return Results, exposing parsing errors to users.

These iterators complement the default iterators by returning `Result<T, ParserErrorWithBytes>`
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<MrtRecord, ParserErrorWithBytes>`
/// allowing users to handle parsing errors explicitly instead of having them logged and skipped.
pub struct FallibleRecordIterator<R> {
parser: BgpkitParser<R>,
elementor: Elementor,
}

impl<R> FallibleRecordIterator<R> {
pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
FallibleRecordIterator {
parser,
elementor: Elementor::new(),
}
}
}

impl<R: Read> Iterator for FallibleRecordIterator<R> {
type Item = Result<MrtRecord, ParserErrorWithBytes>;

fn next(&mut self) -> Option<Self::Item> {
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<BgpElem, ParserErrorWithBytes>`
/// for each successfully parsed element, and surfaces any parsing errors encountered.
pub struct FallibleElemIterator<R> {
cache_elems: Vec<BgpElem>,
record_iter: FallibleRecordIterator<R>,
elementor: Elementor,
}

impl<R> FallibleElemIterator<R> {
pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
FallibleElemIterator {
record_iter: FallibleRecordIterator::new(parser),
cache_elems: vec![],
elementor: Elementor::new(),
}
}
}

impl<R: Read> Iterator for FallibleElemIterator<R> {
type Item = Result<BgpElem, ParserErrorWithBytes>;

fn next(&mut self) -> Option<Self::Item> {
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<Cursor<Vec<u8>>> {
// 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<Cursor<Vec<u8>>> {
// 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());
}
}
Loading
Loading