#![allow(clippy::type_complexity)]
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
net::IpAddr,
};
use super::*;
use crate::dquic::{
net::Family,
qinterface::bind_uri::{BindUri, Scheme},
};
fn pattern_hash(pattern: &BindPattern) -> u64 {
let mut hasher = DefaultHasher::new();
pattern.hash(&mut hasher);
hasher.finish()
}
#[test]
fn parsing_core() {
let cases: Vec<(&str, Box<dyn Fn(&BindPattern)>)> = vec![
(
"iface://v4.enp17s0:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V4));
assert_eq!(
b.host,
BindHost::classify("enp17s0", Some(Family::V4)).unwrap()
);
assert_eq!(b.port, Some(8080));
assert!(b.path_and_query.is_none());
}),
),
(
"iface://v4.en*:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V4));
assert!(b.host.is_glob());
assert_eq!(b.host.as_str(), "en*");
assert_eq!(b.port, Some(8080));
}),
),
(
"iface://enp17s0:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), None);
assert!(!b.host.is_glob());
assert_eq!(b.host.as_str(), "enp17s0");
assert_eq!(b.port, Some(8080));
}),
),
(
"iface://v4.enp17s0",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V4));
assert_eq!(b.host.as_str(), "enp17s0");
assert_eq!(b.port, None);
}),
),
(
"inet://127.0.0.1:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.family(), None);
assert_eq!(b.host.as_str(), "127.0.0.1");
assert_eq!(b.port, Some(8080));
}),
),
(
"127.0.0.1:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "127.0.0.1");
assert_eq!(b.port, Some(8080));
}),
),
(
"enp17s0:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), None);
assert_eq!(b.host.as_str(), "enp17s0");
assert_eq!(b.port, Some(8080));
}),
),
(
"v4.enp17s0:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V4));
assert_eq!(b.host.as_str(), "enp17s0");
assert_eq!(b.port, Some(8080));
}),
),
(
"en*:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), None);
assert!(b.host.is_glob());
assert_eq!(b.host.as_str(), "en*");
assert_eq!(b.port, Some(8080));
}),
),
(
"*",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), None);
assert!(b.host.is_glob());
assert_eq!(b.host.as_str(), "*");
assert_eq!(b.port, None);
}),
),
(
"*:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), None);
assert!(b.host.is_glob());
assert_eq!(b.port, Some(8080));
}),
),
(
"v4.*",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V4));
assert!(b.host.is_glob());
assert_eq!(b.port, None);
}),
),
(
"v6.*:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V6));
assert!(b.host.is_glob());
assert_eq!(b.port, Some(8080));
}),
),
(
"enp17s0",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), None);
assert_eq!(b.host.as_str(), "enp17s0");
assert_eq!(b.port, None);
}),
),
(
"iface://v4.en*:8080/?stun_server=stun.genmeta.net",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert_eq!(b.host.family(), Some(Family::V4));
assert!(b.host.is_glob());
assert_eq!(b.port, Some(8080));
assert_eq!(
b.path_and_query_str(),
Some("/?stun_server=stun.genmeta.net")
);
}),
),
(
"iface://v4.enp17s0:8080?stun=true",
Box::new(|b: &BindPattern| {
assert_eq!(b.path_and_query_str(), Some("?stun=true"));
}),
),
(
"[ew]*:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Iface);
assert!(b.host.is_glob());
assert_eq!(b.host.as_str(), "[ew]*");
assert_eq!(b.port, Some(8080));
}),
),
(
"iface://v4.en*:8080/?key=value",
Box::new(|b: &BindPattern| {
let pq = b.path_and_query.as_ref().unwrap();
assert_eq!(pq, "/?key=value");
}),
),
];
for (input, check) in &cases {
let b: BindPattern = input
.parse()
.unwrap_or_else(|e| panic!("failed to parse '{input}': {e}"));
check(&b);
}
}
#[test]
fn parsing_ipv6_bracket() {
let cases: Vec<(&str, Box<dyn Fn(&BindPattern)>)> = vec![
(
"inet://[::1]:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.family(), None);
assert_eq!(b.host.as_str(), "::1");
assert_eq!(b.port, Some(8080));
assert!(b.host.is_ip_addr());
}),
),
(
"[::1]:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "::1");
assert_eq!(b.port, Some(8080));
}),
),
(
"inet://[2001:db8::1]:443",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "2001:db8::1");
assert_eq!(b.port, Some(443));
assert!(b.host.is_ip_addr());
}),
),
(
"[fe80::1]:8080",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "fe80::1");
assert_eq!(b.port, Some(8080));
}),
),
(
"inet://[::]:0",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "::");
assert_eq!(b.port, Some(0));
}),
),
(
"[::1]",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "::1");
assert_eq!(b.port, None);
}),
),
(
"inet://[::1]:8080/?key=value",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert_eq!(b.host.as_str(), "::1");
assert_eq!(b.port, Some(8080));
assert_eq!(b.path_and_query_str(), Some("/?key=value"));
}),
),
(
"[::1]:8080",
Box::new(|b: &BindPattern| {
assert!(b.host.is_ip_addr());
assert!(b.host.as_ip_addr().unwrap().is_ipv6());
}),
),
];
for (input, check) in &cases {
let b: BindPattern = input
.parse()
.unwrap_or_else(|e| panic!("failed to parse '{input}': {e}"));
check(&b);
}
}
#[test]
fn parsing_bare_ip() {
let cases: Vec<(&str, Box<dyn Fn(&BindPattern)>)> = vec![
(
"::1",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert!(b.host.is_ip_addr());
assert_eq!(b.host.as_str(), "::1");
assert_eq!(b.port, None);
assert!(b.path_and_query.is_none());
}),
),
(
"::",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert!(b.host.is_ip_addr());
assert_eq!(b.host.as_str(), "::");
assert_eq!(b.port, None);
}),
),
(
"2001:db8::1",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert!(b.host.is_ip_addr());
assert_eq!(b.host.as_str(), "2001:db8::1");
assert_eq!(b.port, None);
}),
),
(
"192.168.1.1",
Box::new(|b: &BindPattern| {
assert_eq!(b.scheme, Scheme::Inet);
assert!(b.host.is_ip_addr());
assert_eq!(b.host.as_str(), "192.168.1.1");
assert_eq!(b.port, None);
}),
),
];
for (input, check) in &cases {
let b: BindPattern = input
.parse()
.unwrap_or_else(|e| panic!("failed to parse '{input}': {e}"));
check(&b);
}
}
#[test]
fn parsing_suffix_edge_cases() {
let no_scheme: BindPattern = "V4.enp17s0:8080?reuse=true".parse().unwrap();
assert_eq!(no_scheme.scheme, Scheme::Iface);
assert_eq!(no_scheme.host.family(), Some(Family::V4));
assert_eq!(no_scheme.host.as_str(), "enp17s0");
assert_eq!(no_scheme.port, Some(8080));
assert_eq!(no_scheme.path_and_query_str(), Some("?reuse=true"));
let bare_ip: BindPattern = "127.0.0.1?temporary=true".parse().unwrap();
assert_eq!(bare_ip.scheme, Scheme::Inet);
assert_eq!(bare_ip.host.as_str(), "127.0.0.1");
assert_eq!(bare_ip.port, None);
assert_eq!(bare_ip.path_and_query_str(), Some("?temporary=true"));
}
#[test]
fn display_roundtrip() {
let cases: &[(&str, &str)] = &[
("iface://v4.enp17s0:8080", "iface://v4.enp17s0:8080"),
(
"iface://V6.enp17s0:8080?reuse=true",
"iface://v6.enp17s0:8080/?reuse=true",
),
("iface://v4.enp17s0", "iface://v4.enp17s0"),
("iface://enp17s0:8080", "iface://enp17s0:8080"),
("::1", "inet://[::1]"),
("inet://[::1]:8080", "inet://[::1]:8080"),
("inet://[2001:db8::1]:443", "inet://[2001:db8::1]:443"),
];
for (input, expected) in cases {
let b: BindPattern = input
.parse()
.unwrap_or_else(|e| panic!("failed to parse '{input}': {e}"));
assert_eq!(b.to_string(), *expected, "display mismatch for '{input}'");
}
}
#[test]
fn hash_includes_path_and_query_suffix() {
let with_suffix: BindPattern = "iface://v4.enp17s0:8080/?reuse=true".parse().unwrap();
let same_suffix: BindPattern = "iface://v4.enp17s0:8080/?reuse=true".parse().unwrap();
assert_eq!(pattern_hash(&with_suffix), pattern_hash(&same_suffix));
let without_suffix: BindPattern = "iface://v4.enp17s0:8080".parse().unwrap();
let same_without_suffix: BindPattern = "iface://v4.enp17s0:8080".parse().unwrap();
assert_ne!(pattern_hash(&with_suffix), pattern_hash(&without_suffix));
assert_eq!(
pattern_hash(&without_suffix),
pattern_hash(&same_without_suffix)
);
}
#[test]
fn glob_matching() {
{
let host = BindHost::classify("enp17s0", None).unwrap();
assert!(host.matches("enp17s0"));
assert!(!host.matches("wlan0"));
}
{
let host = BindHost::classify("en*", None).unwrap();
assert!(host.matches("enp17s0"));
assert!(host.matches("eno1"));
assert!(!host.matches("wlan0"));
let star = BindHost::classify("*", None).unwrap();
assert!(star.matches("anything"));
}
{
let host = BindHost::classify("[ew]*", None).unwrap();
assert!(host.is_glob());
assert!(host.matches("enp17s0"));
assert!(host.matches("wlan0"));
assert!(!host.matches("lo"));
}
{
let host = BindHost::classify("wlan[01]", None).unwrap();
assert!(host.is_glob());
assert!(host.matches("wlan0"));
assert!(host.matches("wlan1"));
assert!(!host.matches("wlan2"));
}
}
#[test]
fn classify() {
{
let host = BindHost::classify("127.0.0.1", None).unwrap();
assert!(host.is_ip_addr());
assert!(!host.is_glob());
assert_eq!(host.as_str(), "127.0.0.1");
}
{
let host = BindHost::classify("[::1]", None).unwrap();
assert!(host.is_ip_addr());
assert_eq!(host.as_str(), "::1");
assert_eq!(host.as_ip_addr().unwrap(), "::1".parse::<IpAddr>().unwrap());
}
{
let host = BindHost::classify("[ew]", None).unwrap();
assert!(host.is_glob());
assert!(!host.is_ip_addr());
}
}
#[test]
fn families() {
{
let b: BindPattern = "enp17s0:8080".parse().unwrap();
assert_eq!(b.host.families(), [Family::V4, Family::V6]);
}
{
let b: BindPattern = "v4.enp17s0:8080".parse().unwrap();
assert_eq!(b.host.families(), [Family::V4]);
}
}
#[test]
fn expand_basic() {
{
let b: BindPattern = "iface://v4.enp17s0:8080".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris(["enp17s0"]).map(|u| u.to_string()).collect();
assert_eq!(uris, vec!["iface://v4.enp17s0:8080/"]);
}
{
let b: BindPattern = "iface://enp17s0:8080".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris(["enp17s0"]).map(|u| u.to_string()).collect();
assert_eq!(
uris,
vec!["iface://v4.enp17s0:8080/", "iface://v6.enp17s0:8080/"]
);
}
{
let b: BindPattern = "iface://v4.enp17s0".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris(["enp17s0"]).map(|u| u.to_string()).collect();
assert_eq!(uris.len(), 1);
assert!(uris[0].starts_with("iface://v4.enp17s0:0/"));
}
{
let b: BindPattern = "127.0.0.1:8080".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris([]).map(|u| u.to_string()).collect();
assert_eq!(uris, vec!["inet://127.0.0.1:8080/"]);
}
{
let b: BindPattern = "iface://v4.en*:8080/?stun_server=stun.genmeta.net"
.parse()
.unwrap();
let uris: Vec<_> = b.to_bind_uris(["enp17s0"]).map(|u| u.to_string()).collect();
assert_eq!(
uris,
vec!["iface://v4.enp17s0:8080/?stun_server=stun.genmeta.net"]
);
}
}
#[test]
fn expand_glob() {
{
let b: BindPattern = "en*:8080".parse().unwrap();
let interfaces = ["enp17s0", "eno1", "wlan0", "lo"];
let uris: Vec<_> = b.to_bind_uris(interfaces).collect();
assert_eq!(uris.len(), 4);
}
{
let b: BindPattern = "*:8080".parse().unwrap();
let interfaces = ["enp17s0", "wlan0"];
let uris: Vec<_> = b.to_bind_uris(interfaces).collect();
assert_eq!(uris.len(), 4);
}
}
#[test]
fn expand_ipv6() {
{
let b: BindPattern = "::1".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris([]).map(|u| u.to_string()).collect();
assert_eq!(uris.len(), 1);
assert!(uris[0].starts_with("inet://[::1]:0/"));
}
{
let b: BindPattern = "inet://[::1]:8080".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris([]).map(|u| u.to_string()).collect();
assert_eq!(uris, vec!["inet://[::1]:8080/"]);
}
{
let b: BindPattern = "[::1]".parse().unwrap();
let uris: Vec<_> = b.to_bind_uris([]).map(|u| u.to_string()).collect();
assert_eq!(uris.len(), 1);
assert!(uris[0].starts_with("inet://[::1]:0/"));
}
}
#[test]
fn ipv6_ip_pattern_matches_generated_bind_uri() {
let pattern: BindPattern = "inet://[::]:4433".parse().unwrap();
let bind_uri: BindUri = "inet://[::]:4433".parse().unwrap();
assert!(pattern.matches(&bind_uri));
}
#[test]
fn parse_errors() {
assert!(
"v4.127.0.0.1:8080".parse::<BindPattern>().is_err(),
"family prefix should be rejected for IP addresses"
);
assert!(
"inet://v6.[::1]:8080".parse::<BindPattern>().is_err(),
"family prefix should be rejected for IP addresses"
);
assert!(
"iface://enp17s0:70000".parse::<BindPattern>().is_err(),
"ports above u16::MAX should be rejected"
);
}
#[test]
fn iface_pattern_matches_generated_bind_uri() {
let pattern: BindPattern = "iface://v4.en*:8080".parse().unwrap();
let generated = pattern
.to_bind_uris(["enp17s0"])
.next()
.expect("pattern should generate one bind uri");
assert!(
pattern.matches(&generated),
"pattern {pattern} should match generated bind uri {generated}"
);
}
#[test]
fn iface_pattern_matches_family_interface_and_port_separately() {
let pattern: BindPattern = "iface://v4.en*:8080".parse().unwrap();
assert!(pattern.matches(&"iface://v4.enp17s0:8080".parse().unwrap()));
assert!(!pattern.matches(&"iface://v6.enp17s0:8080".parse().unwrap()));
assert!(!pattern.matches(&"iface://v4.wlan0:8080".parse().unwrap()));
assert!(!pattern.matches(&"iface://v4.enp17s0:8081".parse().unwrap()));
let wildcard_family_and_port: BindPattern = "iface://en*".parse().unwrap();
assert!(wildcard_family_and_port.matches(&"iface://v6.eno1:4433".parse().unwrap()));
}
#[test]
fn pattern_matching_rejects_scheme_and_host_class_mismatches() {
let iface_pattern: BindPattern = "iface://v4.en*:8080".parse().unwrap();
let inet_uri: BindUri = "inet://127.0.0.1:8080".parse().unwrap();
assert!(!iface_pattern.matches(&inet_uri));
let iface_ip_pattern: BindPattern = "iface://127.0.0.1:8080".parse().unwrap();
let iface_uri: BindUri = "iface://v4.enp17s0:8080".parse().unwrap();
assert!(!iface_ip_pattern.matches(&iface_uri));
}
#[test]
fn inet_pattern_matches_ip_address_and_port_only() {
let pattern: BindPattern = "inet://127.0.0.1:8080".parse().unwrap();
assert!(pattern.matches(&"inet://127.0.0.1:8080".parse().unwrap()));
assert!(!pattern.matches(&"inet://127.0.0.1:8081".parse().unwrap()));
assert!(!pattern.matches(&"inet://127.0.0.2:8080".parse().unwrap()));
let non_ip_pattern: BindPattern = "inet://*:8080".parse().unwrap();
assert!(!non_ip_pattern.matches(&"inet://127.0.0.1:8080".parse().unwrap()));
}
#[test]
fn wildcard_port_matches_any_actual_port_for_supported_schemes() {
let iface_pattern: BindPattern = "iface://v4.en*".parse().unwrap();
assert!(iface_pattern.matches(&"iface://v4.enp17s0:0".parse().unwrap()));
assert!(iface_pattern.matches(&"iface://v4.enp17s0:4433".parse().unwrap()));
let inet_pattern: BindPattern = "inet://127.0.0.1".parse().unwrap();
assert!(inet_pattern.matches(&"inet://127.0.0.1:0".parse().unwrap()));
assert!(inet_pattern.matches(&"inet://127.0.0.1:4433".parse().unwrap()));
}