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