bgpkit-parser 0.11.1

MRT/BGP/BMP data processing library
Documentation
/*!
parser module maintains the main logic for processing BGP and MRT messages.
*/
use std::io::Read;

#[macro_use]
pub mod utils;
pub mod bgp;
pub mod bmp;
pub mod filter;
pub mod iters;
pub mod mrt;

#[cfg(feature = "rislive")]
pub mod rislive;

pub(crate) use self::utils::*;

use crate::models::MrtRecord;
pub use mrt::mrt_elem::Elementor;
#[cfg(feature = "oneio")]
use oneio::{get_cache_reader, get_reader};

pub use crate::error::{ParserError, ParserErrorWithBytes};
pub use bmp::{parse_bmp_msg, parse_openbmp_header, parse_openbmp_msg};
pub use filter::*;
pub use iters::*;
pub use mrt::*;

#[cfg(feature = "rislive")]
pub use rislive::parse_ris_live_message;

pub struct BgpkitParser<R> {
    reader: R,
    core_dump: bool,
    filters: Vec<Filter>,
    options: ParserOptions,
}

pub(crate) struct ParserOptions {
    show_warnings: bool,
}
impl Default for ParserOptions {
    fn default() -> Self {
        ParserOptions {
            show_warnings: true,
        }
    }
}

#[cfg(feature = "oneio")]
impl BgpkitParser<Box<dyn Read + Send>> {
    /// Creating a new parser from a object that implements [Read] trait.
    pub fn new(path: &str) -> Result<Self, ParserErrorWithBytes> {
        let reader = get_reader(path)?;
        Ok(BgpkitParser {
            reader,
            core_dump: false,
            filters: vec![],
            options: ParserOptions::default(),
        })
    }

    /// Creating a new parser that also caches the remote content to a local cache directory.
    ///
    /// The cache file name is generated by the following format: `cache-<crc32 of file name>-<file name>`.
    /// For example, the remote file `http://archive.routeviews.org/route-views.chile/bgpdata/2023.03/RIBS/rib.20230326.0600.bz2`
    /// will be cached as `cache-682cb1eb-rib.20230326.0600.bz2` in the cache directory.
    pub fn new_cached(path: &str, cache_dir: &str) -> Result<Self, ParserErrorWithBytes> {
        let file_name = path.rsplit('/').next().unwrap().to_string();
        let new_file_name = format!(
            "cache-{}",
            add_suffix_to_filename(file_name.as_str(), crc32(path).as_str())
        );
        let reader = get_cache_reader(path, cache_dir, Some(new_file_name), false)?;
        Ok(BgpkitParser {
            reader,
            core_dump: false,
            filters: vec![],
            options: ParserOptions::default(),
        })
    }
}

#[cfg(feature = "oneio")]
fn add_suffix_to_filename(filename: &str, suffix: &str) -> String {
    let mut parts: Vec<&str> = filename.split('.').collect(); // Split filename by dots
    if parts.len() > 1 {
        let last_part = parts.pop().unwrap(); // Remove the last part (suffix) from the parts vector
        let new_last_part = format!("{}.{}", suffix, last_part); // Add the suffix to the last part
        parts.push(&new_last_part); // Add the updated last part back to the parts vector
        parts.join(".") // Join the parts back into a filename string with dots
    } else {
        // If the filename does not have any dots, simply append the suffix to the end
        format!("{}.{}", filename, suffix)
    }
}

impl<R: Read> BgpkitParser<R> {
    /// Creating a new parser from an object that implements [Read] trait.
    pub fn from_reader(reader: R) -> Self {
        BgpkitParser {
            reader,
            core_dump: false,
            filters: vec![],
            options: ParserOptions::default(),
        }
    }

    /// This is used in for loop `for item in parser{}`
    pub fn next_record(&mut self) -> Result<MrtRecord, ParserErrorWithBytes> {
        parse_mrt_record(&mut self.reader)
    }
}

impl<R> BgpkitParser<R> {
    pub fn enable_core_dump(self) -> Self {
        BgpkitParser {
            reader: self.reader,
            core_dump: true,
            filters: self.filters,
            options: self.options,
        }
    }

    pub fn disable_warnings(self) -> Self {
        let mut options = self.options;
        options.show_warnings = false;
        BgpkitParser {
            reader: self.reader,
            core_dump: self.core_dump,
            filters: self.filters,
            options,
        }
    }

    pub fn add_filter(
        self,
        filter_type: &str,
        filter_value: &str,
    ) -> Result<Self, ParserErrorWithBytes> {
        let mut filters = self.filters;
        filters.push(Filter::new(filter_type, filter_value)?);
        Ok(BgpkitParser {
            reader: self.reader,
            core_dump: self.core_dump,
            filters,
            options: self.options,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_with_reader() {
        // bzip2 reader for a compressed file
        let reader = oneio::get_reader("http://archive.routeviews.org/route-views.ny/bgpdata/2023.02/UPDATES/updates.20230215.0630.bz2").unwrap();
        assert_eq!(
            12683,
            BgpkitParser::from_reader(reader).into_elem_iter().count()
        );

        // remote reader for an uncompressed updates file
        let reader = oneio::get_reader("https://spaces.bgpkit.org/parser/update-example").unwrap();
        assert_eq!(
            8160,
            BgpkitParser::from_reader(reader).into_elem_iter().count()
        );
    }

    #[test]
    fn test_new_cached_with_reader() {
        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
        let parser = BgpkitParser::new_cached(url, "/tmp/bgpkit-parser-tests")
            .unwrap()
            .enable_core_dump()
            .disable_warnings();
        let count = parser.into_elem_iter().count();
        assert_eq!(8160, count);
        let parser = BgpkitParser::new_cached(url, "/tmp/bgpkit-parser-tests").unwrap();
        let count = parser.into_elem_iter().count();
        assert_eq!(8160, count);
    }
}