nautilus_common/msgbus/
matching.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 2Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use super::core::{MStr, Pattern, Topic};
17
18/// Match a topic and a string pattern using iterative backtracking algorithm
19/// pattern can contains -
20/// '*' - match 0 or more characters after this
21/// '?' - match any character once
22/// 'a-z' - match the specific character
23pub fn is_matching_backtracking(topic: MStr<Topic>, pattern: MStr<Pattern>) -> bool {
24    let topic_bytes = topic.as_bytes();
25    let pattern_bytes = pattern.as_bytes();
26
27    is_matching(topic_bytes, pattern_bytes)
28}
29
30#[must_use]
31pub fn is_matching(topic: &[u8], pattern: &[u8]) -> bool {
32    // Stack to store states for backtracking (topic_idx, pattern_idx)
33    let mut stack = vec![(0, 0)];
34
35    while let Some((mut i, mut j)) = stack.pop() {
36        loop {
37            // Found a match if we've consumed both strings
38            if i == topic.len() && j == pattern.len() {
39                return true;
40            }
41
42            // If we've reached the end of the pattern, break to try other paths
43            if j == pattern.len() {
44                break;
45            }
46
47            // Handle '*' wildcard
48            if pattern[j] == b'*' {
49                // Try skipping '*' entirely first
50                stack.push((i, j + 1));
51
52                // Continue with matching current character and keeping '*'
53                if i < topic.len() {
54                    i += 1;
55                    continue;
56                }
57                break;
58            }
59            // Handle '?' or exact character match
60            else if i < topic.len() && (pattern[j] == b'?' || topic[i] == pattern[j]) {
61                // Continue matching linearly without stack operations
62                i += 1;
63                j += 1;
64                continue;
65            }
66
67            // No match found in current path
68            break;
69        }
70    }
71
72    false
73}