use super::{Protocol, Server, ServerCategory};
use std::collections::HashSet;
use std::iter::FromIterator;
pub trait Filter {
fn filter(&self, _: &Server) -> bool;
}
pub struct CountryFilter {
country: String,
}
impl CountryFilter {
#[deprecated(
since = "1.0.0",
note = "Inefficient, use the From-trait implementation instead"
)]
pub fn from_code(countrycode: String) -> CountryFilter {
CountryFilter {
country: countrycode.to_ascii_uppercase(),
}
}
}
impl Filter for CountryFilter {
fn filter(&self, server: &Server) -> bool {
self.country == server.flag
}
}
impl<'a> From<&'a str> for CountryFilter {
fn from(countrycode: &str) -> CountryFilter {
CountryFilter {
country: countrycode.to_ascii_uppercase(),
}
}
}
#[derive(Debug, PartialEq)]
pub enum Region {
EuropeanUnion,
EuropeanEconomicArea,
Benelux,
FiveEyes,
SixEyes,
NineEyes,
FourteenEyes,
}
impl Region {
pub fn from_str(region_short: &str) -> Option<Region> {
match region_short {
"EU" | "ЕЮ" => Some(Region::EuropeanUnion),
"EEA" => Some(Region::EuropeanEconomicArea),
"BENELUX" => Some(Region::Benelux),
"5E" => Some(Region::FiveEyes),
"6E" => Some(Region::SixEyes),
"9E" => Some(Region::NineEyes),
"14E" => Some(Region::FourteenEyes),
_ => None,
}
}
pub fn from_str_options() -> [(&'static str, &'static str); 8] {
[
("EU", "The European Union"),
("ЕЮ", "The European Union (Cyrillic notation)"),
("EEA", "The European Economic Area"),
("BENELUX", "Countries of the Benelux"),
("5E", "Countries involved in the Five Eyes programme."),
("6E", "Countries involved in the Six Eyes programme."),
("9E", "Countries involved in the Nine Eyes programme."),
("14E", "Countries involved in the Fourteen Eyes programme."),
]
}
pub fn short(&self) -> &'static str {
match self {
Region::EuropeanUnion => "EU",
Region::EuropeanEconomicArea => "EEA",
Region::Benelux => "BENELUX",
Region::FiveEyes => "5E",
Region::SixEyes => "6E",
Region::NineEyes => "9E",
Region::FourteenEyes => "14E",
}
}
pub fn countries(&self) -> Vec<&str> {
match self {
Region::EuropeanEconomicArea => vec![
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE",
"IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "NO",
"LI", "IS",
],
Region::EuropeanUnion => vec![
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE",
"IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE",
],
Region::Benelux => vec!["BE", "LU", "NL"],
Region::FiveEyes => vec!["AU", "CA", "NZ", "GB", "US"],
Region::SixEyes => vec!["AU", "CA", "FR", "NZ", "GB", "US"],
Region::NineEyes => vec!["AU", "CA", "DK", "FR", "NL", "NO", "NZ", "GB", "US"],
Region::FourteenEyes => vec![
"AU", "BE", "CA", "DE", "DK", "ES", "FR", "IT", "NL", "NO", "NZ", "GB", "SE", "US",
],
}
}
}
pub struct CountriesFilter {
countries: HashSet<String>,
}
impl CountriesFilter {
#[deprecated(
since = "1.1.0",
note = "Use the Region object instead. It has more regions and works better."
)]
#[allow(deprecated)]
pub fn from_region(region: &str) -> Option<CountriesFilter> {
match region.to_lowercase().as_ref() {
"eu" | "ею" => Some(CountriesFilter {
countries: HashSet::from_iter(
Self::region_countries("EU")
.unwrap()
.iter()
.map(|s| String::from(*s)),
),
}),
_ => None,
}
}
#[deprecated(
since = "1.1.0",
note = "Use the Region object instead. It has more regions and works better."
)]
pub fn available_regions() -> &'static [&'static str] {
&["EU", "ЕЮ"]
}
#[deprecated(
since = "1.1.0",
note = "Use the Region object instead. It has more regions and works better."
)]
pub fn region_countries(region: &str) -> Option<&'static [&'static str]> {
match region {
"EU" | "ЕЮ" => Some(&[
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE",
"IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE",
]),
_ => None,
}
}
}
impl From<Region> for CountriesFilter {
fn from(region: Region) -> CountriesFilter {
CountriesFilter {
countries: HashSet::from_iter(
region
.countries()
.into_iter()
.map(String::from),
),
}
}
}
impl From<HashSet<String>> for CountriesFilter {
fn from(countries: HashSet<String>) -> CountriesFilter {
CountriesFilter { countries }
}
}
impl Filter for CountriesFilter {
fn filter(&self, server: &Server) -> bool {
self.countries.contains(&server.flag)
}
}
pub struct ProtocolFilter {
protocol: Protocol,
}
impl From<Protocol> for ProtocolFilter {
fn from(protocol: Protocol) -> ProtocolFilter {
ProtocolFilter { protocol }
}
}
impl Filter for ProtocolFilter {
fn filter(&self, server: &Server) -> bool {
match self.protocol {
Protocol::Tcp => server.features.openvpn_tcp,
Protocol::Udp => server.features.openvpn_udp,
Protocol::Pptp => server.features.pptp,
Protocol::L2tp => server.features.l2tp,
Protocol::OpenVPNXTcp => server.features.openvpn_tcp,
Protocol::OpenVPNXUdp => server.features.openvpn_udp,
Protocol::Socks => server.features.socks,
Protocol::CyberSecProxy => server.features.proxy_cybersec,
Protocol::SslProxy => server.features.proxy_ssl,
Protocol::CyberSecSslProxy => server.features.proxy_ssl_cybersec,
Protocol::Proxy => server.features.proxy,
Protocol::WireGuardUdp => server.features.wireguard_udp,
}
}
}
pub struct LoadFilter {
load: u8,
}
impl From<u8> for LoadFilter {
fn from(load: u8) -> LoadFilter {
LoadFilter { load }
}
}
impl Filter for LoadFilter {
fn filter(&self, server: &Server) -> bool {
server.load.cmp(&self.load) != std::cmp::Ordering::Greater
}
}
pub struct CombinedFilter {
filters: Vec<Box<dyn Filter>>,
}
impl CombinedFilter {
pub fn new() -> CombinedFilter {
CombinedFilter {
filters: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> CombinedFilter {
CombinedFilter {
filters: Vec::with_capacity(capacity),
}
}
}
impl From<Vec<Box<dyn Filter>>> for CombinedFilter {
fn from(filters: Vec<Box<dyn Filter>>) -> CombinedFilter {
CombinedFilter { filters }
}
}
impl CombinedFilter {
pub fn add_filter(&mut self, filter: Box<dyn Filter>) {
self.filters.push(filter);
}
}
impl Filter for CombinedFilter {
fn filter(&self, server: &Server) -> bool {
self.filters
.iter()
.any(|filter| filter.filter(server))
}
}
pub struct CategoryFilter {
category: ServerCategory,
}
impl From<ServerCategory> for CategoryFilter {
fn from(category: ServerCategory) -> CategoryFilter {
CategoryFilter { category }
}
}
impl Filter for CategoryFilter {
fn filter(&self, server: &Server) -> bool {
server.categories.contains(&self.category)
}
}
pub struct NegatingFilter(Box<dyn Filter>);
impl NegatingFilter {
pub fn new(filter: impl Filter + 'static) -> Self {
Self(Box::new(filter))
}
}
impl From<Box<dyn Filter + 'static>> for NegatingFilter {
fn from(filter: Box<dyn Filter + 'static>) -> Self {
Self(filter)
}
}
impl Filter for NegatingFilter {
fn filter(&self, server: &Server) -> bool {
!self.0.filter(server)
}
}
#[cfg(test)]
mod tests {
use super::super::Servers;
use super::*;
#[test]
#[allow(deprecated)]
fn country_filter_simple_legacy() {
let mut data = Servers::dummy_data();
data.filter(&CountryFilter::from_code("sg".to_string()));
let server_opt = data.perfect_server();
assert!(server_opt.is_some());
assert_eq!(server_opt.unwrap().flag, "SG");
}
#[test]
#[allow(deprecated)]
fn country_filter_advanced_legacy() {
let mut data = Servers::dummy_data();
data.filter(&CountryFilter::from_code("Sg".to_string()));
let server_opt = data.perfect_server();
assert!(server_opt.is_some());
assert_eq!(server_opt.unwrap().flag, "SG");
}
#[test]
fn country_filter_simple() {
let mut data = Servers::dummy_data();
data.filter(&CountryFilter::from("sg"));
let server_opt = data.perfect_server();
assert!(server_opt.is_some());
assert_eq!(server_opt.unwrap().flag, "SG");
}
#[test]
fn country_filter_advanced() {
let mut data = Servers::dummy_data();
data.filter(&CountryFilter::from("Sg"));
let server_opt = data.perfect_server();
assert!(server_opt.is_some());
assert_eq!(server_opt.unwrap().flag, "SG");
}
#[test]
#[allow(deprecated)]
fn countries_filter_regions_give_some() {
for region in CountriesFilter::available_regions() {
assert!(CountriesFilter::from_region(region).is_some());
}
}
#[test]
fn countries_filter_empty() {
let mut data = Servers::dummy_data();
data.filter(&CountriesFilter::from(HashSet::with_capacity(0)));
let server_opt = data.perfect_server();
assert_eq!(server_opt, None);
}
#[test]
fn countries_filter_simple() {
let mut data = Servers::dummy_data();
let vec = vec!["AE", "AL", "AR"];
data.filter(&CountriesFilter::from(HashSet::from_iter(
vec.iter().map(|x| x.to_string()),
)));
let server_opt = data.perfect_server();
assert!(server_opt.is_some());
assert!(vec.contains(&server_opt.unwrap().flag.as_str()));
}
#[test]
fn valid_regions() {
assert_eq!(
Region::from_str("EU").unwrap().countries(),
vec![
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE",
"IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE",
]
);
assert_eq!(
Region::from_str("ЕЮ").unwrap().countries(),
vec![
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE",
"IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE",
]
);
assert_eq!(
Region::from_str("5E").unwrap().countries(),
vec!["AU", "CA", "NZ", "GB", "US"]
);
assert_eq!(
Region::from_str("6E").unwrap().countries(),
vec!["AU", "CA", "FR", "NZ", "GB", "US"]
);
assert_eq!(
Region::from_str("9E").unwrap().countries(),
vec!["AU", "CA", "DK", "FR", "NL", "NO", "NZ", "GB", "US"]
);
assert_eq!(
Region::from_str("14E").unwrap().countries(),
vec![
"AU", "BE", "CA", "DE", "DK", "ES", "FR", "IT", "NL", "NO", "NZ", "GB", "SE", "US",
],
);
for (region, _) in Region::from_str_options().iter() {
assert!(Region::from_str(region).is_some());
}
}
#[test]
fn invalid_regions() {
assert_eq!(Region::from_str("blablabla"), None);
assert_eq!(Region::from_str(""), None);
assert_eq!(Region::from_str("idk"), None);
assert_eq!(Region::from_str("test"), None);
assert_eq!(Region::from_str("12e"), None);
assert_eq!(Region::from_str("15e"), None);
}
}