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}