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