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            peer_bgp_id: None,
994            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
995            next_hop: None,
996            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
997            origin_asns: Some(vec![Asn::new_16bit(12345)]),
998            origin: None,
999            local_pref: None,
1000            med: None,
1001            communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
1002                12345,
1003                [678910, 111213],
1004            ))]),
1005            atomic: false,
1006            aggr_asn: None,
1007            aggr_ip: None,
1008            only_to_customer: None,
1009            unknown: None,
1010            elem_type: ElemType::ANNOUNCE,
1011            deprecated: None,
1012        };
1013
1014        let mut filters = vec![];
1015
1016        let filter = Filter::new("origin_asn", "12345").unwrap();
1017        filters.push(filter.clone());
1018        assert!(elem.match_filter(&filter));
1019
1020        let filter = Filter::new("origin_asn", "678910").unwrap();
1021        assert!(!elem.match_filter(&filter));
1022
1023        let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
1024        filters.push(filter.clone());
1025        assert!(elem.match_filter(&filter));
1026
1027        let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
1028        filters.push(filter.clone());
1029        assert!(elem.match_filter(&filter));
1030
1031        let filter = Filter::new("peer_asn", "12345").unwrap();
1032        filters.push(filter.clone());
1033        assert!(elem.match_filter(&filter));
1034
1035        let filter = Filter::new("type", "a").unwrap();
1036        filters.push(filter.clone());
1037        assert!(elem.match_filter(&filter));
1038
1039        let filter = Filter::new("ts_start", "1637437798").unwrap();
1040        filters.push(filter.clone());
1041        assert!(elem.match_filter(&filter));
1042
1043        let filter = Filter::new("ts_end", "1637437798").unwrap();
1044        filters.push(filter.clone());
1045        assert!(elem.match_filter(&filter));
1046
1047        let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
1048        filters.push(filter.clone());
1049        assert!(elem.match_filter(&filter));
1050
1051        let filter = Filter::new("ip_version", "4").unwrap();
1052        filters.push(filter.clone());
1053        assert!(elem.match_filter(&filter));
1054
1055        let filter = Filter::new("ip", "ipv6").unwrap();
1056        assert!(!elem.match_filter(&filter));
1057
1058        let filter = Filter::new("community", r"12345:678910:111213$").unwrap();
1059        filters.push(filter.clone());
1060        assert!(elem.match_filter(&filter));
1061
1062        assert!(elem.match_filters(&filters));
1063    }
1064
1065    #[test]
1066    fn test_negated_filters() {
1067        let elem = BgpElem {
1068            timestamp: 1637437798_f64,
1069            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
1070            peer_asn: Asn::new_32bit(12345),
1071            peer_bgp_id: None,
1072            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
1073            next_hop: None,
1074            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
1075            origin_asns: Some(vec![Asn::new_16bit(12345)]),
1076            origin: None,
1077            local_pref: None,
1078            med: None,
1079            communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
1080                12345,
1081                [678910, 111213],
1082            ))]),
1083            atomic: false,
1084            aggr_asn: None,
1085            aggr_ip: None,
1086            only_to_customer: None,
1087            unknown: None,
1088            elem_type: ElemType::ANNOUNCE,
1089            deprecated: None,
1090        };
1091
1092        // Test negated origin_asn filter (using value-based negation: origin_asn=!12345)
1093        // elem has origin_asn 12345, so origin_asn=!12345 should NOT match
1094        let filter = Filter::new("origin_asn", "!12345").unwrap();
1095        assert!(!elem.match_filter(&filter));
1096
1097        // elem has origin_asn 12345, so origin_asn=!99999 should match
1098        let filter = Filter::new("origin_asn", "!99999").unwrap();
1099        assert!(elem.match_filter(&filter));
1100
1101        // Test negated prefix filter
1102        // elem has prefix 192.168.1.0/24, so prefix=!192.168.1.0/24 should NOT match
1103        let filter = Filter::new("prefix", "!192.168.1.0/24").unwrap();
1104        assert!(!elem.match_filter(&filter));
1105
1106        // elem has prefix 192.168.1.0/24, so prefix=!10.0.0.0/8 should match
1107        let filter = Filter::new("prefix", "!10.0.0.0/8").unwrap();
1108        assert!(elem.match_filter(&filter));
1109
1110        // Test negated peer_ip filter
1111        // elem has peer_ip 192.168.1.1, so peer_ip=!192.168.1.1 should NOT match
1112        let filter = Filter::new("peer_ip", "!192.168.1.1").unwrap();
1113        assert!(!elem.match_filter(&filter));
1114
1115        // elem has peer_ip 192.168.1.1, so peer_ip=!10.0.0.1 should match
1116        let filter = Filter::new("peer_ip", "!10.0.0.1").unwrap();
1117        assert!(elem.match_filter(&filter));
1118
1119        // Test negated peer_asn filter
1120        // elem has peer_asn 12345, so peer_asn=!12345 should NOT match
1121        let filter = Filter::new("peer_asn", "!12345").unwrap();
1122        assert!(!elem.match_filter(&filter));
1123
1124        // elem has peer_asn 12345, so peer_asn=!99999 should match
1125        let filter = Filter::new("peer_asn", "!99999").unwrap();
1126        assert!(elem.match_filter(&filter));
1127
1128        // Test negated type filter
1129        // elem has type ANNOUNCE, so type=!a should NOT match
1130        let filter = Filter::new("type", "!a").unwrap();
1131        assert!(!elem.match_filter(&filter));
1132
1133        // elem has type ANNOUNCE, so type=!w should match
1134        let filter = Filter::new("type", "!w").unwrap();
1135        assert!(elem.match_filter(&filter));
1136
1137        // Test negated ip_version filter
1138        // elem has IPv4 prefix, so ip_version=!4 should NOT match
1139        let filter = Filter::new("ip_version", "!4").unwrap();
1140        assert!(!elem.match_filter(&filter));
1141
1142        // elem has IPv4 prefix, so ip_version=!6 should match
1143        let filter = Filter::new("ip_version", "!6").unwrap();
1144        assert!(elem.match_filter(&filter));
1145
1146        // Test negated as_path filter
1147        // elem has as_path "174 1916 52888", so negated matching regex should NOT match
1148        let filter = Filter::new("as_path", r"!174 1916 52888$").unwrap();
1149        assert!(!elem.match_filter(&filter));
1150
1151        // elem has as_path "174 1916 52888", so negated non-matching regex should match
1152        let filter = Filter::new("as_path", r"!99999$").unwrap();
1153        assert!(elem.match_filter(&filter));
1154
1155        // Test negated community filter
1156        let filter = Filter::new("community", r"!12345:678910:111213$").unwrap();
1157        assert!(!elem.match_filter(&filter));
1158
1159        let filter = Filter::new("community", r"!99999:99999$").unwrap();
1160        assert!(elem.match_filter(&filter));
1161
1162        // Test negated peer_ips filter (multi-value uses !value,!value syntax)
1163        let filter = Filter::new("peer_ips", "!192.168.1.1, !10.0.0.1").unwrap();
1164        assert!(!elem.match_filter(&filter)); // elem's peer_ip is in the list
1165
1166        let filter = Filter::new("peer_ips", "!10.0.0.1, !10.0.0.2").unwrap();
1167        assert!(elem.match_filter(&filter)); // elem's peer_ip is NOT in the list
1168
1169        // Test combining positive and negated filters
1170        let filters = vec![
1171            Filter::new("origin_asn", "12345").unwrap(),   // matches
1172            Filter::new("peer_asn", "!99999").unwrap(),    // matches (not 99999)
1173            Filter::new("prefix", "!10.0.0.0/8").unwrap(), // matches (not 10.0.0.0/8)
1174        ];
1175        assert!(elem.match_filters(&filters));
1176
1177        // Test combining filters where one fails
1178        let filters = vec![
1179            Filter::new("origin_asn", "12345").unwrap(),  // matches
1180            Filter::new("origin_asn", "!12345").unwrap(), // does NOT match
1181        ];
1182        assert!(!elem.match_filters(&filters));
1183    }
1184
1185    #[test]
1186    fn test_negated_filters_on_mrt_file() {
1187        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1188        let parser = BgpkitParser::new(url).unwrap();
1189        let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
1190
1191        // Count all elems from peer 185.1.8.65
1192        let filters = vec![Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap())];
1193        let count_with_peer = elems.iter().filter(|e| e.match_filters(&filters)).count();
1194        assert_eq!(count_with_peer, 3393);
1195
1196        // Count all elems NOT from peer 185.1.8.65 (using value-based negation)
1197        let filters = vec![Filter::new("peer_ip", "!185.1.8.65").unwrap()];
1198        let count_without_peer = elems.iter().filter(|e| e.match_filters(&filters)).count();
1199        assert_eq!(count_without_peer, elems.len() - 3393);
1200
1201        // Verify total adds up
1202        assert_eq!(count_with_peer + count_without_peer, elems.len());
1203
1204        // Test negated type filter
1205        let filters = vec![Filter::Type(ElemType::WITHDRAW)];
1206        let count_withdrawals = elems.iter().filter(|e| e.match_filters(&filters)).count();
1207        assert_eq!(count_withdrawals, 379);
1208
1209        let filters = vec![Filter::new("type", "!w").unwrap()];
1210        let count_not_withdrawals = elems.iter().filter(|e| e.match_filters(&filters)).count();
1211        assert_eq!(count_not_withdrawals, elems.len() - 379);
1212
1213        // Test negated prefix filter (using value-based negation)
1214        let filters = vec![Filter::Prefix(
1215            IpNet::from_str("190.115.192.0/22").unwrap(),
1216            PrefixMatchType::Exact,
1217        )];
1218        let count_with_prefix = elems.iter().filter(|e| e.match_filters(&filters)).count();
1219
1220        let filters = vec![Filter::new("prefix", "!190.115.192.0/22").unwrap()];
1221        let count_without_prefix = elems.iter().filter(|e| e.match_filters(&filters)).count();
1222        assert_eq!(count_with_prefix + count_without_prefix, elems.len());
1223
1224        // Test negated prefix_super filter (using value-based negation)
1225        let filters = vec![Filter::Prefix(
1226            IpNet::from_str("190.115.192.0/24").unwrap(),
1227            PrefixMatchType::IncludeSuper,
1228        )];
1229        let count_with_super = elems.iter().filter(|e| e.match_filters(&filters)).count();
1230
1231        let filters = vec![Filter::new("prefix_super", "!190.115.192.0/24").unwrap()];
1232        let count_without_super = elems.iter().filter(|e| e.match_filters(&filters)).count();
1233        assert_eq!(count_with_super + count_without_super, elems.len());
1234
1235        // Test negated prefix_sub filter (using value-based negation)
1236        let filters = vec![Filter::Prefix(
1237            IpNet::from_str("190.115.192.0/22").unwrap(),
1238            PrefixMatchType::IncludeSub,
1239        )];
1240        let count_with_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1241
1242        let filters = vec![Filter::new("prefix_sub", "!190.115.192.0/22").unwrap()];
1243        let count_without_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1244        assert_eq!(count_with_sub + count_without_sub, elems.len());
1245
1246        // Test negated prefix_super_sub filter (using value-based negation)
1247        let filters = vec![Filter::Prefix(
1248            IpNet::from_str("190.115.192.0/23").unwrap(),
1249            PrefixMatchType::IncludeSuperSub,
1250        )];
1251        let count_with_super_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1252
1253        let filters = vec![Filter::new("prefix_super_sub", "!190.115.192.0/23").unwrap()];
1254        let count_without_super_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1255        assert_eq!(count_with_super_sub + count_without_super_sub, elems.len());
1256    }
1257
1258    #[test]
1259    fn test_double_negation_rejected() {
1260        // Double negation should be rejected with a clear error message
1261        // Value-based negation: origin_asn=!!13335
1262        let result = Filter::new("origin_asn", "!!13335");
1263        assert!(result.is_err());
1264        let err = result.unwrap_err();
1265        assert!(err.to_string().contains("double negation"));
1266
1267        let result = Filter::new("prefix", "!!!10.0.0.0/8");
1268        assert!(result.is_err());
1269        let err = result.unwrap_err();
1270        assert!(err.to_string().contains("double negation"));
1271    }
1272
1273    #[test]
1274    fn test_timestamp_negation_rejected() {
1275        // Timestamp filter negation should be rejected (value-based negation)
1276        let result = Filter::new("ts_start", "!1637437798");
1277        assert!(result.is_err());
1278        let err = result.unwrap_err();
1279        assert!(err
1280            .to_string()
1281            .contains("timestamp filter 'ts_start' does not support negation"));
1282
1283        let result = Filter::new("ts_end", "!1637437798");
1284        assert!(result.is_err());
1285        let err = result.unwrap_err();
1286        assert!(err
1287            .to_string()
1288            .contains("timestamp filter 'ts_end' does not support negation"));
1289
1290        let result = Filter::new("start_ts", "!1637437798");
1291        assert!(result.is_err());
1292
1293        let result = Filter::new("end_ts", "!1637437798");
1294        assert!(result.is_err());
1295    }
1296
1297    #[test]
1298    fn test_multiple_origin_asns() -> Result<()> {
1299        // Test parsing multiple origin ASNs
1300        let filter = Filter::new("origin_asns", "12345,67890,13335").unwrap();
1301        match filter {
1302            Filter::OriginAsns(asns) => {
1303                assert_eq!(asns.len(), 3);
1304                assert!(asns.contains(&12345));
1305                assert!(asns.contains(&67890));
1306                assert!(asns.contains(&13335));
1307            }
1308            _ => panic!("Expected OriginAsns filter"),
1309        }
1310
1311        // Test with spaces in the list
1312        let filter = Filter::new("origin_asns", "12345, 67890, 13335").unwrap();
1313        match filter {
1314            Filter::OriginAsns(asns) => {
1315                assert_eq!(asns.len(), 3);
1316            }
1317            _ => panic!("Expected OriginAsns filter"),
1318        }
1319
1320        Ok(())
1321    }
1322
1323    #[test]
1324    fn test_multiple_prefixes() -> Result<()> {
1325        // Test parsing multiple prefixes
1326        let prefix1 = IpNet::from_str("190.115.192.0/22").unwrap();
1327        let prefix2 = IpNet::from_str("2804:100::/32").unwrap();
1328
1329        let filter = Filter::new("prefixes", "190.115.192.0/22,2804:100::/32").unwrap();
1330        match filter {
1331            Filter::Prefixes(prefixes, match_type) => {
1332                assert_eq!(prefixes.len(), 2);
1333                assert!(prefixes.contains(&prefix1));
1334                assert!(prefixes.contains(&prefix2));
1335                assert_eq!(match_type, PrefixMatchType::Exact);
1336            }
1337            _ => panic!("Expected Prefixes filter"),
1338        }
1339
1340        // Test with spaces
1341        let filter = Filter::new("prefixes", "190.115.192.0/22, 2804:100::/32").unwrap();
1342        match filter {
1343            Filter::Prefixes(prefixes, _) => {
1344                assert_eq!(prefixes.len(), 2);
1345            }
1346            _ => panic!("Expected Prefixes filter"),
1347        }
1348
1349        Ok(())
1350    }
1351
1352    #[test]
1353    fn test_multiple_prefixes_with_match_types() -> Result<()> {
1354        // Test prefixes_super
1355        let filter = Filter::new("prefixes_super", "190.115.192.0/24,2804:100::/32").unwrap();
1356        match filter {
1357            Filter::Prefixes(prefixes, match_type) => {
1358                assert_eq!(prefixes.len(), 2);
1359                assert_eq!(match_type, PrefixMatchType::IncludeSuper);
1360            }
1361            _ => panic!("Expected Prefixes filter with IncludeSuper"),
1362        }
1363
1364        // Test prefixes_sub
1365        let filter = Filter::new("prefixes_sub", "190.115.192.0/22,2804:100::/32").unwrap();
1366        match filter {
1367            Filter::Prefixes(prefixes, match_type) => {
1368                assert_eq!(prefixes.len(), 2);
1369                assert_eq!(match_type, PrefixMatchType::IncludeSub);
1370            }
1371            _ => panic!("Expected Prefixes filter with IncludeSub"),
1372        }
1373
1374        // Test prefixes_super_sub
1375        let filter = Filter::new("prefixes_super_sub", "190.115.192.0/23,2804:100::/32").unwrap();
1376        match filter {
1377            Filter::Prefixes(prefixes, match_type) => {
1378                assert_eq!(prefixes.len(), 2);
1379                assert_eq!(match_type, PrefixMatchType::IncludeSuperSub);
1380            }
1381            _ => panic!("Expected Prefixes filter with IncludeSuperSub"),
1382        }
1383
1384        Ok(())
1385    }
1386
1387    #[test]
1388    fn test_multiple_peer_asns() -> Result<()> {
1389        // Test parsing multiple peer ASNs
1390        let filter = Filter::new("peer_asns", "12345,67890,13335").unwrap();
1391        match filter {
1392            Filter::PeerAsns(asns) => {
1393                assert_eq!(asns.len(), 3);
1394                assert!(asns.contains(&12345));
1395                assert!(asns.contains(&67890));
1396                assert!(asns.contains(&13335));
1397            }
1398            _ => panic!("Expected PeerAsns filter"),
1399        }
1400
1401        Ok(())
1402    }
1403
1404    #[test]
1405    fn test_negated_multiple_filters() -> Result<()> {
1406        // Test negated origin_asns (using value-based negation: !value,!value)
1407        let filter = Filter::new("origin_asns", "!13335,!15169").unwrap();
1408        assert!(matches!(filter, Filter::Negated(_)));
1409
1410        // Test negated prefixes
1411        let filter = Filter::new("prefixes", "!1.1.1.0/24,!8.8.8.0/24").unwrap();
1412        assert!(matches!(filter, Filter::Negated(_)));
1413
1414        // Test negated peer_asns
1415        let filter = Filter::new("peer_asns", "!12345,!67890").unwrap();
1416        assert!(matches!(filter, Filter::Negated(_)));
1417
1418        Ok(())
1419    }
1420
1421    #[test]
1422    fn test_invalid_multiple_filters() {
1423        // Test invalid origin ASN in list
1424        let result = Filter::new("origin_asns", "12345,not_a_number,67890");
1425        assert!(result.is_err());
1426
1427        // Test invalid prefix in list
1428        let result = Filter::new("prefixes", "1.1.1.0/24,invalid_prefix");
1429        assert!(result.is_err());
1430
1431        // Test invalid peer ASN in list
1432        let result = Filter::new("peer_asns", "12345,invalid,67890");
1433        assert!(result.is_err());
1434
1435        // Test invalid peer IP in list
1436        let result = Filter::new("peer_ips", "192.168.1.1,invalid_ip");
1437        assert!(result.is_err());
1438
1439        // Test mixed positive/negative values (not allowed)
1440        let result = Filter::new("origin_asns", "12345,!67890");
1441        assert!(result.is_err());
1442        assert!(result
1443            .unwrap_err()
1444            .to_string()
1445            .contains("cannot mix positive and negative values"));
1446
1447        let result = Filter::new("prefixes", "1.1.1.0/24,!8.8.8.0/24");
1448        assert!(result.is_err());
1449        assert!(result
1450            .unwrap_err()
1451            .to_string()
1452            .contains("cannot mix positive and negative values"));
1453
1454        let result = Filter::new("peer_ips", "192.168.1.1,!10.0.0.1");
1455        assert!(result.is_err());
1456        assert!(result
1457            .unwrap_err()
1458            .to_string()
1459            .contains("cannot mix positive and negative values"));
1460
1461        let result = Filter::new("peer_asns", "!12345,67890");
1462        assert!(result.is_err());
1463        assert!(result
1464            .unwrap_err()
1465            .to_string()
1466            .contains("cannot mix positive and negative values"));
1467
1468        // Test empty ASN list
1469        let result = Filter::new("origin_asns", "");
1470        assert!(result.is_err());
1471        assert!(result.unwrap_err().to_string().contains("at least one ASN"));
1472
1473        // Test empty prefix list
1474        let result = Filter::new("prefixes", "");
1475        assert!(result.is_err());
1476        assert!(result
1477            .unwrap_err()
1478            .to_string()
1479            .contains("at least one prefix"));
1480
1481        // Test empty IP list
1482        let result = Filter::new("peer_ips", "");
1483        assert!(result.is_err());
1484        assert!(result.unwrap_err().to_string().contains("at least one IP"));
1485
1486        // Test only commas in ASN list (should error after filtering empty strings)
1487        let result = Filter::new("origin_asns", ",,,");
1488        assert!(result.is_err());
1489        assert!(result.unwrap_err().to_string().contains("at least one ASN"));
1490
1491        // Test only commas in prefix list
1492        let result = Filter::new("prefixes", ",,,");
1493        assert!(result.is_err());
1494        assert!(result
1495            .unwrap_err()
1496            .to_string()
1497            .contains("at least one prefix"));
1498
1499        // Test only commas in IP list
1500        let result = Filter::new("peer_ips", ",,,");
1501        assert!(result.is_err());
1502        assert!(result.unwrap_err().to_string().contains("at least one IP"));
1503
1504        // Test trailing commas (should still work by skipping empty strings)
1505        let result = Filter::new("origin_asns", "12345,67890,");
1506        assert!(result.is_ok());
1507
1508        // Test consecutive commas (should still work by skipping empty strings)
1509        let result = Filter::new("origin_asns", "12345,,67890");
1510        assert!(result.is_ok());
1511
1512        // Test trailing commas for peer IPs
1513        let result = Filter::new("peer_ips", "192.168.1.1,192.168.1.2,");
1514        assert!(result.is_ok());
1515    }
1516
1517    #[test]
1518    fn test_multiple_filters_or_logic_behavior() {
1519        // Create a test element
1520        let elem = BgpElem {
1521            timestamp: 1637437798_f64,
1522            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
1523            peer_asn: Asn::new_32bit(12345),
1524            peer_bgp_id: None,
1525            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
1526            next_hop: None,
1527            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
1528            origin_asns: Some(vec![Asn::new_16bit(12345)]),
1529            origin: None,
1530            local_pref: None,
1531            med: None,
1532            communities: None,
1533            atomic: false,
1534            aggr_asn: None,
1535            aggr_ip: None,
1536            only_to_customer: None,
1537            unknown: None,
1538            elem_type: ElemType::ANNOUNCE,
1539            deprecated: None,
1540        };
1541
1542        // Test OriginAsns with OR logic - element has origin ASN 12345
1543        let filter = Filter::new("origin_asns", "12345,67890,99999").unwrap();
1544        assert!(elem.match_filter(&filter)); // Should match because 12345 is in the list
1545
1546        let filter = Filter::new("origin_asns", "67890,99999").unwrap();
1547        assert!(!elem.match_filter(&filter)); // Should NOT match because 12345 is not in the list
1548
1549        // Test Prefixes with OR logic - element has prefix 192.168.1.0/24
1550        let filter = Filter::new("prefixes", "192.168.1.0/24,10.0.0.0/8,172.16.0.0/12").unwrap();
1551        assert!(elem.match_filter(&filter)); // Should match
1552
1553        let filter = Filter::new("prefixes", "10.0.0.0/8,172.16.0.0/12").unwrap();
1554        assert!(!elem.match_filter(&filter)); // Should NOT match
1555
1556        // Test PeerAsns with OR logic - element has peer ASN 12345
1557        let filter = Filter::new("peer_asns", "12345,67890").unwrap();
1558        assert!(elem.match_filter(&filter)); // Should match
1559
1560        let filter = Filter::new("peer_asns", "67890,99999").unwrap();
1561        assert!(!elem.match_filter(&filter)); // Should NOT match
1562
1563        // Test negated multiple filters (using value-based negation: !value,!value)
1564        let filter = Filter::new("origin_asns", "!67890,!99999").unwrap();
1565        assert!(elem.match_filter(&filter)); // Should match because origin ASN is NOT in the list
1566
1567        let filter = Filter::new("origin_asns", "!12345,!67890").unwrap();
1568        assert!(!elem.match_filter(&filter)); // Should NOT match because origin ASN IS in the list
1569    }
1570}