bgpkit_parser/parser/
mod.rs

1/*!
2parser module maintains the main logic for processing BGP and MRT messages.
3*/
4use std::io::Read;
5
6#[macro_use]
7pub mod utils;
8pub mod bgp;
9pub mod bmp;
10pub mod filter;
11pub mod iters;
12pub mod mrt;
13
14#[cfg(feature = "rislive")]
15pub mod rislive;
16
17pub(crate) use self::utils::*;
18
19use crate::models::MrtRecord;
20pub use mrt::mrt_elem::Elementor;
21#[cfg(feature = "oneio")]
22use oneio::{get_cache_reader, get_reader};
23
24pub use crate::error::{ParserError, ParserErrorWithBytes};
25pub use bmp::{parse_bmp_msg, parse_openbmp_header, parse_openbmp_msg};
26pub use filter::*;
27pub use iters::*;
28pub use mrt::*;
29
30#[cfg(feature = "rislive")]
31pub use rislive::parse_ris_live_message;
32
33pub struct BgpkitParser<R> {
34    reader: R,
35    core_dump: bool,
36    filters: Vec<Filter>,
37    options: ParserOptions,
38}
39
40pub(crate) struct ParserOptions {
41    show_warnings: bool,
42}
43impl Default for ParserOptions {
44    fn default() -> Self {
45        ParserOptions {
46            show_warnings: true,
47        }
48    }
49}
50
51#[cfg(feature = "oneio")]
52impl BgpkitParser<Box<dyn Read + Send>> {
53    /// Creating a new parser from a object that implements [Read] trait.
54    pub fn new(path: &str) -> Result<Self, ParserErrorWithBytes> {
55        let reader = get_reader(path)?;
56        Ok(BgpkitParser {
57            reader,
58            core_dump: false,
59            filters: vec![],
60            options: ParserOptions::default(),
61        })
62    }
63
64    /// Creating a new parser that also caches the remote content to a local cache directory.
65    ///
66    /// The cache file name is generated by the following format: `cache-<crc32 of file name>-<file name>`.
67    /// For example, the remote file `http://archive.routeviews.org/route-views.chile/bgpdata/2023.03/RIBS/rib.20230326.0600.bz2`
68    /// will be cached as `cache-682cb1eb-rib.20230326.0600.bz2` in the cache directory.
69    pub fn new_cached(path: &str, cache_dir: &str) -> Result<Self, ParserErrorWithBytes> {
70        let file_name = path.rsplit('/').next().unwrap().to_string();
71        let new_file_name = format!(
72            "cache-{}",
73            add_suffix_to_filename(file_name.as_str(), crc32(path).as_str())
74        );
75        let reader = get_cache_reader(path, cache_dir, Some(new_file_name), false)?;
76        Ok(BgpkitParser {
77            reader,
78            core_dump: false,
79            filters: vec![],
80            options: ParserOptions::default(),
81        })
82    }
83}
84
85#[cfg(feature = "oneio")]
86fn add_suffix_to_filename(filename: &str, suffix: &str) -> String {
87    let mut parts: Vec<&str> = filename.split('.').collect(); // Split filename by dots
88    if parts.len() > 1 {
89        let last_part = parts.pop().unwrap(); // Remove the last part (suffix) from the parts vector
90        let new_last_part = format!("{}.{}", suffix, last_part); // Add the suffix to the last part
91        parts.push(&new_last_part); // Add the updated last part back to the parts vector
92        parts.join(".") // Join the parts back into a filename string with dots
93    } else {
94        // If the filename does not have any dots, simply append the suffix to the end
95        format!("{}.{}", filename, suffix)
96    }
97}
98
99impl<R: Read> BgpkitParser<R> {
100    /// Creating a new parser from an object that implements [Read] trait.
101    pub fn from_reader(reader: R) -> Self {
102        BgpkitParser {
103            reader,
104            core_dump: false,
105            filters: vec![],
106            options: ParserOptions::default(),
107        }
108    }
109
110    /// This is used in for loop `for item in parser{}`
111    pub fn next_record(&mut self) -> Result<MrtRecord, ParserErrorWithBytes> {
112        parse_mrt_record(&mut self.reader)
113    }
114}
115
116impl<R> BgpkitParser<R> {
117    pub fn enable_core_dump(self) -> Self {
118        BgpkitParser {
119            reader: self.reader,
120            core_dump: true,
121            filters: self.filters,
122            options: self.options,
123        }
124    }
125
126    pub fn disable_warnings(self) -> Self {
127        let mut options = self.options;
128        options.show_warnings = false;
129        BgpkitParser {
130            reader: self.reader,
131            core_dump: self.core_dump,
132            filters: self.filters,
133            options,
134        }
135    }
136
137    pub fn add_filter(
138        self,
139        filter_type: &str,
140        filter_value: &str,
141    ) -> Result<Self, ParserErrorWithBytes> {
142        let mut filters = self.filters;
143        filters.push(Filter::new(filter_type, filter_value)?);
144        Ok(BgpkitParser {
145            reader: self.reader,
146            core_dump: self.core_dump,
147            filters,
148            options: self.options,
149        })
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_new_with_reader() {
159        // bzip2 reader for a compressed file
160        let reader = oneio::get_reader("http://archive.routeviews.org/route-views.ny/bgpdata/2023.02/UPDATES/updates.20230215.0630.bz2").unwrap();
161        assert_eq!(
162            12683,
163            BgpkitParser::from_reader(reader).into_elem_iter().count()
164        );
165
166        // remote reader for an uncompressed updates file
167        let reader = oneio::get_reader("https://spaces.bgpkit.org/parser/update-example").unwrap();
168        assert_eq!(
169            8160,
170            BgpkitParser::from_reader(reader).into_elem_iter().count()
171        );
172    }
173
174    #[test]
175    fn test_new_cached_with_reader() {
176        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
177        let parser = BgpkitParser::new_cached(url, "/tmp/bgpkit-parser-tests")
178            .unwrap()
179            .enable_core_dump()
180            .disable_warnings();
181        let count = parser.into_elem_iter().count();
182        assert_eq!(8160, count);
183        let parser = BgpkitParser::new_cached(url, "/tmp/bgpkit-parser-tests").unwrap();
184        let count = parser.into_elem_iter().count();
185        assert_eq!(8160, count);
186    }
187}