syscall_map/
static_map.rs

1use crate::murmur3_32;
2
3/// Static syscall map using lifetimes for compile-time and borrowed data
4/// Supports both static (compile-time) and dynamic (runtime) syscall lists via lifetimes
5pub struct SyscallMap<'a> {
6    pub(crate) entries: &'a [(u32, &'a str)],
7}
8
9impl<'a> SyscallMap<'a> {
10    /// Create from a pre-sorted slice of (hash, name) pairs
11    /// Works for both static and dynamic lifetimes
12    pub const fn from_entries(entries: &'a [(u32, &'a str)]) -> Self {
13        // Check for hash conflicts
14        let mut i = 0;
15        while i < entries.len() - 1 {
16            if entries[i].0 == entries[i + 1].0 {
17                panic!("Hash conflict detected between syscalls");
18            }
19            i += 1;
20        }
21
22        Self { entries }
23    }
24
25    pub const fn get(&self, hash: u32) -> Option<&'a str> {
26        // Binary search in const context
27        let mut left = 0;
28        let mut right = self.entries.len();
29
30        while left < right {
31            let mid = (left + right) / 2;
32            if self.entries[mid].0 == hash {
33                return Some(self.entries[mid].1);
34            } else if self.entries[mid].0 < hash {
35                left = mid + 1;
36            } else {
37                right = mid;
38            }
39        }
40        None
41    }
42
43    pub const fn len(&self) -> usize {
44        self.entries.len()
45    }
46
47    pub const fn is_empty(&self) -> bool {
48        self.entries.is_empty()
49    }
50}
51
52/// Helper function for compile-time syscall map creation
53/// Computes hashes and sorts entries at compile time
54pub const fn compute_syscall_entries_const<'a, const N: usize>(
55    syscalls: &'a [&'a str; N],
56) -> [(u32, &'a str); N] {
57    let mut entries: [(u32, &str); N] = [(0, ""); N];
58    let mut i = 0;
59    while i < N {
60        entries[i] = (murmur3_32(syscalls[i]), syscalls[i]);
61        i += 1;
62    }
63
64    // Sort the entries at compile time using bubble sort
65    let mut i = 0;
66    while i < N {
67        let mut j = 0;
68        while j < N - i - 1 {
69            if entries[j].0 > entries[j + 1].0 {
70                let temp = entries[j];
71                entries[j] = entries[j + 1];
72                entries[j + 1] = temp;
73            }
74            j += 1;
75        }
76        i += 1;
77    }
78
79    entries
80}
81
82/// Runtime helper for dynamic syscall lists
83/// Computes hashes and sorts entries, borrowing from the input
84///
85/// The caller must own the string data (e.g., Vec<String>) and pass references.
86/// This function returns references to those owned strings.
87pub fn compute_syscall_entries<'a, T: AsRef<str>>(syscalls: &'a [T]) -> Vec<(u32, &'a str)> {
88    let mut entries: Vec<(u32, &'a str)> = syscalls
89        .iter()
90        .map(|name| (murmur3_32(name.as_ref()), name.as_ref()))
91        .collect();
92
93    entries.sort_by_key(|(hash, _)| *hash);
94
95    // Check for conflicts
96    for i in 0..entries.len().saturating_sub(1) {
97        if entries[i].0 == entries[i + 1].0 {
98            panic!(
99                "Hash conflict detected between syscalls '{}' and '{}'",
100                entries[i].1,
101                entries[i + 1].1
102            );
103        }
104    }
105
106    entries
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_const_evaluation() {
115        // Verify const evaluation works at compile time
116        const ABORT_HASH: u32 = murmur3_32("abort");
117        const SOL_LOG_HASH: u32 = murmur3_32("sol_log_");
118
119        // Create a test map
120        const TEST_SYSCALLS: &[&str; 2] = &["abort", "sol_log_"];
121        const TEST_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(TEST_SYSCALLS);
122        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
123
124        // Verify the hashes are computed correctly and can look up syscalls
125        assert_eq!(TEST_MAP.get(ABORT_HASH), Some("abort"));
126        assert_eq!(TEST_MAP.get(SOL_LOG_HASH), Some("sol_log_"));
127    }
128
129    #[test]
130    fn test_nonexistent_syscall() {
131        // Test that non-existent syscalls return None
132        const TEST_SYSCALLS: &[&str; 1] = &["test"];
133        const TEST_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(TEST_SYSCALLS);
134        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
135
136        assert_eq!(TEST_MAP.get(0xDEADBEEF), None);
137    }
138
139    #[test]
140    fn test_dynamic_syscalls() {
141        // Example: Create a dynamic syscall map with owned strings
142        // The caller owns the strings (e.g., from user input, config file, etc.)
143        let owned_syscalls: Vec<String> = vec![
144            String::from("my_custom_syscall"),
145            String::from("another_syscall"),
146        ];
147
148        // Compute entries - they borrow from owned_syscalls
149        let entries = compute_syscall_entries(&owned_syscalls);
150
151        // Create the map - it borrows from entries
152        let map = SyscallMap::from_entries(&entries);
153
154        // Verify lookups work
155        let hash1 = murmur3_32("my_custom_syscall");
156        let hash2 = murmur3_32("another_syscall");
157
158        assert_eq!(map.get(hash1), Some("my_custom_syscall"));
159        assert_eq!(map.get(hash2), Some("another_syscall"));
160
161        // The lifetimes ensure owned_syscalls outlives both entries and map
162    }
163
164    #[test]
165    fn test_dynamic_syscalls_with_str_slices() {
166        // Also works with &str slices
167        let syscalls: Vec<&str> = vec!["syscall_a", "syscall_b", "syscall_c"];
168
169        let entries = compute_syscall_entries(&syscalls);
170        let map = SyscallMap::from_entries(&entries);
171
172        assert_eq!(map.get(murmur3_32("syscall_a")), Some("syscall_a"));
173        assert_eq!(map.get(murmur3_32("syscall_b")), Some("syscall_b"));
174        assert_eq!(map.get(murmur3_32("syscall_c")), Some("syscall_c"));
175    }
176
177    #[test]
178    fn test_static_custom_map() {
179        // Example: Create a static custom syscall map at compile time
180        const CUSTOM_SYSCALLS: &[&str; 2] = &["test1", "test2"];
181        const CUSTOM_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(CUSTOM_SYSCALLS);
182        const CUSTOM_MAP: SyscallMap<'static> = SyscallMap::from_entries(CUSTOM_ENTRIES);
183
184        assert_eq!(CUSTOM_MAP.get(murmur3_32("test1")), Some("test1"));
185        assert_eq!(CUSTOM_MAP.get(murmur3_32("test2")), Some("test2"));
186    }
187
188    #[test]
189    fn test_syscall_map_len_and_is_empty() {
190        // Test with non-empty map
191        const TEST_SYSCALLS: &[&str; 3] = &["a", "b", "c"];
192        const TEST_ENTRIES: &[(u32, &str); 3] = &compute_syscall_entries_const(TEST_SYSCALLS);
193        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
194
195        assert!(!TEST_MAP.is_empty());
196        assert_eq!(TEST_MAP.len(), 3);
197
198        // Test with single entry map
199        const SINGLE: &[&str; 1] = &["x"];
200        const SINGLE_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(SINGLE);
201        const SINGLE_MAP: SyscallMap<'static> = SyscallMap::from_entries(SINGLE_ENTRIES);
202
203        assert!(!SINGLE_MAP.is_empty());
204        assert_eq!(SINGLE_MAP.len(), 1);
205    }
206
207    #[test]
208    fn test_binary_search_edge_cases() {
209        // Test binary search with various sizes
210        const SINGLE: &[&str; 1] = &["single"];
211        const SINGLE_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(SINGLE);
212        const SINGLE_MAP: SyscallMap<'static> = SyscallMap::from_entries(SINGLE_ENTRIES);
213
214        assert_eq!(SINGLE_MAP.get(murmur3_32("single")), Some("single"));
215        assert_eq!(SINGLE_MAP.get(0xFFFFFFFF), None);
216
217        // Test with multiple entries
218        const MULTI: &[&str; 5] = &["a", "b", "c", "d", "e"];
219        const MULTI_ENTRIES: &[(u32, &str); 5] = &compute_syscall_entries_const(MULTI);
220        const MULTI_MAP: SyscallMap<'static> = SyscallMap::from_entries(MULTI_ENTRIES);
221
222        // Test all entries
223        assert_eq!(MULTI_MAP.get(murmur3_32("a")), Some("a"));
224        assert_eq!(MULTI_MAP.get(murmur3_32("b")), Some("b"));
225        assert_eq!(MULTI_MAP.get(murmur3_32("c")), Some("c"));
226        assert_eq!(MULTI_MAP.get(murmur3_32("d")), Some("d"));
227        assert_eq!(MULTI_MAP.get(murmur3_32("e")), Some("e"));
228    }
229
230    #[test]
231    #[should_panic(expected = "Hash conflict")]
232    fn test_compute_syscall_entries_hash_conflict_panic() {
233        let syscalls = vec!["duplicate".to_string(), "duplicate".to_string()];
234        let _ = compute_syscall_entries(&syscalls); // Should panic
235    }
236}