Skip to main content

musli_zerocopy/trie/
walk.rs

1use core::slice;
2
3use crate::endian::{ByteOrder, Native};
4use crate::error::{CoerceError, CoerceErrorKind, ErrorKind};
5use crate::slice::{BinarySearch, Slice, binary_search_by};
6use crate::stack::Stack;
7use crate::{Buf, Error, Ref, ZeroCopy};
8
9use super::{Flavor, LinksRef, StackEntry, prefix};
10
11pub(super) struct Walk<'a, 'buf, T, E, F, S>
12where
13    T: ZeroCopy,
14    E: ByteOrder,
15    F: Flavor,
16    S: Stack<StackEntry<'buf, T, E, F>>,
17{
18    // Buffer being walked.
19    buf: &'buf Buf,
20    // State of the current walker.
21    state: WalkState<'a, 'buf, T, E, F>,
22    // A stack which indicates the links who's children we should visit next,
23    // and an index corresponding to the child to visit.
24    stack: S,
25}
26
27impl<'a, 'buf, T, E, F, S> Walk<'a, 'buf, T, E, F, S>
28where
29    T: ZeroCopy,
30    E: ByteOrder,
31    F: Flavor,
32    S: Stack<StackEntry<'buf, T, E, F>>,
33{
34    pub(super) fn find(buf: &'buf Buf, links: LinksRef<T, E, F>, prefix: &'a [u8]) -> Self {
35        Self {
36            buf,
37            state: WalkState::Find(links, prefix),
38            stack: S::new(),
39        }
40    }
41
42    pub(super) fn poll(&mut self) -> Result<Option<(&'buf [u8], &'buf T)>, Error> {
43        'outer: loop {
44            match self.state {
45                WalkState::Find(this, &[]) => {
46                    let iter = self.buf.load(this.values)?.iter();
47
48                    if !self.stack.try_push((this, 0, &[])) {
49                        return Err(Error::new(ErrorKind::StackOverflow {
50                            capacity: S::CAPACITY,
51                        }));
52                    }
53
54                    self.state = WalkState::Values(&[], iter);
55                    continue;
56                }
57                WalkState::Find(this, string) => {
58                    let mut this = this;
59                    let mut string = string;
60                    let mut len = 0;
61
62                    let node = 'node: loop {
63                        let search = binary_search_by(self.buf, this.children, |c| {
64                            Ok(self.buf.load(c.string)?.cmp(string))
65                        })?;
66
67                        match search {
68                            BinarySearch::Found(n) => {
69                                break 'node self.buf.load(this.children.get_unchecked(n))?;
70                            }
71                            BinarySearch::Missing(n) => {
72                                // For missing nodes, we need to find any
73                                // neighbor for which the current string is a
74                                // prefix. So unless `n` is out of bounds we
75                                // look at the prior or current index `n`.
76                                //
77                                // Note that thanks to structural invariants,
78                                // only one node may be a matching prefix.
79                                let iter = n
80                                    .checked_sub(1)
81                                    .into_iter()
82                                    .chain((n < this.children.len()).then_some(n));
83
84                                for n in iter {
85                                    let child = self.buf.load(this.children.get_unchecked(n))?;
86
87                                    // Find common prefix and split nodes if necessary.
88                                    let prefix = prefix(self.buf.load(child.string)?, string);
89
90                                    if prefix == 0 {
91                                        continue;
92                                    }
93
94                                    if prefix != string.len() {
95                                        len += prefix;
96                                        string = &string[prefix..];
97                                        this = child.links;
98                                        continue 'node;
99                                    }
100
101                                    break 'node child;
102                                }
103
104                                // Falling through here indicates that we have
105                                // not found anything. Assigning the stack state
106                                // with an empty stack will cause the iterator
107                                // to continuously return `None`.
108                                self.state = WalkState::Stack;
109                                continue 'outer;
110                            }
111                        };
112                    };
113
114                    let prefix = prefix_string(node.string, len)?;
115                    let prefix = self.buf.load(prefix)?;
116
117                    if !self.stack.try_push((node.links, 0, prefix)) {
118                        return Err(Error::new(ErrorKind::StackOverflow {
119                            capacity: S::CAPACITY,
120                        }));
121                    }
122
123                    self.state =
124                        WalkState::Values(prefix, self.buf.load(node.links.values)?.iter());
125                }
126                WalkState::Values(prefix, ref mut values) => {
127                    let Some(value) = values.next() else {
128                        self.state = WalkState::Stack;
129                        continue;
130                    };
131
132                    return Ok(Some((prefix, value)));
133                }
134                WalkState::Stack => loop {
135                    let Some((links, index, prefix)) = self.stack.pop() else {
136                        break 'outer;
137                    };
138
139                    let Some(node) = links.children.get(index) else {
140                        continue;
141                    };
142
143                    let node = self.buf.load(node)?;
144
145                    let new_prefix = prefix_string(node.string, prefix.len())?;
146                    let new_prefix = self.buf.load(new_prefix)?;
147
148                    self.state =
149                        WalkState::Values(new_prefix, self.buf.load(node.links.values)?.iter());
150
151                    if !self.stack.try_push((links, index + 1, prefix)) {
152                        return Err(Error::new(ErrorKind::StackOverflow {
153                            capacity: S::CAPACITY,
154                        }));
155                    }
156
157                    if !self.stack.try_push((node.links, 0, new_prefix)) {
158                        return Err(Error::new(ErrorKind::StackOverflow {
159                            capacity: S::CAPACITY,
160                        }));
161                    }
162
163                    continue 'outer;
164                },
165            }
166        }
167
168        Ok(None)
169    }
170}
171
172/// Calculate a prefix string based on an existing string in the trie.
173///
174/// We use the fact that during construction the trie must have been provided a
175/// complete string reference, so any substring that we constructed must be
176/// prefixed with its complete counterpart.
177fn prefix_string<S>(string: S, prefix_len: usize) -> Result<Ref<[u8], Native, usize>, CoerceError>
178where
179    S: Slice<u8>,
180{
181    // NB: All of these operations have to be checked, since they are preformed
182    // over untrusted data and we'd like to avoid a panic.
183
184    let string_offset = string.offset();
185    let string_len = string.len();
186
187    let Some(real_start) = string_offset.checked_sub(prefix_len) else {
188        return Err(CoerceError::new(CoerceErrorKind::Underflow {
189            at: string_offset,
190            len: prefix_len,
191        }));
192    };
193
194    let Some(real_end) = string_offset.checked_add(string_len) else {
195        return Err(CoerceError::new(CoerceErrorKind::Overflow {
196            at: string_offset,
197            len: string_len,
198        }));
199    };
200
201    Ref::try_with_metadata(real_start, real_end - real_start)
202}
203
204enum WalkState<'a, 'buf, T, E, F>
205where
206    T: ZeroCopy,
207    E: ByteOrder,
208    F: Flavor,
209{
210    // Initial state where we need to lookup the specified prefix in the trie.
211    Find(LinksRef<T, E, F>, &'a [u8]),
212    // Values are being yielded.
213    Values(&'buf [u8], slice::Iter<'buf, T>),
214    // Stack traversal.
215    Stack,
216}