use crate::filters::network::{
FilterPart, NetworkFilter, NetworkFilterMask, NetworkFilterMaskHelper,
};
use itertools::*;
use std::borrow::Cow;
use std::collections::HashMap;
trait Optimization {
fn fusion<'a>(&self, filters: Vec<NetworkFilter<'a>>) -> NetworkFilter<'a>;
fn group_by_criteria(&self, filter: &NetworkFilter<'_>) -> String;
fn select(&self, filter: &NetworkFilter<'_>) -> bool;
}
pub fn is_filter_optimizable_by_patterns(filter: &NetworkFilter<'_>) -> bool {
filter.opt_domains.is_none()
&& filter.opt_not_domains.is_none()
&& !filter.is_hostname_anchor()
&& !filter.is_redirect()
&& !filter.is_csp()
}
pub fn optimize<'a>(filters: Vec<NetworkFilter<'a>>) -> Vec<NetworkFilter<'a>> {
let mut optimized: Vec<NetworkFilter<'a>> = Vec::new();
let simple_pattern_group = SimplePatternGroup {};
let (fused, unfused) = apply_optimisation(&simple_pattern_group, filters);
optimized.extend(fused);
optimized.extend(unfused);
optimized.sort_by_key(|f| f.id);
optimized
}
fn apply_optimisation<'a, T: Optimization>(
optimization: &T,
filters: Vec<NetworkFilter<'a>>,
) -> (Vec<NetworkFilter<'a>>, Vec<NetworkFilter<'a>>) {
let (positive, mut negative): (Vec<NetworkFilter<'a>>, Vec<NetworkFilter<'a>>) =
filters.into_iter().partition_map(|f| {
if optimization.select(&f) {
Either::Left(f)
} else {
Either::Right(f)
}
});
let mut to_fuse: HashMap<String, Vec<NetworkFilter<'a>>> =
HashMap::with_capacity(positive.len());
positive
.into_iter()
.for_each(|f| insert_dup(&mut to_fuse, optimization.group_by_criteria(&f), f));
let mut fused = Vec::with_capacity(to_fuse.len());
for (_, group) in to_fuse {
if group.len() > 1 {
fused.push(optimization.fusion(group));
} else {
group.into_iter().for_each(|f| negative.push(f));
}
}
fused.shrink_to_fit();
(fused, negative)
}
fn insert_dup<K, V>(map: &mut HashMap<K, Vec<V>>, k: K, v: V)
where
K: std::cmp::Ord + std::hash::Hash,
{
map.entry(k).or_default().push(v)
}
struct SimplePatternGroup {}
impl Optimization for SimplePatternGroup {
fn fusion<'a>(&self, filters: Vec<NetworkFilter<'a>>) -> NetworkFilter<'a> {
let base_filter = &filters[0]; let mut filter = base_filter.clone();
let is_regex = filters.iter().any(NetworkFilter::is_regex);
let is_complete_regex = filters.iter().any(|f| f.is_complete_regex());
filter.mask.set(NetworkFilterMask::IS_REGEX, is_regex);
filter
.mask
.set(NetworkFilterMask::IS_COMPLETE_REGEX, is_complete_regex);
let combined_raw_line = filters
.iter()
.filter_map(|f| f.raw_line.as_deref())
.join(" <+> ");
if !combined_raw_line.is_empty() {
filter.raw_line = Some(Cow::Owned(combined_raw_line));
}
if filters
.iter()
.any(|f| matches!(f.filter, FilterPart::Empty))
{
filter.filter = FilterPart::Empty;
} else {
let mut flat_patterns: Vec<Cow<'a, str>> = Vec::with_capacity(filters.len());
for f in filters {
match f.filter {
FilterPart::Empty => (),
FilterPart::Simple(s) => flat_patterns.push(s),
FilterPart::AnyOf(s) => flat_patterns.extend(s),
}
}
if flat_patterns.is_empty() {
filter.filter = FilterPart::Empty;
} else if flat_patterns.len() == 1 {
filter.filter = FilterPart::Simple(flat_patterns.into_iter().next().unwrap())
} else {
filter.filter = FilterPart::AnyOf(flat_patterns)
}
}
filter
}
fn group_by_criteria(&self, filter: &NetworkFilter<'_>) -> String {
format!("{:b}:{:?}", filter.mask, filter.is_complete_regex())
}
fn select(&self, filter: &NetworkFilter<'_>) -> bool {
is_filter_optimizable_by_patterns(filter)
}
}
#[cfg(test)]
#[path = "../tests/unit/optimizer.rs"]
mod unit_tests;