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- `otc` -- only-to-customer ASN (RFC 9234); `*` matches "present", `!*` matches "absent"
21- `next_hop` -- next hop IP address; `*` matches "present", `!*` matches "absent"
22- `origin` -- origin attribute (`igp`, `egp`, `incomplete`); `*`/`!*` for presence
23- `local_pref` -- local preference value; `*`/`!*` for presence
24- `med` -- multi-exit discriminator value; `*`/`!*` for presence
25- `atomic` -- atomic aggregate flag (`true`/`false`)
26- `aggr_asn` -- aggregator ASN; `*`/`!*` for presence
27- `aggr_ip` -- aggregator IP address; `*`/`!*` for presence
28- `peer_bgp_id` -- peer BGP identifier (router ID); `*`/`!*` for presence
29
30### Negative Filters
31
32Most filters support negation by prefixing the filter value with `!`. For example:
33- `origin_asn=!13335` -- matches elements where origin AS is NOT 13335
34- `prefix=!10.0.0.0/8` -- matches elements where prefix is NOT 10.0.0.0/8
35- `peer_ip=!192.168.1.1` -- matches elements where peer IP is NOT 192.168.1.1
36
37For multi-value filters, you can negate all values:
38- `origin_asns=!13335,!15169` -- matches elements where origin AS is NOT 13335 AND NOT 15169
39- Mixing positive and negative values in the same filter is not allowed
40
41**Note**: Timestamp filters (`ts_start`, `ts_end`) do not support negation as the behavior would be unintuitive.
42
43[Filter::new] function takes a `str` as the filter type and `str` as the filter value and returns a
44Result of a [Filter] or a parsing error.
45
46[BgpkitParser](crate::BgpkitParser) implements the function `add_filter("filter_type", "filter_value")` that takes the parser's ownership itself
47and returns a new parser with specified filter added. See the example below.
48
49### Example
50
51```no_run
52use bgpkit_parser::BgpkitParser;
53
54/// This example shows how to parse a MRT file and filter by prefix.
55env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
56
57log::info!("downloading updates file");
58let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
59    .add_filter("prefix", "211.98.251.0/24").unwrap()
60    .add_filter("type", "a").unwrap();
61
62// iterating through the parser. the iterator returns `BgpElem` one at a time.
63log::info!("parsing updates file");
64for elem in parser {
65    log::info!("{}", elem);
66}
67log::info!("done");
68```
69
70### Example with Negative Filter
71
72```no_run
73use bgpkit_parser::BgpkitParser;
74
75// Filter out all elements from AS 13335 (Cloudflare)
76let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
77    .add_filter("origin_asn", "!13335").unwrap();
78
79for elem in parser {
80    println!("{}", elem);
81}
82```
83
84### Example with Multiple Filters (OR Logic)
85
86```no_run
87use bgpkit_parser::BgpkitParser;
88
89// Filter elements from multiple origin ASNs (matches ANY of the specified ASNs)
90let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
91    .add_filter("origin_asns", "13335,15169,8075").unwrap();
92
93for elem in parser {
94    println!("{}", elem);
95}
96
97// Filter elements NOT from these ASNs (matches if NOT ANY of the specified ASNs)
98let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
99    .add_filter("origin_asns", "!13335,!15169,!8075").unwrap();
100
101for elem in parser {
102    println!("{}", elem);
103}
104
105// Filter elements matching multiple prefixes (matches ANY of the specified prefixes)
106let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
107    .add_filter("prefixes", "1.1.1.0/24,8.8.8.0/24").unwrap();
108
109for elem in parser {
110    println!("{}", elem);
111}
112```
113
114Note, by default, the prefix filtering is for the exact prefix. You can include super-prefixes or
115sub-prefixes when filtering by using `"prefix_super"`, `"prefix_sub"`, or  `"prefix_super_sub"` as
116the filter type string. For multiple prefixes, use `"prefixes_super"`, `"prefixes_sub"`, or `"prefixes_super_sub"`.
117
118### Note
119
120[BgpElem] and [BgpRouteElem] implement the filtering capability. Route-level filtering only has
121access to route identity, peer metadata, timestamp, and AS path, so `community` filters do not match
122[BgpRouteElem] values. Support for [MrtRecord] will come in later releases.
123
124*/
125use crate::models::*;
126use crate::parser::ComparableRegex;
127use crate::ParserError;
128use crate::ParserError::FilterError;
129use ipnet::IpNet;
130use std::net::{IpAddr, Ipv4Addr};
131use std::str::FromStr;
132
133/// Filter enum: definition of types of filters
134///
135/// The available filters are (`filter_type` (`FilterType`) -- definition):
136/// - `origin_asn` (`OriginAsn(u32)`) -- origin AS number
137/// - `origin_asns` (`OriginAsns(Vec<u32>)`) -- multiple origin AS numbers (OR logic)
138/// - `prefix(_super, _sub, _super_sub)` (`Prefix(IpNet, PrefixMatchType)`) -- network prefix and match type
139/// - `prefixes(_super, _sub, _super_sub)` (`Prefixes(Vec<IpNet>, PrefixMatchType)`) -- multiple network prefixes (OR logic)
140/// - `peer_ip` (`PeerIp(IpAddr)`) -- peer's IP address
141/// - `peer_ips` (`PeerIps(Vec<IpAddr>)`) -- peers' IP addresses (OR logic)
142/// - `peer_asn` (`PeerAsn(u32)`) -- peer's AS number
143/// - `peer_asns` (`PeerAsns(Vec<u32>)`) -- multiple peer AS numbers (OR logic)
144/// - `type` (`Type(ElemType)`) -- message type (`withdraw` or `announce`)
145/// - `ts_start` (`TsStart(f64)`) and `ts_end` (`TsEnd(f64)`) -- start and end unix timestamp
146/// - `as_path` (`ComparableRegex`) -- regular expression for AS path string
147/// - `community` (`ComparableRegex`) -- regular expression for community string
148/// - `ip_version` (`IpVersion`) -- IP version (`ipv4` or `ipv6`)
149/// - `otc` (`OnlyToCustomer(Asn)`) -- only-to-customer (RFC 9234) ASN; `*` for "present", `!*` for "absent"
150/// - `next_hop` (`NextHop(IpAddr)`) -- next hop IP address; `*` for "present", `!*` for "absent"
151/// - `origin` (`Origin(Origin)`) -- origin attribute (`igp`, `egp`, `incomplete`); `*` for "present", `!*` for "absent"
152/// - `local_pref` (`LocalPref(u32)`) -- local preference value; `*` for "present", `!*` for "absent"
153/// - `med` (`Med(u32)`) -- multi-exit discriminator value; `*` for "present", `!*` for "absent"
154/// - `atomic` (`Atomic(bool)`) -- atomic aggregate flag
155/// - `aggr_asn` (`AggrAsn(Asn)`) -- aggregator ASN; `*` for "present", `!*` for "absent"
156/// - `aggr_ip` (`AggrIp(Ipv4Addr)`) -- aggregator IP address; `*` for "present", `!*` for "absent"
157/// - `peer_bgp_id` (`PeerBgpId(Ipv4Addr)`) -- peer BGP identifier (router ID); `*` for "present", `!*` for "absent"
158/// - `present` (`FieldPresent(PresentField)`) -- presence check for optional fields; use `*` as value
159///
160/// **Negative filters**: Most filters support negation by prefixing the filter value with `!`.
161/// For example, `origin_asn=!13335` matches elements where origin AS is NOT 13335.
162/// This creates a `Negated(Box<Filter>)` variant that inverts the match result.
163///
164/// **Presence filters**: Fields that are `Option<T>` support `*` as a wildcard value to check
165/// whether the field is present (`Some`) or absent (`None`). For example, `otc=*` matches
166/// elements that carry an OTC value, while `otc=!*` matches elements without one.
167#[derive(Debug, Clone, PartialEq)]
168pub enum Filter {
169    OriginAsn(u32),
170    OriginAsns(Vec<u32>),
171    Prefix(IpNet, PrefixMatchType),
172    Prefixes(Vec<IpNet>, PrefixMatchType),
173    PeerIp(IpAddr),
174    PeerIps(Vec<IpAddr>),
175    PeerAsn(u32),
176    PeerAsns(Vec<u32>),
177    Type(ElemType),
178    IpVersion(IpVersion),
179    TsStart(f64),
180    TsEnd(f64),
181    AsPath(ComparableRegex),
182    Community(ComparableRegex),
183    OnlyToCustomer(Asn),
184    NextHop(IpAddr),
185    Origin(Origin),
186    LocalPref(u32),
187    Med(u32),
188    Atomic(bool),
189    AggrAsn(Asn),
190    AggrIp(Ipv4Addr),
191    PeerBgpId(Ipv4Addr),
192    /// Matches when the specified optional field is present (`Some`). Use `*` as the
193    /// filter value to construct this, or `!*` to negate (absent).
194    FieldPresent(PresentField),
195    /// Negated filter - matches when the inner filter does NOT match
196    Negated(Box<Filter>),
197}
198
199/// Identifies which optional `BgpElem` field to check for presence.
200///
201/// Constructed by passing `*` as the filter value for any optional field filter type
202/// (e.g. `otc=*`, `next_hop=*`). Negate with `!*` to check for absence.
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum PresentField {
205    OnlyToCustomer,
206    NextHop,
207    Origin,
208    LocalPref,
209    Med,
210    AggrAsn,
211    AggrIp,
212    PeerBgpId,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum IpVersion {
217    Ipv4,
218    Ipv6,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum PrefixMatchType {
223    Exact,
224    IncludeSuper,
225    IncludeSub,
226    IncludeSuperSub,
227}
228
229fn parse_time_str(time_str: &str) -> Option<chrono::NaiveDateTime> {
230    if let Ok(t) = time_str.parse::<f64>() {
231        return chrono::DateTime::from_timestamp(t as i64, 0).map(|t| t.naive_utc());
232    }
233    if let Ok(t) = chrono::DateTime::parse_from_rfc3339(time_str) {
234        return Some(t.naive_utc());
235    }
236    None
237}
238
239fn parse_asn_list(filter_value: &str) -> Result<(Vec<u32>, bool), ParserError> {
240    let mut asns = vec![];
241    let mut all_negated: Option<bool> = None;
242
243    for asn_str in filter_value.replace(' ', "").split(',') {
244        // Skip empty strings (from consecutive or trailing commas)
245        if asn_str.is_empty() {
246            continue;
247        }
248
249        let (is_negated, actual_value) = if let Some(stripped) = asn_str.strip_prefix('!') {
250            (true, stripped)
251        } else {
252            (false, asn_str)
253        };
254
255        // Check for mixed positive/negative values
256        match all_negated {
257            None => all_negated = Some(is_negated),
258            Some(prev) if prev != is_negated => {
259                return Err(FilterError(
260                    "cannot mix positive and negative values in the same filter".to_string(),
261                ));
262            }
263            _ => {}
264        }
265
266        match u32::from_str(actual_value) {
267            Ok(v) => asns.push(v),
268            Err(_) => return Err(FilterError(format!("cannot parse ASN from {actual_value}"))),
269        }
270    }
271    // Validate that at least one ASN was provided
272    if asns.is_empty() {
273        return Err(FilterError(
274            "ASN list filter requires at least one ASN".to_string(),
275        ));
276    }
277    Ok((asns, all_negated.unwrap_or(false)))
278}
279
280fn parse_prefix_list(filter_value: &str) -> Result<(Vec<IpNet>, bool), ParserError> {
281    let mut prefixes = vec![];
282    let mut all_negated: Option<bool> = None;
283
284    for prefix_str in filter_value.replace(' ', "").split(',') {
285        // Skip empty strings (from consecutive or trailing commas)
286        if prefix_str.is_empty() {
287            continue;
288        }
289
290        let (is_negated, actual_value) = if let Some(stripped) = prefix_str.strip_prefix('!') {
291            (true, stripped)
292        } else {
293            (false, prefix_str)
294        };
295
296        // Check for mixed positive/negative values
297        match all_negated {
298            None => all_negated = Some(is_negated),
299            Some(prev) if prev != is_negated => {
300                return Err(FilterError(
301                    "cannot mix positive and negative values in the same filter".to_string(),
302                ));
303            }
304            _ => {}
305        }
306
307        match IpNet::from_str(actual_value) {
308            Ok(v) => prefixes.push(v),
309            Err(_) => {
310                return Err(FilterError(format!(
311                    "cannot parse prefix from {actual_value}"
312                )))
313            }
314        }
315    }
316    // Validate that at least one prefix was provided
317    if prefixes.is_empty() {
318        return Err(FilterError(
319            "prefix list filter requires at least one prefix".to_string(),
320        ));
321    }
322    Ok((prefixes, all_negated.unwrap_or(false)))
323}
324
325fn parse_ip_list(filter_value: &str) -> Result<(Vec<IpAddr>, bool), ParserError> {
326    let mut ips = vec![];
327    let mut all_negated: Option<bool> = None;
328
329    for ip_str in filter_value.replace(' ', "").split(',') {
330        // Skip empty strings (from consecutive or trailing commas)
331        if ip_str.is_empty() {
332            continue;
333        }
334
335        let (is_negated, actual_value) = if let Some(stripped) = ip_str.strip_prefix('!') {
336            (true, stripped)
337        } else {
338            (false, ip_str)
339        };
340
341        // Check for mixed positive/negative values
342        match all_negated {
343            None => all_negated = Some(is_negated),
344            Some(prev) if prev != is_negated => {
345                return Err(FilterError(
346                    "cannot mix positive and negative values in the same filter".to_string(),
347                ));
348            }
349            _ => {}
350        }
351
352        match IpAddr::from_str(actual_value) {
353            Ok(v) => ips.push(v),
354            Err(_) => {
355                return Err(FilterError(format!(
356                    "cannot parse IP address from {actual_value}"
357                )))
358            }
359        }
360    }
361    // Validate that at least one IP was provided
362    if ips.is_empty() {
363        return Err(FilterError(
364            "IP list filter requires at least one IP address".to_string(),
365        ));
366    }
367    Ok((ips, all_negated.unwrap_or(false)))
368}
369
370impl Filter {
371    pub fn new(filter_type: &str, filter_value: &str) -> Result<Filter, ParserError> {
372        // Multi-value filters handle their own negation detection internally
373        // (each value can be prefixed with !, and all must be consistent)
374        let multi_value_filters = [
375            "origin_asns",
376            "prefixes",
377            "prefixes_super",
378            "prefixes_sub",
379            "prefixes_super_sub",
380            "peer_ips",
381            "peer_asns",
382        ];
383
384        if multi_value_filters.contains(&filter_type) {
385            // Pass directly to new_base - it handles negation internally
386            return Self::new_base(filter_type, filter_value);
387        }
388
389        // For single-value filters, check for negation in filter_value (e.g., origin_asn=!13335)
390        let (negated, actual_value) = if let Some(stripped) = filter_value.strip_prefix('!') {
391            // Reject double negation (e.g., "!!13335")
392            if stripped.starts_with('!') {
393                return Err(FilterError(format!(
394                    "invalid filter value '{}': double negation is not allowed",
395                    filter_value
396                )));
397            }
398            (true, stripped)
399        } else {
400            (false, filter_value)
401        };
402
403        // Reject negation for timestamp filters (unintuitive behavior)
404        if negated
405            && (filter_type == "ts_start"
406                || filter_type == "start_ts"
407                || filter_type == "ts_end"
408                || filter_type == "end_ts")
409        {
410            return Err(FilterError(format!(
411                "timestamp filter '{}' does not support negation",
412                filter_type
413            )));
414        }
415
416        // Presence wildcard for optional fields: `field=*` or `field=!*`
417        if actual_value == "*" {
418            if let Some(field) = present_field_for(filter_type) {
419                let present = Filter::FieldPresent(field);
420                return if negated {
421                    Ok(Filter::Negated(Box::new(present)))
422                } else {
423                    Ok(present)
424                };
425            }
426        }
427
428        let base_filter = Self::new_base(filter_type, actual_value)?;
429
430        if negated {
431            Ok(Filter::Negated(Box::new(base_filter)))
432        } else {
433            Ok(base_filter)
434        }
435    }
436
437    fn new_base(filter_type: &str, filter_value: &str) -> Result<Filter, ParserError> {
438        match filter_type {
439            "origin_asn" => match u32::from_str(filter_value) {
440                Ok(v) => Ok(Filter::OriginAsn(v)),
441                Err(_) => Err(FilterError(format!(
442                    "cannot parse origin asn from {filter_value}"
443                ))),
444            },
445            "origin_asns" => {
446                let (asns, negated) = parse_asn_list(filter_value)?;
447                let filter = Filter::OriginAsns(asns);
448                if negated {
449                    Ok(Filter::Negated(Box::new(filter)))
450                } else {
451                    Ok(filter)
452                }
453            }
454            "prefix" => match IpNet::from_str(filter_value) {
455                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::Exact)),
456                Err(_) => Err(FilterError(format!(
457                    "cannot parse prefix from {filter_value}"
458                ))),
459            },
460            "prefix_super" => match IpNet::from_str(filter_value) {
461                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuper)),
462                Err(_) => Err(FilterError(format!(
463                    "cannot parse prefix from {filter_value}"
464                ))),
465            },
466            "prefix_sub" => match IpNet::from_str(filter_value) {
467                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSub)),
468                Err(_) => Err(FilterError(format!(
469                    "cannot parse prefix from {filter_value}"
470                ))),
471            },
472            "prefix_super_sub" => match IpNet::from_str(filter_value) {
473                Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuperSub)),
474                Err(_) => Err(FilterError(format!(
475                    "cannot parse prefix from {filter_value}"
476                ))),
477            },
478            "prefixes" => {
479                let (prefixes, negated) = parse_prefix_list(filter_value)?;
480                let filter = Filter::Prefixes(prefixes, PrefixMatchType::Exact);
481                if negated {
482                    Ok(Filter::Negated(Box::new(filter)))
483                } else {
484                    Ok(filter)
485                }
486            }
487            "prefixes_super" => {
488                let (prefixes, negated) = parse_prefix_list(filter_value)?;
489                let filter = Filter::Prefixes(prefixes, PrefixMatchType::IncludeSuper);
490                if negated {
491                    Ok(Filter::Negated(Box::new(filter)))
492                } else {
493                    Ok(filter)
494                }
495            }
496            "prefixes_sub" => {
497                let (prefixes, negated) = parse_prefix_list(filter_value)?;
498                let filter = Filter::Prefixes(prefixes, PrefixMatchType::IncludeSub);
499                if negated {
500                    Ok(Filter::Negated(Box::new(filter)))
501                } else {
502                    Ok(filter)
503                }
504            }
505            "prefixes_super_sub" => {
506                let (prefixes, negated) = parse_prefix_list(filter_value)?;
507                let filter = Filter::Prefixes(prefixes, PrefixMatchType::IncludeSuperSub);
508                if negated {
509                    Ok(Filter::Negated(Box::new(filter)))
510                } else {
511                    Ok(filter)
512                }
513            }
514            "peer_ip" => match IpAddr::from_str(filter_value) {
515                Ok(v) => Ok(Filter::PeerIp(v)),
516                Err(_) => Err(FilterError(format!(
517                    "cannot parse peer IP from {filter_value}"
518                ))),
519            },
520            "peer_ips" => {
521                let (ips, negated) = parse_ip_list(filter_value)?;
522                let filter = Filter::PeerIps(ips);
523                if negated {
524                    Ok(Filter::Negated(Box::new(filter)))
525                } else {
526                    Ok(filter)
527                }
528            }
529            "peer_asn" => match u32::from_str(filter_value) {
530                Ok(v) => Ok(Filter::PeerAsn(v)),
531                Err(_) => Err(FilterError(format!(
532                    "cannot parse peer asn from {filter_value}"
533                ))),
534            },
535            "peer_asns" => {
536                let (asns, negated) = parse_asn_list(filter_value)?;
537                let filter = Filter::PeerAsns(asns);
538                if negated {
539                    Ok(Filter::Negated(Box::new(filter)))
540                } else {
541                    Ok(filter)
542                }
543            }
544            "type" => match filter_value {
545                "w" | "withdraw" | "withdrawal" => Ok(Filter::Type(ElemType::WITHDRAW)),
546                "a" | "announce" | "announcement" => Ok(Filter::Type(ElemType::ANNOUNCE)),
547                _ => Err(FilterError(format!(
548                    "cannot parse elem type from {filter_value}"
549                ))),
550            },
551            "ts_start" | "start_ts" => match parse_time_str(filter_value) {
552                Some(t) => Ok(Filter::TsStart(t.and_utc().timestamp() as f64)),
553                None => Err(FilterError(format!(
554                    "cannot parse TsStart filter from {filter_value}"
555                ))),
556            },
557            "ts_end" | "end_ts" => match parse_time_str(filter_value) {
558                Some(t) => Ok(Filter::TsEnd(t.and_utc().timestamp() as f64)),
559                None => Err(FilterError(format!(
560                    "cannot parse TsEnd filter from {filter_value}"
561                ))),
562            },
563            "as_path" => match ComparableRegex::new(filter_value) {
564                Ok(v) => Ok(Filter::AsPath(v)),
565                Err(_) => Err(FilterError(format!(
566                    "cannot parse AS path regex from {filter_value}"
567                ))),
568            },
569            "community" => match ComparableRegex::new(filter_value) {
570                Ok(v) => Ok(Filter::Community(v)),
571                Err(_) => Err(FilterError(format!(
572                    "cannot parse Community regex from {filter_value}"
573                ))),
574            },
575            "ip_version" | "ip" => match filter_value {
576                "4" | "v4" | "ipv4" => Ok(Filter::IpVersion(IpVersion::Ipv4)),
577                "6" | "v6" | "ipv6" => Ok(Filter::IpVersion(IpVersion::Ipv6)),
578                _ => Err(FilterError(format!(
579                    "cannot parse IP version from {filter_value}"
580                ))),
581            },
582            "otc" => match u32::from_str(filter_value) {
583                Ok(v) => Ok(Filter::OnlyToCustomer(Asn::from(v))),
584                Err(_) => Err(FilterError(format!(
585                    "cannot parse OTC ASN from {filter_value}"
586                ))),
587            },
588            "next_hop" => match IpAddr::from_str(filter_value) {
589                Ok(v) => Ok(Filter::NextHop(v)),
590                Err(_) => Err(FilterError(format!(
591                    "cannot parse next hop IP from {filter_value}"
592                ))),
593            },
594            "origin" => match filter_value.to_lowercase().as_str() {
595                "igp" => Ok(Filter::Origin(Origin::IGP)),
596                "egp" => Ok(Filter::Origin(Origin::EGP)),
597                "incomplete" => Ok(Filter::Origin(Origin::INCOMPLETE)),
598                _ => Err(FilterError(format!(
599                    "cannot parse origin from {filter_value} (expected: igp, egp, or incomplete)"
600                ))),
601            },
602            "local_pref" => match u32::from_str(filter_value) {
603                Ok(v) => Ok(Filter::LocalPref(v)),
604                Err(_) => Err(FilterError(format!(
605                    "cannot parse local_pref from {filter_value}"
606                ))),
607            },
608            "med" => match u32::from_str(filter_value) {
609                Ok(v) => Ok(Filter::Med(v)),
610                Err(_) => Err(FilterError(format!("cannot parse med from {filter_value}"))),
611            },
612            "atomic" => match filter_value {
613                "true" | "t" | "1" => Ok(Filter::Atomic(true)),
614                "false" | "f" | "0" => Ok(Filter::Atomic(false)),
615                _ => Err(FilterError(format!(
616                    "cannot parse atomic from {filter_value} (expected: true or false)"
617                ))),
618            },
619            "aggr_asn" => match u32::from_str(filter_value) {
620                Ok(v) => Ok(Filter::AggrAsn(Asn::from(v))),
621                Err(_) => Err(FilterError(format!(
622                    "cannot parse aggregator ASN from {filter_value}"
623                ))),
624            },
625            "aggr_ip" => match Ipv4Addr::from_str(filter_value) {
626                Ok(v) => Ok(Filter::AggrIp(v)),
627                Err(_) => Err(FilterError(format!(
628                    "cannot parse aggregator IP from {filter_value}"
629                ))),
630            },
631            "peer_bgp_id" => match Ipv4Addr::from_str(filter_value) {
632                Ok(v) => Ok(Filter::PeerBgpId(v)),
633                Err(_) => Err(FilterError(format!(
634                    "cannot parse peer BGP ID from {filter_value}"
635                ))),
636            },
637            _ => Err(FilterError(format!("unknown filter type: {filter_type}"))),
638        }
639    }
640}
641
642/// Maps a filter type string to its `PresentField` if the field is optional.
643/// Used for the `*` presence wildcard.
644fn present_field_for(filter_type: &str) -> Option<PresentField> {
645    match filter_type {
646        "otc" => Some(PresentField::OnlyToCustomer),
647        "next_hop" => Some(PresentField::NextHop),
648        "origin" => Some(PresentField::Origin),
649        "local_pref" => Some(PresentField::LocalPref),
650        "med" => Some(PresentField::Med),
651        "aggr_asn" => Some(PresentField::AggrAsn),
652        "aggr_ip" => Some(PresentField::AggrIp),
653        "peer_bgp_id" => Some(PresentField::PeerBgpId),
654        _ => None,
655    }
656}
657
658/// Returns true if the filter variant requires full `BgpElem` attributes not available
659/// on `BgpRouteElem`. Used by route-level filtering to fail-closed for both non-negated
660/// and negated elem-only filters.
661fn is_elem_only_filter(filter: &Filter) -> bool {
662    matches!(
663        filter,
664        Filter::Community(_)
665            | Filter::OnlyToCustomer(_)
666            | Filter::NextHop(_)
667            | Filter::Origin(_)
668            | Filter::LocalPref(_)
669            | Filter::Med(_)
670            | Filter::Atomic(_)
671            | Filter::AggrAsn(_)
672            | Filter::AggrIp(_)
673            | Filter::PeerBgpId(_)
674            | Filter::FieldPresent(_)
675    )
676}
677
678/// Returns true if this view carries full `BgpElem` attributes (as opposed to the
679/// lightweight `BgpRouteElem`). Used to decide whether elem-only filters should be
680/// evaluated or fail-closed.
681fn is_full_elem_view<T: RouteFilterView>(view: &T) -> bool {
682    view.supports_community_filter()
683}
684
685pub trait Filterable {
686    fn match_filter(&self, filter: &Filter) -> bool;
687    fn match_filters(&self, filters: &[Filter]) -> bool {
688        filters.iter().all(|f| self.match_filter(f))
689    }
690}
691
692trait RouteFilterView {
693    fn timestamp(&self) -> f64;
694    fn elem_type(&self) -> ElemType;
695    fn peer_ip(&self) -> IpAddr;
696    fn peer_asn(&self) -> Asn;
697    fn prefix(&self) -> &NetworkPrefix;
698    fn as_path(&self) -> Option<&AsPath>;
699    fn matches_origin_asn(&self, asn: Asn) -> bool;
700
701    // --- BgpElem-only fields (default returns None / false for BgpRouteElem) ---
702
703    fn next_hop(&self) -> Option<IpAddr> {
704        None
705    }
706    fn origin(&self) -> Option<Origin> {
707        None
708    }
709    fn local_pref(&self) -> Option<u32> {
710        None
711    }
712    fn med(&self) -> Option<u32> {
713        None
714    }
715    fn atomic(&self) -> bool {
716        false
717    }
718    fn aggr_asn(&self) -> Option<Asn> {
719        None
720    }
721    fn aggr_ip(&self) -> Option<Ipv4Addr> {
722        None
723    }
724    fn peer_bgp_id(&self) -> Option<Ipv4Addr> {
725        None
726    }
727    fn only_to_customer(&self) -> Option<Asn> {
728        None
729    }
730
731    fn matches_community(&self, _regex: &ComparableRegex) -> bool {
732        false
733    }
734
735    fn supports_community_filter(&self) -> bool {
736        false
737    }
738}
739
740const fn same_family(prefix_1: &IpNet, prefix_2: &IpNet) -> bool {
741    matches!(
742        (prefix_1, prefix_2),
743        (IpNet::V4(_), IpNet::V4(_)) | (IpNet::V6(_), IpNet::V6(_))
744    )
745}
746
747fn prefix_match(match_prefix: &IpNet, input_prefix: &IpNet, t: &PrefixMatchType) -> bool {
748    let exact = input_prefix.eq(match_prefix);
749    match t {
750        PrefixMatchType::Exact => exact,
751        PrefixMatchType::IncludeSuper => {
752            if exact {
753                exact
754            } else if !same_family(match_prefix, input_prefix) {
755                // version not match
756                false
757            } else {
758                // input_prefix is super prefix of match_prefix
759                match_prefix.addr() >= input_prefix.addr()
760                    && match_prefix.broadcast() <= input_prefix.broadcast()
761            }
762        }
763        PrefixMatchType::IncludeSub => {
764            if exact {
765                exact
766            } else if !same_family(match_prefix, input_prefix) {
767                // version not match
768                false
769            } else {
770                // input_prefix is sub prefix of match_prefix
771                match_prefix.addr() <= input_prefix.addr()
772                    && match_prefix.broadcast() >= input_prefix.broadcast()
773            }
774        }
775        PrefixMatchType::IncludeSuperSub => {
776            if exact {
777                exact
778            } else if !same_family(match_prefix, input_prefix) {
779                // version not match
780                false
781            } else {
782                // input_prefix is super prefix of match_prefix
783                (match_prefix.addr() >= input_prefix.addr()
784                    && match_prefix.broadcast() <= input_prefix.broadcast())
785                    || (match_prefix.addr() <= input_prefix.addr()
786                        && match_prefix.broadcast() >= input_prefix.broadcast())
787            }
788        }
789    }
790}
791
792fn match_route_view_filter<T: RouteFilterView>(view: &T, filter: &Filter) -> bool {
793    // All elem-only filters (both direct and negated) fail-closed on route-level views.
794    // Without this guard, a non-negated filter like Atomic(false) would incorrectly
795    // match BgpRouteElem because RouteFilterView::atomic() defaults to false.
796    if !is_full_elem_view(view) {
797        let inner = match filter {
798            Filter::Negated(inner) => inner.as_ref(),
799            f => f,
800        };
801        if is_elem_only_filter(inner) {
802            return false;
803        }
804    }
805    match filter {
806        Filter::Negated(inner) => !match_route_view_filter(view, inner),
807        Filter::OriginAsn(v) => view.matches_origin_asn((*v).into()),
808        Filter::OriginAsns(v) => v.iter().any(|asn| view.matches_origin_asn((*asn).into())),
809        Filter::Prefix(v, t) => prefix_match(v, &view.prefix().prefix, t),
810        Filter::Prefixes(v, t) => v
811            .iter()
812            .any(|prefix| prefix_match(prefix, &view.prefix().prefix, t)),
813        Filter::PeerIp(v) => view.peer_ip() == *v,
814        Filter::PeerIps(v) => v.contains(&view.peer_ip()),
815        Filter::PeerAsn(v) => view.peer_asn().eq(v),
816        Filter::PeerAsns(v) => v.iter().any(|asn| view.peer_asn().eq(asn)),
817        Filter::Type(v) => view.elem_type().eq(v),
818        Filter::TsStart(v) => view.timestamp() >= *v,
819        Filter::TsEnd(v) => view.timestamp() <= *v,
820        Filter::AsPath(v) => view
821            .as_path()
822            .map(|path| v.is_match(path.to_string().as_str()))
823            .unwrap_or(false),
824        Filter::Community(r) => view.supports_community_filter() && view.matches_community(r),
825        Filter::IpVersion(version) => match version {
826            IpVersion::Ipv4 => view.prefix().prefix.addr().is_ipv4(),
827            IpVersion::Ipv6 => view.prefix().prefix.addr().is_ipv6(),
828        },
829        Filter::OnlyToCustomer(v) => view.only_to_customer() == Some(*v),
830        Filter::NextHop(v) => view.next_hop() == Some(*v),
831        Filter::Origin(v) => view.origin() == Some(*v),
832        Filter::LocalPref(v) => view.local_pref() == Some(*v),
833        Filter::Med(v) => view.med() == Some(*v),
834        Filter::Atomic(v) => view.atomic() == *v,
835        Filter::AggrAsn(v) => view.aggr_asn() == Some(*v),
836        Filter::AggrIp(v) => view.aggr_ip() == Some(*v),
837        Filter::PeerBgpId(v) => view.peer_bgp_id() == Some(*v),
838        Filter::FieldPresent(field) => match field {
839            PresentField::OnlyToCustomer => view.only_to_customer().is_some(),
840            PresentField::NextHop => view.next_hop().is_some(),
841            PresentField::Origin => view.origin().is_some(),
842            PresentField::LocalPref => view.local_pref().is_some(),
843            PresentField::Med => view.med().is_some(),
844            PresentField::AggrAsn => view.aggr_asn().is_some(),
845            PresentField::AggrIp => view.aggr_ip().is_some(),
846            PresentField::PeerBgpId => view.peer_bgp_id().is_some(),
847        },
848    }
849}
850
851impl RouteFilterView for BgpElem {
852    fn timestamp(&self) -> f64 {
853        self.timestamp
854    }
855
856    fn elem_type(&self) -> ElemType {
857        self.elem_type
858    }
859
860    fn peer_ip(&self) -> IpAddr {
861        self.peer_ip
862    }
863
864    fn peer_asn(&self) -> Asn {
865        self.peer_asn
866    }
867
868    fn prefix(&self) -> &NetworkPrefix {
869        &self.prefix
870    }
871
872    fn as_path(&self) -> Option<&AsPath> {
873        self.as_path.as_ref()
874    }
875
876    fn matches_origin_asn(&self, asn: Asn) -> bool {
877        self.origin_asns
878            .as_ref()
879            .map(|origins| origins.contains(&asn))
880            .unwrap_or(false)
881    }
882
883    fn next_hop(&self) -> Option<IpAddr> {
884        self.next_hop
885    }
886    fn origin(&self) -> Option<Origin> {
887        self.origin
888    }
889    fn local_pref(&self) -> Option<u32> {
890        self.local_pref
891    }
892    fn med(&self) -> Option<u32> {
893        self.med
894    }
895    fn atomic(&self) -> bool {
896        self.atomic
897    }
898    fn aggr_asn(&self) -> Option<Asn> {
899        self.aggr_asn
900    }
901    fn aggr_ip(&self) -> Option<Ipv4Addr> {
902        self.aggr_ip
903    }
904    fn peer_bgp_id(&self) -> Option<Ipv4Addr> {
905        self.peer_bgp_id
906    }
907    fn only_to_customer(&self) -> Option<Asn> {
908        self.only_to_customer
909    }
910
911    fn matches_community(&self, regex: &ComparableRegex) -> bool {
912        self.communities
913            .as_ref()
914            .map(|communities| communities.iter().any(|c| regex.is_match(c.to_string())))
915            .unwrap_or(false)
916    }
917
918    fn supports_community_filter(&self) -> bool {
919        true
920    }
921}
922
923impl RouteFilterView for BgpRouteElem {
924    fn timestamp(&self) -> f64 {
925        self.timestamp
926    }
927
928    fn elem_type(&self) -> ElemType {
929        self.elem_type
930    }
931
932    fn peer_ip(&self) -> IpAddr {
933        self.peer_ip
934    }
935
936    fn peer_asn(&self) -> Asn {
937        self.peer_asn
938    }
939
940    fn prefix(&self) -> &NetworkPrefix {
941        &self.prefix
942    }
943
944    fn as_path(&self) -> Option<&AsPath> {
945        self.as_path.as_deref()
946    }
947
948    fn matches_origin_asn(&self, asn: Asn) -> bool {
949        self.as_path
950            .as_ref()
951            .map(|path| path.iter_origins().any(|origin| origin == asn))
952            .unwrap_or(false)
953    }
954}
955
956impl Filterable for BgpElem {
957    fn match_filter(&self, filter: &Filter) -> bool {
958        match_route_view_filter(self, filter)
959    }
960}
961
962impl Filterable for BgpRouteElem {
963    fn match_filter(&self, filter: &Filter) -> bool {
964        match_route_view_filter(self, filter)
965    }
966}
967
968#[cfg(test)]
969mod tests {
970    use super::*;
971    use crate::BgpkitParser;
972    use anyhow::Result;
973    use std::str::FromStr;
974    use std::sync::Arc;
975
976    fn filter_test_elem() -> BgpElem {
977        BgpElem {
978            timestamp: 1637437798_f64,
979            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
980            peer_asn: Asn::new_32bit(12345),
981            peer_bgp_id: None,
982            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
983            next_hop: None,
984            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
985            origin_asns: Some(vec![Asn::new_32bit(52888)]),
986            origin: None,
987            local_pref: None,
988            med: None,
989            communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
990                12345,
991                [678910, 111213],
992            ))]),
993            atomic: false,
994            aggr_asn: None,
995            aggr_ip: None,
996            only_to_customer: None,
997            unknown: None,
998            elem_type: ElemType::ANNOUNCE,
999            deprecated: None,
1000        }
1001    }
1002
1003    fn route_projection(elem: &BgpElem) -> BgpRouteElem {
1004        BgpRouteElem {
1005            timestamp: elem.timestamp,
1006            elem_type: elem.elem_type,
1007            peer_ip: elem.peer_ip,
1008            peer_asn: elem.peer_asn,
1009            prefix: elem.prefix,
1010            as_path: elem.as_path.clone().map(Arc::new),
1011        }
1012    }
1013
1014    #[test]
1015    fn test_route_community_filters_fail_closed() {
1016        let elem = filter_test_elem();
1017        let route = route_projection(&elem);
1018        let community = Filter::new("community", r"12345:.*").unwrap();
1019        let negated_community = Filter::new("community", r"!12345:.*").unwrap();
1020
1021        assert!(elem.match_filter(&community));
1022        assert!(!elem.match_filter(&negated_community));
1023        assert!(!route.match_filter(&community));
1024        assert!(!route.match_filter(&negated_community));
1025    }
1026
1027    #[test]
1028    fn test_filters_on_mrt_file() {
1029        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1030        let parser = BgpkitParser::new(url).unwrap();
1031        let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
1032
1033        let filters = vec![Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap())];
1034        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1035        assert_eq!(count, 3393);
1036
1037        let filters = vec![
1038            Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap()),
1039            Filter::Prefix(
1040                IpNet::from_str("190.115.192.0/22").unwrap(),
1041                PrefixMatchType::Exact,
1042            ),
1043        ];
1044        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1045        assert_eq!(count, 5);
1046
1047        let filters = vec![Filter::Prefix(
1048            IpNet::from_str("190.115.192.0/24").unwrap(),
1049            PrefixMatchType::IncludeSuper,
1050        )];
1051        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1052        assert_eq!(count, 18);
1053
1054        let filters = vec![Filter::Prefix(
1055            IpNet::from_str("190.115.192.0/22").unwrap(),
1056            PrefixMatchType::IncludeSub,
1057        )];
1058        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1059        assert_eq!(count, 42);
1060
1061        let filters = vec![Filter::Prefix(
1062            IpNet::from_str("190.115.192.0/23").unwrap(),
1063            PrefixMatchType::IncludeSuperSub,
1064        )];
1065        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1066        assert_eq!(count, 24);
1067
1068        let filters = vec![Filter::new("as_path", r" ?174 1916 52888$").unwrap()];
1069        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1070        assert_eq!(count, 12);
1071
1072        // filter by community starting with some value
1073        let filters = vec![Filter::new("community", r"60924:.*").unwrap()];
1074        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1075        assert_eq!(count, 4243);
1076
1077        // filter by community ending with some value
1078        let filters = vec![Filter::new("community", r".+:784$").unwrap()];
1079        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1080        assert_eq!(count, 107);
1081
1082        // filter by community with large community (i.e. with 3 values, separated by ':')
1083        let filters = vec![Filter::new("community", r"\d+:\d+:\d+$").unwrap()];
1084        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1085        assert_eq!(count, 4397);
1086
1087        let filters = vec![
1088            Filter::TsStart(1637437798_f64),
1089            Filter::TsEnd(1637437798_f64),
1090        ];
1091        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1092        assert_eq!(count, 13);
1093
1094        let filters = vec![Filter::Type(ElemType::WITHDRAW)];
1095        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1096        assert_eq!(count, 379);
1097
1098        let filters = vec![
1099            Filter::Type(ElemType::WITHDRAW),
1100            Filter::Prefix(
1101                IpNet::from_str("2804:100::/32").unwrap(),
1102                PrefixMatchType::Exact,
1103            ),
1104        ];
1105        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1106        assert_eq!(count, 1);
1107
1108        // test filtering by multiple peers
1109        /*
1110        1167 185.1.8.3
1111        1563 185.1.8.50
1112        3393 185.1.8.65
1113          51 185.1.8.89
1114         834 2001:7f8:73:0:3:fa4:0:1
1115          94 2001:7f8:73::c2a8:0:1
1116        1058 2001:7f8:73::edfc:0:2
1117         */
1118        let filters = vec![Filter::PeerIps(vec![
1119            IpAddr::from_str("185.1.8.65").unwrap(),
1120            IpAddr::from_str("2001:7f8:73:0:3:fa4:0:1").unwrap(),
1121        ])];
1122        let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
1123        assert_eq!(count, 3393 + 834);
1124    }
1125
1126    #[test]
1127    fn test_filter_incorrect_filters() {
1128        // filter by community with large community (i.e. with 3 values, separated by ':')
1129        let incorrect_filters = [
1130            Filter::new("community", r"[abc"),
1131            Filter::new("as_path", r"[0-9"),
1132            Filter::new("prefix_super_sub", "-192.-168.-1.1/24"),
1133        ];
1134        assert!(incorrect_filters
1135            .iter()
1136            .all(|f| matches!(f, Err(FilterError(_)))));
1137    }
1138
1139    #[test]
1140    fn test_parsing_time_str() {
1141        let ts = chrono::NaiveDateTime::from_str("2021-11-20T19:49:58").unwrap();
1142        assert_eq!(parse_time_str("1637437798"), Some(ts));
1143        assert_eq!(parse_time_str("2021-11-20T19:49:58Z"), Some(ts));
1144        assert_eq!(parse_time_str("2021-11-20T19:49:58+00:00"), Some(ts));
1145
1146        assert_eq!(parse_time_str("2021-11-20T19:49:58"), None);
1147        assert_eq!(parse_time_str("2021-11-20T19:49:58ZDXV"), None);
1148        assert_eq!(parse_time_str("2021-11-20 19:49:58"), None);
1149        assert_eq!(parse_time_str("2021-11-20"), None);
1150    }
1151
1152    #[test]
1153    fn test_filter_iter() -> Result<()> {
1154        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1155        let parser = BgpkitParser::new(url)?
1156            .add_filter("peer_ip", "185.1.8.50")?
1157            .add_filter("type", "w")?;
1158        let count = parser.into_elem_iter().count();
1159        assert_eq!(count, 39);
1160
1161        let parser = BgpkitParser::new(url)?
1162            .add_filter("ts_start", "1637437798")?
1163            .add_filter("ts_end", "2021-11-20T19:49:58Z")?;
1164        let count = parser.into_elem_iter().count();
1165        assert_eq!(count, 13);
1166        Ok(())
1167    }
1168
1169    #[test]
1170    fn test_filter_iter_with_negation() -> Result<()> {
1171        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1172
1173        // Test negative filter with add_filter - exclude peer 185.1.8.65
1174        // From test_filters_on_mrt_file, peer 185.1.8.65 has 3393 elements out of 8160 total
1175        let parser = BgpkitParser::new(url)?.add_filter("peer_ip", "!185.1.8.65")?;
1176        let count = parser.into_elem_iter().count();
1177        assert_eq!(count, 8160 - 3393);
1178
1179        // Test negative type filter - get all non-withdrawals
1180        // From test_filters_on_mrt_file, there are 379 withdrawals out of 8160 total
1181        let parser = BgpkitParser::new(url)?.add_filter("type", "!w")?;
1182        let count = parser.into_elem_iter().count();
1183        assert_eq!(count, 8160 - 379);
1184
1185        // Test combining positive and negative filters
1186        // Get elements from peer 185.1.8.50 that are NOT withdrawals
1187        let parser = BgpkitParser::new(url)?
1188            .add_filter("peer_ip", "185.1.8.50")?
1189            .add_filter("type", "!w")?;
1190        let count = parser.into_elem_iter().count();
1191        // peer 185.1.8.50 has 1563 total, 39 withdrawals -> 1563 - 39 = 1524 non-withdrawals
1192        assert_eq!(count, 1563 - 39);
1193
1194        Ok(())
1195    }
1196
1197    #[test]
1198    fn test_filter_iter_multi_peers() {
1199        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1200        let parser = BgpkitParser::new(url)
1201            .unwrap()
1202            .add_filter("peer_ips", "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1")
1203            .unwrap();
1204        let count = parser.into_elem_iter().count();
1205        assert_eq!(count, 3393 + 834);
1206    }
1207
1208    #[test]
1209    fn test_prefix_match() {
1210        // network
1211        let p1 = IpNet::from_str("10.1.1.0/24").unwrap();
1212        let p1_exact = IpNet::from_str("10.1.1.0/24").unwrap();
1213        let p1_super = IpNet::from_str("10.1.0.0/16").unwrap();
1214        let p1_sub = IpNet::from_str("10.1.1.0/25").unwrap();
1215
1216        let p2 = IpNet::from_str("2001:0DB8:0000:000b::/64").unwrap();
1217
1218        // exact
1219        assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::Exact));
1220        assert!(!prefix_match(&p1, &p1_sub, &PrefixMatchType::Exact));
1221        assert!(!prefix_match(&p1, &p1_super, &PrefixMatchType::Exact));
1222        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::Exact));
1223
1224        // include super
1225        assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSuper));
1226        assert!(!prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSuper));
1227        assert!(prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSuper));
1228        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuper));
1229
1230        // include sub
1231        assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSub));
1232        assert!(prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSub));
1233        assert!(!prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSub));
1234        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSub));
1235
1236        // include both
1237        assert!(prefix_match(
1238            &p1,
1239            &p1_exact,
1240            &PrefixMatchType::IncludeSuperSub
1241        ));
1242        assert!(prefix_match(
1243            &p1,
1244            &p1_sub,
1245            &PrefixMatchType::IncludeSuperSub
1246        ));
1247        assert!(prefix_match(
1248            &p1,
1249            &p1_super,
1250            &PrefixMatchType::IncludeSuperSub
1251        ));
1252        assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuperSub));
1253    }
1254
1255    #[test]
1256    fn test_filter_new() {
1257        let filter = Filter::new("origin_asn", "12345").unwrap();
1258        assert_eq!(filter, Filter::OriginAsn(12345));
1259
1260        // Test negated filters (value-based negation syntax)
1261        let filter = Filter::new("origin_asn", "!12345").unwrap();
1262        assert_eq!(filter, Filter::Negated(Box::new(Filter::OriginAsn(12345))));
1263
1264        let filter = Filter::new("prefix", "!192.168.1.0/24").unwrap();
1265        assert_eq!(
1266            filter,
1267            Filter::Negated(Box::new(Filter::Prefix(
1268                IpNet::from_str("192.168.1.0/24").unwrap(),
1269                PrefixMatchType::Exact
1270            )))
1271        );
1272
1273        let filter = Filter::new("peer_ip", "!192.168.1.1").unwrap();
1274        assert_eq!(
1275            filter,
1276            Filter::Negated(Box::new(Filter::PeerIp(
1277                IpAddr::from_str("192.168.1.1").unwrap()
1278            )))
1279        );
1280
1281        let filter = Filter::new("peer_asn", "!12345").unwrap();
1282        assert_eq!(filter, Filter::Negated(Box::new(Filter::PeerAsn(12345))));
1283
1284        let filter = Filter::new("type", "!w").unwrap();
1285        assert_eq!(
1286            filter,
1287            Filter::Negated(Box::new(Filter::Type(ElemType::WITHDRAW)))
1288        );
1289
1290        let filter = Filter::new("ip_version", "!4").unwrap();
1291        assert_eq!(
1292            filter,
1293            Filter::Negated(Box::new(Filter::IpVersion(IpVersion::Ipv4)))
1294        );
1295
1296        let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
1297        assert_eq!(
1298            filter,
1299            Filter::Prefix(
1300                IpNet::from_str("192.168.1.0/24").unwrap(),
1301                PrefixMatchType::Exact
1302            )
1303        );
1304        let filter = Filter::new("prefix_super", "192.168.1.0/24").unwrap();
1305        assert_eq!(
1306            filter,
1307            Filter::Prefix(
1308                IpNet::from_str("192.168.1.0/24").unwrap(),
1309                PrefixMatchType::IncludeSuper
1310            )
1311        );
1312        let filter = Filter::new("prefix_sub", "192.168.1.0/24").unwrap();
1313        assert_eq!(
1314            filter,
1315            Filter::Prefix(
1316                IpNet::from_str("192.168.1.0/24").unwrap(),
1317                PrefixMatchType::IncludeSub
1318            )
1319        );
1320        let filter = Filter::new("prefix_super_sub", "192.168.1.0/24").unwrap();
1321        assert_eq!(
1322            filter,
1323            Filter::Prefix(
1324                IpNet::from_str("192.168.1.0/24").unwrap(),
1325                PrefixMatchType::IncludeSuperSub
1326            )
1327        );
1328
1329        let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
1330        assert_eq!(
1331            filter,
1332            Filter::PeerIp(IpAddr::from_str("192.168.1.1").unwrap())
1333        );
1334
1335        let filter = Filter::new("peer_asn", "12345").unwrap();
1336        assert_eq!(filter, Filter::PeerAsn(12345));
1337
1338        let filter = Filter::new("type", "w").unwrap();
1339        assert_eq!(filter, Filter::Type(ElemType::WITHDRAW));
1340
1341        let filter = Filter::new("ts_start", "1637437798").unwrap();
1342        assert_eq!(filter, Filter::TsStart(1637437798_f64));
1343
1344        let filter = Filter::new("ts_end", "1637437798").unwrap();
1345        assert_eq!(filter, Filter::TsEnd(1637437798_f64));
1346
1347        let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
1348        assert_eq!(
1349            filter,
1350            Filter::AsPath(ComparableRegex::new(r" ?174 1916 52888$").unwrap())
1351        );
1352
1353        assert!(Filter::new("origin_asn", "not a number").is_err());
1354        assert!(Filter::new("peer_asn", "not a number").is_err());
1355        assert!(Filter::new("ts_start", "not a number").is_err());
1356        assert!(Filter::new("ts_end", "not a number").is_err());
1357        assert!(Filter::new("prefix", "not a prefix").is_err());
1358        assert!(Filter::new("prefix_super", "not a prefix").is_err());
1359        assert!(Filter::new("prefix_sub", "not a prefix").is_err());
1360        assert!(Filter::new("peer_ip", "not a IP").is_err());
1361        assert!(Filter::new("peer_ips", "not,a,IP").is_err());
1362        assert!(Filter::new("type", "not a type").is_err());
1363        assert!(Filter::new("as_path", "[abc").is_err());
1364        assert!(Filter::new("ip_version", "5").is_err());
1365        assert!(Filter::new("unknown_filter", "some_value").is_err());
1366    }
1367
1368    #[test]
1369    fn test_filterable_match_filter() {
1370        let elem = filter_test_elem();
1371
1372        let mut filters = vec![];
1373
1374        let filter = Filter::new("origin_asn", "52888").unwrap();
1375        filters.push(filter.clone());
1376        assert!(elem.match_filter(&filter));
1377
1378        let filter = Filter::new("origin_asn", "678910").unwrap();
1379        assert!(!elem.match_filter(&filter));
1380
1381        let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
1382        filters.push(filter.clone());
1383        assert!(elem.match_filter(&filter));
1384
1385        let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
1386        filters.push(filter.clone());
1387        assert!(elem.match_filter(&filter));
1388
1389        let filter = Filter::new("peer_asn", "12345").unwrap();
1390        filters.push(filter.clone());
1391        assert!(elem.match_filter(&filter));
1392
1393        let filter = Filter::new("type", "a").unwrap();
1394        filters.push(filter.clone());
1395        assert!(elem.match_filter(&filter));
1396
1397        let filter = Filter::new("ts_start", "1637437798").unwrap();
1398        filters.push(filter.clone());
1399        assert!(elem.match_filter(&filter));
1400
1401        let filter = Filter::new("ts_end", "1637437798").unwrap();
1402        filters.push(filter.clone());
1403        assert!(elem.match_filter(&filter));
1404
1405        let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
1406        filters.push(filter.clone());
1407        assert!(elem.match_filter(&filter));
1408
1409        let filter = Filter::new("ip_version", "4").unwrap();
1410        filters.push(filter.clone());
1411        assert!(elem.match_filter(&filter));
1412
1413        let filter = Filter::new("ip", "ipv6").unwrap();
1414        assert!(!elem.match_filter(&filter));
1415
1416        let filter = Filter::new("community", r"12345:678910:111213$").unwrap();
1417        filters.push(filter.clone());
1418        assert!(elem.match_filter(&filter));
1419
1420        assert!(elem.match_filters(&filters));
1421    }
1422
1423    #[test]
1424    fn test_route_filterable_matches_elem_for_route_level_filters() {
1425        let elem = filter_test_elem();
1426        let route = route_projection(&elem);
1427
1428        let cases = vec![
1429            (
1430                "origin_asn matches",
1431                Filter::new("origin_asn", "52888").unwrap(),
1432                true,
1433            ),
1434            (
1435                "origin_asn misses",
1436                Filter::new("origin_asn", "64496").unwrap(),
1437                false,
1438            ),
1439            (
1440                "origin_asns matches",
1441                Filter::new("origin_asns", "64496,52888").unwrap(),
1442                true,
1443            ),
1444            (
1445                "origin_asns misses",
1446                Filter::new("origin_asns", "64496,64497").unwrap(),
1447                false,
1448            ),
1449            (
1450                "prefix exact matches",
1451                Filter::new("prefix", "192.168.1.0/24").unwrap(),
1452                true,
1453            ),
1454            (
1455                "prefix exact misses",
1456                Filter::new("prefix", "192.168.2.0/24").unwrap(),
1457                false,
1458            ),
1459            (
1460                "prefix_super matches",
1461                Filter::new("prefix_super", "192.168.1.128/25").unwrap(),
1462                true,
1463            ),
1464            (
1465                "prefix_sub matches",
1466                Filter::new("prefix_sub", "192.168.0.0/23").unwrap(),
1467                true,
1468            ),
1469            (
1470                "prefix_super_sub matches",
1471                Filter::new("prefix_super_sub", "192.168.1.128/25").unwrap(),
1472                true,
1473            ),
1474            (
1475                "prefixes matches",
1476                Filter::new("prefixes", "10.0.0.0/8,192.168.1.0/24").unwrap(),
1477                true,
1478            ),
1479            (
1480                "peer_ip matches",
1481                Filter::new("peer_ip", "192.168.1.1").unwrap(),
1482                true,
1483            ),
1484            (
1485                "peer_ips matches",
1486                Filter::new("peer_ips", "192.168.1.2,192.168.1.1").unwrap(),
1487                true,
1488            ),
1489            (
1490                "peer_asn matches",
1491                Filter::new("peer_asn", "12345").unwrap(),
1492                true,
1493            ),
1494            (
1495                "peer_asns matches",
1496                Filter::new("peer_asns", "12346,12345").unwrap(),
1497                true,
1498            ),
1499            ("type matches", Filter::new("type", "a").unwrap(), true),
1500            ("type misses", Filter::new("type", "w").unwrap(), false),
1501            (
1502                "ts_start matches",
1503                Filter::new("ts_start", "1637437797").unwrap(),
1504                true,
1505            ),
1506            (
1507                "ts_end matches",
1508                Filter::new("ts_end", "1637437799").unwrap(),
1509                true,
1510            ),
1511            (
1512                "as_path matches",
1513                Filter::new("as_path", r"174 1916 52888$").unwrap(),
1514                true,
1515            ),
1516            (
1517                "as_path misses",
1518                Filter::new("as_path", r"64496$").unwrap(),
1519                false,
1520            ),
1521            (
1522                "ip_version matches",
1523                Filter::new("ip_version", "4").unwrap(),
1524                true,
1525            ),
1526            (
1527                "ip_version misses",
1528                Filter::new("ip_version", "6").unwrap(),
1529                false,
1530            ),
1531            (
1532                "negated origin_asn matches",
1533                Filter::new("origin_asn", "!64496").unwrap(),
1534                true,
1535            ),
1536            (
1537                "negated origin_asn misses",
1538                Filter::new("origin_asn", "!52888").unwrap(),
1539                false,
1540            ),
1541        ];
1542
1543        for (name, filter, expected) in cases {
1544            assert_eq!(elem.match_filter(&filter), expected, "{name} BgpElem");
1545            assert_eq!(route.match_filter(&filter), expected, "{name} BgpRouteElem");
1546            assert_eq!(
1547                elem.match_filter(&filter),
1548                route.match_filter(&filter),
1549                "{name} parity"
1550            );
1551        }
1552
1553        let filters = vec![
1554            Filter::new("origin_asn", "52888").unwrap(),
1555            Filter::new("peer_asn", "!64496").unwrap(),
1556            Filter::new("prefix_super", "192.168.1.128/25").unwrap(),
1557        ];
1558        assert!(elem.match_filters(&filters));
1559        assert_eq!(elem.match_filters(&filters), route.match_filters(&filters));
1560    }
1561
1562    #[test]
1563    fn test_route_filterable_does_not_match_community_filters() {
1564        let elem = filter_test_elem();
1565        let route = route_projection(&elem);
1566        let filter = Filter::new("community", r"12345:678910:111213$").unwrap();
1567
1568        assert!(elem.match_filter(&filter));
1569        assert!(!route.match_filter(&filter));
1570    }
1571
1572    #[test]
1573    fn test_negated_filters() {
1574        let elem = BgpElem {
1575            timestamp: 1637437798_f64,
1576            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
1577            peer_asn: Asn::new_32bit(12345),
1578            peer_bgp_id: None,
1579            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
1580            next_hop: None,
1581            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
1582            origin_asns: Some(vec![Asn::new_16bit(12345)]),
1583            origin: None,
1584            local_pref: None,
1585            med: None,
1586            communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
1587                12345,
1588                [678910, 111213],
1589            ))]),
1590            atomic: false,
1591            aggr_asn: None,
1592            aggr_ip: None,
1593            only_to_customer: None,
1594            unknown: None,
1595            elem_type: ElemType::ANNOUNCE,
1596            deprecated: None,
1597        };
1598
1599        // Test negated origin_asn filter (using value-based negation: origin_asn=!12345)
1600        // elem has origin_asn 12345, so origin_asn=!12345 should NOT match
1601        let filter = Filter::new("origin_asn", "!12345").unwrap();
1602        assert!(!elem.match_filter(&filter));
1603
1604        // elem has origin_asn 12345, so origin_asn=!99999 should match
1605        let filter = Filter::new("origin_asn", "!99999").unwrap();
1606        assert!(elem.match_filter(&filter));
1607
1608        // Test negated prefix filter
1609        // elem has prefix 192.168.1.0/24, so prefix=!192.168.1.0/24 should NOT match
1610        let filter = Filter::new("prefix", "!192.168.1.0/24").unwrap();
1611        assert!(!elem.match_filter(&filter));
1612
1613        // elem has prefix 192.168.1.0/24, so prefix=!10.0.0.0/8 should match
1614        let filter = Filter::new("prefix", "!10.0.0.0/8").unwrap();
1615        assert!(elem.match_filter(&filter));
1616
1617        // Test negated peer_ip filter
1618        // elem has peer_ip 192.168.1.1, so peer_ip=!192.168.1.1 should NOT match
1619        let filter = Filter::new("peer_ip", "!192.168.1.1").unwrap();
1620        assert!(!elem.match_filter(&filter));
1621
1622        // elem has peer_ip 192.168.1.1, so peer_ip=!10.0.0.1 should match
1623        let filter = Filter::new("peer_ip", "!10.0.0.1").unwrap();
1624        assert!(elem.match_filter(&filter));
1625
1626        // Test negated peer_asn filter
1627        // elem has peer_asn 12345, so peer_asn=!12345 should NOT match
1628        let filter = Filter::new("peer_asn", "!12345").unwrap();
1629        assert!(!elem.match_filter(&filter));
1630
1631        // elem has peer_asn 12345, so peer_asn=!99999 should match
1632        let filter = Filter::new("peer_asn", "!99999").unwrap();
1633        assert!(elem.match_filter(&filter));
1634
1635        // Test negated type filter
1636        // elem has type ANNOUNCE, so type=!a should NOT match
1637        let filter = Filter::new("type", "!a").unwrap();
1638        assert!(!elem.match_filter(&filter));
1639
1640        // elem has type ANNOUNCE, so type=!w should match
1641        let filter = Filter::new("type", "!w").unwrap();
1642        assert!(elem.match_filter(&filter));
1643
1644        // Test negated ip_version filter
1645        // elem has IPv4 prefix, so ip_version=!4 should NOT match
1646        let filter = Filter::new("ip_version", "!4").unwrap();
1647        assert!(!elem.match_filter(&filter));
1648
1649        // elem has IPv4 prefix, so ip_version=!6 should match
1650        let filter = Filter::new("ip_version", "!6").unwrap();
1651        assert!(elem.match_filter(&filter));
1652
1653        // Test negated as_path filter
1654        // elem has as_path "174 1916 52888", so negated matching regex should NOT match
1655        let filter = Filter::new("as_path", r"!174 1916 52888$").unwrap();
1656        assert!(!elem.match_filter(&filter));
1657
1658        // elem has as_path "174 1916 52888", so negated non-matching regex should match
1659        let filter = Filter::new("as_path", r"!99999$").unwrap();
1660        assert!(elem.match_filter(&filter));
1661
1662        // Test negated community filter
1663        let filter = Filter::new("community", r"!12345:678910:111213$").unwrap();
1664        assert!(!elem.match_filter(&filter));
1665
1666        let filter = Filter::new("community", r"!99999:99999$").unwrap();
1667        assert!(elem.match_filter(&filter));
1668
1669        // Test negated peer_ips filter (multi-value uses !value,!value syntax)
1670        let filter = Filter::new("peer_ips", "!192.168.1.1, !10.0.0.1").unwrap();
1671        assert!(!elem.match_filter(&filter)); // elem's peer_ip is in the list
1672
1673        let filter = Filter::new("peer_ips", "!10.0.0.1, !10.0.0.2").unwrap();
1674        assert!(elem.match_filter(&filter)); // elem's peer_ip is NOT in the list
1675
1676        // Test combining positive and negated filters
1677        let filters = vec![
1678            Filter::new("origin_asn", "12345").unwrap(),   // matches
1679            Filter::new("peer_asn", "!99999").unwrap(),    // matches (not 99999)
1680            Filter::new("prefix", "!10.0.0.0/8").unwrap(), // matches (not 10.0.0.0/8)
1681        ];
1682        assert!(elem.match_filters(&filters));
1683
1684        // Test combining filters where one fails
1685        let filters = vec![
1686            Filter::new("origin_asn", "12345").unwrap(),  // matches
1687            Filter::new("origin_asn", "!12345").unwrap(), // does NOT match
1688        ];
1689        assert!(!elem.match_filters(&filters));
1690    }
1691
1692    #[test]
1693    fn test_negated_filters_on_mrt_file() {
1694        let url = "https://spaces.bgpkit.org/parser/update-example.gz";
1695        let parser = BgpkitParser::new(url).unwrap();
1696        let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
1697
1698        // Count all elems from peer 185.1.8.65
1699        let filters = vec![Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap())];
1700        let count_with_peer = elems.iter().filter(|e| e.match_filters(&filters)).count();
1701        assert_eq!(count_with_peer, 3393);
1702
1703        // Count all elems NOT from peer 185.1.8.65 (using value-based negation)
1704        let filters = vec![Filter::new("peer_ip", "!185.1.8.65").unwrap()];
1705        let count_without_peer = elems.iter().filter(|e| e.match_filters(&filters)).count();
1706        assert_eq!(count_without_peer, elems.len() - 3393);
1707
1708        // Verify total adds up
1709        assert_eq!(count_with_peer + count_without_peer, elems.len());
1710
1711        // Test negated type filter
1712        let filters = vec![Filter::Type(ElemType::WITHDRAW)];
1713        let count_withdrawals = elems.iter().filter(|e| e.match_filters(&filters)).count();
1714        assert_eq!(count_withdrawals, 379);
1715
1716        let filters = vec![Filter::new("type", "!w").unwrap()];
1717        let count_not_withdrawals = elems.iter().filter(|e| e.match_filters(&filters)).count();
1718        assert_eq!(count_not_withdrawals, elems.len() - 379);
1719
1720        // Test negated prefix filter (using value-based negation)
1721        let filters = vec![Filter::Prefix(
1722            IpNet::from_str("190.115.192.0/22").unwrap(),
1723            PrefixMatchType::Exact,
1724        )];
1725        let count_with_prefix = elems.iter().filter(|e| e.match_filters(&filters)).count();
1726
1727        let filters = vec![Filter::new("prefix", "!190.115.192.0/22").unwrap()];
1728        let count_without_prefix = elems.iter().filter(|e| e.match_filters(&filters)).count();
1729        assert_eq!(count_with_prefix + count_without_prefix, elems.len());
1730
1731        // Test negated prefix_super filter (using value-based negation)
1732        let filters = vec![Filter::Prefix(
1733            IpNet::from_str("190.115.192.0/24").unwrap(),
1734            PrefixMatchType::IncludeSuper,
1735        )];
1736        let count_with_super = elems.iter().filter(|e| e.match_filters(&filters)).count();
1737
1738        let filters = vec![Filter::new("prefix_super", "!190.115.192.0/24").unwrap()];
1739        let count_without_super = elems.iter().filter(|e| e.match_filters(&filters)).count();
1740        assert_eq!(count_with_super + count_without_super, elems.len());
1741
1742        // Test negated prefix_sub filter (using value-based negation)
1743        let filters = vec![Filter::Prefix(
1744            IpNet::from_str("190.115.192.0/22").unwrap(),
1745            PrefixMatchType::IncludeSub,
1746        )];
1747        let count_with_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1748
1749        let filters = vec![Filter::new("prefix_sub", "!190.115.192.0/22").unwrap()];
1750        let count_without_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1751        assert_eq!(count_with_sub + count_without_sub, elems.len());
1752
1753        // Test negated prefix_super_sub filter (using value-based negation)
1754        let filters = vec![Filter::Prefix(
1755            IpNet::from_str("190.115.192.0/23").unwrap(),
1756            PrefixMatchType::IncludeSuperSub,
1757        )];
1758        let count_with_super_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1759
1760        let filters = vec![Filter::new("prefix_super_sub", "!190.115.192.0/23").unwrap()];
1761        let count_without_super_sub = elems.iter().filter(|e| e.match_filters(&filters)).count();
1762        assert_eq!(count_with_super_sub + count_without_super_sub, elems.len());
1763    }
1764
1765    #[test]
1766    fn test_double_negation_rejected() {
1767        // Double negation should be rejected with a clear error message
1768        // Value-based negation: origin_asn=!!13335
1769        let result = Filter::new("origin_asn", "!!13335");
1770        assert!(result.is_err());
1771        let err = result.unwrap_err();
1772        assert!(err.to_string().contains("double negation"));
1773
1774        let result = Filter::new("prefix", "!!!10.0.0.0/8");
1775        assert!(result.is_err());
1776        let err = result.unwrap_err();
1777        assert!(err.to_string().contains("double negation"));
1778    }
1779
1780    #[test]
1781    fn test_timestamp_negation_rejected() {
1782        // Timestamp filter negation should be rejected (value-based negation)
1783        let result = Filter::new("ts_start", "!1637437798");
1784        assert!(result.is_err());
1785        let err = result.unwrap_err();
1786        assert!(err
1787            .to_string()
1788            .contains("timestamp filter 'ts_start' does not support negation"));
1789
1790        let result = Filter::new("ts_end", "!1637437798");
1791        assert!(result.is_err());
1792        let err = result.unwrap_err();
1793        assert!(err
1794            .to_string()
1795            .contains("timestamp filter 'ts_end' does not support negation"));
1796
1797        let result = Filter::new("start_ts", "!1637437798");
1798        assert!(result.is_err());
1799
1800        let result = Filter::new("end_ts", "!1637437798");
1801        assert!(result.is_err());
1802    }
1803
1804    #[test]
1805    fn test_multiple_origin_asns() -> Result<()> {
1806        // Test parsing multiple origin ASNs
1807        let filter = Filter::new("origin_asns", "12345,67890,13335").unwrap();
1808        match filter {
1809            Filter::OriginAsns(asns) => {
1810                assert_eq!(asns.len(), 3);
1811                assert!(asns.contains(&12345));
1812                assert!(asns.contains(&67890));
1813                assert!(asns.contains(&13335));
1814            }
1815            _ => panic!("Expected OriginAsns filter"),
1816        }
1817
1818        // Test with spaces in the list
1819        let filter = Filter::new("origin_asns", "12345, 67890, 13335").unwrap();
1820        match filter {
1821            Filter::OriginAsns(asns) => {
1822                assert_eq!(asns.len(), 3);
1823            }
1824            _ => panic!("Expected OriginAsns filter"),
1825        }
1826
1827        Ok(())
1828    }
1829
1830    #[test]
1831    fn test_multiple_prefixes() -> Result<()> {
1832        // Test parsing multiple prefixes
1833        let prefix1 = IpNet::from_str("190.115.192.0/22").unwrap();
1834        let prefix2 = IpNet::from_str("2804:100::/32").unwrap();
1835
1836        let filter = Filter::new("prefixes", "190.115.192.0/22,2804:100::/32").unwrap();
1837        match filter {
1838            Filter::Prefixes(prefixes, match_type) => {
1839                assert_eq!(prefixes.len(), 2);
1840                assert!(prefixes.contains(&prefix1));
1841                assert!(prefixes.contains(&prefix2));
1842                assert_eq!(match_type, PrefixMatchType::Exact);
1843            }
1844            _ => panic!("Expected Prefixes filter"),
1845        }
1846
1847        // Test with spaces
1848        let filter = Filter::new("prefixes", "190.115.192.0/22, 2804:100::/32").unwrap();
1849        match filter {
1850            Filter::Prefixes(prefixes, _) => {
1851                assert_eq!(prefixes.len(), 2);
1852            }
1853            _ => panic!("Expected Prefixes filter"),
1854        }
1855
1856        Ok(())
1857    }
1858
1859    #[test]
1860    fn test_multiple_prefixes_with_match_types() -> Result<()> {
1861        // Test prefixes_super
1862        let filter = Filter::new("prefixes_super", "190.115.192.0/24,2804:100::/32").unwrap();
1863        match filter {
1864            Filter::Prefixes(prefixes, match_type) => {
1865                assert_eq!(prefixes.len(), 2);
1866                assert_eq!(match_type, PrefixMatchType::IncludeSuper);
1867            }
1868            _ => panic!("Expected Prefixes filter with IncludeSuper"),
1869        }
1870
1871        // Test prefixes_sub
1872        let filter = Filter::new("prefixes_sub", "190.115.192.0/22,2804:100::/32").unwrap();
1873        match filter {
1874            Filter::Prefixes(prefixes, match_type) => {
1875                assert_eq!(prefixes.len(), 2);
1876                assert_eq!(match_type, PrefixMatchType::IncludeSub);
1877            }
1878            _ => panic!("Expected Prefixes filter with IncludeSub"),
1879        }
1880
1881        // Test prefixes_super_sub
1882        let filter = Filter::new("prefixes_super_sub", "190.115.192.0/23,2804:100::/32").unwrap();
1883        match filter {
1884            Filter::Prefixes(prefixes, match_type) => {
1885                assert_eq!(prefixes.len(), 2);
1886                assert_eq!(match_type, PrefixMatchType::IncludeSuperSub);
1887            }
1888            _ => panic!("Expected Prefixes filter with IncludeSuperSub"),
1889        }
1890
1891        Ok(())
1892    }
1893
1894    #[test]
1895    fn test_multiple_peer_asns() -> Result<()> {
1896        // Test parsing multiple peer ASNs
1897        let filter = Filter::new("peer_asns", "12345,67890,13335").unwrap();
1898        match filter {
1899            Filter::PeerAsns(asns) => {
1900                assert_eq!(asns.len(), 3);
1901                assert!(asns.contains(&12345));
1902                assert!(asns.contains(&67890));
1903                assert!(asns.contains(&13335));
1904            }
1905            _ => panic!("Expected PeerAsns filter"),
1906        }
1907
1908        Ok(())
1909    }
1910
1911    #[test]
1912    fn test_negated_multiple_filters() -> Result<()> {
1913        // Test negated origin_asns (using value-based negation: !value,!value)
1914        let filter = Filter::new("origin_asns", "!13335,!15169").unwrap();
1915        assert!(matches!(filter, Filter::Negated(_)));
1916
1917        // Test negated prefixes
1918        let filter = Filter::new("prefixes", "!1.1.1.0/24,!8.8.8.0/24").unwrap();
1919        assert!(matches!(filter, Filter::Negated(_)));
1920
1921        // Test negated peer_asns
1922        let filter = Filter::new("peer_asns", "!12345,!67890").unwrap();
1923        assert!(matches!(filter, Filter::Negated(_)));
1924
1925        Ok(())
1926    }
1927
1928    #[test]
1929    fn test_invalid_multiple_filters() {
1930        // Test invalid origin ASN in list
1931        let result = Filter::new("origin_asns", "12345,not_a_number,67890");
1932        assert!(result.is_err());
1933
1934        // Test invalid prefix in list
1935        let result = Filter::new("prefixes", "1.1.1.0/24,invalid_prefix");
1936        assert!(result.is_err());
1937
1938        // Test invalid peer ASN in list
1939        let result = Filter::new("peer_asns", "12345,invalid,67890");
1940        assert!(result.is_err());
1941
1942        // Test invalid peer IP in list
1943        let result = Filter::new("peer_ips", "192.168.1.1,invalid_ip");
1944        assert!(result.is_err());
1945
1946        // Test mixed positive/negative values (not allowed)
1947        let result = Filter::new("origin_asns", "12345,!67890");
1948        assert!(result.is_err());
1949        assert!(result
1950            .unwrap_err()
1951            .to_string()
1952            .contains("cannot mix positive and negative values"));
1953
1954        let result = Filter::new("prefixes", "1.1.1.0/24,!8.8.8.0/24");
1955        assert!(result.is_err());
1956        assert!(result
1957            .unwrap_err()
1958            .to_string()
1959            .contains("cannot mix positive and negative values"));
1960
1961        let result = Filter::new("peer_ips", "192.168.1.1,!10.0.0.1");
1962        assert!(result.is_err());
1963        assert!(result
1964            .unwrap_err()
1965            .to_string()
1966            .contains("cannot mix positive and negative values"));
1967
1968        let result = Filter::new("peer_asns", "!12345,67890");
1969        assert!(result.is_err());
1970        assert!(result
1971            .unwrap_err()
1972            .to_string()
1973            .contains("cannot mix positive and negative values"));
1974
1975        // Test empty ASN list
1976        let result = Filter::new("origin_asns", "");
1977        assert!(result.is_err());
1978        assert!(result.unwrap_err().to_string().contains("at least one ASN"));
1979
1980        // Test empty prefix list
1981        let result = Filter::new("prefixes", "");
1982        assert!(result.is_err());
1983        assert!(result
1984            .unwrap_err()
1985            .to_string()
1986            .contains("at least one prefix"));
1987
1988        // Test empty IP list
1989        let result = Filter::new("peer_ips", "");
1990        assert!(result.is_err());
1991        assert!(result.unwrap_err().to_string().contains("at least one IP"));
1992
1993        // Test only commas in ASN list (should error after filtering empty strings)
1994        let result = Filter::new("origin_asns", ",,,");
1995        assert!(result.is_err());
1996        assert!(result.unwrap_err().to_string().contains("at least one ASN"));
1997
1998        // Test only commas in prefix list
1999        let result = Filter::new("prefixes", ",,,");
2000        assert!(result.is_err());
2001        assert!(result
2002            .unwrap_err()
2003            .to_string()
2004            .contains("at least one prefix"));
2005
2006        // Test only commas in IP list
2007        let result = Filter::new("peer_ips", ",,,");
2008        assert!(result.is_err());
2009        assert!(result.unwrap_err().to_string().contains("at least one IP"));
2010
2011        // Test trailing commas (should still work by skipping empty strings)
2012        let result = Filter::new("origin_asns", "12345,67890,");
2013        assert!(result.is_ok());
2014
2015        // Test consecutive commas (should still work by skipping empty strings)
2016        let result = Filter::new("origin_asns", "12345,,67890");
2017        assert!(result.is_ok());
2018
2019        // Test trailing commas for peer IPs
2020        let result = Filter::new("peer_ips", "192.168.1.1,192.168.1.2,");
2021        assert!(result.is_ok());
2022    }
2023
2024    #[test]
2025    fn test_multiple_filters_or_logic_behavior() {
2026        // Create a test element
2027        let elem = BgpElem {
2028            timestamp: 1637437798_f64,
2029            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
2030            peer_asn: Asn::new_32bit(12345),
2031            peer_bgp_id: None,
2032            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
2033            next_hop: None,
2034            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
2035            origin_asns: Some(vec![Asn::new_16bit(12345)]),
2036            origin: None,
2037            local_pref: None,
2038            med: None,
2039            communities: None,
2040            atomic: false,
2041            aggr_asn: None,
2042            aggr_ip: None,
2043            only_to_customer: None,
2044            unknown: None,
2045            elem_type: ElemType::ANNOUNCE,
2046            deprecated: None,
2047        };
2048
2049        // Test OriginAsns with OR logic - element has origin ASN 12345
2050        let filter = Filter::new("origin_asns", "12345,67890,99999").unwrap();
2051        assert!(elem.match_filter(&filter)); // Should match because 12345 is in the list
2052
2053        let filter = Filter::new("origin_asns", "67890,99999").unwrap();
2054        assert!(!elem.match_filter(&filter)); // Should NOT match because 12345 is not in the list
2055
2056        // Test Prefixes with OR logic - element has prefix 192.168.1.0/24
2057        let filter = Filter::new("prefixes", "192.168.1.0/24,10.0.0.0/8,172.16.0.0/12").unwrap();
2058        assert!(elem.match_filter(&filter)); // Should match
2059
2060        let filter = Filter::new("prefixes", "10.0.0.0/8,172.16.0.0/12").unwrap();
2061        assert!(!elem.match_filter(&filter)); // Should NOT match
2062
2063        // Test PeerAsns with OR logic - element has peer ASN 12345
2064        let filter = Filter::new("peer_asns", "12345,67890").unwrap();
2065        assert!(elem.match_filter(&filter)); // Should match
2066
2067        let filter = Filter::new("peer_asns", "67890,99999").unwrap();
2068        assert!(!elem.match_filter(&filter)); // Should NOT match
2069
2070        // Test negated multiple filters (using value-based negation: !value,!value)
2071        let filter = Filter::new("origin_asns", "!67890,!99999").unwrap();
2072        assert!(elem.match_filter(&filter)); // Should match because origin ASN is NOT in the list
2073
2074        let filter = Filter::new("origin_asns", "!12345,!67890").unwrap();
2075        assert!(!elem.match_filter(&filter)); // Should NOT match because origin ASN IS in the list
2076    }
2077
2078    fn elem_with_attrs() -> BgpElem {
2079        BgpElem {
2080            timestamp: 1637437798_f64,
2081            peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
2082            peer_asn: Asn::new_32bit(12345),
2083            peer_bgp_id: Some(Ipv4Addr::from_str("10.0.0.1").unwrap()),
2084            prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
2085            next_hop: Some(IpAddr::from_str("10.0.0.2").unwrap()),
2086            as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
2087            origin_asns: Some(vec![Asn::new_32bit(52888)]),
2088            origin: Some(Origin::IGP),
2089            local_pref: Some(100),
2090            med: Some(50),
2091            communities: None,
2092            atomic: true,
2093            aggr_asn: Some(Asn::new_32bit(65100)),
2094            aggr_ip: Some(Ipv4Addr::from_str("10.0.0.3").unwrap()),
2095            only_to_customer: Some(Asn::new_32bit(65200)),
2096            unknown: None,
2097            elem_type: ElemType::ANNOUNCE,
2098            deprecated: None,
2099        }
2100    }
2101
2102    #[test]
2103    fn test_extended_filters_value_match() {
2104        let elem = elem_with_attrs();
2105
2106        // OTC
2107        assert!(elem.match_filter(&Filter::new("otc", "65200").unwrap()));
2108        assert!(!elem.match_filter(&Filter::new("otc", "99999").unwrap()));
2109
2110        // Next hop
2111        assert!(elem.match_filter(&Filter::new("next_hop", "10.0.0.2").unwrap()));
2112        assert!(!elem.match_filter(&Filter::new("next_hop", "10.0.0.9").unwrap()));
2113
2114        // Origin
2115        assert!(elem.match_filter(&Filter::new("origin", "igp").unwrap()));
2116        assert!(elem.match_filter(&Filter::new("origin", "IGP").unwrap())); // case-insensitive
2117        assert!(!elem.match_filter(&Filter::new("origin", "egp").unwrap()));
2118
2119        // Local pref
2120        assert!(elem.match_filter(&Filter::new("local_pref", "100").unwrap()));
2121        assert!(!elem.match_filter(&Filter::new("local_pref", "200").unwrap()));
2122
2123        // MED
2124        assert!(elem.match_filter(&Filter::new("med", "50").unwrap()));
2125        assert!(!elem.match_filter(&Filter::new("med", "99").unwrap()));
2126
2127        // Atomic
2128        assert!(elem.match_filter(&Filter::new("atomic", "true").unwrap()));
2129        assert!(!elem.match_filter(&Filter::new("atomic", "false").unwrap()));
2130
2131        // Aggr ASN
2132        assert!(elem.match_filter(&Filter::new("aggr_asn", "65100").unwrap()));
2133        assert!(!elem.match_filter(&Filter::new("aggr_asn", "99999").unwrap()));
2134
2135        // Aggr IP
2136        assert!(elem.match_filter(&Filter::new("aggr_ip", "10.0.0.3").unwrap()));
2137        assert!(!elem.match_filter(&Filter::new("aggr_ip", "10.0.0.9").unwrap()));
2138
2139        // Peer BGP ID
2140        assert!(elem.match_filter(&Filter::new("peer_bgp_id", "10.0.0.1").unwrap()));
2141        assert!(!elem.match_filter(&Filter::new("peer_bgp_id", "10.0.0.9").unwrap()));
2142    }
2143
2144    #[test]
2145    fn test_extended_filters_negation() {
2146        let elem = elem_with_attrs();
2147
2148        // Negated value match
2149        assert!(elem.match_filter(&Filter::new("otc", "!99999").unwrap()));
2150        assert!(!elem.match_filter(&Filter::new("otc", "!65200").unwrap()));
2151        assert!(elem.match_filter(&Filter::new("next_hop", "!10.0.0.9").unwrap()));
2152        assert!(!elem.match_filter(&Filter::new("next_hop", "!10.0.0.2").unwrap()));
2153        assert!(elem.match_filter(&Filter::new("origin", "!egp").unwrap()));
2154        assert!(elem.match_filter(&Filter::new("local_pref", "!200").unwrap()));
2155        assert!(elem.match_filter(&Filter::new("med", "!99").unwrap()));
2156        assert!(elem.match_filter(&Filter::new("atomic", "!false").unwrap()));
2157        assert!(elem.match_filter(&Filter::new("aggr_asn", "!99999").unwrap()));
2158        assert!(elem.match_filter(&Filter::new("aggr_ip", "!10.0.0.9").unwrap()));
2159        assert!(elem.match_filter(&Filter::new("peer_bgp_id", "!10.0.0.9").unwrap()));
2160    }
2161
2162    #[test]
2163    fn test_extended_filters_presence() {
2164        let elem_full = elem_with_attrs();
2165        let elem_empty = filter_test_elem(); // all optional fields are None
2166
2167        // Present on full elem
2168        assert!(elem_full.match_filter(&Filter::new("otc", "*").unwrap()));
2169        assert!(elem_full.match_filter(&Filter::new("next_hop", "*").unwrap()));
2170        assert!(elem_full.match_filter(&Filter::new("origin", "*").unwrap()));
2171        assert!(elem_full.match_filter(&Filter::new("local_pref", "*").unwrap()));
2172        assert!(elem_full.match_filter(&Filter::new("med", "*").unwrap()));
2173        assert!(elem_full.match_filter(&Filter::new("aggr_asn", "*").unwrap()));
2174        assert!(elem_full.match_filter(&Filter::new("aggr_ip", "*").unwrap()));
2175        assert!(elem_full.match_filter(&Filter::new("peer_bgp_id", "*").unwrap()));
2176
2177        // Absent on empty elem
2178        assert!(!elem_empty.match_filter(&Filter::new("otc", "*").unwrap()));
2179        assert!(!elem_empty.match_filter(&Filter::new("next_hop", "*").unwrap()));
2180        assert!(!elem_empty.match_filter(&Filter::new("origin", "*").unwrap()));
2181        assert!(!elem_empty.match_filter(&Filter::new("local_pref", "*").unwrap()));
2182        assert!(!elem_empty.match_filter(&Filter::new("med", "*").unwrap()));
2183        assert!(!elem_empty.match_filter(&Filter::new("aggr_asn", "*").unwrap()));
2184        assert!(!elem_empty.match_filter(&Filter::new("aggr_ip", "*").unwrap()));
2185        assert!(!elem_empty.match_filter(&Filter::new("peer_bgp_id", "*").unwrap()));
2186
2187        // Negated presence (absent) on empty elem
2188        assert!(elem_empty.match_filter(&Filter::new("otc", "!*").unwrap()));
2189        assert!(elem_empty.match_filter(&Filter::new("next_hop", "!*").unwrap()));
2190        assert!(elem_empty.match_filter(&Filter::new("origin", "!*").unwrap()));
2191        assert!(elem_empty.match_filter(&Filter::new("local_pref", "!*").unwrap()));
2192        assert!(elem_empty.match_filter(&Filter::new("med", "!*").unwrap()));
2193        assert!(elem_empty.match_filter(&Filter::new("aggr_asn", "!*").unwrap()));
2194        assert!(elem_empty.match_filter(&Filter::new("aggr_ip", "!*").unwrap()));
2195        assert!(elem_empty.match_filter(&Filter::new("peer_bgp_id", "!*").unwrap()));
2196
2197        // Negated presence on full elem → should not match
2198        assert!(!elem_full.match_filter(&Filter::new("otc", "!*").unwrap()));
2199        assert!(!elem_full.match_filter(&Filter::new("next_hop", "!*").unwrap()));
2200    }
2201
2202    #[test]
2203    fn test_extended_filters_on_route_elem() {
2204        let elem = elem_with_attrs();
2205        let route = route_projection(&elem);
2206
2207        // Non-negated elem-only filters do NOT match route elements
2208        assert!(!route.match_filter(&Filter::new("otc", "65200").unwrap()));
2209        assert!(!route.match_filter(&Filter::new("next_hop", "10.0.0.2").unwrap()));
2210        assert!(!route.match_filter(&Filter::new("otc", "*").unwrap()));
2211        // Atomic(false) must not match route elems even though the default atomic() returns false
2212        assert!(!route.match_filter(&Filter::new("atomic", "false").unwrap()));
2213        assert!(!route.match_filter(&Filter::new("atomic", "true").unwrap()));
2214
2215        // Negated elem-only filters also do NOT match route elements (fail-closed)
2216        assert!(!route.match_filter(&Filter::new("otc", "!65200").unwrap()));
2217        assert!(!route.match_filter(&Filter::new("otc", "!*").unwrap()));
2218
2219        // Route-level filters still work
2220        assert!(route.match_filter(&Filter::new("origin_asn", "52888").unwrap()));
2221        assert!(route.match_filter(&Filter::new("prefix", "192.168.1.0/24").unwrap()));
2222    }
2223
2224    #[test]
2225    fn test_extended_filter_parsing_errors() {
2226        assert!(Filter::new("otc", "not_a_number").is_err());
2227        assert!(Filter::new("next_hop", "not_an_ip").is_err());
2228        assert!(Filter::new("origin", "invalid").is_err());
2229        assert!(Filter::new("local_pref", "not_a_number").is_err());
2230        assert!(Filter::new("med", "not_a_number").is_err());
2231        assert!(Filter::new("atomic", "not_a_bool").is_err());
2232        assert!(Filter::new("aggr_asn", "not_a_number").is_err());
2233        assert!(Filter::new("aggr_ip", "not_an_ip").is_err());
2234        assert!(Filter::new("peer_bgp_id", "not_an_ip").is_err());
2235    }
2236}