Skip to main content

bgpkit_parser/parser/
filter.rs

1/*!
2## Message Filters
3
4The filter module defines a number of available filters that users can use, and implements
5the filtering mechanism for [BgpElem].
6
7The available filters are:
8- `origin_asn` -- origin AS number
9- `origin_asns` -- multiple origin AS numbers (OR logic)
10- `prefix` -- network prefix and match type
11- `prefixes` -- multiple network prefixes (OR logic)
12- `peer_ip` -- peer's IP address
13- `peer_ips` -- peers' IP addresses (OR logic)
14- `peer_asn` -- peer's AS number
15- `peer_asns` -- multiple peer AS numbers (OR logic)
16- `type` -- message type (`withdraw` or `announce`)
17- `ts_start` -- start and end unix timestamp
18- `as_path` -- regular expression for AS path string
19- `ip_version` -- IP version (`ipv4` or `ipv6`)
20
21### Negative Filters
22
23Most filters support negation by prefixing the filter value with `!`. For example:
24- `origin_asn=!13335` -- matches elements where origin AS is NOT 13335
25- `prefix=!10.0.0.0/8` -- matches elements where prefix is NOT 10.0.0.0/8
26- `peer_ip=!192.168.1.1` -- matches elements where peer IP is NOT 192.168.1.1
27
28For multi-value filters, you can negate all values:
29- `origin_asns=!13335,!15169` -- matches elements where origin AS is NOT 13335 AND NOT 15169
30- Mixing positive and negative values in the same filter is not allowed
31
32**Note**: Timestamp filters (`ts_start`, `ts_end`) do not support negation as the behavior would be unintuitive.
33
34[Filter::new] function takes a `str` as the filter type and `str` as the filter value and returns a
35Result of a [Filter] or a parsing error.
36
37[BgpkitParser](crate::BgpkitParser) implements the function `add_filter("filter_type", "filter_value")` that takes the parser's ownership itself
38and returns a new parser with specified filter added. See the example below.
39
40### Example
41
42```no_run
43use bgpkit_parser::BgpkitParser;
44
45/// This example shows how to parse a MRT file and filter by prefix.
46env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
47
48log::info!("downloading updates file");
49let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
50    .add_filter("prefix", "211.98.251.0/24").unwrap()
51    .add_filter("type", "a").unwrap();
52
53// iterating through the parser. the iterator returns `BgpElem` one at a time.
54log::info!("parsing updates file");
55for elem in parser {
56    log::info!("{}", &elem);
57}
58log::info!("done");
59```
60
61### Example with Negative Filter
62
63```no_run
64use bgpkit_parser::BgpkitParser;
65
66// Filter out all elements from AS 13335 (Cloudflare)
67let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
68    .add_filter("origin_asn", "!13335").unwrap();
69
70for elem in parser {
71    println!("{}", elem);
72}
73```
74
75### Example with Multiple Filters (OR Logic)
76
77```no_run
78use bgpkit_parser::BgpkitParser;
79
80// Filter elements from multiple origin ASNs (matches ANY of the specified ASNs)
81let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
82    .add_filter("origin_asns", "13335,15169,8075").unwrap();
83
84for elem in parser {
85    println!("{}", elem);
86}
87
88// Filter elements NOT from these ASNs (matches if NOT ANY of the specified ASNs)
89let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
90    .add_filter("origin_asns", "!13335,!15169,!8075").unwrap();
91
92for elem in parser {
93    println!("{}", elem);
94}
95
96// Filter elements matching multiple prefixes (matches ANY of the specified prefixes)
97let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
98    .add_filter("prefixes", "1.1.1.0/24,8.8.8.0/24").unwrap();
99
100for elem in parser {
101    println!("{}", elem);
102}
103```
104
105Note, by default, the prefix filtering is for the exact prefix. You can include super-prefixes or
106sub-prefixes when filtering by using `"prefix_super"`, `"prefix_sub"`, or  `"prefix_super_sub"` as
107the filter type string. For multiple prefixes, use `"prefixes_super"`, `"prefixes_sub"`, or `"prefixes_super_sub"`.
108
109### Note
110
111Currently, only [BgpElem] implements the filtering capability. Support for [MrtRecord] will come in
112later releases.
113
114*/
115use crate::models::*;
116use crate::parser::ComparableRegex;
117use crate::ParserError;
118use crate::ParserError::FilterError;
119use ipnet::IpNet;
120use std::net::IpAddr;
121use std::str::FromStr;
122
123/// Filter enum: definition of types of filters
124///
125/// The available filters are (`filter_type` (`FilterType`) -- definition):
126/// - `origin_asn` (`OriginAsn(u32)`) -- origin AS number
127/// - `origin_asns` (`OriginAsns(Vec<u32>)`) -- multiple origin AS numbers (OR logic)
128/// - `prefix(_super, _sub, _super_sub)` (`Prefix(IpNet, PrefixMatchType)`) -- network prefix and match type
129/// - `prefixes(_super, _sub, _super_sub)` (`Prefixes(Vec<IpNet>, PrefixMatchType)`) -- multiple network prefixes (OR logic)
130/// - `peer_ip` (`PeerIp(IpAddr)`) -- peer's IP address
131/// - `peer_ips` (`PeerIps(Vec<IpAddr>)`) -- peers' IP addresses (OR logic)
132/// - `peer_asn` (`PeerAsn(u32)`) -- peer's AS number
133/// - `peer_asns` (`PeerAsns(Vec<u32>)`) -- multiple peer AS numbers (OR logic)
134/// - `type` (`Type(ElemType)`) -- message type (`withdraw` or `announce`)
135/// - `ts_start` (`TsStart(f64)`) and `ts_end` (`TsEnd(f64)`) -- start and end unix timestamp
136/// - `as_path` (`ComparableRegex`) -- regular expression for AS path string
137/// - `community` (`ComparableRegex`) -- regular expression for community string
138/// - `ip_version` (`IpVersion`) -- IP version (`ipv4` or `ipv6`)
139///
140/// **Negative filters**: Most filters support negation by prefixing the filter value with `!`.
141/// For example, `origin_asn=!13335` matches elements where origin AS is NOT 13335.
142/// This creates a `Negated(Box<Filter>)` variant that inverts the match result.
143#[derive(Debug, Clone, PartialEq)]
144pub enum Filter {
145    OriginAsn(u32),
146    OriginAsns(Vec<u32>),
147    Prefix(IpNet, PrefixMatchType),
148    Prefixes(Vec<IpNet>, PrefixMatchType),
149    PeerIp(IpAddr),
150    PeerIps(Vec<IpAddr>),
151    PeerAsn(u32),
152    PeerAsns(Vec<u32>),
153    Type(ElemType),
154    IpVersion(IpVersion),
155    TsStart(f64),
156    TsEnd(f64),
157    AsPath(ComparableRegex),
158    Community(ComparableRegex),
159    /// Negated filter - matches when the inner filter does NOT match
160    Negated(Box<Filter>),
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub enum IpVersion {
165    Ipv4,
166    Ipv6,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum PrefixMatchType {
171    Exact,
172    IncludeSuper,
173    IncludeSub,
174    IncludeSuperSub,
175}
176
177fn parse_time_str(time_str: &str) -> Option<chrono::NaiveDateTime> {
178    if let Ok(t) = time_str.parse::<f64>() {
179        return chrono::DateTime::from_timestamp(t as i64, 0).map(|t| t.naive_utc());
180    }
181    if let Ok(t) = chrono::DateTime::parse_from_rfc3339(time_str) {
182        return Some(t.naive_utc());
183    }
184    None
185}
186
187fn parse_asn_list(filter_value: &str) -> Result<(Vec<u32>, bool), ParserError> {
188    let mut asns = vec![];
189    let mut all_negated: Option<bool> = None;
190
191    for asn_str in filter_value.replace(' ', "").split(',') {
192        // Skip empty strings (from consecutive or trailing commas)
193        if asn_str.is_empty() {
194            continue;
195        }
196
197        let (is_negated, actual_value) = if let Some(stripped) = asn_str.strip_prefix('!') {
198            (true, stripped)
199        } else {
200            (false, asn_str)
201        };
202
203        // Check for mixed positive/negative values
204        match all_negated {
205            None => all_negated = Some(is_negated),
206            Some(prev) if prev != is_negated => {
207                return Err(FilterError(
208                    "cannot mix positive and negative values in the same filter".to_string(),
209                ));
210            }
211            _ => {}
212        }
213
214        match u32::from_str(actual_value) {
215            Ok(v) => asns.push(v),
216            Err(_) => return Err(FilterError(format!("cannot parse ASN from {actual_value}"))),
217        }
218    }
219    // Validate that at least one ASN was provided
220    if asns.is_empty() {
221        return Err(FilterError(
222            "ASN list filter requires at least one ASN".to_string(),
223        ));
224    }
225    Ok((asns, all_negated.unwrap_or(false)))
226}
227
228fn parse_prefix_list(filter_value: &str) -> Result<(Vec<IpNet>, bool), ParserError> {
229    let mut prefixes = vec![];
230    let mut all_negated: Option<bool> = None;
231
232    for prefix_str in filter_value.replace(' ', "").split(',') {
233        // Skip empty strings (from consecutive or trailing commas)
234        if prefix_str.is_empty() {
235            continue;
236        }
237
238        let (is_negated, actual_value) = if let Some(stripped) = prefix_str.strip_prefix('!') {
239            (true, stripped)
240        } else {
241            (false, prefix_str)
242        };
243
244        // Check for mixed positive/negative values
245        match all_negated {
246            None => all_negated = Some(is_negated),
247            Some(prev) if prev != is_negated => {
248                return Err(FilterError(
249                    "cannot mix positive and negative values in the same filter".to_string(),
250                ));
251            }
252            _ => {}
253        }
254
255        match IpNet::from_str(actual_value) {
256            Ok(v) => prefixes.push(v),
257            Err(_) => {
258                return Err(FilterError(format!(
259                    "cannot parse prefix from {actual_value}"
260                )))
261            }
262        }
263    }
264    // Validate that at least one prefix was provided
265    if prefixes.is_empty() {
266        return Err(FilterError(
267            "prefix list filter requires at least one prefix".to_string(),
268        ));
269    }
270    Ok((prefixes, all_negated.unwrap_or(false)))
271}
272
273fn parse_ip_list(filter_value: &str) -> Result<(Vec<IpAddr>, bool), ParserError> {
274    let mut ips = vec![];
275    let mut all_negated: Option<bool> = None;
276
277    for ip_str in filter_value.replace(' ', "").split(',') {
278        // Skip empty strings (from consecutive or trailing commas)
279        if ip_str.is_empty() {
280            continue;
281        }
282
283        let (is_negated, actual_value) = if let Some(stripped) = ip_str.strip_prefix('!') {
284            (true, stripped)
285        } else {
286            (false, ip_str)
287        };
288
289        // Check for mixed positive/negative values
290        match all_negated {
291            None => all_negated = Some(is_negated),
292            Some(prev) if prev != is_negated => {
293                return Err(FilterError(
294                    "cannot mix positive and negative values in the same filter".to_string(),
295                ));
296            }
297            _ => {}
298        }
299
300        match IpAddr::from_str(actual_value) {
301            Ok(v) => ips.push(v),
302            Err(_) => {
303                return Err(FilterError(format!(
304                    "cannot parse IP address from {actual_value}"
305                )))
306            }
307        }
308    }
309    // Validate that at least one IP was provided
310    if ips.is_empty() {
311        return Err(FilterError(
312            "IP list filter requires at least one IP address".to_string(),
313        ));
314    }
315    Ok((ips, all_negated.unwrap_or(false)))
316}
317
318impl Filter {
319    pub fn new(filter_type: &str, filter_value: &str) -> Result<Filter, ParserError> {
320        // Multi-value filters handle their own negation detection internally
321        // (each value can be prefixed with !, and all must be consistent)
322        let multi_value_filters = [
323            "origin_asns",
324            "prefixes",
325            "prefixes_super",
326            "prefixes_sub",
327            "prefixes_super_sub",
328            "peer_ips",
329            "peer_asns",
330        ];
331
332        if multi_value_filters.contains(&filter_type) {
333            // Pass directly to new_base - it handles negation internally
334            return Self::new_base(filter_type, filter_value);
335        }
336
337        // For single-value filters, check for negation in filter_value (e.g., origin_asn=!13335)
338        let (negated, actual_value) = if let Some(stripped) = filter_value.strip_prefix('!') {
339            // Reject double negation (e.g., "!!13335")
340            if stripped.starts_with('!') {
341                return Err(FilterError(format!(
342                    "invalid filter value '{}': double negation is not allowed",
343                    filter_value
344                )));
345            }
346            (true, stripped)
347        } else {
348            (false, filter_value)
349        };
350
351        // Reject negation for timestamp filters (unintuitive behavior)
352        if negated
353            && (filter_type == "ts_start"
354                || filter_type == "start_ts"
355                || filter_type == "ts_end"
356                || filter_type == "end_ts")
357        {
358            return Err(FilterError(format!(
359                "timestamp filter '{}' does not support negation",
360                filter_type
361            )));
362        }
363
364        let base_filter = Self::new_base(filter_type, actual_value)?;
365
366        if negated {
367            Ok(Filter::Negated(Box::new(base_filter)))
368        } else {
369            Ok(base_filter)
370        }
371    }
372
373    fn new_base(filter_type: &str, filter_value: &str) -> Result<Filter, ParserError> {
374        match filter_type {
375            "origin_asn" => match u32::from_str(filter_value) {
376                Ok(v) => Ok(Filter::OriginAsn(v)),
377                Err(_) => Err(FilterError(format!(
378                    "cannot parse origin asn from {filter_value}"
379                ))),
380            },
381            "origin_asns" => {
382                let (asns, negated) = parse_asn_list(filter_value)?;
383                let filter = Filter::OriginAsns(asns);
384                if negated {
385                    Ok(Filter::Negated(Box::new(filter)))
386                } else {
387                    Ok(filter)
388                }
389            }
390            "prefix" => match IpNet::from_str(filter_value) {
391                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::Exact)),
392                Err(_) => Err(FilterError(format!(
393                    "cannot parse prefix from {filter_value}"
394                ))),
395            },
396            "prefix_super" => match IpNet::from_str(filter_value) {
397                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuper)),
398                Err(_) => Err(FilterError(format!(
399                    "cannot parse prefix from {filter_value}"
400                ))),
401            },
402            "prefix_sub" => match IpNet::from_str(filter_value) {
403                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSub)),
404                Err(_) => Err(FilterError(format!(
405                    "cannot parse prefix from {filter_value}"
406                ))),
407            },
408            "prefix_super_sub" => match IpNet::from_str(filter_value) {
409                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuperSub)),
410                Err(_) => Err(FilterError(format!(
411                    "cannot parse prefix from {filter_value}"
412                ))),
413            },
414            "prefixes" => {
415                let (prefixes, negated) = parse_prefix_list(filter_value)?;
416                let filter = Filter::Prefixes(prefixes, PrefixMatchType::Exact);
417                if negated {
418                    Ok(Filter::Negated(Box::new(filter)))
419                } else {
420                    Ok(filter)
421                }
422            }
423            "prefixes_super" => {
424                let (prefixes, negated) = parse_prefix_list(filter_value)?;
425                let filter = Filter::Prefixes(prefixes, PrefixMatchType::IncludeSuper);
426                if negated {
427                    Ok(Filter::Negated(Box::new(filter)))
428                } else {
429                    Ok(filter)
430                }
431            }
432            "prefixes_sub" => {
433                let (prefixes, negated) = parse_prefix_list(filter_value)?;
434                let filter = Filter::Prefixes(prefixes, PrefixMatchType::IncludeSub);
435                if negated {
436                    Ok(Filter::Negated(Box::new(filter)))
437                } else {
438                    Ok(filter)
439                }
440            }
441            "prefixes_super_sub" => {
442                let (prefixes, negated) = parse_prefix_list(filter_value)?;
443                let filter = Filter::Prefixes(prefixes, PrefixMatchType::IncludeSuperSub);
444                if negated {
445                    Ok(Filter::Negated(Box::new(filter)))
446                } else {
447                    Ok(filter)
448                }
449            }
450            "peer_ip" => match IpAddr::from_str(filter_value) {
451                Ok(v) => Ok(Filter::PeerIp(v)),
452                Err(_) => Err(FilterError(format!(
453                    "cannot parse peer IP from {filter_value}"
454                ))),
455            },
456            "peer_ips" => {
457                let (ips, negated) = parse_ip_list(filter_value)?;
458                let filter = Filter::PeerIps(ips);
459                if negated {
460                    Ok(Filter::Negated(Box::new(filter)))
461                } else {
462                    Ok(filter)
463                }
464            }
465            "peer_asn" => match u32::from_str(filter_value) {
466                Ok(v) => Ok(Filter::PeerAsn(v)),
467                Err(_) => Err(FilterError(format!(
468                    "cannot parse peer asn from {filter_value}"
469                ))),
470            },
471            "peer_asns" => {
472                let (asns, negated) = parse_asn_list(filter_value)?;
473                let filter = Filter::PeerAsns(asns);
474                if negated {
475                    Ok(Filter::Negated(Box::new(filter)))
476                } else {
477                    Ok(filter)
478                }
479            }
480            "type" => match filter_value {
481                "w" | "withdraw" | "withdrawal" => Ok(Filter::Type(ElemType::WITHDRAW)),
482                "a" | "announce" | "announcement" => Ok(Filter::Type(ElemType::ANNOUNCE)),
483                _ => Err(FilterError(format!(
484                    "cannot parse elem type from {filter_value}"
485                ))),
486            },
487            "ts_start" | "start_ts" => match parse_time_str(filter_value) {
488                Some(t) => Ok(Filter::TsStart(t.and_utc().timestamp() as f64)),
489                None => Err(FilterError(format!(
490                    "cannot parse TsStart filter from {filter_value}"
491                ))),
492            },
493            "ts_end" | "end_ts" => match parse_time_str(filter_value) {
494                Some(t) => Ok(Filter::TsEnd(t.and_utc().timestamp() as f64)),
495                None => Err(FilterError(format!(
496                    "cannot parse TsEnd filter from {filter_value}"
497                ))),
498            },
499            "as_path" => match ComparableRegex::new(filter_value) {
500                Ok(v) => Ok(Filter::AsPath(v)),
501                Err(_) => Err(FilterError(format!(
502                    "cannot parse AS path regex from {filter_value}"
503                ))),
504            },
505            "community" => match ComparableRegex::new(filter_value) {
506                Ok(v) => Ok(Filter::Community(v)),
507                Err(_) => Err(FilterError(format!(
508                    "cannot parse Community regex from {filter_value}"
509                ))),
510            },
511            "ip_version" | "ip" => match filter_value {
512                "4" | "v4" | "ipv4" => Ok(Filter::IpVersion(IpVersion::Ipv4)),
513                "6" | "v6" | "ipv6" => Ok(Filter::IpVersion(IpVersion::Ipv6)),
514                _ => Err(FilterError(format!(
515                    "cannot parse IP version from {filter_value}"
516                ))),
517            },
518            _ => Err(FilterError(format!("unknown filter type: {filter_type}"))),
519        }
520    }
521}
522
523pub trait Filterable {
524    fn match_filter(&self, filter: &Filter) -> bool;
525    fn match_filters(&self, filters: &[Filter]) -> bool;
526}
527
528const fn same_family(prefix_1: &IpNet, prefix_2: &IpNet) -> bool {
529    matches!(
530        (prefix_1, prefix_2),
531        (IpNet::V4(_), IpNet::V4(_)) | (IpNet::V6(_), IpNet::V6(_))
532    )
533}
534
535fn prefix_match(match_prefix: &IpNet, input_prefix: &IpNet, t: &PrefixMatchType) -> bool {
536    let exact = input_prefix.eq(match_prefix);
537    match t {
538        PrefixMatchType::Exact => exact,
539        PrefixMatchType::IncludeSuper => {
540            if exact {
541                exact
542            } else if !same_family(match_prefix, input_prefix) {
543                // version not match
544                false
545            } else {
546                // input_prefix is super prefix of match_prefix
547                match_prefix.addr() >= input_prefix.addr()
548                    && match_prefix.broadcast() <= input_prefix.broadcast()
549            }
550        }
551        PrefixMatchType::IncludeSub => {
552            if exact {
553                exact
554            } else if !same_family(match_prefix, input_prefix) {
555                // version not match
556                false
557            } else {
558                // input_prefix is sub prefix of match_prefix
559                match_prefix.addr() <= input_prefix.addr()
560                    && match_prefix.broadcast() >= input_prefix.broadcast()
561            }
562        }
563        PrefixMatchType::IncludeSuperSub => {
564            if exact {
565                exact
566            } else if !same_family(match_prefix, input_prefix) {
567                // version not match
568                false
569            } else {
570                // input_prefix is super prefix of match_prefix
571                (match_prefix.addr() >= input_prefix.addr()
572                    && match_prefix.broadcast() <= input_prefix.broadcast())
573                    || (match_prefix.addr() <= input_prefix.addr()
574                        && match_prefix.broadcast() >= input_prefix.broadcast())
575            }
576        }
577    }
578}
579
580impl Filterable for BgpElem {
581    fn match_filter(&self, filter: &Filter) -> bool {
582        match filter {
583            Filter::Negated(inner) => !self.match_filter(inner),
584            Filter::OriginAsn(v) => {
585                let asn: Asn = (*v).into();
586                if let Some(origins) = &self.origin_asns {
587                    origins.contains(&asn)
588                } else {
589                    false
590                }
591            }
592            Filter::OriginAsns(v) => {
593                if let Some(origins) = &self.origin_asns {
594                    v.iter().any(|asn| {
595                        let asn_obj: Asn = (*asn).into();
596                        origins.contains(&asn_obj)
597                    })
598                } else {
599                    false
600                }
601            }
602            Filter::Prefix(v, t) => prefix_match(v, &self.prefix.prefix, t),
603            Filter::Prefixes(v, t) => v
604                .iter()
605                .any(|prefix| prefix_match(prefix, &self.prefix.prefix, t)),
606            Filter::PeerIp(v) => self.peer_ip == *v,
607            Filter::PeerIps(v) => v.contains(&self.peer_ip),
608            Filter::PeerAsn(v) => self.peer_asn.eq(v),
609            Filter::PeerAsns(v) => v.iter().any(|asn| self.peer_asn.eq(asn)),
610            Filter::Type(v) => self.elem_type.eq(v),
611            Filter::TsStart(v) => self.timestamp >= *v,
612            Filter::TsEnd(v) => self.timestamp <= *v,
613            Filter::AsPath(v) => {
614                if let Some(path) = &self.as_path {
615                    v.is_match(path.to_string().as_str())
616                } else {
617                    false
618                }
619            }
620            Filter::Community(r) => {
621                if let Some(communities) = &self.communities {
622                    communities.iter().any(|c| r.is_match(c.to_string()))
623                } else {
624                    false
625                }
626            }
627            Filter::IpVersion(version) => match version {
628                IpVersion::Ipv4 => self.prefix.prefix.addr().is_ipv4(),
629                IpVersion::Ipv6 => self.prefix.prefix.addr().is_ipv6(),
630            },
631        }
632    }
633
634    fn match_filters(&self, filters: &[Filter]) -> bool {
635        filters.iter().all(|f| self.match_filter(f))
636    }
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642    use crate::BgpkitParser;
643    use anyhow::Result;
644    use std::str::FromStr;
645
646    #[test]
647    fn test_filters_on_mrt_file() {
648        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
649        let parser = BgpkitParser::new(url).unwrap();
650        let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
651
652        let filters = vec![Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap())];
653        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
654        assert_eq!(count, 3393);
655
656        let filters = vec![
657            Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap()),
658            Filter::Prefix(
659                IpNet::from_str("190.115.192.0/22").unwrap(),
660                PrefixMatchType::Exact,
661            ),
662        ];
663        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
664        assert_eq!(count, 5);
665
666        let filters = vec![Filter::Prefix(
667            IpNet::from_str("190.115.192.0/24").unwrap(),
668            PrefixMatchType::IncludeSuper,
669        )];
670        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
671        assert_eq!(count, 18);
672
673        let filters = vec![Filter::Prefix(
674            IpNet::from_str("190.115.192.0/22").unwrap(),
675            PrefixMatchType::IncludeSub,
676        )];
677        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
678        assert_eq!(count, 42);
679
680        let filters = vec![Filter::Prefix(
681            IpNet::from_str("190.115.192.0/23").unwrap(),
682            PrefixMatchType::IncludeSuperSub,
683        )];
684        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
685        assert_eq!(count, 24);
686
687        let filters = vec![Filter::new("as_path", r" ?174 1916 52888$").unwrap()];
688        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
689        assert_eq!(count, 12);
690
691        // filter by community starting with some value
692        let filters = vec![Filter::new("community", r"60924:.*").unwrap()];
693        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
694        assert_eq!(count, 4243);
695
696        // filter by community ending with some value
697        let filters = vec![Filter::new("community", r".+:784$").unwrap()];
698        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
699        assert_eq!(count, 107);
700
701        // filter by community with large community (i.e. with 3 values, separated by ':')
702        let filters = vec![Filter::new("community", r"\d+:\d+:\d+$").unwrap()];
703        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
704        assert_eq!(count, 4397);
705
706        let filters = vec![
707            Filter::TsStart(1637437798_f64),
708            Filter::TsEnd(1637437798_f64),
709        ];
710        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
711        assert_eq!(count, 13);
712
713        let filters = vec![Filter::Type(ElemType::WITHDRAW)];
714        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
715        assert_eq!(count, 379);
716
717        let filters = vec![
718            Filter::Type(ElemType::WITHDRAW),
719            Filter::Prefix(
720                IpNet::from_str("2804:100::/32").unwrap(),
721                PrefixMatchType::Exact,
722            ),
723        ];
724        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
725        assert_eq!(count, 1);
726
727        // test filtering by multiple peers
728        /*
729        1167 185.1.8.3
730        1563 185.1.8.50
731        3393 185.1.8.65
732          51 185.1.8.89
733         834 2001:7f8:73:0:3:fa4:0:1
734          94 2001:7f8:73::c2a8:0:1
735        1058 2001:7f8:73::edfc:0:2
736         */
737        let filters = vec![Filter::PeerIps(vec![
738            IpAddr::from_str("185.1.8.65").unwrap(),
739            IpAddr::from_str("2001:7f8:73:0:3:fa4:0:1").unwrap(),
740        ])];
741        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
742        assert_eq!(count, 3393 + 834);
743    }
744
745    #[test]
746    fn test_filter_incorrect_filters() {
747        // filter by community with large community (i.e. with 3 values, separated by ':')
748        let incorrect_filters = [
749            Filter::new("community", r"[abc"),
750            Filter::new("as_path", r"[0-9"),
751            Filter::new("prefix_super_sub", "-192.-168.-1.1/24"),
752        ];
753        assert!(incorrect_filters
754            .iter()
755            .all(|f| matches!(f, Err(FilterError(_)))));
756    }
757
758    #[test]
759    fn test_parsing_time_str() {
760        let ts = chrono::NaiveDateTime::from_str("2021-11-20T19:49:58").unwrap();
761        assert_eq!(parse_time_str("1637437798"), Some(ts));
762        assert_eq!(parse_time_str("2021-11-20T19:49:58Z"), Some(ts));
763        assert_eq!(parse_time_str("2021-11-20T19:49:58+00:00"), Some(ts));
764
765        assert_eq!(parse_time_str("2021-11-20T19:49:58"), None);
766        assert_eq!(parse_time_str("2021-11-20T19:49:58ZDXV"), None);
767        assert_eq!(parse_time_str("2021-11-20 19:49:58"), None);
768        assert_eq!(parse_time_str("2021-11-20"), None);
769    }
770
771    #[test]
772    fn test_filter_iter() -> Result<()> {
773        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
774        let parser = BgpkitParser::new(url)?
775            .add_filter("peer_ip", "185.1.8.50")?
776            .add_filter("type", "w")?;
777        let count = parser.into_elem_iter().count();
778        assert_eq!(count, 39);
779
780        let parser = BgpkitParser::new(url)?
781            .add_filter("ts_start", "1637437798")?
782            .add_filter("ts_end", "2021-11-20T19:49:58Z")?;
783        let count = parser.into_elem_iter().count();
784        assert_eq!(count, 13);
785        Ok(())
786    }
787
788    #[test]
789    fn test_filter_iter_with_negation() -> Result<()> {
790        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
791
792        // Test negative filter with add_filter - exclude peer 185.1.8.65
793        // From test_filters_on_mrt_file, peer 185.1.8.65 has 3393 elements out of 8160 total
794        let parser = BgpkitParser::new(url)?.add_filter("peer_ip", "!185.1.8.65")?;
795        let count = parser.into_elem_iter().count();
796        assert_eq!(count, 8160 - 3393);
797
798        // Test negative type filter - get all non-withdrawals
799        // From test_filters_on_mrt_file, there are 379 withdrawals out of 8160 total
800        let parser = BgpkitParser::new(url)?.add_filter("type", "!w")?;
801        let count = parser.into_elem_iter().count();
802        assert_eq!(count, 8160 - 379);
803
804        // Test combining positive and negative filters
805        // Get elements from peer 185.1.8.50 that are NOT withdrawals
806        let parser = BgpkitParser::new(url)?
807            .add_filter("peer_ip", "185.1.8.50")?
808            .add_filter("type", "!w")?;
809        let count = parser.into_elem_iter().count();
810        // peer 185.1.8.50 has 1563 total, 39 withdrawals -> 1563 - 39 = 1524 non-withdrawals
811        assert_eq!(count, 1563 - 39);
812
813        Ok(())
814    }
815
816    #[test]
817    fn test_filter_iter_multi_peers() {
818        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
819        let parser = BgpkitParser::new(url)
820            .unwrap()
821            .add_filter("peer_ips", "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1")
822            .unwrap();
823        let count = parser.into_elem_iter().count();
824        assert_eq!(count, 3393 + 834);
825    }
826
827    #[test]
828    fn test_prefix_match() {
829        // network
830        let p1 = IpNet::from_str("10.1.1.0/24").unwrap();
831        let p1_exact = IpNet::from_str("10.1.1.0/24").unwrap();
832        let p1_super = IpNet::from_str("10.1.0.0/16").unwrap();
833        let p1_sub = IpNet::from_str("10.1.1.0/25").unwrap();
834
835        let p2 = IpNet::from_str("2001:0DB8:0000:000b::/64").unwrap();
836
837        // exact
838        assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::Exact));
839        assert!(!prefix_match(&p1, &p1_sub, &PrefixMatchType::Exact));
840        assert!(!prefix_match(&p1, &p1_super, &PrefixMatchType::Exact));
841        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::Exact));
842
843        // include super
844        assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSuper));
845        assert!(!prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSuper));
846        assert!(prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSuper));
847        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuper));
848
849        // include sub
850        assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSub));
851        assert!(prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSub));
852        assert!(!prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSub));
853        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSub));
854
855        // include both
856        assert!(prefix_match(
857            &p1,
858            &p1_exact,
859            &PrefixMatchType::IncludeSuperSub
860        ));
861        assert!(prefix_match(
862            &p1,
863            &p1_sub,
864            &PrefixMatchType::IncludeSuperSub
865        ));
866        assert!(prefix_match(
867            &p1,
868            &p1_super,
869            &PrefixMatchType::IncludeSuperSub
870        ));
871        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuperSub));
872    }
873
874    #[test]
875    fn test_filter_new() {
876        let filter = Filter::new("origin_asn", "12345").unwrap();
877        assert_eq!(filter, Filter::OriginAsn(12345));
878
879        // Test negated filters (value-based negation syntax)
880        let filter = Filter::new("origin_asn", "!12345").unwrap();
881        assert_eq!(filter, Filter::Negated(Box::new(Filter::OriginAsn(12345))));
882
883        let filter = Filter::new("prefix", "!192.168.1.0/24").unwrap();
884        assert_eq!(
885            filter,
886            Filter::Negated(Box::new(Filter::Prefix(
887                IpNet::from_str("192.168.1.0/24").unwrap(),
888                PrefixMatchType::Exact
889            )))
890        );
891
892        let filter = Filter::new("peer_ip", "!192.168.1.1").unwrap();
893        assert_eq!(
894            filter,
895            Filter::Negated(Box::new(Filter::PeerIp(
896                IpAddr::from_str("192.168.1.1").unwrap()
897            )))
898        );
899
900        let filter = Filter::new("peer_asn", "!12345").unwrap();
901        assert_eq!(filter, Filter::Negated(Box::new(Filter::PeerAsn(12345))));
902
903        let filter = Filter::new("type", "!w").unwrap();
904        assert_eq!(
905            filter,
906            Filter::Negated(Box::new(Filter::Type(ElemType::WITHDRAW)))
907        );
908
909        let filter = Filter::new("ip_version", "!4").unwrap();
910        assert_eq!(
911            filter,
912            Filter::Negated(Box::new(Filter::IpVersion(IpVersion::Ipv4)))
913        );
914
915        let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
916        assert_eq!(
917            filter,
918            Filter::Prefix(
919                IpNet::from_str("192.168.1.0/24").unwrap(),
920                PrefixMatchType::Exact
921            )
922        );
923        let filter = Filter::new("prefix_super", "192.168.1.0/24").unwrap();
924        assert_eq!(
925            filter,
926            Filter::Prefix(
927                IpNet::from_str("192.168.1.0/24").unwrap(),
928                PrefixMatchType::IncludeSuper
929            )
930        );
931        let filter = Filter::new("prefix_sub", "192.168.1.0/24").unwrap();
932        assert_eq!(
933            filter,
934            Filter::Prefix(
935                IpNet::from_str("192.168.1.0/24").unwrap(),
936                PrefixMatchType::IncludeSub
937            )
938        );
939        let filter = Filter::new("prefix_super_sub", "192.168.1.0/24").unwrap();
940        assert_eq!(
941            filter,
942            Filter::Prefix(
943                IpNet::from_str("192.168.1.0/24").unwrap(),
944                PrefixMatchType::IncludeSuperSub
945            )
946        );
947
948        let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
949        assert_eq!(
950            filter,
951            Filter::PeerIp(IpAddr::from_str("192.168.1.1").unwrap())
952        );
953
954        let filter = Filter::new("peer_asn", "12345").unwrap();
955        assert_eq!(filter, Filter::PeerAsn(12345));
956
957        let filter = Filter::new("type", "w").unwrap();
958        assert_eq!(filter, Filter::Type(ElemType::WITHDRAW));
959
960        let filter = Filter::new("ts_start", "1637437798").unwrap();
961        assert_eq!(filter, Filter::TsStart(1637437798_f64));
962
963        let filter = Filter::new("ts_end", "1637437798").unwrap();
964        assert_eq!(filter, Filter::TsEnd(1637437798_f64));
965
966        let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
967        assert_eq!(
968            filter,
969            Filter::AsPath(ComparableRegex::new(r" ?174 1916 52888$").unwrap())
970        );
971
972        assert!(Filter::new("origin_asn", "not a number").is_err());
973        assert!(Filter::new("peer_asn", "not a number").is_err());
974        assert!(Filter::new("ts_start", "not a number").is_err());
975        assert!(Filter::new("ts_end", "not a number").is_err());
976        assert!(Filter::new("prefix", "not a prefix").is_err());
977        assert!(Filter::new("prefix_super", "not a prefix").is_err());
978        assert!(Filter::new("prefix_sub", "not a prefix").is_err());
979        assert!(Filter::new("peer_ip", "not a IP").is_err());
980        assert!(Filter::new("peer_ips", "not,a,IP").is_err());
981        assert!(Filter::new("type", "not a type").is_err());
982        assert!(Filter::new("as_path", "[abc").is_err());
983        assert!(Filter::new("ip_version", "5").is_err());
984        assert!(Filter::new("unknown_filter", "some_value").is_err());
985    }
986
987    #[test]
988    fn test_filterable_match_filter() {
989        let elem = BgpElem {
990            timestamp: 1637437798_f64,
991            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
992            peer_asn: Asn::new_32bit(12345),
993            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
994            next_hop: None,
995            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
996            origin_asns: Some(vec![Asn::new_16bit(12345)]),
997            origin: None,
998            local_pref: None,
999            med: None,
1000            communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
1001                12345,
1002                [678910, 111213],
1003            ))]),
1004            atomic: false,
1005            aggr_asn: None,
1006            aggr_ip: None,
1007            only_to_customer: None,
1008            unknown: None,
1009            elem_type: ElemType::ANNOUNCE,
1010            deprecated: None,
1011        };
1012
1013        let mut filters = vec![];
1014
1015        let filter = Filter::new("origin_asn", "12345").unwrap();
1016        filters.push(filter.clone());
1017        assert!(elem.match_filter(&filter));
1018
1019        let filter = Filter::new("origin_asn", "678910").unwrap();
1020        assert!(!elem.match_filter(&filter));
1021
1022        let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
1023        filters.push(filter.clone());
1024        assert!(elem.match_filter(&filter));
1025
1026        let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
1027        filters.push(filter.clone());
1028        assert!(elem.match_filter(&filter));
1029
1030        let filter = Filter::new("peer_asn", "12345").unwrap();
1031        filters.push(filter.clone());
1032        assert!(elem.match_filter(&filter));
1033
1034        let filter = Filter::new("type", "a").unwrap();
1035        filters.push(filter.clone());
1036        assert!(elem.match_filter(&filter));
1037
1038        let filter = Filter::new("ts_start", "1637437798").unwrap();
1039        filters.push(filter.clone());
1040        assert!(elem.match_filter(&filter));
1041
1042        let filter = Filter::new("ts_end", "1637437798").unwrap();
1043        filters.push(filter.clone());
1044        assert!(elem.match_filter(&filter));
1045
1046        let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
1047        filters.push(filter.clone());
1048        assert!(elem.match_filter(&filter));
1049
1050        let filter = Filter::new("ip_version", "4").unwrap();
1051        filters.push(filter.clone());
1052        assert!(elem.match_filter(&filter));
1053
1054        let filter = Filter::new("ip", "ipv6").unwrap();
1055        assert!(!elem.match_filter(&filter));
1056
1057        let filter = Filter::new("community", r"12345:678910:111213$").unwrap();
1058        filters.push(filter.clone());
1059        assert!(elem.match_filter(&filter));
1060
1061        assert!(elem.match_filters(&filters));
1062    }
1063
1064    #[test]
1065    fn test_negated_filters() {
1066        let elem = BgpElem {
1067            timestamp: 1637437798_f64,
1068            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
1069            peer_asn: Asn::new_32bit(12345),
1070            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
1071            next_hop: None,
1072            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
1073            origin_asns: Some(vec![Asn::new_16bit(12345)]),
1074            origin: None,
1075            local_pref: None,
1076            med: None,
1077            communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
1078                12345,
1079                [678910, 111213],
1080            ))]),
1081            atomic: false,
1082            aggr_asn: None,
1083            aggr_ip: None,
1084            only_to_customer: None,
1085            unknown: None,
1086            elem_type: ElemType::ANNOUNCE,
1087            deprecated: None,
1088        };
1089
1090        // Test negated origin_asn filter (using value-based negation: origin_asn=!12345)
1091        // elem has origin_asn 12345, so origin_asn=!12345 should NOT match
1092        let filter = Filter::new("origin_asn", "!12345").unwrap();
1093        assert!(!elem.match_filter(&filter));
1094
1095        // elem has origin_asn 12345, so origin_asn=!99999 should match
1096        let filter = Filter::new("origin_asn", "!99999").unwrap();
1097        assert!(elem.match_filter(&filter));
1098
1099        // Test negated prefix filter
1100        // elem has prefix 192.168.1.0/24, so prefix=!192.168.1.0/24 should NOT match
1101        let filter = Filter::new("prefix", "!192.168.1.0/24").unwrap();
1102        assert!(!elem.match_filter(&filter));
1103
1104        // elem has prefix 192.168.1.0/24, so prefix=!10.0.0.0/8 should match
1105        let filter = Filter::new("prefix", "!10.0.0.0/8").unwrap();
1106        assert!(elem.match_filter(&filter));
1107
1108        // Test negated peer_ip filter
1109        // elem has peer_ip 192.168.1.1, so peer_ip=!192.168.1.1 should NOT match
1110        let filter = Filter::new("peer_ip", "!192.168.1.1").unwrap();
1111        assert!(!elem.match_filter(&filter));
1112
1113        // elem has peer_ip 192.168.1.1, so peer_ip=!10.0.0.1 should match
1114        let filter = Filter::new("peer_ip", "!10.0.0.1").unwrap();
1115        assert!(elem.match_filter(&filter));
1116
1117        // Test negated peer_asn filter
1118        // elem has peer_asn 12345, so peer_asn=!12345 should NOT match
1119        let filter = Filter::new("peer_asn", "!12345").unwrap();
1120        assert!(!elem.match_filter(&filter));
1121
1122        // elem has peer_asn 12345, so peer_asn=!99999 should match
1123        let filter = Filter::new("peer_asn", "!99999").unwrap();
1124        assert!(elem.match_filter(&filter));
1125
1126        // Test negated type filter
1127        // elem has type ANNOUNCE, so type=!a should NOT match
1128        let filter = Filter::new("type", "!a").unwrap();
1129        assert!(!elem.match_filter(&filter));
1130
1131        // elem has type ANNOUNCE, so type=!w should match
1132        let filter = Filter::new("type", "!w").unwrap();
1133        assert!(elem.match_filter(&filter));
1134
1135        // Test negated ip_version filter
1136        // elem has IPv4 prefix, so ip_version=!4 should NOT match
1137        let filter = Filter::new("ip_version", "!4").unwrap();
1138        assert!(!elem.match_filter(&filter));
1139
1140        // elem has IPv4 prefix, so ip_version=!6 should match
1141        let filter = Filter::new("ip_version", "!6").unwrap();
1142        assert!(elem.match_filter(&filter));
1143
1144        // Test negated as_path filter
1145        // elem has as_path "174 1916 52888", so negated matching regex should NOT match
1146        let filter = Filter::new("as_path", r"!174 1916 52888$").unwrap();
1147        assert!(!elem.match_filter(&filter));
1148
1149        // elem has as_path "174 1916 52888", so negated non-matching regex should match
1150        let filter = Filter::new("as_path", r"!99999$").unwrap();
1151        assert!(elem.match_filter(&filter));
1152
1153        // Test negated community filter
1154        let filter = Filter::new("community", r"!12345:678910:111213$").unwrap();
1155        assert!(!elem.match_filter(&filter));
1156
1157        let filter = Filter::new("community", r"!99999:99999$").unwrap();
1158        assert!(elem.match_filter(&filter));
1159
1160        // Test negated peer_ips filter (multi-value uses !value,!value syntax)
1161        let filter = Filter::new("peer_ips", "!192.168.1.1, !10.0.0.1").unwrap();
1162        assert!(!elem.match_filter(&filter)); // elem's peer_ip is in the list
1163
1164        let filter = Filter::new("peer_ips", "!10.0.0.1, !10.0.0.2").unwrap();
1165        assert!(elem.match_filter(&filter)); // elem's peer_ip is NOT in the list
1166
1167        // Test combining positive and negated filters
1168        let filters = vec![
1169            Filter::new("origin_asn", "12345").unwrap(),   // matches
1170            Filter::new("peer_asn", "!99999").unwrap(),    // matches (not 99999)
1171            Filter::new("prefix", "!10.0.0.0/8").unwrap(), // matches (not 10.0.0.0/8)
1172        ];
1173        assert!(elem.match_filters(&filters));
1174
1175        // Test combining filters where one fails
1176        let filters = vec![
1177            Filter::new("origin_asn", "12345").unwrap(),  // matches
1178            Filter::new("origin_asn", "!12345").unwrap(), // does NOT match
1179        ];
1180        assert!(!elem.match_filters(&filters));
1181    }
1182
1183    #[test]
1184    fn test_negated_filters_on_mrt_file() {
1185        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1186        let parser = BgpkitParser::new(url).unwrap();
1187        let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
1188
1189        // Count all elems from peer 185.1.8.65
1190        let filters = vec![Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap())];
1191        let count_with_peer = elems.iter().filter(|e| e.match_filters(&filters)).count();
1192        assert_eq!(count_with_peer, 3393);
1193
1194        // Count all elems NOT from peer 185.1.8.65 (using value-based negation)
1195        let filters = vec![Filter::new("peer_ip", "!185.1.8.65").unwrap()];
1196        let count_without_peer = elems.iter().filter(|e| e.match_filters(&filters)).count();
1197        assert_eq!(count_without_peer, elems.len() - 3393);
1198
1199        // Verify total adds up
1200        assert_eq!(count_with_peer + count_without_peer, elems.len());
1201
1202        // Test negated type filter
1203        let filters = vec![Filter::Type(ElemType::WITHDRAW)];
1204        let count_withdrawals = elems.iter().filter(|e| e.match_filters(&filters)).count();
1205        assert_eq!(count_withdrawals, 379);
1206
1207        let filters = vec![Filter::new("type", "!w").unwrap()];
1208        let count_not_withdrawals = elems.iter().filter(|e| e.match_filters(&filters)).count();
1209        assert_eq!(count_not_withdrawals, elems.len() - 379);
1210
1211        // Test negated prefix filter (using value-based negation)
1212        let filters = vec![Filter::Prefix(
1213            IpNet::from_str("190.115.192.0/22").unwrap(),
1214            PrefixMatchType::Exact,
1215        )];
1216        let count_with_prefix = elems.iter().filter(|e| e.match_filters(&filters)).count();
1217
1218        let filters = vec![Filter::new("prefix", "!190.115.192.0/22").unwrap()];
1219        let count_without_prefix = elems.iter().filter(|e| e.match_filters(&filters)).count();
1220        assert_eq!(count_with_prefix + count_without_prefix, elems.len());
1221
1222        // Test negated prefix_super filter (using value-based negation)
1223        let filters = vec![Filter::Prefix(
1224            IpNet::from_str("190.115.192.0/24").unwrap(),
1225            PrefixMatchType::IncludeSuper,
1226        )];
1227        let count_with_super = elems.iter().filter(|e| e.match_filters(&filters)).count();
1228
1229        let filters = vec![Filter::new("prefix_super", "!190.115.192.0/24").unwrap()];
1230        let count_without_super = elems.iter().filter(|e| e.match_filters(&filters)).count();
1231        assert_eq!(count_with_super + count_without_super, elems.len());
1232
1233        // Test negated prefix_sub filter (using value-based negation)
1234        let filters = vec![Filter::Prefix(
1235            IpNet::from_str("190.115.192.0/22").unwrap(),
1236            PrefixMatchType::IncludeSub,
1237        )];
1238        let count_with_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1239
1240        let filters = vec![Filter::new("prefix_sub", "!190.115.192.0/22").unwrap()];
1241        let count_without_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1242        assert_eq!(count_with_sub + count_without_sub, elems.len());
1243
1244        // Test negated prefix_super_sub filter (using value-based negation)
1245        let filters = vec![Filter::Prefix(
1246            IpNet::from_str("190.115.192.0/23").unwrap(),
1247            PrefixMatchType::IncludeSuperSub,
1248        )];
1249        let count_with_super_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1250
1251        let filters = vec![Filter::new("prefix_super_sub", "!190.115.192.0/23").unwrap()];
1252        let count_without_super_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1253        assert_eq!(count_with_super_sub + count_without_super_sub, elems.len());
1254    }
1255
1256    #[test]
1257    fn test_double_negation_rejected() {
1258        // Double negation should be rejected with a clear error message
1259        // Value-based negation: origin_asn=!!13335
1260        let result = Filter::new("origin_asn", "!!13335");
1261        assert!(result.is_err());
1262        let err = result.unwrap_err();
1263        assert!(err.to_string().contains("double negation"));
1264
1265        let result = Filter::new("prefix", "!!!10.0.0.0/8");
1266        assert!(result.is_err());
1267        let err = result.unwrap_err();
1268        assert!(err.to_string().contains("double negation"));
1269    }
1270
1271    #[test]
1272    fn test_timestamp_negation_rejected() {
1273        // Timestamp filter negation should be rejected (value-based negation)
1274        let result = Filter::new("ts_start", "!1637437798");
1275        assert!(result.is_err());
1276        let err = result.unwrap_err();
1277        assert!(err
1278            .to_string()
1279            .contains("timestamp filter 'ts_start' does not support negation"));
1280
1281        let result = Filter::new("ts_end", "!1637437798");
1282        assert!(result.is_err());
1283        let err = result.unwrap_err();
1284        assert!(err
1285            .to_string()
1286            .contains("timestamp filter 'ts_end' does not support negation"));
1287
1288        let result = Filter::new("start_ts", "!1637437798");
1289        assert!(result.is_err());
1290
1291        let result = Filter::new("end_ts", "!1637437798");
1292        assert!(result.is_err());
1293    }
1294
1295    #[test]
1296    fn test_multiple_origin_asns() -> Result<()> {
1297        // Test parsing multiple origin ASNs
1298        let filter = Filter::new("origin_asns", "12345,67890,13335").unwrap();
1299        match filter {
1300            Filter::OriginAsns(asns) => {
1301                assert_eq!(asns.len(), 3);
1302                assert!(asns.contains(&12345));
1303                assert!(asns.contains(&67890));
1304                assert!(asns.contains(&13335));
1305            }
1306            _ => panic!("Expected OriginAsns filter"),
1307        }
1308
1309        // Test with spaces in the list
1310        let filter = Filter::new("origin_asns", "12345, 67890, 13335").unwrap();
1311        match filter {
1312            Filter::OriginAsns(asns) => {
1313                assert_eq!(asns.len(), 3);
1314            }
1315            _ => panic!("Expected OriginAsns filter"),
1316        }
1317
1318        Ok(())
1319    }
1320
1321    #[test]
1322    fn test_multiple_prefixes() -> Result<()> {
1323        // Test parsing multiple prefixes
1324        let prefix1 = IpNet::from_str("190.115.192.0/22").unwrap();
1325        let prefix2 = IpNet::from_str("2804:100::/32").unwrap();
1326
1327        let filter = Filter::new("prefixes", "190.115.192.0/22,2804:100::/32").unwrap();
1328        match filter {
1329            Filter::Prefixes(prefixes, match_type) => {
1330                assert_eq!(prefixes.len(), 2);
1331                assert!(prefixes.contains(&prefix1));
1332                assert!(prefixes.contains(&prefix2));
1333                assert_eq!(match_type, PrefixMatchType::Exact);
1334            }
1335            _ => panic!("Expected Prefixes filter"),
1336        }
1337
1338        // Test with spaces
1339        let filter = Filter::new("prefixes", "190.115.192.0/22, 2804:100::/32").unwrap();
1340        match filter {
1341            Filter::Prefixes(prefixes, _) => {
1342                assert_eq!(prefixes.len(), 2);
1343            }
1344            _ => panic!("Expected Prefixes filter"),
1345        }
1346
1347        Ok(())
1348    }
1349
1350    #[test]
1351    fn test_multiple_prefixes_with_match_types() -> Result<()> {
1352        // Test prefixes_super
1353        let filter = Filter::new("prefixes_super", "190.115.192.0/24,2804:100::/32").unwrap();
1354        match filter {
1355            Filter::Prefixes(prefixes, match_type) => {
1356                assert_eq!(prefixes.len(), 2);
1357                assert_eq!(match_type, PrefixMatchType::IncludeSuper);
1358            }
1359            _ => panic!("Expected Prefixes filter with IncludeSuper"),
1360        }
1361
1362        // Test prefixes_sub
1363        let filter = Filter::new("prefixes_sub", "190.115.192.0/22,2804:100::/32").unwrap();
1364        match filter {
1365            Filter::Prefixes(prefixes, match_type) => {
1366                assert_eq!(prefixes.len(), 2);
1367                assert_eq!(match_type, PrefixMatchType::IncludeSub);
1368            }
1369            _ => panic!("Expected Prefixes filter with IncludeSub"),
1370        }
1371
1372        // Test prefixes_super_sub
1373        let filter = Filter::new("prefixes_super_sub", "190.115.192.0/23,2804:100::/32").unwrap();
1374        match filter {
1375            Filter::Prefixes(prefixes, match_type) => {
1376                assert_eq!(prefixes.len(), 2);
1377                assert_eq!(match_type, PrefixMatchType::IncludeSuperSub);
1378            }
1379            _ => panic!("Expected Prefixes filter with IncludeSuperSub"),
1380        }
1381
1382        Ok(())
1383    }
1384
1385    #[test]
1386    fn test_multiple_peer_asns() -> Result<()> {
1387        // Test parsing multiple peer ASNs
1388        let filter = Filter::new("peer_asns", "12345,67890,13335").unwrap();
1389        match filter {
1390            Filter::PeerAsns(asns) => {
1391                assert_eq!(asns.len(), 3);
1392                assert!(asns.contains(&12345));
1393                assert!(asns.contains(&67890));
1394                assert!(asns.contains(&13335));
1395            }
1396            _ => panic!("Expected PeerAsns filter"),
1397        }
1398
1399        Ok(())
1400    }
1401
1402    #[test]
1403    fn test_negated_multiple_filters() -> Result<()> {
1404        // Test negated origin_asns (using value-based negation: !value,!value)
1405        let filter = Filter::new("origin_asns", "!13335,!15169").unwrap();
1406        assert!(matches!(filter, Filter::Negated(_)));
1407
1408        // Test negated prefixes
1409        let filter = Filter::new("prefixes", "!1.1.1.0/24,!8.8.8.0/24").unwrap();
1410        assert!(matches!(filter, Filter::Negated(_)));
1411
1412        // Test negated peer_asns
1413        let filter = Filter::new("peer_asns", "!12345,!67890").unwrap();
1414        assert!(matches!(filter, Filter::Negated(_)));
1415
1416        Ok(())
1417    }
1418
1419    #[test]
1420    fn test_invalid_multiple_filters() {
1421        // Test invalid origin ASN in list
1422        let result = Filter::new("origin_asns", "12345,not_a_number,67890");
1423        assert!(result.is_err());
1424
1425        // Test invalid prefix in list
1426        let result = Filter::new("prefixes", "1.1.1.0/24,invalid_prefix");
1427        assert!(result.is_err());
1428
1429        // Test invalid peer ASN in list
1430        let result = Filter::new("peer_asns", "12345,invalid,67890");
1431        assert!(result.is_err());
1432
1433        // Test invalid peer IP in list
1434        let result = Filter::new("peer_ips", "192.168.1.1,invalid_ip");
1435        assert!(result.is_err());
1436
1437        // Test mixed positive/negative values (not allowed)
1438        let result = Filter::new("origin_asns", "12345,!67890");
1439        assert!(result.is_err());
1440        assert!(result
1441            .unwrap_err()
1442            .to_string()
1443            .contains("cannot mix positive and negative values"));
1444
1445        let result = Filter::new("prefixes", "1.1.1.0/24,!8.8.8.0/24");
1446        assert!(result.is_err());
1447        assert!(result
1448            .unwrap_err()
1449            .to_string()
1450            .contains("cannot mix positive and negative values"));
1451
1452        let result = Filter::new("peer_ips", "192.168.1.1,!10.0.0.1");
1453        assert!(result.is_err());
1454        assert!(result
1455            .unwrap_err()
1456            .to_string()
1457            .contains("cannot mix positive and negative values"));
1458
1459        let result = Filter::new("peer_asns", "!12345,67890");
1460        assert!(result.is_err());
1461        assert!(result
1462            .unwrap_err()
1463            .to_string()
1464            .contains("cannot mix positive and negative values"));
1465
1466        // Test empty ASN list
1467        let result = Filter::new("origin_asns", "");
1468        assert!(result.is_err());
1469        assert!(result.unwrap_err().to_string().contains("at least one ASN"));
1470
1471        // Test empty prefix list
1472        let result = Filter::new("prefixes", "");
1473        assert!(result.is_err());
1474        assert!(result
1475            .unwrap_err()
1476            .to_string()
1477            .contains("at least one prefix"));
1478
1479        // Test empty IP list
1480        let result = Filter::new("peer_ips", "");
1481        assert!(result.is_err());
1482        assert!(result.unwrap_err().to_string().contains("at least one IP"));
1483
1484        // Test only commas in ASN list (should error after filtering empty strings)
1485        let result = Filter::new("origin_asns", ",,,");
1486        assert!(result.is_err());
1487        assert!(result.unwrap_err().to_string().contains("at least one ASN"));
1488
1489        // Test only commas in prefix list
1490        let result = Filter::new("prefixes", ",,,");
1491        assert!(result.is_err());
1492        assert!(result
1493            .unwrap_err()
1494            .to_string()
1495            .contains("at least one prefix"));
1496
1497        // Test only commas in IP list
1498        let result = Filter::new("peer_ips", ",,,");
1499        assert!(result.is_err());
1500        assert!(result.unwrap_err().to_string().contains("at least one IP"));
1501
1502        // Test trailing commas (should still work by skipping empty strings)
1503        let result = Filter::new("origin_asns", "12345,67890,");
1504        assert!(result.is_ok());
1505
1506        // Test consecutive commas (should still work by skipping empty strings)
1507        let result = Filter::new("origin_asns", "12345,,67890");
1508        assert!(result.is_ok());
1509
1510        // Test trailing commas for peer IPs
1511        let result = Filter::new("peer_ips", "192.168.1.1,192.168.1.2,");
1512        assert!(result.is_ok());
1513    }
1514
1515    #[test]
1516    fn test_multiple_filters_or_logic_behavior() {
1517        // Create a test element
1518        let elem = BgpElem {
1519            timestamp: 1637437798_f64,
1520            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
1521            peer_asn: Asn::new_32bit(12345),
1522            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
1523            next_hop: None,
1524            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
1525            origin_asns: Some(vec![Asn::new_16bit(12345)]),
1526            origin: None,
1527            local_pref: None,
1528            med: None,
1529            communities: None,
1530            atomic: false,
1531            aggr_asn: None,
1532            aggr_ip: None,
1533            only_to_customer: None,
1534            unknown: None,
1535            elem_type: ElemType::ANNOUNCE,
1536            deprecated: None,
1537        };
1538
1539        // Test OriginAsns with OR logic - element has origin ASN 12345
1540        let filter = Filter::new("origin_asns", "12345,67890,99999").unwrap();
1541        assert!(elem.match_filter(&filter)); // Should match because 12345 is in the list
1542
1543        let filter = Filter::new("origin_asns", "67890,99999").unwrap();
1544        assert!(!elem.match_filter(&filter)); // Should NOT match because 12345 is not in the list
1545
1546        // Test Prefixes with OR logic - element has prefix 192.168.1.0/24
1547        let filter = Filter::new("prefixes", "192.168.1.0/24,10.0.0.0/8,172.16.0.0/12").unwrap();
1548        assert!(elem.match_filter(&filter)); // Should match
1549
1550        let filter = Filter::new("prefixes", "10.0.0.0/8,172.16.0.0/12").unwrap();
1551        assert!(!elem.match_filter(&filter)); // Should NOT match
1552
1553        // Test PeerAsns with OR logic - element has peer ASN 12345
1554        let filter = Filter::new("peer_asns", "12345,67890").unwrap();
1555        assert!(elem.match_filter(&filter)); // Should match
1556
1557        let filter = Filter::new("peer_asns", "67890,99999").unwrap();
1558        assert!(!elem.match_filter(&filter)); // Should NOT match
1559
1560        // Test negated multiple filters (using value-based negation: !value,!value)
1561        let filter = Filter::new("origin_asns", "!67890,!99999").unwrap();
1562        assert!(elem.match_filter(&filter)); // Should match because origin ASN is NOT in the list
1563
1564        let filter = Filter::new("origin_asns", "!12345,!67890").unwrap();
1565        assert!(!elem.match_filter(&filter)); // Should NOT match because origin ASN IS in the list
1566    }
1567}