Skip to main content

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                entries.swap(j, j + 1);
71            }
72            j += 1;
73        }
74        i += 1;
75    }
76
77    entries
78}
79
80/// Runtime helper for dynamic syscall lists
81/// Computes hashes and sorts entries, borrowing from the input
82///
83/// The caller must own the string data (e.g., Vec<String>) and pass references.
84/// This function returns references to those owned strings.
85pub fn compute_syscall_entries<'a, T: AsRef<str>>(syscalls: &'a [T]) -> Vec<(u32, &'a str)> {
86    let mut entries: Vec<(u32, &'a str)> = syscalls
87        .iter()
88        .map(|name| (murmur3_32(name.as_ref()), name.as_ref()))
89        .collect();
90
91    entries.sort_by_key(|(hash, _)| *hash);
92
93    // Check for conflicts
94    for i in 0..entries.len().saturating_sub(1) {
95        if entries[i].0 == entries[i + 1].0 {
96            panic!(
97                "Hash conflict detected between syscalls '{}' and '{}'",
98                entries[i].1,
99                entries[i + 1].1
100            );
101        }
102    }
103
104    entries
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_const_evaluation() {
113        // Verify const evaluation works at compile time
114        const ABORT_HASH: u32 = murmur3_32("abort");
115        const SOL_LOG_HASH: u32 = murmur3_32("sol_log_");
116
117        // Create a test map
118        const TEST_SYSCALLS: &[&str; 2] = &["abort", "sol_log_"];
119        const TEST_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(TEST_SYSCALLS);
120        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
121
122        // Verify the hashes are computed correctly and can look up syscalls
123        assert_eq!(TEST_MAP.get(ABORT_HASH), Some("abort"));
124        assert_eq!(TEST_MAP.get(SOL_LOG_HASH), Some("sol_log_"));
125    }
126
127    #[test]
128    fn test_nonexistent_syscall() {
129        // Test that non-existent syscalls return None
130        const TEST_SYSCALLS: &[&str; 1] = &["test"];
131        const TEST_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(TEST_SYSCALLS);
132        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
133
134        assert_eq!(TEST_MAP.get(0xDEADBEEF), None);
135    }
136
137    #[test]
138    fn test_dynamic_syscalls() {
139        // Example: Create a dynamic syscall map with owned strings
140        // The caller owns the strings (e.g., from user input, config file, etc.)
141        let owned_syscalls: Vec<String> = vec![
142            String::from("my_custom_syscall"),
143            String::from("another_syscall"),
144        ];
145
146        // Compute entries - they borrow from owned_syscalls
147        let entries = compute_syscall_entries(&owned_syscalls);
148
149        // Create the map - it borrows from entries
150        let map = SyscallMap::from_entries(&entries);
151
152        // Verify lookups work
153        let hash1 = murmur3_32("my_custom_syscall");
154        let hash2 = murmur3_32("another_syscall");
155
156        assert_eq!(map.get(hash1), Some("my_custom_syscall"));
157        assert_eq!(map.get(hash2), Some("another_syscall"));
158
159        // The lifetimes ensure owned_syscalls outlives both entries and map
160    }
161
162    #[test]
163    fn test_dynamic_syscalls_with_str_slices() {
164        // Also works with &str slices
165        let syscalls: Vec<&str> = vec!["syscall_a", "syscall_b", "syscall_c"];
166
167        let entries = compute_syscall_entries(&syscalls);
168        let map = SyscallMap::from_entries(&entries);
169
170        assert_eq!(map.get(murmur3_32("syscall_a")), Some("syscall_a"));
171        assert_eq!(map.get(murmur3_32("syscall_b")), Some("syscall_b"));
172        assert_eq!(map.get(murmur3_32("syscall_c")), Some("syscall_c"));
173    }
174
175    #[test]
176    fn test_static_custom_map() {
177        // Example: Create a static custom syscall map at compile time
178        const CUSTOM_SYSCALLS: &[&str; 2] = &["test1", "test2"];
179        const CUSTOM_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(CUSTOM_SYSCALLS);
180        const CUSTOM_MAP: SyscallMap<'static> = SyscallMap::from_entries(CUSTOM_ENTRIES);
181
182        assert_eq!(CUSTOM_MAP.get(murmur3_32("test1")), Some("test1"));
183        assert_eq!(CUSTOM_MAP.get(murmur3_32("test2")), Some("test2"));
184    }
185
186    #[test]
187    fn test_syscall_map_len_and_is_empty() {
188        // Test with non-empty map
189        const TEST_SYSCALLS: &[&str; 3] = &["a", "b", "c"];
190        const TEST_ENTRIES: &[(u32, &str); 3] = &compute_syscall_entries_const(TEST_SYSCALLS);
191        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
192
193        assert!(!TEST_MAP.is_empty());
194        assert_eq!(TEST_MAP.len(), 3);
195
196        // Test with single entry map
197        const SINGLE: &[&str; 1] = &["x"];
198        const SINGLE_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(SINGLE);
199        const SINGLE_MAP: SyscallMap<'static> = SyscallMap::from_entries(SINGLE_ENTRIES);
200
201        assert!(!SINGLE_MAP.is_empty());
202        assert_eq!(SINGLE_MAP.len(), 1);
203    }
204
205    #[test]
206    fn test_binary_search_edge_cases() {
207        // Test binary search with various sizes
208        const SINGLE: &[&str; 1] = &["single"];
209        const SINGLE_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(SINGLE);
210        const SINGLE_MAP: SyscallMap<'static> = SyscallMap::from_entries(SINGLE_ENTRIES);
211
212        assert_eq!(SINGLE_MAP.get(murmur3_32("single")), Some("single"));
213        assert_eq!(SINGLE_MAP.get(0xFFFFFFFF), None);
214
215        // Test with multiple entries
216        const MULTI: &[&str; 5] = &["a", "b", "c", "d", "e"];
217        const MULTI_ENTRIES: &[(u32, &str); 5] = &compute_syscall_entries_const(MULTI);
218        const MULTI_MAP: SyscallMap<'static> = SyscallMap::from_entries(MULTI_ENTRIES);
219
220        // Test all entries
221        assert_eq!(MULTI_MAP.get(murmur3_32("a")), Some("a"));
222        assert_eq!(MULTI_MAP.get(murmur3_32("b")), Some("b"));
223        assert_eq!(MULTI_MAP.get(murmur3_32("c")), Some("c"));
224        assert_eq!(MULTI_MAP.get(murmur3_32("d")), Some("d"));
225        assert_eq!(MULTI_MAP.get(murmur3_32("e")), Some("e"));
226    }
227
228    #[test]
229    #[should_panic(expected = "Hash conflict")]
230    fn test_compute_syscall_entries_hash_conflict_panic() {
231        let syscalls = vec!["duplicate".to_string(), "duplicate".to_string()];
232        let _ = compute_syscall_entries(&syscalls); // Should panic
233    }
234}