musli_zerocopy/trie/
walk.rs

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