use core::fmt;
use crate::std::string::String;
use crate::address::{AsDomainRef, Domain};
use rama_core::bytes::BytesMut;
use radix_trie::{Trie, TrieCommon};
#[derive(Debug, Clone)]
pub struct DomainTrie<T> {
trie: Trie<String, NodeData<T>>,
}
#[derive(Debug, Clone)]
struct NodeData<T> {
exact: Option<T>,
subtree: Option<T>,
}
impl<T> NodeData<T> {
fn empty() -> Self {
Self {
exact: None,
subtree: None,
}
}
}
impl<T> Default for DomainTrie<T> {
fn default() -> Self {
Self {
trie: Default::default(),
}
}
}
#[non_exhaustive]
pub struct DomainMatch<'a, T> {
pub value: &'a T,
pub kind: MatchKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MatchKind {
Exact,
Subtree {
apex: Domain,
},
}
impl<T: fmt::Debug> fmt::Debug for DomainMatch<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DomainMatch")
.field("value", &self.value)
.field("kind", &self.kind)
.finish()
}
}
impl<T: PartialEq> PartialEq for DomainMatch<'_, T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value && self.kind == other.kind
}
}
impl<T: PartialEq + Eq> Eq for DomainMatch<'_, T> {}
impl<T> DomainTrie<T> {
#[inline]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.trie.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.trie.len()
}
#[must_use]
pub fn with_insert_domain(mut self, domain: impl AsDomainRef, value: T) -> Self {
self.insert_domain(domain, value);
self
}
pub fn insert_domain(&mut self, domain: impl AsDomainRef, value: T) -> &mut Self {
let s = domain.domain_as_str();
let (apex, is_subtree) = match s.strip_prefix("*.") {
Some(rest) => (rest, true),
None => (s, false),
};
let reversed = reverse_domain(apex);
if let Some(node) = self.trie.get_mut(&reversed) {
node.merge_from(value, is_subtree);
} else {
let mut node = NodeData::empty();
node.merge_from(value, is_subtree);
self.trie.insert(reversed, node);
}
self
}
#[must_use]
pub fn with_insert_domain_iter<I, S>(mut self, domains: I, value: T) -> Self
where
I: IntoIterator<Item = S>,
S: AsDomainRef,
T: Clone,
{
self.insert_domain_iter(domains, value);
self
}
pub fn insert_domain_iter<I, S>(&mut self, domains: I, value: T) -> &mut Self
where
I: IntoIterator<Item = S>,
S: AsDomainRef,
T: Clone,
{
let mut iter = domains.into_iter();
if let Some(mut prev) = iter.next() {
for curr in iter {
self.insert_domain(prev, value.clone());
prev = curr;
}
self.insert_domain(prev, value);
}
self
}
pub fn extend<I, S>(&mut self, iter: I) -> &mut Self
where
I: IntoIterator<Item = (S, T)>,
S: AsDomainRef,
{
for (domain, value) in iter {
self.insert_domain(domain, value);
}
self
}
pub fn is_match(&self, domain: impl AsDomainRef) -> bool {
let mut key = reverse_domain(domain.domain_as_str());
let mut is_first = true;
loop {
if let Some(node) = self.trie.get(&key) {
if is_first && node.exact.is_some() {
return true;
}
if node.subtree.is_some() {
return true;
}
}
if !truncate_to_parent(&mut key) {
return false;
}
is_first = false;
}
}
pub fn get_value(&self, domain: impl AsDomainRef) -> Option<&T> {
let mut key = reverse_domain(domain.domain_as_str());
let mut is_first = true;
loop {
if let Some(node) = self.trie.get(&key) {
if is_first && let Some(v) = node.exact.as_ref() {
return Some(v);
}
if let Some(v) = node.subtree.as_ref() {
return Some(v);
}
}
if !truncate_to_parent(&mut key) {
return None;
}
is_first = false;
}
}
pub fn get(&self, domain: impl AsDomainRef) -> Option<DomainMatch<'_, T>> {
let mut key = reverse_domain(domain.domain_as_str());
let mut is_first = true;
loop {
if let Some(node) = self.trie.get(&key) {
if is_first && let Some(value) = node.exact.as_ref() {
return Some(DomainMatch {
value,
kind: MatchKind::Exact,
});
}
if let Some(value) = node.subtree.as_ref() {
return Some(DomainMatch {
value,
kind: MatchKind::Subtree {
apex: reversed_key_to_domain(&key),
},
});
}
}
if !truncate_to_parent(&mut key) {
return None;
}
is_first = false;
}
}
pub fn match_exact(&self, domain: impl AsDomainRef) -> Option<&T> {
let key = reverse_domain(domain.domain_as_str());
self.trie
.get(&key)
.and_then(|n| n.exact.as_ref().or(n.subtree.as_ref()))
}
#[inline]
pub fn is_match_exact(&self, domain: impl AsDomainRef) -> bool {
self.match_exact(domain).is_some()
}
pub fn iter(&self) -> impl Iterator<Item = (Domain, &T)> {
self.trie.iter().flat_map(|(s, node)| {
let apex = reversed_key_to_domain(s);
let exact = node.exact.as_ref().map(|v| (apex.clone(), v));
let subtree = node.subtree.as_ref().map(|v| {
let wildcard = apex.try_as_wildcard().unwrap_or_else(|_| apex.clone());
(wildcard, v)
});
exact.into_iter().chain(subtree)
})
}
}
impl<T> NodeData<T> {
fn merge_from(&mut self, value: T, is_subtree: bool) {
if is_subtree {
self.subtree = Some(value);
} else {
self.exact = Some(value);
}
}
}
fn reverse_domain(domain: &str) -> String {
let from = domain.trim_matches('.');
let mut out = String::with_capacity(from.len() + 1);
let mut parts = from.split('.').rev();
if let Some(first) = parts.next() {
out.push_str(first);
}
for part in parts {
out.push('.');
out.push_str(part);
}
out.push('.');
out
}
fn truncate_to_parent(key: &mut String) -> bool {
if key.pop() != Some('.') {
return false;
}
if let Some(pos) = key.rfind('.') {
key.truncate(pos + 1);
true
} else {
key.clear();
false
}
}
fn reversed_key_to_domain(reversed: &str) -> Domain {
let from = reversed.trim_matches('.');
let mut builder = BytesMut::with_capacity(from.len());
let mut iter = from.split('.').rev();
if let Some(part) = iter.next() {
builder.extend_from_slice(part.as_bytes());
}
for part in iter {
builder.extend_from_slice(b".");
builder.extend_from_slice(part.as_bytes());
}
unsafe { Domain::from_maybe_borrowed_unchecked(builder.freeze()) }
}
impl<S, T> FromIterator<(S, T)> for DomainTrie<T>
where
S: AsDomainRef,
{
#[inline]
fn from_iter<I: IntoIterator<Item = (S, T)>>(iter: I) -> Self {
let mut trie = Self::default();
trie.extend(iter);
trie
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_reverse_domain() {
assert_eq!(reverse_domain("example.com."), "com.example.");
assert_eq!(reverse_domain("example.com.."), "com.example.");
assert_eq!(reverse_domain("example.com"), "com.example.");
assert_eq!(reverse_domain(".example.com"), "com.example.");
assert_eq!(reverse_domain("..example.com"), "com.example.");
assert_eq!(reverse_domain(".example.com."), "com.example.");
assert_eq!(reverse_domain("...example.com..."), "com.example.");
assert_eq!(reverse_domain("sub.example.com"), "com.example.sub.");
assert_eq!(reverse_domain("localhost"), "localhost.");
assert_eq!(reverse_domain(""), ".");
}
fn exact_match<'a>(value: &'a &'static str) -> DomainMatch<'a, &'static str> {
DomainMatch {
value,
kind: MatchKind::Exact,
}
}
fn subtree_match<'a>(
value: &'a &'static str,
apex: &'static str,
) -> DomainMatch<'a, &'static str> {
DomainMatch {
value,
kind: MatchKind::Subtree {
apex: Domain::from_static(apex),
},
}
}
#[test]
fn exact_does_not_match_descendants() {
let m = DomainTrie::new().with_insert_domain("example.com", "v");
assert_eq!(m.get("example.com"), Some(exact_match(&"v")));
assert!(m.get("foo.example.com").is_none());
assert!(m.get("bar.foo.example.com").is_none());
}
#[test]
fn wildcard_matches_apex_and_descendants() {
let m = DomainTrie::new().with_insert_domain("*.example.com", "v");
assert_eq!(
m.get("example.com"),
Some(subtree_match(&"v", "example.com"))
);
assert_eq!(
m.get("foo.example.com"),
Some(subtree_match(&"v", "example.com"))
);
assert_eq!(
m.get("a.b.example.com"),
Some(subtree_match(&"v", "example.com"))
);
assert!(m.get("example.org").is_none());
}
#[test]
fn exact_and_subtree_coexist_at_same_name() {
let mut m = DomainTrie::new();
m.insert_domain("example.com", "exact");
m.insert_domain("*.example.com", "subtree");
assert_eq!(m.get("example.com"), Some(exact_match(&"exact")));
assert_eq!(
m.get("foo.example.com"),
Some(subtree_match(&"subtree", "example.com"))
);
}
#[test]
fn deepest_exact_does_not_shadow_higher_subtree() {
let mut m = DomainTrie::new();
m.insert_domain("*.example.com", "subtree");
m.insert_domain("api.example.com", "exact-deep");
assert_eq!(m.get("api.example.com"), Some(exact_match(&"exact-deep")));
assert_eq!(
m.get("v1.api.example.com"),
Some(subtree_match(&"subtree", "example.com"))
);
}
#[test]
fn most_specific_subtree_wins() {
let m = DomainTrie::new()
.with_insert_domain("*.example.com", "outer")
.with_insert_domain("*.bar.example.com", "inner");
assert_eq!(
m.get("foo.bar.example.com"),
Some(subtree_match(&"inner", "bar.example.com"))
);
assert_eq!(
m.get("bar.example.com"),
Some(subtree_match(&"inner", "bar.example.com"))
);
assert_eq!(
m.get("baz.example.com"),
Some(subtree_match(&"outer", "example.com"))
);
}
#[test]
fn is_match_shortcut() {
let m = DomainTrie::new()
.with_insert_domain("example.com", "v1")
.with_insert_domain("*.other.com", "v2");
assert!(m.is_match("example.com"));
assert!(!m.is_match("foo.example.com"));
assert!(m.is_match("other.com"));
assert!(m.is_match("any.thing.other.com"));
assert!(!m.is_match("nope.org"));
}
#[test]
fn match_exact_only_hits_stored_name() {
let m = DomainTrie::new()
.with_insert_domain("example.com", "v1")
.with_insert_domain("*.other.com", "v2");
assert_eq!(m.match_exact("example.com"), Some(&"v1"));
assert_eq!(m.match_exact("other.com"), Some(&"v2"));
assert_eq!(m.match_exact("foo.example.com"), None);
assert_eq!(m.match_exact("foo.other.com"), None);
}
#[test]
fn no_label_boundary_confusion() {
let m = DomainTrie::new().with_insert_domain("*.example.com", "v");
assert_eq!(
m.get("bazfoo.bar.example.com"),
Some(subtree_match(&"v", "example.com"))
);
let m2 = DomainTrie::new().with_insert_domain("*.kegel.com", "v");
assert!(m2.get("gel.com").is_none());
}
#[test]
fn regression_no_byte_prefix_false_match() {
let m = DomainTrie::new().with_insert_domain("*.example.com", "v");
assert!(m.get("examplea.com").is_none());
assert!(m.get("foo.examplea.com").is_none());
assert!(m.get("aexample.com").is_none());
assert!(m.get("foo.aexample.com").is_none());
assert_eq!(
m.get("foo.example.com"),
Some(subtree_match(&"v", "example.com"))
);
}
#[test]
fn get_returns_kind_exact_for_exact_entry() {
let m = DomainTrie::new().with_insert_domain("example.com", "v");
let hit = m.get("example.com").unwrap();
assert_eq!(hit.value, &"v");
assert_eq!(hit.kind, MatchKind::Exact);
assert!(m.get("foo.example.com").is_none());
}
#[test]
fn get_returns_kind_subtree_with_apex_for_subtree_entry() {
let m = DomainTrie::new().with_insert_domain("*.example.com", "v");
let hit_apex = m.get("example.com").unwrap();
let hit_child = m.get("a.b.example.com").unwrap();
assert_eq!(hit_apex.value, &"v");
assert_eq!(
hit_apex.kind,
MatchKind::Subtree {
apex: Domain::from_static("example.com")
}
);
assert_eq!(hit_child.value, &"v");
assert_eq!(
hit_child.kind,
MatchKind::Subtree {
apex: Domain::from_static("example.com")
}
);
if let MatchKind::Subtree { apex } = &hit_child.kind {
assert_eq!(
apex.try_as_wildcard().unwrap(),
Domain::from_static("*.example.com")
);
}
}
#[test]
fn get_prefers_exact_over_subtree_at_same_name() {
let mut m = DomainTrie::new();
m.insert_domain("example.com", "exact");
m.insert_domain("*.example.com", "subtree");
let hit = m.get("example.com").unwrap();
assert_eq!(hit.value, &"exact");
assert_eq!(hit.kind, MatchKind::Exact);
let hit_child = m.get("foo.example.com").unwrap();
assert_eq!(hit_child.value, &"subtree");
assert!(matches!(hit_child.kind, MatchKind::Subtree { .. }));
}
#[test]
fn get_does_not_let_exact_node_shadow_higher_subtree() {
let mut m = DomainTrie::new();
m.insert_domain("*.example.com", "subtree");
m.insert_domain("api.example.com", "exact-deep");
let hit = m.get("v1.api.example.com").unwrap();
assert_eq!(hit.value, &"subtree");
assert_eq!(
hit.kind,
MatchKind::Subtree {
apex: Domain::from_static("example.com")
}
);
}
#[test]
fn iter_emits_apex_and_wildcard_forms() {
let m = DomainTrie::new()
.with_insert_domain("example.com", "exact")
.with_insert_domain("*.other.com", "subtree");
let mut out: Vec<_> = m.iter().map(|(d, v)| (d.to_string(), *v)).collect();
out.sort();
assert_eq!(
out,
vec![
("*.other.com".to_owned(), "subtree"),
("example.com".to_owned(), "exact"),
]
);
}
}