Skip to main content

sozu_lib/router/
pattern_trie.rs

1use std::{collections::HashMap, fmt::Debug, iter, str};
2
3use regex::bytes::{Regex, RegexBuilder};
4
5pub type Key = Vec<u8>;
6pub type KeyValue<K, V> = (K, V);
7
8#[derive(Debug, PartialEq, Eq)]
9pub enum InsertResult {
10    Ok,
11    Existing,
12    Failed,
13}
14
15#[derive(Debug, PartialEq, Eq)]
16pub enum RemoveResult {
17    Ok,
18    NotFound,
19}
20
21fn find_last_dot(input: &[u8]) -> Option<usize> {
22    //println!("find_last_dot: input = {}", from_utf8(input).unwrap());
23    (0..input.len()).rev().find(|&i| input[i] == b'.')
24}
25
26fn find_last_slash(input: &[u8]) -> Option<usize> {
27    //println!("find_last_dot: input = {}", from_utf8(input).unwrap());
28    (0..input.len()).rev().find(|&i| input[i] == b'/')
29}
30
31/// The stored pattern for one regex hostname segment: the configured
32/// `segment` anchored at both ends so `Regex::is_match` — a substring
33/// search — only succeeds on a whole-label match. `cdn[0-9]+` must match
34/// `cdn1` and not `cdn1xxx`.
35///
36/// The group is NOT decoration. `|` binds looser than concatenation, so the
37/// ungrouped `\Aa|b\z` parses as `(\Aa)|(b\z)` and each branch keeps only
38/// one anchor — measured, it still matches `axx`, and with three branches the
39/// middle one keeps NEITHER anchor and matches `zzbzz` as a bare substring.
40/// `(?:` … `)` is non-capturing, so `captures_len` and therefore every
41/// `$HOST[n]` rewrite index (`lookup_with_path` hands the segment regex to
42/// `router/mod.rs`'s `RouteResult::new_with_trie`, which reads
43/// `caps.iter().skip(1)`) are unchanged.
44///
45/// The segment is compiled ON ITS OWN first and only a segment that compiles
46/// is wrapped. The wrapper supplies one `(` and one `)`, so an unbalanced
47/// segment can be balanced BY the wrapping: `a)(b` is rejected by
48/// `Regex::new`, yet `\A(?:a)(b)\z` compiles and matches `ab`. Without the
49/// first compile, anchoring would silently promote a segment the trie rejects
50/// today into a live route matching something the operator never wrote.
51///
52/// The same string is the identity of a `regexps` entry — the dedup scan in
53/// `insert_recursive`, `remove_recursive` and `lookup_mut` all compare
54/// `Regex::as_str()` against it — so every site must build it here. The one
55/// site that also needs a compiled `Regex` goes through
56/// [`compiled_segment`], which wraps this function and does not respell it.
57///
58/// `None` means "this segment is not a usable regex": insert fails, and
59/// remove/lookup find nothing, which is correct because insert never stored
60/// it. `PathRule::anchored_regex` in `router/mod.rs` is the same shape for
61/// the path side; it is not shared because it returns a compiled `Regex` and
62/// three of the four call sites here need only the string, on a control-plane
63/// path that would otherwise recompile a regex per call.
64fn anchored_segment(segment: &str) -> Option<String> {
65    Regex::new(segment).ok()?;
66    let anchored = format!("\\A(?:{segment})\\z");
67    debug_assert!(
68        anchored.starts_with("\\A(?:") && anchored.ends_with(")\\z"),
69        "segment regex must be fully anchored AND grouped so every alternation \
70         branch matches the whole segment only",
71    );
72    Some(anchored)
73}
74
75/// Compile the stored pattern for one regex hostname segment.
76///
77/// The pattern string comes from [`anchored_segment`] and from nowhere
78/// else: it is the identity a `regexps` entry is addressed by --
79/// `insert_recursive`, `remove_recursive` and `lookup_mut` all compare
80/// `Regex::as_str()` against it -- and `RegexBuilder` leaves `as_str()`
81/// as the pattern it was handed, so a stored segment stays addressable
82/// and removable.
83///
84/// Compiled CASE-INSENSITIVELY, for the same reason `DomainRule::from_str`
85/// is (`router/mod.rs`): a hostname is case-insensitive (RFC 9110 §4.2.3)
86/// and the keys reaching `domain_lookup` are ASCII-lowercased, while the
87/// segment source is no longer folded on the way in. `Router::add_tree_rule`
88/// used to hand the whole hostname to `idna::domain_to_ascii`, which folded
89/// `/API[0-9]/` to `/api[0-9]/` and made an uppercase literal match by
90/// accident -- and folded `\D` to `\d` in the same pass, inverting the
91/// class the operator wrote (sozu#1377). It now stops at the literal
92/// labels, and the fold happens here instead, where it reaches literals
93/// only: case insensitivity does not touch `\d`/`\D`, `\w`/`\W` or
94/// `\s`/`\S`.
95///
96/// The folding is the crate default, i.e. Unicode simple case folding and
97/// NOT ASCII-only -- `.unicode(false)` is deliberately left unset, exactly
98/// as in `DomainRule::from_str`, whose comment carries the measurement:
99/// it would reject `\p{L}` patterns that compile and route today.
100///
101/// The group `anchored_segment` supplies is non-capturing and the builder
102/// adds none, so `captures_len` -- and therefore every `$HOST[n]` rewrite
103/// index, read from `caps.iter().skip(1)` in `router/mod.rs`'s
104/// `RouteResult::new_with_trie` -- is unchanged.
105fn compiled_segment(segment: &str) -> Option<Regex> {
106    let anchored = anchored_segment(segment)?;
107    let compiled = RegexBuilder::new(&anchored)
108        .case_insensitive(true)
109        .build()
110        .ok()?;
111    debug_assert_eq!(
112        compiled.as_str(),
113        anchored,
114        "a `regexps` entry is addressed by `as_str()`, so the builder must \
115         not respell the pattern `anchored_segment` produced",
116    );
117    Some(compiled)
118}
119
120/// Implementation of a trie tree structure.
121/// In Sozu this is used to store and lookup domains recursively.
122/// Each node represents a "level domain".
123/// A leaf node (leftmost label) can be a wildcard, a regex pattern or a plain string.
124/// Leaves also store a value associated with the complete domain.
125/// For Sozu it is a list of (PathRule, MethodRule, ClusterId). See the Router strucure.
126#[derive(Debug, Default, Clone)]
127pub struct TrieNode<V> {
128    key_value: Option<KeyValue<Key, V>>,
129    wildcard: Option<KeyValue<Key, V>>,
130    children: HashMap<Key, TrieNode<V>>,
131    regexps: Vec<(Regex, TrieNode<V>)>,
132}
133
134/// One step of a trie traversal where a non-literal segment matched.
135///
136/// `Wildcard` carries the actual segment bytes consumed by a `*` wildcard
137/// (so a router that wants to capture them can splice them into a rewrite
138/// template). `Regexp` carries both the matched bytes and the regex itself
139/// so the caller can re-run `Regex::captures` to pull explicit groups.
140#[derive(Debug)]
141pub enum TrieSubMatch<'a, 'b> {
142    Wildcard(&'a [u8]),
143    Regexp(&'a [u8], &'b Regex),
144}
145
146/// Ordered list of non-literal trie segments visited during a successful
147/// `lookup_with_path` traversal, in walk order: the trie walks a hostname from
148/// its rightmost label, so the first entry is the rightmost matched segment.
149/// Routers feed the entries into rewrite templates (`$HOST[n]`) so frontend
150/// rewrites can reach into the matched segments, and number them in hostname
151/// order, left to right (sozu#1595). Empty when only literal segments matched.
152pub type TrieMatches<'a, 'b> = Vec<TrieSubMatch<'a, 'b>>;
153
154/// Sink for the non-literal trie segments a lookup consumed.
155///
156/// [`TrieNode::lookup`] and [`TrieNode::lookup_with_path`] run the SAME
157/// walk, so the precedence order is stated once. They differ only in
158/// whether they record the segments it matched, and a caller with no
159/// rewrite template to fill must not pay for a record it will drop:
160/// [`NoTrace`] monomorphises every sink call away, so SNI resolution
161/// (`lib/src/tls.rs`, `lib/src/protocol/tcp_preread/`) still allocates
162/// nothing per lookup.
163///
164/// That is a claim about the SINK alone. The `accept` predicate beside it
165/// is a `&mut dyn FnMut`, not a generic parameter, so it is one indirect
166/// call per candidate leaf the walk reaches — including on the SNI path,
167/// where it is the constant `true`. Making it generic too would
168/// monomorphise `lookup_recursive` once per predicate type; it has not
169/// been worth the code size.
170///
171/// The walk BACKTRACKS, so a sink must be rewindable: a candidate that
172/// ends up not answering must leave none of its segments behind.
173trait SubMatchSink<'a, 'b> {
174    /// Position to rewind to when the branch about to be tried fails.
175    fn mark(&self) -> usize;
176    fn rewind(&mut self, mark: usize);
177    fn push(&mut self, sub_match: TrieSubMatch<'a, 'b>);
178}
179
180/// Sink that records nothing.
181struct NoTrace;
182
183impl<'a, 'b> SubMatchSink<'a, 'b> for NoTrace {
184    fn mark(&self) -> usize {
185        0
186    }
187
188    fn rewind(&mut self, _mark: usize) {}
189
190    fn push(&mut self, _sub_match: TrieSubMatch<'a, 'b>) {}
191}
192
193impl<'a, 'b> SubMatchSink<'a, 'b> for TrieMatches<'a, 'b> {
194    fn mark(&self) -> usize {
195        self.len()
196    }
197
198    fn rewind(&mut self, mark: usize) {
199        Vec::truncate(self, mark);
200    }
201
202    fn push(&mut self, sub_match: TrieSubMatch<'a, 'b>) {
203        Vec::push(self, sub_match);
204    }
205}
206
207/// Segments of the trie walk kept on the stack by [`InlineTrieMatches`]
208/// before it spills to the heap. A hostname carries one non-literal segment
209/// per label at most, and routing tables rarely stack more than a couple.
210const INLINE_TRIE_MATCHES: usize = 16;
211
212/// Record of the non-literal segments a walk matched, for
213/// [`crate::router::Router::lookup`]: the first [`INLINE_TRIE_MATCHES`]
214/// entries live in a fixed array on the caller's stack, later ones in a
215/// `Vec` that allocates only when an entry actually lands there.
216///
217/// [`TrieMatches`] is the same record as a plain `Vec`, which a lookup had to
218/// allocate up front even for a literal host that records nothing. The fixed
219/// array plays the part of HAProxy's `regmatch_t pmatch[MAX_MATCH]`
220/// (`src/sample.c:3487` at haproxy `0ceb8c65`), except that HAProxy clamps a
221/// match at `MAX_MATCH` (`src/regex.c:155-156`) where this spills, so no
222/// segment is ever dropped from a `$HOST[n]` template.
223pub(crate) struct InlineTrieMatches<'a, 'b> {
224    inline: [Option<TrieSubMatch<'a, 'b>>; INLINE_TRIE_MATCHES],
225    spilled: Vec<TrieSubMatch<'a, 'b>>,
226    len: usize,
227}
228
229impl<'a, 'b> InlineTrieMatches<'a, 'b> {
230    pub(crate) fn new() -> Self {
231        Self {
232            inline: [const { None }; INLINE_TRIE_MATCHES],
233            spilled: Vec::new(),
234            len: 0,
235        }
236    }
237
238    /// The recorded segments, in walk order (rightmost label first); iterate
239    /// it in reverse for hostname order.
240    pub(crate) fn iter(&self) -> impl DoubleEndedIterator<Item = &TrieSubMatch<'a, 'b>> {
241        self.inline[..self.len.min(INLINE_TRIE_MATCHES)]
242            .iter()
243            .flatten()
244            .chain(self.spilled.iter())
245    }
246}
247
248impl<'a, 'b> SubMatchSink<'a, 'b> for InlineTrieMatches<'a, 'b> {
249    fn mark(&self) -> usize {
250        self.len
251    }
252
253    fn rewind(&mut self, mark: usize) {
254        debug_assert!(mark <= self.len, "a rewind never moves forward");
255        // Inline slots past `len` are dead: `iter` never reads them and the
256        // next `push` overwrites them, so only the spill needs truncating.
257        self.spilled
258            .truncate(mark.saturating_sub(INLINE_TRIE_MATCHES));
259        self.len = mark;
260    }
261
262    fn push(&mut self, sub_match: TrieSubMatch<'a, 'b>) {
263        match self.inline.get_mut(self.len) {
264            Some(slot) => *slot = Some(sub_match),
265            None => self.spilled.push(sub_match),
266        }
267        self.len += 1;
268    }
269}
270
271impl<V: PartialEq> std::cmp::PartialEq for TrieNode<V> {
272    fn eq(&self, other: &Self) -> bool {
273        self.key_value == other.key_value
274            && self.wildcard == other.wildcard
275            && self.children == other.children
276            && self.regexps.len() == other.regexps.len()
277            && self
278                .regexps
279                .iter()
280                .zip(other.regexps.iter())
281                .fold(true, |b, (left, right)| {
282                    b && left.0.as_str() == right.0.as_str() && left.1 == right.1
283                })
284    }
285}
286
287impl<V: Debug + Clone> TrieNode<V> {
288    pub fn new(key: Key, value: V) -> TrieNode<V> {
289        TrieNode {
290            key_value: Some((key, value)),
291            wildcard: None,
292            children: HashMap::new(),
293            regexps: Vec::new(),
294        }
295    }
296
297    pub fn wildcard(key: Key, value: V) -> TrieNode<V> {
298        TrieNode {
299            key_value: None,
300            wildcard: Some((key, value)),
301            children: HashMap::new(),
302            regexps: Vec::new(),
303        }
304    }
305
306    pub fn root() -> TrieNode<V> {
307        TrieNode {
308            key_value: None,
309            wildcard: None,
310            children: HashMap::new(),
311            regexps: Vec::new(),
312        }
313    }
314
315    pub fn is_empty(&self) -> bool {
316        self.key_value.is_none()
317            && self.wildcard.is_none()
318            && self.regexps.is_empty()
319            && self.children.is_empty()
320    }
321
322    /// Store `value` in THIS node's own `key_value` slot: the value of a
323    /// domain whose every segment the recursion has already consumed.
324    ///
325    /// Exact dual of the empty-`partial_key` arm of
326    /// [`TrieNode::remove_recursive`], which clears the same slot. A free
327    /// slot takes the value (`Ok`); an occupied one is `Existing`.
328    ///
329    /// Always ASK the slot, never infer it from how the node was built.
330    /// A regex subtree opened by the `pos > 0` create-path of
331    /// `insert_recursive` is a valueless [`TrieNode::root`] carrying only a
332    /// deeper domain, while one opened by its `pos == 0` path is a
333    /// value-bearing [`TrieNode::new`]. Both are reachable at the same
334    /// `regexps` entry, so the two cannot be told apart from the outside.
335    fn insert_own_value(&mut self, key: &Key, value: V) -> InsertResult {
336        if self.key_value.is_none() {
337            self.key_value = Some((key.to_vec(), value));
338            InsertResult::Ok
339        } else {
340            InsertResult::Existing
341        }
342    }
343
344    pub fn insert(&mut self, key: Key, value: V) -> InsertResult {
345        //println!("insert: key == {}", std::str::from_utf8(&key).unwrap());
346        if key.is_empty() {
347            return InsertResult::Failed;
348        }
349        if key[..] == b"."[..] {
350            return InsertResult::Failed;
351        }
352
353        #[cfg(debug_assertions)]
354        let before = self.count_values();
355
356        let insert_result = self.insert_recursive(&key, &key, value);
357
358        // Post: the value count grows by exactly one on a fresh insert,
359        // is unchanged when the key already existed, and is ALSO unchanged
360        // on `Failed` -- every mutating step in `insert_recursive`
361        // (`children.insert`, `regexps.push`) is guarded by an `Ok` from
362        // the level below, so a rejected key can never leave a half-built
363        // branch behind.
364        //
365        // `Failed` is NOT an internal-invariant break: the two cheap guards
366        // above only rule out the empty key and a bare `.`, while
367        // `insert_recursive` rejects a much wider class of malformed
368        // domains (a key ending in `/` with no openable regex segment, a
369        // regex segment that is not `.`-anchored, a segment that is not a
370        // valid regex, an empty label from a leading or doubled `.`).
371        // Those keys arrive from the control plane -- an `AddHttpFrontend`
372        // over the command socket, or a `LoadState` replay -- so this must
373        // stay a graceful rejection the caller reports. It used to be an
374        // `assert_ne!`, which turned a malformed hostname into a worker
375        // panic (and, on state replay, a restart loop).
376        #[cfg(debug_assertions)]
377        {
378            let after = self.count_values();
379            match insert_result {
380                InsertResult::Ok => debug_assert_eq!(
381                    after,
382                    before + 1,
383                    "a fresh insert must add exactly one value to the trie",
384                ),
385                InsertResult::Existing => debug_assert_eq!(
386                    after, before,
387                    "an Existing insert must not change the trie value count",
388                ),
389                InsertResult::Failed => debug_assert_eq!(
390                    after, before,
391                    "a failed insert must leave the trie value count untouched",
392                ),
393            }
394            self.check_invariants();
395        }
396
397        insert_result
398    }
399
400    pub fn insert_recursive(&mut self, partial_key: &[u8], key: &Key, value: V) -> InsertResult {
401        //println!("insert_rec: key == {}", std::str::from_utf8(partial_key).unwrap());
402        // An empty `partial_key` means the caller handed us a key with an
403        // empty label -- a leading `.` (`.example.com`) or a doubled one --
404        // which the dot-split recursion below cannot consume any further.
405        // Reject it like any other malformed domain instead of panicking:
406        // this input comes from the control plane, not from Sozu itself.
407        //
408        // Only TWO paths can deliver an empty `partial_key`, and both mean
409        // "empty label": the dot-split recursing on `partial_key[..pos]`
410        // with `pos == 0` (`.example.com`, `..`), and the regex arm
411        // recursing on `partial_key[..pos - 1]` with `pos == 1`, i.e. a
412        // leading `.` before the segment (`./test[0-9]/.example.com`).
413        //
414        // So this arm is INPUT VALIDATION, not "the value belongs to this
415        // node". Do not reroute a legitimate insert through it -- the
416        // leftmost-regex case looks like it wants an empty key (that is how
417        // `remove_recursive` reaches its own `key_value`), but taking it
418        // here would make every leading-dot hostname insertable. It calls
419        // `insert_own_value` directly instead.
420        if partial_key.is_empty() {
421            return InsertResult::Failed;
422        }
423        // `partial_key` is always a suffix of the full `key` being
424        // inserted — the recursion only ever shrinks the head, never
425        // rewrites the tail.
426        debug_assert!(
427            partial_key.len() <= key.len(),
428            "insert recursion must consume the key, never grow past it",
429        );
430
431        if partial_key[partial_key.len() - 1] == b'/' {
432            let pos = find_last_slash(&partial_key[..partial_key.len() - 1]);
433
434            if let Some(pos) = pos {
435                if pos > 0 && partial_key[pos - 1] != b'.' {
436                    return InsertResult::Failed;
437                }
438
439                if let Ok(s) = str::from_utf8(&partial_key[pos + 1..partial_key.len() - 1]) {
440                    // `None` is a segment that is not a regex on its own;
441                    // see `anchored_segment` for why that is refused here
442                    // rather than rescued by the wrapping.
443                    let Some(anchored_s) = anchored_segment(s) else {
444                        return InsertResult::Failed;
445                    };
446                    for t in self.regexps.iter_mut() {
447                        if t.0.as_str() == anchored_s {
448                            // `pos > 0`: there is a `.`-separated prefix
449                            // before this regex segment; recurse on it
450                            // (dropping the leading `.` via `pos - 1`).
451                            // `pos == 0`: the regex is the leftmost/only
452                            // segment, so the value belongs to THIS
453                            // subtree's own `key_value` slot -- ask the
454                            // slot via `insert_own_value`, the dual of the
455                            // empty-key arm `remove_recursive` reaches for
456                            // the very same host.
457                            //
458                            // Do NOT assume the subtree is value-bearing
459                            // here. A `regexps` entry is ALSO opened by the
460                            // `pos > 0` create-path below, which builds a
461                            // valueless `TrieNode::root()` holding only the
462                            // deeper domain. So inserting
463                            // `foo./test[0-9]/.example.com` and then
464                            // `/test[0-9]/.example.com` lands on this arm
465                            // with `key_value == None`: answering `Existing`
466                            // unconditionally (as this arm used to) stored
467                            // nothing while reporting the host as already
468                            // present, leaving it permanently un-insertable.
469                            // In debug the router's post-insert reachability
470                            // check (`router/mod.rs`) then killed the worker
471                            // on an `AddHttpFrontend`; in release
472                            // `add_tree_rule` returned `true` and
473                            // `sozu query frontends` listed a route the trie
474                            // never served -- control plane diverging from
475                            // data plane, silently. The reverse insertion
476                            // order always worked, which is why the gap
477                            // stayed invisible.
478                            //
479                            // Older still, this arm did
480                            // `partial_key[..pos - 1]` unconditionally,
481                            // underflowing to `usize::MAX` and panicking on
482                            // `pos == 0` (same latent bug as `lookup_mut`).
483                            if pos > 0 {
484                                return t.1.insert_recursive(&partial_key[..pos - 1], key, value);
485                            }
486                            return t.1.insert_own_value(key, value);
487                        }
488                    }
489
490                    // `anchored_s` above is the very string the dedup scan
491                    // just failed to find, so the entry this opens is
492                    // addressable by `remove_recursive` and `lookup_mut`,
493                    // which rebuild it the same way.
494                    if let Some(r) = compiled_segment(s) {
495                        if pos > 0 {
496                            let mut node = TrieNode::root();
497                            let pos = pos - 1;
498
499                            let res = node.insert_recursive(&partial_key[..pos], key, value);
500
501                            if res == InsertResult::Ok {
502                                self.regexps.push((r, node));
503                            }
504
505                            return res;
506                        } else {
507                            let node = TrieNode::new(key.to_vec(), value);
508                            self.regexps.push((r, node));
509                            return InsertResult::Ok;
510                        }
511                    }
512                }
513            }
514
515            return InsertResult::Failed;
516        }
517
518        let pos = find_last_dot(partial_key);
519        match pos {
520            None => {
521                // Answering `Existing` off a bare `contains_key` is the
522                // same shape as the regex arm above, and is sound only
523                // because the two `children.insert` sites use DISJOINT key
524                // spaces: this arm keys a `TrieNode::new` (value-bearing)
525                // by a dotless `partial_key`, while the dot-split arm keys
526                // a `TrieNode::root()` by `partial_key[pos..]`, which
527                // always starts with `.`. A dotless probe therefore can
528                // only ever find a value-bearing leaf, and such a leaf
529                // never gains children (nothing recurses into it) nor
530                // outlives its value (`remove_recursive` prunes it once
531                // emptied). Break that disjointness and this arm acquires
532                // the regex arm's bug.
533                if self.children.contains_key(partial_key) {
534                    InsertResult::Existing
535                } else if partial_key == &b"*"[..] {
536                    if self.wildcard.is_some() {
537                        InsertResult::Existing
538                    } else {
539                        self.wildcard = Some((key.to_vec(), value));
540                        InsertResult::Ok
541                    }
542                } else {
543                    let node = TrieNode::new(key.to_vec(), value);
544                    self.children.insert(partial_key.to_vec(), node);
545                    InsertResult::Ok
546                }
547            }
548            Some(pos) => {
549                // The dot at `pos` is kept on the child key (suffix) and
550                // stripped from the recursive prefix; the two slices
551                // partition `partial_key` exactly.
552                debug_assert_eq!(
553                    partial_key[..pos].len() + partial_key[pos..].len(),
554                    partial_key.len(),
555                    "dot-split must partition partial_key without losing bytes",
556                );
557                debug_assert_eq!(
558                    partial_key[pos], b'.',
559                    "find_last_dot must point at a '.' byte",
560                );
561                if let Some(child) = self.children.get_mut(&partial_key[pos..]) {
562                    return child.insert_recursive(&partial_key[..pos], key, value);
563                }
564
565                let mut node = TrieNode::root();
566                let res = node.insert_recursive(&partial_key[..pos], key, value);
567
568                if res == InsertResult::Ok {
569                    self.children.insert(partial_key[pos..].to_vec(), node);
570                }
571
572                res
573            }
574        }
575    }
576
577    pub fn remove(&mut self, key: &Key) -> RemoveResult {
578        #[cfg(debug_assertions)]
579        let before = self.count_values();
580
581        let remove_result = self.remove_recursive(key);
582
583        // Post: a successful remove drops exactly one value; a NotFound
584        // is a no-op on the value count. The structural invariants then
585        // guarantee no emptied subtree was stranded by the prune.
586        #[cfg(debug_assertions)]
587        {
588            let after = self.count_values();
589            match remove_result {
590                RemoveResult::Ok => debug_assert_eq!(
591                    after + 1,
592                    before,
593                    "a successful remove must drop exactly one value from the trie",
594                ),
595                RemoveResult::NotFound => debug_assert_eq!(
596                    after, before,
597                    "a NotFound remove must not change the trie value count",
598                ),
599            }
600            self.check_invariants();
601        }
602
603        remove_result
604    }
605
606    pub fn remove_recursive(&mut self, partial_key: &[u8]) -> RemoveResult {
607        //println!("remove: key == {}", std::str::from_utf8(partial_key).unwrap());
608
609        if partial_key.is_empty() {
610            if self.key_value.is_some() {
611                self.key_value = None;
612                return RemoveResult::Ok;
613            } else {
614                return RemoveResult::NotFound;
615            }
616        }
617
618        if partial_key == &b"*"[..] {
619            if self.wildcard.is_some() {
620                self.wildcard = None;
621                return RemoveResult::Ok;
622            } else {
623                return RemoveResult::NotFound;
624            }
625        }
626
627        if partial_key[partial_key.len() - 1] == b'/' {
628            let pos = find_last_slash(&partial_key[..partial_key.len() - 1]);
629
630            if let Some(pos) = pos {
631                if pos > 0 && partial_key[pos - 1] != b'.' {
632                    return RemoveResult::NotFound;
633                }
634
635                if let Ok(s) = str::from_utf8(&partial_key[pos + 1..partial_key.len() - 1]) {
636                    // A segment `insert_recursive` refused stored nothing,
637                    // so there is nothing here to remove either.
638                    let Some(anchored_s) = anchored_segment(s) else {
639                        return RemoveResult::NotFound;
640                    };
641                    // Mirror of `insert_recursive`. `pos > 0`: a
642                    // `.`-separated prefix precedes the regex segment, so
643                    // the value sits deeper in that segment's subtree —
644                    // recurse on the prefix, dropping the separating `.`
645                    // via `pos - 1`. `pos == 0`: the regex is the
646                    // leftmost/only segment and the value IS that
647                    // subtree's own `key_value`, reached with an empty key.
648                    // That slot is asked, never assumed: a subtree opened
649                    // by the `pos > 0` create-path of `insert_recursive` is
650                    // a valueless `TrieNode::root()`, so the empty-key arm
651                    // correctly answers `NotFound` for a leftmost host that
652                    // only ever existed as a deeper domain's segment.
653                    //
654                    // Either way exactly one value goes. Dropping the
655                    // whole `regexps` entry instead — which the `pos == 0`
656                    // arm used to do with `retain` — also deletes every
657                    // deeper domain sharing the segment, and reports `Ok`
658                    // for a host that was never stored.
659                    let rest: &[u8] = if pos > 0 {
660                        &partial_key[..pos - 1]
661                    } else {
662                        &partial_key[..0]
663                    };
664                    // `check_invariants` keeps the anchored patterns
665                    // unique per node, so at most one entry can match.
666                    // `Vec::remove` (never `swap_remove`): `lookup` takes
667                    // the FIRST matching regex, so the order of `regexps`
668                    // is routing behaviour.
669                    if let Some(index) = self
670                        .regexps
671                        .iter()
672                        .position(|(r, _)| r.as_str() == anchored_s)
673                        && self.regexps[index].1.remove_recursive(rest) == RemoveResult::Ok
674                    {
675                        // An emptied regex subtree must be pruned here,
676                        // exactly like an emptied `children` subtree
677                        // below: left behind it strands a valueless node
678                        // and keeps every ancestor non-empty, so nothing
679                        // above it could be pruned either.
680                        if self.regexps[index].1.is_empty() {
681                            self.regexps.remove(index);
682                        }
683                        return RemoveResult::Ok;
684                    }
685                }
686            }
687
688            return RemoveResult::NotFound;
689        }
690
691        let pos = find_last_dot(partial_key);
692        let (prefix, suffix) = match pos {
693            None => (&b""[..], partial_key),
694            Some(pos) => (&partial_key[..pos], &partial_key[pos..]),
695        };
696        //println!("remove: prefix|suffix: {} | {}", std::str::from_utf8(prefix).unwrap(), std::str::from_utf8(suffix).unwrap());
697        debug_assert_eq!(
698            prefix.len() + suffix.len(),
699            partial_key.len(),
700            "dot-split must partition the key without losing or duplicating bytes",
701        );
702
703        match self.children.get_mut(suffix) {
704            Some(child) => match child.remove_recursive(prefix) {
705                RemoveResult::NotFound => RemoveResult::NotFound,
706                RemoveResult::Ok => {
707                    // An emptied child subtree MUST be pruned here so the
708                    // parent never strands a node with no value. After the
709                    // prune the suffix key is gone from `children`.
710                    if child.is_empty() {
711                        self.children.remove(suffix);
712                        debug_assert!(
713                            !self.children.contains_key(suffix),
714                            "an emptied child subtree must be removed from the parent",
715                        );
716                    } else {
717                        // `count_values` is debug-only; gate the whole
718                        // assert so the call does not have to compile in
719                        // release (HARD RULE 2 — E0425 guard).
720                        #[cfg(debug_assertions)]
721                        debug_assert!(
722                            child.count_values() > 0,
723                            "a retained child subtree must still hold at least one value",
724                        );
725                    }
726                    RemoveResult::Ok
727                }
728            },
729            None => RemoveResult::NotFound,
730        }
731    }
732
733    /// Resolve `partial_key` against this subtree, MOST SPECIFIC FIRST.
734    ///
735    /// At every level the candidates are tried in one stated order:
736    ///
737    /// 1. the exact literal child,
738    /// 2. the regex segments, in declaration order,
739    /// 3. the `*` wildcard, which only ever stands for the leftmost label.
740    ///
741    /// A candidate that yields nothing hands the key to the next one: the
742    /// walk BACKTRACKS instead of ending the lookup. `accept` is what
743    /// "yields something" means to the caller — `Router::lookup` accepts a
744    /// leaf only when one of its rules serves this request's path AND
745    /// method, so a hostname candidate whose rules all reject the request
746    /// is skipped rather than answered with a 404 (sozu#1351).
747    ///
748    /// The two fallbacks are NOT interchangeable, and which one a change
749    /// may drop is not guessable — the mutation tests measure it.
750    ///
751    /// - The **regex-loop** fallback is what the reorder requires. The
752    ///   wildcard used to be consulted BEFORE the regex list, so a segment
753    ///   that matches a label but holds no value for this host
754    ///   (`/cdn[0-9]+/`, as opened by `images./cdn[0-9]+/.hello.com`)
755    ///   reached the wildcard only because the wildcard went first. Move
756    ///   the wildcard after the regex list without this fallback and that
757    ///   case becomes a miss:
758    ///   `a_regex_segment_holding_no_value_falls_back_to_the_wildcard`.
759    /// - The **exact-child** fallback answers to the add-path fix instead.
760    ///   An exact hostname now owns its own node, carrying only the paths
761    ///   written for it, so without this fallback every other path on that
762    ///   host stops resolving. Dropping it does NOT redden the regex case
763    ///   above; it reddens
764    ///   `a_hostname_candidate_that_serves_no_rule_falls_through_to_the_next`
765    ///   and the leak test.
766    ///
767    /// Both still ship together — the add fix and the reorder each need
768    /// one — but a future reader deciding whether they can be split should
769    /// read that pairing, not assume it.
770    ///
771    /// The walk visits each node at most once: the trie is a tree, so two
772    /// candidates never share a node. The cost is therefore bounded by the
773    /// number of nodes matching the key, not by the product of the
774    /// per-level candidate counts.
775    fn lookup_recursive<'a, 'b, S: SubMatchSink<'a, 'b>>(
776        &'b self,
777        partial_key: &'a [u8],
778        accept_wildcard: bool,
779        trace: &mut S,
780        accept: &mut dyn FnMut(&KeyValue<Key, V>) -> bool,
781    ) -> Option<&'b KeyValue<Key, V>> {
782        if partial_key.is_empty() {
783            return match self.key_value.as_ref() {
784                Some(key_value) if accept(key_value) => Some(key_value),
785                _ => None,
786            };
787        }
788
789        let pos = find_last_dot(partial_key);
790        let (prefix, suffix) = match pos {
791            None => (&b""[..], partial_key),
792            Some(pos) => (&partial_key[..pos], &partial_key[pos..]),
793        };
794        // The dot-split partitions the key exactly: prefix ++ suffix is
795        // the whole input, and a dotted split puts the `.` at the head
796        // of the suffix (this is the byte the wildcard/regex arms strip).
797        debug_assert_eq!(
798            prefix.len() + suffix.len(),
799            partial_key.len(),
800            "dot-split must partition the key without losing or duplicating bytes",
801        );
802        debug_assert!(
803            !suffix.is_empty(),
804            "the suffix the trie matches children against must be non-empty",
805        );
806        debug_assert!(
807            pos.is_none() || suffix.first() == Some(&b'.'),
808            "a dotted split must place the separator at the head of the suffix",
809        );
810
811        // 1. the exact literal child.
812        if let Some(child) = self.children.get(suffix) {
813            let mark = trace.mark();
814            if let Some(found) = child.lookup_recursive(prefix, accept_wildcard, trace, accept) {
815                return Some(found);
816            }
817            trace.rewind(mark);
818        }
819
820        // The bytes a non-literal segment stands for: `suffix` without the
821        // separator the dot-split kept at its head.
822        let segment = if suffix[0] == b'.' {
823            &suffix[1..]
824        } else {
825            suffix
826        };
827
828        // 2. the regex segments, in declaration order.
829        for (regexp, child) in self.regexps.iter() {
830            if !regexp.is_match(segment) {
831                continue;
832            }
833            let mark = trace.mark();
834            trace.push(TrieSubMatch::Regexp(segment, regexp));
835            if let Some(found) = child.lookup_recursive(prefix, accept_wildcard, trace, accept) {
836                return Some(found);
837            }
838            trace.rewind(mark);
839        }
840
841        // 3. the wildcard, which stands for one leftmost label, and only
842        //    when the caller accepts one.
843        if prefix.is_empty()
844            && accept_wildcard
845            && let Some(key_value) = self.wildcard.as_ref()
846            && accept(key_value)
847        {
848            trace.push(TrieSubMatch::Wildcard(segment));
849            return Some(key_value);
850        }
851
852        None
853    }
854
855    /// Look up `partial_key` and additionally collect the non-literal segments
856    /// that matched along the way (`TrieMatches`).
857    ///
858    /// Equivalent to `lookup` for callers that don't need the captures, but
859    /// frontends with `$HOST[n]` rewrite templates need the matched segments
860    /// to fill the placeholders. The accumulator is passed in by value so
861    /// callers can pre-size it (`Vec::with_capacity`) and we own the path
862    /// returned alongside the value. It carries the segments of the
863    /// candidate that ANSWERED: the walk rewinds the ones it tried and
864    /// abandoned.
865    ///
866    /// `accept` filters candidates — see `TrieNode::lookup_recursive`
867    /// for the precedence order it is applied in. Pass
868    /// `&mut |_: &KeyValue<Key, V>| true` to take the first leaf found.
869    pub fn lookup_with_path<'a, 'b>(
870        &'b self,
871        partial_key: &'a [u8],
872        accept_wildcard: bool,
873        mut trace: TrieMatches<'a, 'b>,
874        accept: &mut dyn FnMut(&KeyValue<Key, V>) -> bool,
875    ) -> Option<(&'b KeyValue<Key, V>, TrieMatches<'a, 'b>)> {
876        self.lookup_recursive(partial_key, accept_wildcard, &mut trace, accept)
877            .map(|key_value| (key_value, trace))
878    }
879
880    /// [`TrieNode::lookup_with_path`] recording into a caller-owned
881    /// [`InlineTrieMatches`], so a lookup that records at most
882    /// [`INLINE_TRIE_MATCHES`] segments allocates nothing. `trace` must be
883    /// empty; it holds the segments of the candidate that answered.
884    pub(crate) fn lookup_with_inline_path<'a, 'b>(
885        &'b self,
886        partial_key: &'a [u8],
887        accept_wildcard: bool,
888        trace: &mut InlineTrieMatches<'a, 'b>,
889        accept: &mut dyn FnMut(&KeyValue<Key, V>) -> bool,
890    ) -> Option<&'b KeyValue<Key, V>> {
891        debug_assert_eq!(trace.mark(), 0, "a lookup starts from an empty record");
892        self.lookup_recursive(partial_key, accept_wildcard, trace, accept)
893    }
894
895    /// Request-addressed lookup: which entry serves `partial_key`.
896    ///
897    /// Same walk and same precedence as [`TrieNode::lookup_with_path`] —
898    /// stated once, in `TrieNode::lookup_recursive` — without recording
899    /// the segments it matched. This is the resolver SNI goes through
900    /// (`lib/src/tls.rs`, `lib/src/protocol/tcp_preread/`), so certificate
901    /// selection follows the same order as HTTP routing.
902    pub fn lookup(&self, partial_key: &[u8], accept_wildcard: bool) -> Option<&KeyValue<Key, V>> {
903        self.lookup_recursive(
904            partial_key,
905            accept_wildcard,
906            &mut NoTrace,
907            &mut |_: &KeyValue<Key, V>| true,
908        )
909    }
910
911    pub fn lookup_mut(
912        &mut self,
913        partial_key: &[u8],
914        accept_wildcard: bool,
915    ) -> Option<&mut KeyValue<Key, V>> {
916        //println!("lookup: key == {}", std::str::from_utf8(partial_key).unwrap());
917
918        if partial_key.is_empty() {
919            return self.key_value.as_mut();
920        }
921
922        if partial_key == &b"*"[..] {
923            return self.wildcard.as_mut();
924        }
925
926        if partial_key[partial_key.len() - 1] == b'/' {
927            let pos = find_last_slash(&partial_key[..partial_key.len() - 1]);
928
929            if let Some(pos) = pos {
930                if pos > 0 && partial_key[pos - 1] != b'.' {
931                    return None;
932                }
933
934                if let Ok(s) = str::from_utf8(&partial_key[pos + 1..partial_key.len() - 1]) {
935                    // A segment `insert_recursive` refused was never stored,
936                    // so it is addressable by nothing here.
937                    let anchored_s = anchored_segment(s)?;
938                    for t in self.regexps.iter_mut() {
939                        if t.0.as_str() == anchored_s {
940                            // `pos == 0` means the regex is the leftmost
941                            // segment, so the value is that subtree's own
942                            // `key_value`, reached via the empty-prefix
943                            // recursion (`lookup_mut(b"")` returns
944                            // `key_value`). The subtree is NOT necessarily
945                            // value-bearing — the `pos > 0` create-path of
946                            // `insert_recursive` opens a valueless
947                            // `TrieNode::root()` — but nothing here assumes
948                            // it is: the empty-key arm returns the `Option`
949                            // as it finds it, which is `None` exactly when
950                            // the leftmost host was never stored. The
951                            // pre-fix `partial_key[..pos - 1]` underflowed to
952                            // `usize::MAX` and panicked on `pos == 0` — the
953                            // same latent bug as the insert dedup loop. Drop
954                            // the leading `.` only when there is one.
955                            let rest = if pos > 0 {
956                                &partial_key[..pos - 1]
957                            } else {
958                                &partial_key[..0]
959                            };
960                            return t.1.lookup_mut(rest, accept_wildcard);
961                        }
962                    }
963                }
964            }
965
966            return None;
967        }
968
969        let pos = find_last_dot(partial_key);
970        let (prefix, suffix) = match pos {
971            None => (&b""[..], partial_key),
972            Some(pos) => (&partial_key[..pos], &partial_key[pos..]),
973        };
974        //println!("lookup: prefix|suffix: {} | {}", std::str::from_utf8(prefix).unwrap(), std::str::from_utf8(suffix).unwrap());
975        debug_assert_eq!(
976            prefix.len() + suffix.len(),
977            partial_key.len(),
978            "dot-split must partition the key without losing or duplicating bytes",
979        );
980        debug_assert!(
981            !suffix.is_empty(),
982            "the suffix the trie matches children against must be non-empty",
983        );
984
985        match self.children.get_mut(suffix) {
986            Some(child) => child.lookup_mut(prefix, accept_wildcard),
987            None => {
988                //println!("no child found, testing wildcard and regexps");
989
990                if prefix.is_empty() && self.wildcard.is_some() && accept_wildcard {
991                    //println!("no dot, wildcard applies");
992                    self.wildcard.as_mut()
993                } else {
994                    //println!("there's still a subdomain, wildcard does not apply");
995
996                    // A literal segment resolves through a matching regex
997                    // segment only when the caller accepts a non-literal
998                    // entry for its key -- the guard `insert_sni_route`
999                    // (`lib/src/tcp.rs`) already documents for the wildcard
1000                    // slot right above, extended to the regex list.
1001                    //
1002                    // `lookup_mut` addresses a KEY, not a request: it is
1003                    // how `add_tree_rule`/`remove_tree_rule` and
1004                    // `insert_sni_route`/`remove_sni_route` reach the node
1005                    // a configured hostname owns, and both its `*` case and
1006                    // its trailing-`/` case above match by IDENTITY. This
1007                    // arm did not, so adding `test4.example.com` after
1008                    // `/test[0-9]/.example.com` pushed the exact host's
1009                    // rule onto the REGEX segment's leaf and the whole
1010                    // family served it -- a routing leak the operator
1011                    // could not see, since `sozu query frontends` showed
1012                    // exactly what they had typed (sozu#1351). The
1013                    // request-addressed resolvers (`lookup`,
1014                    // `lookup_with_path`) are where a literal host is meant
1015                    // to match a regex segment.
1016                    if !accept_wildcard {
1017                        return None;
1018                    }
1019
1020                    for &mut (ref regexp, ref mut child) in self.regexps.iter_mut() {
1021                        let suffix = if suffix[0] == b'.' {
1022                            &suffix[1..]
1023                        } else {
1024                            suffix
1025                        };
1026                        //println!("testing regexp: {} on suffix {}", r.as_str(), str::from_utf8(s).unwrap());
1027
1028                        if regexp.is_match(suffix) {
1029                            //println!("matched");
1030                            return child.lookup_mut(prefix, accept_wildcard);
1031                        }
1032                    }
1033
1034                    None
1035                }
1036            }
1037        }
1038    }
1039
1040    pub fn print(&self) {
1041        self.print_recursive(b"", 0)
1042    }
1043
1044    pub fn print_recursive(&self, partial_key: &[u8], indent: u8) {
1045        let raw_prefix: Vec<u8> = iter::repeat_n(b' ', 2 * indent as usize).collect();
1046        let prefix = str::from_utf8(&raw_prefix).unwrap();
1047
1048        print!("{}{}: ", prefix, str::from_utf8(partial_key).unwrap());
1049        if let Some((ref key, ref value)) = self.key_value {
1050            print!("({}, {:?}) | ", str::from_utf8(key).unwrap(), value);
1051        } else {
1052            print!("None | ");
1053        }
1054
1055        if let Some((key, value)) = &self.wildcard {
1056            println!("({}, {:?})", str::from_utf8(key).unwrap(), value);
1057        } else {
1058            println!("None");
1059        }
1060
1061        for (child_key, child) in self.children.iter() {
1062            child.print_recursive(child_key, indent + 1);
1063        }
1064
1065        for (regexp, child) in self.regexps.iter() {
1066            //print!("{}{}:", prefix, regexp.as_str());
1067            child.print_recursive(regexp.as_str().as_bytes(), indent + 1);
1068        }
1069    }
1070
1071    /// Visit every stored value in the trie (the literal `key_value` and
1072    /// the leftmost `wildcard` slot of every node, plus all
1073    /// regex-subtree leaves) and invoke `f` on each. Used by the router
1074    /// to walk all routes for cross-cutting refreshes (e.g. listener-
1075    /// default HSTS reflow) without rebuilding the trie.
1076    pub fn for_each_value_mut<F: FnMut(&mut V)>(&mut self, f: &mut F) {
1077        if let Some((_, ref mut value)) = self.key_value {
1078            f(value);
1079        }
1080        if let Some((_, ref mut value)) = self.wildcard {
1081            f(value);
1082        }
1083        for child in self.children.values_mut() {
1084            child.for_each_value_mut(f);
1085        }
1086        for (_, child) in self.regexps.iter_mut() {
1087            child.for_each_value_mut(f);
1088        }
1089    }
1090
1091    /// Count every value slot reachable from this node: the literal
1092    /// `key_value`, the leftmost `wildcard`, plus all values stored in
1093    /// child subtrees and regex subtrees. Used only by the
1094    /// `#[cfg(debug_assertions)]` invariant checks as the leaf-count
1095    /// accounting (`inserts − removes`); never called in release.
1096    #[cfg(debug_assertions)]
1097    fn count_values(&self) -> usize {
1098        let local = self.key_value.is_some() as usize + self.wildcard.is_some() as usize;
1099        let in_children: usize = self.children.values().map(TrieNode::count_values).sum();
1100        let in_regexps: usize = self.regexps.iter().map(|(_, c)| c.count_values()).sum();
1101        local + in_children + in_regexps
1102    }
1103
1104    /// Full structural invariant sweep for the trie, asserted as a
1105    /// run-to-completion postcondition at the end of every mutating
1106    /// public operation. Encodes the cross-field invariants that the
1107    /// recursive insert/remove logic must preserve:
1108    ///
1109    /// - **No stranded interior node**: every non-root node reachable
1110    ///   through `children` / `regexps` must hold a value somewhere in
1111    ///   its subtree (`!is_empty()` and `count_values() > 0`). A node
1112    ///   that holds neither a value nor any descendant value is a leak —
1113    ///   `remove_recursive` is supposed to prune it via `is_empty()`.
1114    /// - **Unique regex segments**: the anchored pattern strings stored
1115    ///   in `regexps` are unique within a node (insert dedups by
1116    ///   `as_str()` before pushing a new subtree).
1117    /// - **Child-key invariant**: no child is keyed by the empty slice.
1118    ///
1119    /// `debug_assertions`-only; compiled out of release builds.
1120    #[cfg(debug_assertions)]
1121    fn check_invariants(&self) {
1122        // Regex segment patterns are unique per node.
1123        for i in 0..self.regexps.len() {
1124            for j in (i + 1)..self.regexps.len() {
1125                debug_assert_ne!(
1126                    self.regexps[i].0.as_str(),
1127                    self.regexps[j].0.as_str(),
1128                    "trie node must not hold two subtrees for the same regex segment",
1129                );
1130            }
1131        }
1132
1133        for (child_key, child) in self.children.iter() {
1134            debug_assert!(
1135                !child_key.is_empty(),
1136                "trie child must not be keyed by the empty segment",
1137            );
1138            // A child subtree that has been fully emptied must have been
1139            // pruned by remove_recursive; reaching it here means a
1140            // subtree was stranded.
1141            debug_assert!(
1142                !child.is_empty(),
1143                "trie must not strand an empty child subtree (remove must prune)",
1144            );
1145            debug_assert!(
1146                child.count_values() > 0,
1147                "trie child subtree must lead to at least one value",
1148            );
1149            child.check_invariants();
1150        }
1151
1152        for (_, child) in self.regexps.iter() {
1153            debug_assert!(
1154                !child.is_empty(),
1155                "trie must not strand an empty regex subtree (remove must prune)",
1156            );
1157            debug_assert!(
1158                child.count_values() > 0,
1159                "trie regex subtree must lead to at least one value",
1160            );
1161            child.check_invariants();
1162        }
1163    }
1164
1165    pub fn domain_insert(&mut self, key: Key, value: V) -> InsertResult {
1166        self.insert(key, value)
1167    }
1168
1169    pub fn domain_remove(&mut self, key: &Key) -> RemoveResult {
1170        self.remove(key)
1171    }
1172
1173    pub fn domain_lookup(&self, key: &[u8], accept_wildcard: bool) -> Option<&KeyValue<Key, V>> {
1174        self.lookup(key, accept_wildcard)
1175    }
1176
1177    /// Key-addressed mutable accessor: the node the configured hostname
1178    /// `key` OWNS, never the node that would serve a request for it.
1179    ///
1180    /// With `accept_wildcard: false` a literal key may not resolve into a
1181    /// non-literal entry — neither the `*` slot nor a matching regex
1182    /// segment — which is what keeps `add_tree_rule` and `insert_sni_route`
1183    /// from pushing a rule onto an entry that serves a whole family of
1184    /// hosts (sozu#1351). Use [`TrieNode::domain_lookup`] to ask which
1185    /// entry serves a host.
1186    pub fn domain_lookup_mut(
1187        &mut self,
1188        key: &[u8],
1189        accept_wildcard: bool,
1190    ) -> Option<&mut KeyValue<Key, V>> {
1191        self.lookup_mut(key, accept_wildcard)
1192    }
1193
1194    pub fn size(&self) -> usize {
1195        ::std::mem::size_of::<TrieNode<V>>()
1196            + ::std::mem::size_of::<Option<KeyValue<Key, V>>>() * 2
1197            + self
1198                .children
1199                .iter()
1200                .fold(0, |acc, c| acc + c.0.len() + c.1.size())
1201    }
1202
1203    pub fn to_hashmap(&self) -> HashMap<Key, V> {
1204        let mut h = HashMap::new();
1205
1206        self.to_hashmap_recursive(&mut h);
1207
1208        h
1209    }
1210
1211    pub fn to_hashmap_recursive(&self, h: &mut HashMap<Key, V>) {
1212        if let Some((key, value)) = &self.key_value {
1213            h.insert(key.clone(), value.clone());
1214        }
1215
1216        if let Some((key, value)) = &self.wildcard {
1217            h.insert(key.clone(), value.clone());
1218        }
1219
1220        for child in self.children.values() {
1221            child.to_hashmap_recursive(h);
1222        }
1223    }
1224}
1225
1226#[cfg(test)]
1227mod tests {
1228    use super::*;
1229    use quickcheck::quickcheck;
1230
1231    #[test]
1232    fn insert() {
1233        let mut root: TrieNode<u8> = TrieNode::root();
1234        root.print();
1235
1236        assert_eq!(
1237            root.domain_insert(Vec::from(&b"abcd"[..]), 1),
1238            InsertResult::Ok
1239        );
1240        root.print();
1241        assert_eq!(
1242            root.domain_insert(Vec::from(&b"abce"[..]), 2),
1243            InsertResult::Ok
1244        );
1245        root.print();
1246        assert_eq!(
1247            root.domain_insert(Vec::from(&b"abgh"[..]), 3),
1248            InsertResult::Ok
1249        );
1250        root.print();
1251
1252        assert_eq!(
1253            root.domain_lookup(&b"abce"[..], true),
1254            Some(&(b"abce"[..].to_vec(), 2))
1255        );
1256        //assert!(false);
1257    }
1258
1259    #[test]
1260    fn remove() {
1261        let mut root: TrieNode<u8> = TrieNode::root();
1262        println!("creating root:");
1263        root.print();
1264
1265        println!("adding (abcd, 1)");
1266        assert_eq!(root.insert(Vec::from(&b"abcd"[..]), 1), InsertResult::Ok);
1267        root.print();
1268        println!("adding (abce, 2)");
1269        assert_eq!(root.insert(Vec::from(&b"abce"[..]), 2), InsertResult::Ok);
1270        root.print();
1271        println!("adding (abgh, 3)");
1272        assert_eq!(root.insert(Vec::from(&b"abgh"[..]), 3), InsertResult::Ok);
1273        root.print();
1274
1275        let mut root2: TrieNode<u8> = TrieNode::root();
1276
1277        assert_eq!(root2.insert(Vec::from(&b"abcd"[..]), 1), InsertResult::Ok);
1278        assert_eq!(root2.insert(Vec::from(&b"abgh"[..]), 3), InsertResult::Ok);
1279
1280        println!("before remove");
1281        root.print();
1282        assert_eq!(root.remove(&Vec::from(&b"abce"[..])), RemoveResult::Ok);
1283        println!("after remove");
1284        root.print();
1285
1286        println!("expected");
1287        root2.print();
1288        assert_eq!(root, root2);
1289
1290        assert_eq!(root.remove(&Vec::from(&b"abgh"[..])), RemoveResult::Ok);
1291        println!("after remove");
1292        root.print();
1293        println!("expected");
1294        let mut root3: TrieNode<u8> = TrieNode::root();
1295        assert_eq!(root3.insert(Vec::from(&b"abcd"[..]), 1), InsertResult::Ok);
1296        root3.print();
1297        assert_eq!(root, root3);
1298    }
1299
1300    #[test]
1301    fn insert_remove_through_regex() {
1302        let mut root: TrieNode<u8> = TrieNode::root();
1303        println!("creating root:");
1304        root.print();
1305
1306        println!("adding (www./.*/.com, 1)");
1307        assert_eq!(
1308            root.insert(Vec::from(&b"www./.*/.com"[..]), 1),
1309            InsertResult::Ok
1310        );
1311        root.print();
1312        println!("adding (www.doc./.*/.com, 2)");
1313        assert_eq!(
1314            root.insert(Vec::from(&b"www.doc./.*/.com"[..]), 2),
1315            InsertResult::Ok
1316        );
1317        root.print();
1318        assert_eq!(
1319            root.domain_lookup(b"www.sozu.com".as_ref(), false),
1320            Some(&(b"www./.*/.com".to_vec(), 1))
1321        );
1322        assert_eq!(
1323            root.domain_lookup(b"www.doc.sozu.com".as_ref(), false),
1324            Some(&(b"www.doc./.*/.com".to_vec(), 2))
1325        );
1326
1327        assert_eq!(
1328            root.domain_remove(&b"www./.*/.com".to_vec()),
1329            RemoveResult::Ok
1330        );
1331        root.print();
1332        assert_eq!(root.domain_lookup(b"www.sozu.com".as_ref(), false), None);
1333        assert_eq!(
1334            root.domain_lookup(b"www.doc.sozu.com".as_ref(), false),
1335            Some(&(b"www.doc./.*/.com".to_vec(), 2))
1336        );
1337    }
1338
1339    /// Segment regexes must match the entire segment, not just a prefix.
1340    /// Without `\A...\z` anchoring the previous behaviour matched any
1341    /// segment whose prefix satisfied the pattern, silently widening the
1342    /// routing surface. This regression test exercises the exact-match
1343    /// invariant that anchoring guarantees.
1344    /// `insert` must REJECT a malformed domain, never panic. These keys
1345    /// come from the control plane (`AddHttpFrontend`, or a `LoadState`
1346    /// replay), so an `assert_ne!(_, Failed)` here -- which is what this
1347    /// used to be -- turned a bad hostname into a worker crash, repeated
1348    /// on every restart replay. A rejected key must also leave the trie
1349    /// completely untouched.
1350    #[test]
1351    fn insert_rejects_malformed_keys_without_panicking() {
1352        for key in [
1353            &b""[..],
1354            b".",
1355            b"..",
1356            b"...",
1357            b"/",
1358            b"///",
1359            b"a/",
1360            b".a",
1361            b".a.b",
1362            b"example.com/",
1363            b"www.example.com/",
1364            // A regex segment must be `.`-anchored on its left.
1365            b"abc/[0-9]+/.example.com",
1366            // ... and must actually compile as a regex.
1367            b"/[/.example.com",
1368            b"a/*/",
1369        ] {
1370            let mut root: TrieNode<u8> = TrieNode::root();
1371            assert_eq!(
1372                root.insert(key.to_vec(), 1),
1373                InsertResult::Failed,
1374                "{:?} must be rejected",
1375                String::from_utf8_lossy(key),
1376            );
1377            assert!(
1378                root.is_empty(),
1379                "{:?} was rejected but still mutated the trie",
1380                String::from_utf8_lossy(key),
1381            );
1382            assert_eq!(root.domain_lookup(key, false), None);
1383            assert_eq!(root.domain_lookup(key, true), None);
1384        }
1385    }
1386
1387    /// A rejected key must not disturb the entries already in the trie
1388    /// either -- the failed insert has to be a no-op on a populated table.
1389    #[test]
1390    fn a_rejected_insert_leaves_existing_entries_intact() {
1391        let mut root: TrieNode<u8> = TrieNode::root();
1392        assert_eq!(
1393            root.insert(b"www.example.com".to_vec(), 1),
1394            InsertResult::Ok
1395        );
1396        assert_eq!(root.insert(b"*.wild.com".to_vec(), 2), InsertResult::Ok);
1397        assert_eq!(
1398            root.insert(b"www.example.com/".to_vec(), 3),
1399            InsertResult::Failed
1400        );
1401        assert_eq!(
1402            root.domain_lookup(b"www.example.com", false),
1403            Some(&(b"www.example.com".to_vec(), 1))
1404        );
1405        assert_eq!(
1406            root.domain_lookup(b"any.wild.com", true),
1407            Some(&(b"*.wild.com".to_vec(), 2))
1408        );
1409    }
1410
1411    #[test]
1412    fn segment_regex_rejects_partial_matches() {
1413        let mut root: TrieNode<u8> = TrieNode::root();
1414        // The regex segment `cdn[0-9]+` must match `cdn1`, `cdn99`, etc.
1415        // exactly — never `cdn1xxx` or `xxxcdn1` as a prefix/suffix.
1416        assert_eq!(
1417            root.insert(Vec::from(&b"/cdn[0-9]+/.example.com"[..]), 7),
1418            InsertResult::Ok
1419        );
1420
1421        // Exact-match cases still resolve.
1422        assert_eq!(
1423            root.domain_lookup(b"cdn1.example.com".as_ref(), false),
1424            Some(&(b"/cdn[0-9]+/.example.com".to_vec(), 7))
1425        );
1426        assert_eq!(
1427            root.domain_lookup(b"cdn123.example.com".as_ref(), false),
1428            Some(&(b"/cdn[0-9]+/.example.com".to_vec(), 7))
1429        );
1430
1431        // Trailing characters past the digit run must fail. Pre-anchoring
1432        // the trie would have matched `cdn1xxx` because `cdn[0-9]+` ate
1433        // the `cdn1` prefix; with `\A...\z` the segment is rejected.
1434        assert_eq!(
1435            root.domain_lookup(b"cdn1xxx.example.com".as_ref(), false),
1436            None
1437        );
1438        // Leading characters likewise must fail.
1439        assert_eq!(
1440            root.domain_lookup(b"xxxcdn1.example.com".as_ref(), false),
1441            None
1442        );
1443        // Non-digit middle bytes break the digit run and the segment.
1444        assert_eq!(
1445            root.domain_lookup(b"cdnabc.example.com".as_ref(), false),
1446            None
1447        );
1448    }
1449
1450    /// `anchored_segment` is the single source of a `regexps` entry's
1451    /// identity, so its exact output is pinned here rather than inferred
1452    /// from routing verdicts: `insert_recursive`'s dedup scan,
1453    /// `remove_recursive` and `lookup_mut` all address an entry by comparing
1454    /// `Regex::as_str()` against this string, and a second spelling would
1455    /// make a stored segment un-removable and un-addressable.
1456    ///
1457    /// The group is non-capturing, so `captures_len` — and therefore every
1458    /// `$HOST[n]` rewrite index `lookup_with_path` feeds
1459    /// `router/mod.rs`'s `RouteResult::new_with_trie` — is unchanged.
1460    ///
1461    /// To SEE THIS RED: build `format!("\\A{segment}\\z")` in
1462    /// `anchored_segment`, the form this tree carried up to 2.2.1, and relax
1463    /// the `debug_assert!` beside it to `starts_with("\\A")` /
1464    /// `ends_with("\\z")` — in a debug build that assertion fires first, in
1465    /// `anchored_segment` itself rather than in a test. The first assertion
1466    /// here then fails with
1467    /// `left: Some("\\Acdn[0-9]+\\z"), right: Some("\\A(?:cdn[0-9]+)\\z")`.
1468    /// Make the wrapper CAPTURING instead — `format!("\\A({segment})\\z")` —
1469    /// and the `captures_len` assertions catch it.
1470    #[test]
1471    fn anchored_segment_wraps_in_a_non_capturing_group() {
1472        assert_eq!(
1473            anchored_segment("cdn[0-9]+").as_deref(),
1474            Some("\\A(?:cdn[0-9]+)\\z"),
1475            "the wrapper must anchor AND group",
1476        );
1477        assert_eq!(
1478            Regex::new(&anchored_segment("a|b|c").expect("an alternation must wrap"))
1479                .expect("the wrapped alternation must compile")
1480                .captures_len(),
1481            1,
1482            "the group must be non-capturing, so `$HOST[n]` indices are untouched",
1483        );
1484        assert_eq!(
1485            Regex::new(&anchored_segment("cdn([0-9]+)").expect("a group must wrap"))
1486                .expect("the wrapped group must compile")
1487                .captures_len(),
1488            2,
1489            "the operator's own group must stay at index 1",
1490        );
1491
1492        // A segment that is not a regex on its own is refused, and is NOT
1493        // rescued by the `(` and `)` the wrapper supplies: `a)(b` is
1494        // unbalanced, yet `\A(?:a)(b)\z` compiles and matches `ab`.
1495        // Assembled at run time because the invalidity is the point.
1496        let unbalanced = format!("a{}b", ")(");
1497        assert_eq!(
1498            anchored_segment(&unbalanced),
1499            None,
1500            "{unbalanced} must not be rescued into a valid pattern",
1501        );
1502        let rescued = Regex::new(&format!("\\A(?:a{}b)\\z", ")("))
1503            .expect("the wrapping balances the segment");
1504        assert!(
1505            rescued.is_match(b"ab"),
1506            "{} is what the missing pre-validation would have built",
1507            rescued.as_str(),
1508        );
1509    }
1510
1511    /// sozu#1377 moved the case folding of a tree hostname regex OFF the
1512    /// source and ONTO the compile: `Router::add_tree_rule` no longer
1513    /// hands the segment to `idna::domain_to_ascii` (which folded `\D`
1514    /// into `\d` and inverted the class), and `compiled_segment` folds
1515    /// here instead, where it reaches literals only.
1516    ///
1517    /// Three claims, and all three are load-bearing:
1518    ///
1519    /// - the builder must not RESPELL the pattern, because `as_str()` is
1520    ///   the identity `insert_recursive`, `remove_recursive` and
1521    ///   `lookup_mut` address a `regexps` entry by — a second spelling
1522    ///   makes a stored segment un-removable;
1523    /// - `captures_len` must be untouched, because `$HOST[n]` rewrite
1524    ///   indices come from `caps.iter().skip(1)` with the buffer sized by
1525    ///   `captures_len()` in `router/mod.rs`'s `RouteResult::new_with_trie`;
1526    /// - the fold must reach literals and NOT escapes.
1527    ///
1528    /// To SEE THIS RED: drop `.case_insensitive(true)` from
1529    /// `compiled_segment` — the `API[0-9]` assertion fails. Prepend
1530    /// `(?i)` to the pattern handed to `RegexBuilder` instead of setting
1531    /// the flag — the `as_str()` assertion fails with
1532    /// `left: "(?i)\\A(?:cdn[0-9]+)\\z", right: "\\A(?:cdn[0-9]+)\\z"`.
1533    /// Make `anchored_segment`'s wrapper capturing — the `captures_len`
1534    /// assertions fail.
1535    #[test]
1536    fn compiled_segment_folds_case_without_respelling_its_identity() {
1537        let anchored = anchored_segment("cdn[0-9]+").expect("the segment must wrap");
1538        assert_eq!(
1539            compiled_segment("cdn[0-9]+")
1540                .expect("the segment must compile")
1541                .as_str(),
1542            anchored,
1543            "the compiled entry must carry `anchored_segment`'s exact string",
1544        );
1545
1546        assert_eq!(
1547            compiled_segment("a|b|c")
1548                .expect("an alternation must compile")
1549                .captures_len(),
1550            1,
1551            "folding must not add a capture group",
1552        );
1553        assert_eq!(
1554            compiled_segment("cdn([0-9]+)")
1555                .expect("a group must compile")
1556                .captures_len(),
1557            2,
1558            "the operator's own group must stay at index 1",
1559        );
1560
1561        let uppercase = compiled_segment("API[0-9]").expect("the segment must compile");
1562        assert!(
1563            uppercase.is_match(b"api7"),
1564            "an uppercase literal must meet the ASCII-lowercased lookup key",
1565        );
1566        assert!(uppercase.is_match(b"API7"), "and its own spelling too",);
1567        assert!(
1568            !uppercase.is_match(b"apix"),
1569            "folding must not widen the class the operator wrote",
1570        );
1571
1572        // Case insensitivity does not reach `\d`/`\D`, `\w`/`\W` or
1573        // `\s`/`\S`: that inversion only ever came from folding the
1574        // SOURCE, which is what sozu#1377 removed.
1575        let not_a_digit = compiled_segment("\\D+").expect("the segment must compile");
1576        assert!(not_a_digit.is_match(b"abc"), "`\\D+` matches non-digits");
1577        assert!(
1578            !not_a_digit.is_match(b"777"),
1579            "`\\D+` must never match digits — folded to `\\d+` it matched \
1580             exactly and only those",
1581        );
1582
1583        // A segment that is not a regex on its own compiles to nothing,
1584        // exactly as `anchored_segment` refuses it.
1585        let unbalanced = format!("a{}b", ")(");
1586        assert!(
1587            compiled_segment(&unbalanced).is_none(),
1588            "{unbalanced} must not be rescued into a live entry",
1589        );
1590    }
1591
1592    /// Three branches, because two cannot express the claim: with exactly
1593    /// two, "first and last" is every branch there is. Ungrouped, the MIDDLE
1594    /// branch keeps NEITHER anchor and matches as a bare substring anywhere
1595    /// in the label.
1596    ///
1597    /// To SEE THIS RED: build `format!("\\A{segment}\\z")` in
1598    /// `anchored_segment` and relax the `debug_assert!` beside it, which in
1599    /// a debug build fires first. `axx.example.com` then resolves and the
1600    /// assertion fails with
1601    /// `left: Some(([47, 97, 124, 98, 124, 99, 47, 46, …], 9)), right: None`.
1602    #[test]
1603    fn every_branch_of_an_alternating_segment_regex_matches_the_whole_label_only() {
1604        let mut root: TrieNode<u8> = TrieNode::root();
1605        assert_eq!(
1606            root.insert(Vec::from(&b"/a|b|c/.example.com"[..]), 9),
1607            InsertResult::Ok
1608        );
1609
1610        for whole in ["a.example.com", "b.example.com", "c.example.com"] {
1611            assert_eq!(
1612                root.domain_lookup(whole.as_bytes(), false),
1613                Some(&(b"/a|b|c/.example.com".to_vec(), 9)),
1614                "{whole} is a whole-label match for one branch and must resolve",
1615            );
1616        }
1617        for leak in [
1618            "axx.example.com",
1619            "xxc.example.com",
1620            "zzbzz.example.com",
1621            "bzz.example.com",
1622            "zzb.example.com",
1623        ] {
1624            assert_eq!(
1625                root.domain_lookup(leak.as_bytes(), false),
1626                None,
1627                "{leak} matches no branch WHOLE and must not resolve",
1628            );
1629        }
1630    }
1631
1632    #[test]
1633    fn add_child_to_leaf() {
1634        let mut root1: TrieNode<u8> = TrieNode::root();
1635
1636        println!("creating root1:");
1637        root1.print();
1638        println!("adding (abcd, 1)");
1639        assert_eq!(root1.insert(Vec::from(&b"abcd"[..]), 1), InsertResult::Ok);
1640        root1.print();
1641        println!("adding (abce, 2)");
1642        assert_eq!(root1.insert(Vec::from(&b"abce"[..]), 2), InsertResult::Ok);
1643        root1.print();
1644        println!("adding (abc, 3)");
1645        assert_eq!(root1.insert(Vec::from(&b"abc"[..]), 3), InsertResult::Ok);
1646
1647        println!("root1:");
1648        root1.print();
1649
1650        let mut root2: TrieNode<u8> = TrieNode::root();
1651
1652        assert_eq!(root2.insert(Vec::from(&b"abc"[..]), 3), InsertResult::Ok);
1653        assert_eq!(root2.insert(Vec::from(&b"abcd"[..]), 1), InsertResult::Ok);
1654        assert_eq!(root2.insert(Vec::from(&b"abce"[..]), 2), InsertResult::Ok);
1655
1656        println!("root2:");
1657        root2.print();
1658        assert_eq!(root2.remove(&Vec::from(&b"abc"[..])), RemoveResult::Ok);
1659
1660        println!("root2 after,remove:");
1661        root2.print();
1662        let mut expected: TrieNode<u8> = TrieNode::root();
1663
1664        assert_eq!(
1665            expected.insert(Vec::from(&b"abcd"[..]), 1),
1666            InsertResult::Ok
1667        );
1668        assert_eq!(
1669            expected.insert(Vec::from(&b"abce"[..]), 2),
1670            InsertResult::Ok
1671        );
1672
1673        println!("root2 after insert");
1674        root2.print();
1675        println!("expected");
1676        expected.print();
1677        assert_eq!(root2, expected);
1678    }
1679
1680    #[test]
1681    fn domains() {
1682        let mut root: TrieNode<u8> = TrieNode::root();
1683        root.print();
1684
1685        assert_eq!(
1686            root.domain_insert(Vec::from(&b"www.example.com"[..]), 1),
1687            InsertResult::Ok
1688        );
1689        root.print();
1690        assert_eq!(
1691            root.domain_insert(Vec::from(&b"test.example.com"[..]), 2),
1692            InsertResult::Ok
1693        );
1694        root.print();
1695        assert_eq!(
1696            root.domain_insert(Vec::from(&b"*.alldomains.org"[..]), 3),
1697            InsertResult::Ok
1698        );
1699        root.print();
1700        assert_eq!(
1701            root.domain_insert(Vec::from(&b"alldomains.org"[..]), 4),
1702            InsertResult::Ok
1703        );
1704        assert_eq!(
1705            root.domain_insert(Vec::from(&b"pouet.alldomains.org"[..]), 5),
1706            InsertResult::Ok
1707        );
1708        root.print();
1709        assert_eq!(
1710            root.domain_insert(Vec::from(&b"hello.com"[..]), 6),
1711            InsertResult::Ok
1712        );
1713        assert_eq!(
1714            root.domain_insert(Vec::from(&b"*.hello.com"[..]), 7),
1715            InsertResult::Ok
1716        );
1717        assert_eq!(
1718            root.domain_insert(Vec::from(&b"images./cdn[0-9]+/.hello.com"[..]), 8),
1719            InsertResult::Ok
1720        );
1721        root.print();
1722        assert_eq!(
1723            root.domain_insert(Vec::from(&b"/test[0-9]+/.www.hello.com"[..]), 9),
1724            InsertResult::Ok
1725        );
1726        root.print();
1727
1728        assert_eq!(root.domain_lookup(&b"example.com"[..], true), None);
1729        assert_eq!(
1730            root.domain_lookup(&b"blah.test.example.com"[..], true),
1731            None
1732        );
1733        assert_eq!(
1734            root.domain_lookup(&b"www.example.com"[..], true),
1735            Some(&(b"www.example.com"[..].to_vec(), 1))
1736        );
1737        assert_eq!(
1738            root.domain_lookup(&b"alldomains.org"[..], true),
1739            Some(&(b"alldomains.org"[..].to_vec(), 4))
1740        );
1741        assert_eq!(
1742            root.domain_lookup(&b"test.hello.com"[..], true),
1743            Some(&(b"*.hello.com"[..].to_vec(), 7))
1744        );
1745        assert_eq!(
1746            root.domain_lookup(&b"images.cdn10.hello.com"[..], true),
1747            Some(&(b"images./cdn[0-9]+/.hello.com"[..].to_vec(), 8))
1748        );
1749        assert_eq!(
1750            root.domain_lookup(&b"test42.www.hello.com"[..], true),
1751            Some(&(b"/test[0-9]+/.www.hello.com"[..].to_vec(), 9))
1752        );
1753        assert_eq!(
1754            root.domain_lookup(&b"test.alldomains.org"[..], true),
1755            Some(&(b"*.alldomains.org"[..].to_vec(), 3))
1756        );
1757        assert_eq!(
1758            root.domain_lookup(&b"hello.alldomains.org"[..], true),
1759            Some(&(b"*.alldomains.org"[..].to_vec(), 3))
1760        );
1761        assert_eq!(
1762            root.domain_lookup(&b"pouet.alldomains.org"[..], true),
1763            Some(&(b"pouet.alldomains.org"[..].to_vec(), 5))
1764        );
1765        assert_eq!(
1766            root.domain_lookup(&b"blah.test.alldomains.org"[..], true),
1767            None
1768        );
1769
1770        assert_eq!(
1771            root.domain_remove(&Vec::from(&b"alldomains.org"[..])),
1772            RemoveResult::Ok
1773        );
1774        println!("after remove");
1775        root.print();
1776        assert_eq!(root.domain_lookup(&b"alldomains.org"[..], true), None);
1777        assert_eq!(
1778            root.domain_lookup(&b"test.alldomains.org"[..], true),
1779            Some(&(b"*.alldomains.org"[..].to_vec(), 3))
1780        );
1781        assert_eq!(
1782            root.domain_lookup(&b"hello.alldomains.org"[..], true),
1783            Some(&(b"*.alldomains.org"[..].to_vec(), 3))
1784        );
1785        assert_eq!(
1786            root.domain_lookup(&b"pouet.alldomains.org"[..], true),
1787            Some(&(b"pouet.alldomains.org"[..].to_vec(), 5))
1788        );
1789        assert_eq!(
1790            root.domain_lookup(&b"test.hello.com"[..], true),
1791            Some(&(b"*.hello.com"[..].to_vec(), 7))
1792        );
1793        assert_eq!(
1794            root.domain_lookup(&b"blah.test.alldomains.org"[..], true),
1795            None
1796        );
1797    }
1798
1799    #[test]
1800    fn wildcard() {
1801        let mut root: TrieNode<u8> = TrieNode::root();
1802        root.print();
1803        root.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
1804        root.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
1805        root.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
1806
1807        let res = root.domain_lookup(b"test.services.clever-cloud.com", true);
1808        println!("query result: {res:?}");
1809
1810        assert_eq!(
1811            root.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
1812            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
1813        );
1814    }
1815
1816    /// A key `hm_insert` must skip on both the insert and the lookup pass:
1817    /// the wildcard slot, keyed by literal identity rather than by the
1818    /// child map. `TrieNode::insert_recursive`'s dotless arm stores a value
1819    /// in `self.wildcard` whenever the LEFTMOST label of the key is exactly
1820    /// `"*"` — not only when the whole key is `"*"` — because the
1821    /// recursive dot-split always keeps the original first byte at index 0
1822    /// of every prefix it recurses on, so only the leftmost label can ever
1823    /// reach that arm. `hm_insert`'s lookups pass `accept_wildcard: false`
1824    /// (it is testing plain literal lookup, not `*` resolution), which
1825    /// structurally can never see `self.wildcard` — see
1826    /// `TrieNode::lookup_recursive`'s step 3. A key like `"*."` or
1827    /// `"*.example.com"` therefore inserts `Ok` into the wildcard slot and
1828    /// then reports "did not find key" on lookup: not a router defect, a
1829    /// gap in this oracle's own exclusion list. This was `qc_insert`'s
1830    /// shrunk failure (`{"*.": 0}`) before this filter closed it; see the
1831    /// commit message for the investigation.
1832    fn hm_insert_skips(k: &str) -> bool {
1833        k.is_empty()
1834            || k.as_bytes()[0] == b'.'
1835            || k.contains('/')
1836            || k.split('.').next() == Some("*")
1837    }
1838
1839    fn hm_insert(h: std::collections::HashMap<String, u32>) -> bool {
1840        let mut root: TrieNode<u32> = TrieNode::root();
1841
1842        for (k, v) in h.iter() {
1843            if hm_insert_skips(k) {
1844                continue;
1845            }
1846
1847            //println!("inserting key: '{}', value: '{}'", k, v);
1848            //assert_eq!(root.domain_insert(Vec::from(k.as_bytes()), *v), InsertResult::Ok);
1849            assert_eq!(
1850                root.insert(Vec::from(k.as_bytes()), *v),
1851                InsertResult::Ok,
1852                "could not insert ({k}, {v})"
1853            );
1854            //root.print();
1855        }
1856
1857        //root.print();
1858        for (k, v) in h.iter() {
1859            if hm_insert_skips(k) {
1860                continue;
1861            }
1862
1863            //match root.domain_lookup(k.as_bytes()) {
1864            match root.lookup(k.as_bytes(), false) {
1865                None => {
1866                    println!("did not find key '{k}'");
1867                    return false;
1868                }
1869                Some(&(ref k1, v1)) => {
1870                    if k.as_bytes() != &k1[..] || *v != v1 {
1871                        println!(
1872                            "request ({}, {}), got ({}, {})",
1873                            k,
1874                            v,
1875                            str::from_utf8(&k1[..]).unwrap(),
1876                            v1
1877                        );
1878                        return false;
1879                    }
1880                }
1881            }
1882        }
1883
1884        true
1885    }
1886
1887    quickcheck! {
1888      fn qc_insert(h: std::collections::HashMap<String, u32>) -> bool {
1889        hm_insert(h)
1890      }
1891    }
1892
1893    #[test]
1894    fn insert_disappearing_tree() {
1895        let h: std::collections::HashMap<String, u32> = [
1896            (String::from("\n\u{3}"), 0),
1897            (String::from("\n\u{0}"), 1),
1898            (String::from("\n"), 2),
1899        ]
1900        .iter()
1901        .cloned()
1902        .collect();
1903        assert!(hm_insert(h));
1904    }
1905
1906    #[test]
1907    fn size() {
1908        assert_size!(TrieNode<u32>, 136);
1909    }
1910
1911    /// Regression: a hostname whose LEFTMOST segment is a regex
1912    /// (`/test[0-9]/.example.com`) used to underflow `pos - 1` (to
1913    /// `usize::MAX`) and panic on the second insert (the dedup loop) and
1914    /// on any `lookup_mut`. Both paths now special-case `pos == 0` (the
1915    /// regex is the leftmost/only segment, so the value is that subtree's
1916    /// own `key_value` — asked for, never inferred from how the subtree
1917    /// was built). This asserts the panic is gone and the entry resolves
1918    /// correctly. Here the subtree IS value-bearing because this test
1919    /// inserts the leftmost host first;
1920    /// `inserting_a_leftmost_regex_host_after_a_deeper_sibling_succeeds`
1921    /// covers the order where it is not.
1922    #[test]
1923    fn leftmost_regex_segment_reinsert_and_lookup_mut_do_not_panic() {
1924        let mut root: TrieNode<u8> = TrieNode::root();
1925
1926        assert_eq!(
1927            root.insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 7),
1928            InsertResult::Ok
1929        );
1930        // Second insert of the SAME leftmost-regex host: dedup loop with
1931        // pos == 0. Previously panicked; must now report Existing.
1932        assert_eq!(
1933            root.insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 8),
1934            InsertResult::Existing
1935        );
1936
1937        // lookup_mut on the existing leftmost-regex host: previously
1938        // panicked at `partial_key[..pos - 1]`; must now resolve the leaf
1939        // (value unchanged from the first insert — Existing did not
1940        // overwrite).
1941        //
1942        // The probe is the HOST KEY, not a host the segment matches: the
1943        // underflow lives in the trailing-`/` arm, which only a key ending
1944        // in a regex segment reaches. Probing `test4.example.com` instead
1945        // (as this test did before sozu#1351 made `lookup_mut`
1946        // key-addressed) never ran the arm under test at all — it left
1947        // through the `regexps` scan one level down.
1948        let resolved = root.domain_lookup_mut(b"/test[0-9]/.example.com", false);
1949        assert_eq!(
1950            resolved.map(|(_, v)| *v),
1951            Some(7),
1952            "leftmost-regex host must resolve via lookup_mut without panicking",
1953        );
1954
1955        // The immutable lookup path (never buggy) agrees.
1956        assert_eq!(
1957            root.domain_lookup(b"test4.example.com", false),
1958            Some(&(b"/test[0-9]/.example.com"[..].to_vec(), 7))
1959        );
1960
1961        // Removing the last rule clears the host.
1962        assert_eq!(
1963            root.domain_remove(&Vec::from(&b"/test[0-9]/.example.com"[..])),
1964            RemoveResult::Ok
1965        );
1966        assert_eq!(root.domain_lookup(b"test4.example.com", false), None);
1967    }
1968    /// A hostname whose leftmost segment is a regex and a DEEPER hostname
1969    /// sharing that segment live in the same `(regex, subtree)` entry: the
1970    /// first as the subtree's own `key_value`, the second as one of its
1971    /// children. Whichever arrives first OPENS the entry, so the second
1972    /// insert always lands on `insert_recursive`'s dedup loop — and when
1973    /// the deeper one opened it, the subtree is a valueless
1974    /// `TrieNode::root()` with a free `key_value` slot.
1975    ///
1976    /// That arm used to answer `InsertResult::Existing` unconditionally,
1977    /// on the (false) premise that a matched entry was necessarily built
1978    /// value-bearing by `TrieNode::new`. The leftmost host was then stored
1979    /// nowhere while being reported as already present — permanently
1980    /// un-insertable. The reverse order, which
1981    /// `removing_a_leftmost_regex_host_keeps_its_sibling_domains` uses,
1982    /// always worked, which is why the gap stayed invisible.
1983    ///
1984    /// To SEE THIS RED: in the `pos == 0` arm of the dedup loop in
1985    /// `TrieNode::insert_recursive`, replace
1986    /// `return t.1.insert_own_value(key, value);` with
1987    /// `return InsertResult::Existing;`. The second `assert_eq!` below then
1988    /// reports `left: Existing, right: Ok`, and the lookup after it returns
1989    /// `None`. Both fail in debug AND in release: nothing here depends on a
1990    /// `debug_assert`.
1991    #[test]
1992    fn inserting_a_leftmost_regex_host_after_a_deeper_sibling_succeeds() {
1993        let mut root: TrieNode<u8> = TrieNode::root();
1994
1995        // The DEEPER domain opens the `(regex, subtree)` entry, leaving the
1996        // subtree's own `key_value` slot free.
1997        assert_eq!(
1998            root.domain_insert(Vec::from(&b"foo./test[0-9]/.example.com"[..]), 2),
1999            InsertResult::Ok
2000        );
2001        // The leftmost-regex host now claims that free slot.
2002        assert_eq!(
2003            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 1),
2004            InsertResult::Ok,
2005            "a leftmost-regex host must be insertable after a deeper sibling opened its segment",
2006        );
2007
2008        // BOTH must resolve. The store, not the return value, is what the
2009        // data plane serves.
2010        assert_eq!(
2011            root.domain_lookup(b"test4.example.com", false),
2012            Some(&(b"/test[0-9]/.example.com"[..].to_vec(), 1)),
2013            "the leftmost-regex host must resolve to the rule just inserted",
2014        );
2015        assert_eq!(
2016            root.domain_lookup(b"foo.test4.example.com", false),
2017            Some(&(b"foo./test[0-9]/.example.com"[..].to_vec(), 2)),
2018            "the deeper sibling must survive the second insert",
2019        );
2020
2021        // A genuine duplicate is still `Existing`, and must not overwrite.
2022        assert_eq!(
2023            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 9),
2024            InsertResult::Existing
2025        );
2026        assert_eq!(
2027            root.domain_lookup(b"test4.example.com", false),
2028            Some(&(b"/test[0-9]/.example.com"[..].to_vec(), 1)),
2029            "an Existing insert must not overwrite the stored value",
2030        );
2031    }
2032
2033    /// The insertion order that ALREADY worked before the `pos == 0` fix —
2034    /// leftmost-regex host first, deeper sibling second — must not regress.
2035    /// Here the entry is opened by `TrieNode::new`, so the subtree's
2036    /// `key_value` is occupied when the deeper insert walks past it.
2037    ///
2038    /// To SEE THIS RED: make `TrieNode::insert_own_value` unconditional —
2039    /// `self.key_value = Some((key.to_vec(), value)); InsertResult::Ok`. In a
2040    /// debug build the production guard in `insert()` fires first, on
2041    /// "a fresh insert must add exactly one value to the trie, left: 2,
2042    /// right: 3" — the overwriting duplicate reported `Ok` without growing
2043    /// the value count. Strip that `debug_assert_eq!` too and the failure
2044    /// lands on the duplicate assertion below instead,
2045    /// `left: Ok, right: Existing`, which is the form a release build sees.
2046    #[test]
2047    fn inserting_a_deeper_sibling_after_a_leftmost_regex_host_still_works() {
2048        let mut root: TrieNode<u8> = TrieNode::root();
2049
2050        assert_eq!(
2051            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 1),
2052            InsertResult::Ok
2053        );
2054        assert_eq!(
2055            root.domain_insert(Vec::from(&b"foo./test[0-9]/.example.com"[..]), 2),
2056            InsertResult::Ok
2057        );
2058
2059        assert_eq!(
2060            root.domain_lookup(b"test4.example.com", false),
2061            Some(&(b"/test[0-9]/.example.com"[..].to_vec(), 1))
2062        );
2063        assert_eq!(
2064            root.domain_lookup(b"foo.test4.example.com", false),
2065            Some(&(b"foo./test[0-9]/.example.com"[..].to_vec(), 2))
2066        );
2067
2068        // Re-inserting either one is `Existing`, not a second value.
2069        assert_eq!(
2070            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 8),
2071            InsertResult::Existing
2072        );
2073        assert_eq!(
2074            root.domain_insert(Vec::from(&b"foo./test[0-9]/.example.com"[..]), 8),
2075            InsertResult::Existing
2076        );
2077    }
2078
2079    /// Removal must still behave once BOTH hosts are reachable, whichever
2080    /// order opened the segment and whichever order empties it. This guards
2081    /// the `remove_recursive` fix (which only ever saw the leftmost-first
2082    /// order) against the new insert path, and pins the `regexps` prune: the
2083    /// entry must disappear only when its last value is gone.
2084    ///
2085    /// To SEE THIS RED (insert side): in the `pos == 0` arm of
2086    /// `TrieNode::insert_recursive` replace
2087    /// `return t.1.insert_own_value(key, value);` with
2088    /// `return InsertResult::Existing;`. The test never reaches a
2089    /// `domain_remove` at all — its own deeper-first setup fails first, on
2090    /// the leftmost `assert_eq!(root.domain_insert(..), InsertResult::Ok)`,
2091    /// with `left: Existing, right: Ok`. No production guard fires on the
2092    /// way there: `insert()` returns `Existing`, whose postcondition asserts
2093    /// `after == before`, and that holds precisely because nothing was
2094    /// stored. That silence is the bug — which is why this test pins the
2095    /// insert, and not only the removals it is named for.
2096    ///
2097    /// To SEE THIS RED (remove side): restore the pre-fix `pos == 0` arm of
2098    /// `remove_recursive`, ahead of the `rest` binding:
2099    /// `let len = self.regexps.len();
2100    ///  self.regexps.retain(|(r, _)| r.as_str() != anchored_s);
2101    ///  if len > self.regexps.len() { return RemoveResult::Ok; }`
2102    /// The production guard in `remove()` fires first, on "a successful
2103    /// remove must drop exactly one value from the trie, left: 1, right: 2"
2104    /// — dropping the whole `regexps` entry took the sibling with it. Strip
2105    /// that `debug_assert_eq!` too and the failure lands on this test's own
2106    /// "the other host under the same regex segment must survive".
2107    #[test]
2108    fn removing_regex_segment_hosts_behaves_in_either_insertion_order() {
2109        // Insertion order A: deeper first (the order the insert fix opened).
2110        for remove_leftmost_first in [true, false] {
2111            let mut root: TrieNode<u8> = TrieNode::root();
2112            assert_eq!(
2113                root.domain_insert(Vec::from(&b"foo./test[0-9]/.example.com"[..]), 2),
2114                InsertResult::Ok
2115            );
2116            assert_eq!(
2117                root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 1),
2118                InsertResult::Ok
2119            );
2120            assert_regex_segment_pair_removes_cleanly(&mut root, remove_leftmost_first);
2121        }
2122
2123        // Insertion order B: leftmost first (the pre-existing order).
2124        for remove_leftmost_first in [true, false] {
2125            let mut root: TrieNode<u8> = TrieNode::root();
2126            assert_eq!(
2127                root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 1),
2128                InsertResult::Ok
2129            );
2130            assert_eq!(
2131                root.domain_insert(Vec::from(&b"foo./test[0-9]/.example.com"[..]), 2),
2132                InsertResult::Ok
2133            );
2134            assert_regex_segment_pair_removes_cleanly(&mut root, remove_leftmost_first);
2135        }
2136    }
2137
2138    /// Remove both hosts sharing a regex segment, one order or the other:
2139    /// each removal takes exactly its own host, the survivor still resolves,
2140    /// and the emptied trie is pruned back to a root with no `regexps` entry.
2141    fn assert_regex_segment_pair_removes_cleanly(
2142        root: &mut TrieNode<u8>,
2143        remove_leftmost_first: bool,
2144    ) {
2145        let leftmost = Vec::from(&b"/test[0-9]/.example.com"[..]);
2146        let deeper = Vec::from(&b"foo./test[0-9]/.example.com"[..]);
2147
2148        let (first, second) = if remove_leftmost_first {
2149            (&leftmost, &deeper)
2150        } else {
2151            (&deeper, &leftmost)
2152        };
2153        let (first_host, second_host): (&[u8], &[u8]) = if remove_leftmost_first {
2154            (b"test4.example.com", b"foo.test4.example.com")
2155        } else {
2156            (b"foo.test4.example.com", b"test4.example.com")
2157        };
2158
2159        assert_eq!(root.domain_remove(first), RemoveResult::Ok);
2160        assert_eq!(
2161            root.domain_lookup(first_host, false),
2162            None,
2163            "the removed host must be gone",
2164        );
2165        assert!(
2166            root.domain_lookup(second_host, false).is_some(),
2167            "the other host under the same regex segment must survive",
2168        );
2169        // A second removal of an absent host is NotFound, never a silent Ok
2170        // that takes the survivor with it.
2171        assert_eq!(root.domain_remove(first), RemoveResult::NotFound);
2172        assert!(root.domain_lookup(second_host, false).is_some());
2173
2174        assert_eq!(root.domain_remove(second), RemoveResult::Ok);
2175        assert_eq!(root.domain_lookup(second_host, false), None);
2176        assert!(
2177            root.is_empty(),
2178            "the emptied regex subtree must be pruned back to an empty root",
2179        );
2180    }
2181
2182    /// Removing a leftmost-regex host must drop that one host and nothing
2183    /// else. The `pos == 0` arm of `remove_recursive` used to `retain` the
2184    /// whole `(regex, subtree)` entry out of `regexps`, which also deleted
2185    /// every deeper domain registered under the same segment — and it
2186    /// reported `Ok` even when the leftmost host itself had never been
2187    /// stored. The value lives in that subtree's own `key_value`, so the
2188    /// removal must recurse into it with an empty key, exactly like the
2189    /// `pos > 0` arm recurses on the remaining prefix.
2190    ///
2191    /// To SEE THIS RED: restore the pre-fix `pos == 0` arm of
2192    /// `TrieNode::remove_recursive`:
2193    /// `let len = self.regexps.len();
2194    ///  self.regexps.retain(|(r, _)| r.as_str() != anchored_s);
2195    ///  if len > self.regexps.len() { return RemoveResult::Ok; }`
2196    /// — the sibling lookup below then returns `None`, and in a debug build
2197    /// `remove()` panics first on "a successful remove must drop exactly one
2198    /// value from the trie".
2199    #[test]
2200    fn removing_a_leftmost_regex_host_keeps_its_sibling_domains() {
2201        let mut root: TrieNode<u8> = TrieNode::root();
2202
2203        assert_eq!(
2204            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 1),
2205            InsertResult::Ok
2206        );
2207        assert_eq!(
2208            root.domain_insert(Vec::from(&b"foo./test[0-9]/.example.com"[..]), 2),
2209            InsertResult::Ok
2210        );
2211        assert_eq!(
2212            root.domain_lookup(b"test4.example.com", false),
2213            Some(&(b"/test[0-9]/.example.com"[..].to_vec(), 1))
2214        );
2215        assert_eq!(
2216            root.domain_lookup(b"foo.test4.example.com", false),
2217            Some(&(b"foo./test[0-9]/.example.com"[..].to_vec(), 2))
2218        );
2219
2220        assert_eq!(
2221            root.domain_remove(&Vec::from(&b"/test[0-9]/.example.com"[..])),
2222            RemoveResult::Ok
2223        );
2224        assert_eq!(
2225            root.domain_lookup(b"test4.example.com", false),
2226            None,
2227            "the removed leftmost-regex host must be gone",
2228        );
2229        assert_eq!(
2230            root.domain_lookup(b"foo.test4.example.com", false),
2231            Some(&(b"foo./test[0-9]/.example.com"[..].to_vec(), 2)),
2232            "a sibling domain under the same regex segment must survive",
2233        );
2234
2235        // A leftmost-regex host that is not stored is `NotFound`, not a
2236        // silent `Ok` that takes the surviving sibling with it.
2237        assert_eq!(
2238            root.domain_remove(&Vec::from(&b"/test[0-9]/.example.com"[..])),
2239            RemoveResult::NotFound
2240        );
2241        assert_eq!(
2242            root.domain_lookup(b"foo.test4.example.com", false),
2243            Some(&(b"foo./test[0-9]/.example.com"[..].to_vec(), 2))
2244        );
2245    }
2246
2247    /// An emptied regex subtree must be pruned from `regexps`, exactly like
2248    /// an emptied `children` subtree is pruned from `children`. The `pos > 0`
2249    /// arm of `remove_recursive` recursed into the subtree and returned `Ok`
2250    /// without ever dropping the now valueless `(regex, subtree)` entry,
2251    /// stranding a node that keeps its whole parent chain reachable and
2252    /// non-empty. `insert_remove_through_regex` never empties the subtree,
2253    /// which is why the leak went unnoticed.
2254    ///
2255    /// To SEE THIS RED: delete the
2256    /// `if … .is_empty() { self.regexps.remove(index); }` prune from the
2257    /// regex arm of `TrieNode::remove_recursive` — in a debug build the trie
2258    /// panics first on "a retained child subtree must still hold at least one
2259    /// value", and in release `root.is_empty()` stays false.
2260    #[test]
2261    fn removing_the_last_host_under_a_regex_segment_prunes_the_subtree() {
2262        let mut root: TrieNode<u8> = TrieNode::root();
2263
2264        assert_eq!(
2265            root.domain_insert(Vec::from(&b"www./.*/.com"[..]), 1),
2266            InsertResult::Ok
2267        );
2268        assert_eq!(
2269            root.domain_lookup(b"www.sozu.com", false),
2270            Some(&(b"www./.*/.com"[..].to_vec(), 1))
2271        );
2272
2273        assert_eq!(
2274            root.domain_remove(&Vec::from(&b"www./.*/.com"[..])),
2275            RemoveResult::Ok
2276        );
2277        assert_eq!(root.domain_lookup(b"www.sozu.com", false), None);
2278        assert!(
2279            root.is_empty(),
2280            "dropping the last host under a regex segment must leave an empty trie",
2281        );
2282    }
2283    /// sozu#1351: `lookup_mut` addresses a KEY, `lookup` addresses a
2284    /// REQUEST. The two had drifted: `lookup_mut` resolved a literal
2285    /// segment through the first regex segment matching it, so
2286    /// `add_tree_rule` — which reaches a leaf through `domain_lookup_mut`
2287    /// before deciding whether to insert — attached an exact host's rule
2288    /// to the regex segment's leaf, and the whole regex family served it.
2289    ///
2290    /// `accept_wildcard: false` now means "this literal key may not
2291    /// resolve into a non-literal entry", covering the regex segments the
2292    /// way it already covered the wildcard slot (`tcp.rs`'s
2293    /// `insert_sni_route` documents the same reasoning for `*`).
2294    ///
2295    /// To SEE THIS RED: in `TrieNode::lookup_mut`, drop the
2296    /// `accept_wildcard &&` guard in front of the `self.regexps` scan.
2297    /// The first assertion then resolves to `Some(7)` — the regex leaf.
2298    #[test]
2299    fn lookup_mut_does_not_resolve_a_literal_host_through_a_regex_segment() {
2300        let mut root: TrieNode<u8> = TrieNode::root();
2301        assert_eq!(
2302            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 7),
2303            InsertResult::Ok
2304        );
2305
2306        assert_eq!(
2307            root.domain_lookup_mut(b"test4.example.com", false)
2308                .map(|(_, v)| *v),
2309            None,
2310            "a literal host is not a key of this trie just because a regex \
2311             segment matches it",
2312        );
2313        assert_eq!(
2314            root.domain_lookup_mut(b"/test[0-9]/.example.com", false)
2315                .map(|(_, v)| *v),
2316            Some(7),
2317            "the regex segment's own key still resolves, by regex identity",
2318        );
2319        assert_eq!(
2320            root.domain_lookup(b"test4.example.com", false),
2321            Some(&(b"/test[0-9]/.example.com"[..].to_vec(), 7)),
2322            "while the request-addressed lookup still matches the family",
2323        );
2324
2325        // Which is the point: the exact host can now get its OWN node.
2326        assert_eq!(
2327            root.domain_insert(Vec::from(&b"test4.example.com"[..]), 4),
2328            InsertResult::Ok
2329        );
2330        assert_eq!(
2331            root.domain_lookup(b"test4.example.com", false),
2332            Some(&(b"test4.example.com"[..].to_vec(), 4)),
2333            "and it outranks the regex segment that also matches it",
2334        );
2335    }
2336
2337    /// The request-addressed `lookup` walks its candidates
2338    /// most-specific-first — exact child, then regex segments in
2339    /// declaration order, then the wildcard — and a candidate that holds
2340    /// no value for the host hands it to the next one.
2341    ///
2342    /// This is the walk `lib/src/tls.rs` and
2343    /// `lib/src/protocol/tcp_preread/` resolve SNI with, so certificate
2344    /// selection follows the same order as HTTP routing.
2345    ///
2346    /// To SEE THIS RED: in `TrieNode::lookup_recursive`, move the wildcard
2347    /// block above the `self.regexps` loop — `test7.example.com` then
2348    /// answers with the wildcard entry.
2349    #[test]
2350    fn lookup_prefers_exact_then_regex_then_wildcard() {
2351        let mut root: TrieNode<u8> = TrieNode::root();
2352        assert_eq!(
2353            root.domain_insert(Vec::from(&b"*.example.com"[..]), 1),
2354            InsertResult::Ok
2355        );
2356        assert_eq!(
2357            root.domain_insert(Vec::from(&b"/test[0-9]/.example.com"[..]), 2),
2358            InsertResult::Ok
2359        );
2360        assert_eq!(
2361            root.domain_insert(Vec::from(&b"test4.example.com"[..]), 3),
2362            InsertResult::Ok
2363        );
2364
2365        assert_eq!(
2366            root.domain_lookup(b"test4.example.com", true)
2367                .map(|(_, v)| *v),
2368            Some(3),
2369            "the exact child wins over both",
2370        );
2371        assert_eq!(
2372            root.domain_lookup(b"test7.example.com", true)
2373                .map(|(_, v)| *v),
2374            Some(2),
2375            "a regex segment wins over the wildcard",
2376        );
2377        assert_eq!(
2378            root.domain_lookup(b"other.example.com", true)
2379                .map(|(_, v)| *v),
2380            Some(1),
2381            "the wildcard answers what neither claims",
2382        );
2383        assert_eq!(
2384            root.domain_lookup(b"test7.example.com", false)
2385                .map(|(_, v)| *v),
2386            Some(2),
2387            "and the regex segment still answers when the wildcard is \
2388             refused, which is what makes the order above observable",
2389        );
2390    }
2391
2392    /// A regex segment that matches the label but whose subtree holds
2393    /// nothing for this host must not swallow the lookup: the walk
2394    /// rewinds and tries the wildcard.
2395    ///
2396    /// `images./cdn[0-9]+/.hello.com` opens the `cdn[0-9]+` entry with a
2397    /// VALUELESS subtree (`TrieNode::root`) holding only `images`, so
2398    /// `cdn10.hello.com` matches the segment and finds no value behind it.
2399    ///
2400    /// To SEE THIS RED: in `TrieNode::lookup_recursive`, `return` the
2401    /// result of the recursive call inside the `self.regexps` loop instead
2402    /// of continuing on `None`. `cdn10.hello.com` then answers `None`.
2403    #[test]
2404    fn a_regex_segment_holding_no_value_falls_back_to_the_wildcard() {
2405        let mut root: TrieNode<u8> = TrieNode::root();
2406        assert_eq!(
2407            root.domain_insert(Vec::from(&b"images./cdn[0-9]+/.hello.com"[..]), 8),
2408            InsertResult::Ok
2409        );
2410        assert_eq!(
2411            root.domain_insert(Vec::from(&b"*.hello.com"[..]), 7),
2412            InsertResult::Ok
2413        );
2414
2415        assert_eq!(
2416            root.domain_lookup(b"images.cdn10.hello.com", true)
2417                .map(|(_, v)| *v),
2418            Some(8),
2419            "the regex segment serves the host it was opened for",
2420        );
2421        assert_eq!(
2422            root.domain_lookup(b"cdn10.hello.com", true)
2423                .map(|(_, v)| *v),
2424            Some(7),
2425            "and the wildcard serves the one it was not",
2426        );
2427    }
2428
2429    /// [`InlineTrieMatches`] is a drop-in for the `Vec` record: the same
2430    /// pushes and rewinds, including across the boundary between its inline
2431    /// slots and its spill, leave the same segments in the same order.
2432    #[test]
2433    fn inline_trie_matches_rewinds_across_the_spill_like_a_vec() {
2434        let labels: Vec<Vec<u8>> = (0..INLINE_TRIE_MATCHES + 8)
2435            .map(|index| format!("s{index}").into_bytes())
2436            .collect();
2437        let mut inline = InlineTrieMatches::new();
2438        let mut heap: TrieMatches<'_, '_> = Vec::new();
2439        let segments = |sub_matches: Vec<&TrieSubMatch<'_, '_>>| -> Vec<Vec<u8>> {
2440            sub_matches
2441                .into_iter()
2442                .map(|sub_match| match sub_match {
2443                    TrieSubMatch::Wildcard(segment) | TrieSubMatch::Regexp(segment, _) => {
2444                        segment.to_vec()
2445                    }
2446                })
2447                .collect()
2448        };
2449
2450        // Fill past the inline slots, rewind into them, refill, then rewind
2451        // inside the spill only.
2452        let steps: [(usize, usize); 3] = [
2453            (INLINE_TRIE_MATCHES + 4, INLINE_TRIE_MATCHES - 3),
2454            (INLINE_TRIE_MATCHES + 8, INLINE_TRIE_MATCHES + 2),
2455            (INLINE_TRIE_MATCHES + 8, 0),
2456        ];
2457        for (fill, rewind) in steps {
2458            for label in &labels[inline.mark()..fill] {
2459                SubMatchSink::push(&mut inline, TrieSubMatch::Wildcard(label));
2460                SubMatchSink::push(&mut heap, TrieSubMatch::Wildcard(label));
2461            }
2462            assert_eq!(
2463                segments(inline.iter().collect()),
2464                segments(heap.iter().collect()),
2465                "after filling to {fill}",
2466            );
2467            SubMatchSink::rewind(&mut inline, rewind);
2468            SubMatchSink::rewind(&mut heap, rewind);
2469            assert_eq!(inline.mark(), heap.mark());
2470            assert_eq!(
2471                segments(inline.iter().collect()),
2472                segments(heap.iter().collect()),
2473                "after rewinding to {rewind}",
2474            );
2475        }
2476    }
2477}