syscall_map/
lib.rs

1// Simple const hashmap implementation using binary search on sorted array
2// Supports both static (compile-time) and dynamic (runtime) syscall lists via lifetimes
3pub struct SyscallMap<'a> {
4    entries: &'a [(u32, &'a str)],
5}
6
7impl<'a> SyscallMap<'a> {
8    /// Create from a pre-sorted slice of (hash, name) pairs
9    /// Works for both static and dynamic lifetimes
10    pub const fn from_entries(entries: &'a [(u32, &'a str)]) -> Self {
11        // Check for hash conflicts
12        let mut i = 0;
13        while i < entries.len() - 1 {
14            if entries[i].0 == entries[i + 1].0 {
15                panic!("Hash conflict detected between syscalls");
16            }
17            i += 1;
18        }
19
20        Self { entries }
21    }
22
23    pub const fn get(&self, hash: u32) -> Option<&'a str> {
24        // Binary search in const context
25        let mut left = 0;
26        let mut right = self.entries.len();
27
28        while left < right {
29            let mid = (left + right) / 2;
30            if self.entries[mid].0 == hash {
31                return Some(self.entries[mid].1);
32            } else if self.entries[mid].0 < hash {
33                left = mid + 1;
34            } else {
35                right = mid;
36            }
37        }
38        None
39    }
40
41    pub const fn len(&self) -> usize {
42        self.entries.len()
43    }
44
45    pub const fn is_empty(&self) -> bool {
46        self.entries.is_empty()
47    }
48}
49
50/// Runtime-mutable syscall map that owns its data
51/// This allows for dynamic updates at runtime
52pub struct DynamicSyscallMap {
53    // Store entries as (hash, name) pairs, kept sorted by hash
54    entries: Vec<(u32, String)>,
55}
56
57impl DynamicSyscallMap {
58    /// Create a new dynamic syscall map from owned strings
59    pub fn new(syscalls: Vec<String>) -> Result<Self, String> {
60        let mut entries: Vec<(u32, String)> = syscalls
61            .into_iter()
62            .map(|name| (murmur3_32(&name), name))
63            .collect();
64
65        entries.sort_by_key(|(hash, _)| *hash);
66
67        // Check for conflicts
68        for i in 0..entries.len().saturating_sub(1) {
69            if entries[i].0 == entries[i + 1].0 {
70                return Err(format!(
71                    "Hash conflict detected between syscalls '{}' and '{}'",
72                    entries[i].1, entries[i + 1].1
73                ));
74            }
75        }
76
77        Ok(Self { entries })
78    }
79
80    /// Create from string slices (convenience method)
81    pub fn from_names(names: &[&str]) -> Result<Self, String> {
82        Self::new(names.iter().map(|&s| s.to_string()).collect())
83    }
84
85    /// Look up a syscall by hash
86    pub fn get(&self, hash: u32) -> Option<&str> {
87        match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
88            Ok(idx) => Some(&self.entries[idx].1),
89            Err(_) => None,
90        }
91    }
92
93    /// Add a new syscall at runtime
94    pub fn add(&mut self, name: String) -> Result<(), String> {
95        let hash = murmur3_32(&name);
96
97        // Check if it already exists or would conflict
98        match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
99            Ok(_) => {
100                return Err(format!(
101                    "Hash conflict: '{}' conflicts with existing syscall",
102                    name
103                ));
104            }
105            Err(pos) => {
106                self.entries.insert(pos, (hash, name));
107                Ok(())
108            }
109        }
110    }
111
112    pub fn len(&self) -> usize {
113        self.entries.len()
114    }
115
116    pub fn is_empty(&self) -> bool {
117        self.entries.is_empty()
118    }
119}
120
121/// Convert a static SyscallMap to a dynamic one
122impl<'a> From<&SyscallMap<'a>> for DynamicSyscallMap {
123    fn from(static_map: &SyscallMap<'a>) -> Self {
124        let entries = static_map.entries
125            .iter()
126            .map(|(hash, name)| (*hash, name.to_string()))
127            .collect();
128
129        Self { entries }
130    }
131}
132
133/// Helper function for compile-time syscall map creation
134/// Computes hashes and sorts entries at compile time
135pub const fn compute_syscall_entries_const<'a, const N: usize>(
136    syscalls: &'a [&'a str; N],
137) -> [(u32, &'a str); N] {
138    let mut entries: [(u32, &str); N] = [(0, ""); N];
139    let mut i = 0;
140    while i < N {
141        entries[i] = (murmur3_32(syscalls[i]), syscalls[i]);
142        i += 1;
143    }
144
145    // Sort the entries at compile time using bubble sort
146    let mut i = 0;
147    while i < N {
148        let mut j = 0;
149        while j < N - i - 1 {
150            if entries[j].0 > entries[j + 1].0 {
151                let temp = entries[j];
152                entries[j] = entries[j + 1];
153                entries[j + 1] = temp;
154            }
155            j += 1;
156        }
157        i += 1;
158    }
159
160    entries
161}
162
163/// Runtime helper for dynamic syscall lists
164/// Computes hashes and sorts entries, borrowing from the input
165///
166/// The caller must own the string data (e.g., Vec<String>) and pass references.
167/// This function returns references to those owned strings.
168pub fn compute_syscall_entries<'a, T: AsRef<str>>(
169    syscalls: &'a [T],
170) -> Vec<(u32, &'a str)> {
171    let mut entries: Vec<(u32, &'a str)> = syscalls
172        .iter()
173        .map(|name| (murmur3_32(name.as_ref()), name.as_ref()))
174        .collect();
175
176    entries.sort_by_key(|(hash, _)| *hash);
177
178    // Check for conflicts
179    for i in 0..entries.len().saturating_sub(1) {
180        if entries[i].0 == entries[i + 1].0 {
181            panic!(
182                "Hash conflict detected between syscalls '{}' and '{}'",
183                entries[i].1, entries[i + 1].1
184            );
185        }
186    }
187
188    entries
189}
190
191pub const fn murmur3_32(buf: &str) -> u32 {
192    const fn pre_mix(buf: [u8; 4]) -> u32 {
193        u32::from_le_bytes(buf)
194            .wrapping_mul(0xcc9e2d51)
195            .rotate_left(15)
196            .wrapping_mul(0x1b873593)
197    }
198
199    let mut hash = 0;
200    let mut i = 0;
201    let buf = buf.as_bytes();
202
203    while i < buf.len() / 4 {
204        let buf = [buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], buf[i * 4 + 3]];
205        hash ^= pre_mix(buf);
206        hash = hash.rotate_left(13);
207        hash = hash.wrapping_mul(5).wrapping_add(0xe6546b64);
208
209        i += 1;
210    }
211
212    match buf.len() % 4 {
213        0 => {}
214        1 => {
215            hash = hash ^ pre_mix([buf[i * 4], 0, 0, 0]);
216        }
217        2 => {
218            hash = hash ^ pre_mix([buf[i * 4], buf[i * 4 + 1], 0, 0]);
219        }
220        3 => {
221            hash = hash ^ pre_mix([buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], 0]);
222        }
223        _ => { /* unreachable!() */ }
224    }
225
226    hash = hash ^ buf.len() as u32;
227    hash = hash ^ (hash.wrapping_shr(16));
228    hash = hash.wrapping_mul(0x85ebca6b);
229    hash = hash ^ (hash.wrapping_shr(13));
230    hash = hash.wrapping_mul(0xc2b2ae35);
231    hash = hash ^ (hash.wrapping_shr(16));
232
233    hash
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn test_const_evaluation() {
242        // Verify const evaluation works at compile time
243        const ABORT_HASH: u32 = murmur3_32("abort");
244        const SOL_LOG_HASH: u32 = murmur3_32("sol_log_");
245
246        // Create a test map
247        const TEST_SYSCALLS: &[&str; 2] = &["abort", "sol_log_"];
248        const TEST_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(TEST_SYSCALLS);
249        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
250
251        // Verify the hashes are computed correctly and can look up syscalls
252        assert_eq!(TEST_MAP.get(ABORT_HASH), Some("abort"));
253        assert_eq!(TEST_MAP.get(SOL_LOG_HASH), Some("sol_log_"));
254    }
255
256    #[test]
257    fn test_nonexistent_syscall() {
258        // Test that non-existent syscalls return None
259        const TEST_SYSCALLS: &[&str; 1] = &["test"];
260        const TEST_ENTRIES: &[(u32, &str); 1] = &compute_syscall_entries_const(TEST_SYSCALLS);
261        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
262
263        assert_eq!(TEST_MAP.get(0xDEADBEEF), None);
264    }
265
266    #[test]
267    fn test_dynamic_syscalls() {
268        // Example: Create a dynamic syscall map with owned strings
269        // The caller owns the strings (e.g., from user input, config file, etc.)
270        let owned_syscalls: Vec<String> = vec![
271            String::from("my_custom_syscall"),
272            String::from("another_syscall"),
273        ];
274
275        // Compute entries - they borrow from owned_syscalls
276        let entries = compute_syscall_entries(&owned_syscalls);
277
278        // Create the map - it borrows from entries
279        let map = SyscallMap::from_entries(&entries);
280
281        // Verify lookups work
282        let hash1 = murmur3_32("my_custom_syscall");
283        let hash2 = murmur3_32("another_syscall");
284
285        assert_eq!(map.get(hash1), Some("my_custom_syscall"));
286        assert_eq!(map.get(hash2), Some("another_syscall"));
287
288        // The lifetimes ensure owned_syscalls outlives both entries and map
289    }
290
291    #[test]
292    fn test_dynamic_syscalls_with_str_slices() {
293        // Also works with &str slices
294        let syscalls: Vec<&str> = vec!["syscall_a", "syscall_b", "syscall_c"];
295
296        let entries = compute_syscall_entries(&syscalls);
297        let map = SyscallMap::from_entries(&entries);
298
299        assert_eq!(map.get(murmur3_32("syscall_a")), Some("syscall_a"));
300        assert_eq!(map.get(murmur3_32("syscall_b")), Some("syscall_b"));
301        assert_eq!(map.get(murmur3_32("syscall_c")), Some("syscall_c"));
302    }
303
304    #[test]
305    fn test_static_custom_map() {
306        // Example: Create a static custom syscall map at compile time
307        const CUSTOM_SYSCALLS: &[&str; 2] = &["test1", "test2"];
308        const CUSTOM_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(CUSTOM_SYSCALLS);
309        const CUSTOM_MAP: SyscallMap<'static> = SyscallMap::from_entries(CUSTOM_ENTRIES);
310
311        assert_eq!(CUSTOM_MAP.get(murmur3_32("test1")), Some("test1"));
312        assert_eq!(CUSTOM_MAP.get(murmur3_32("test2")), Some("test2"));
313    }
314
315    #[test]
316    fn test_dynamic_mutable_map() {
317        // Example: Create a fully dynamic, mutable syscall map
318        let mut map = DynamicSyscallMap::from_names(&["initial_syscall"]).unwrap();
319
320        // Initial lookup works
321        assert_eq!(
322            map.get(murmur3_32("initial_syscall")),
323            Some("initial_syscall")
324        );
325
326        // Add new syscalls at runtime
327        map.add("runtime_syscall_1".to_string()).unwrap();
328        map.add("runtime_syscall_2".to_string()).unwrap();
329
330        // All lookups work
331        assert_eq!(
332            map.get(murmur3_32("initial_syscall")),
333            Some("initial_syscall")
334        );
335        assert_eq!(
336            map.get(murmur3_32("runtime_syscall_1")),
337            Some("runtime_syscall_1")
338        );
339        assert_eq!(
340            map.get(murmur3_32("runtime_syscall_2")),
341            Some("runtime_syscall_2")
342        );
343
344        // Non-existent syscall returns None
345        assert_eq!(map.get(0xDEADBEEF), None);
346
347        // Verify count
348        assert_eq!(map.len(), 3);
349    }
350
351    #[test]
352    fn test_dynamic_map_with_owned_strings() {
353        // Create from owned strings directly
354        let syscalls = vec![
355            String::from("custom_1"),
356            String::from("custom_2"),
357            String::from("custom_3"),
358        ];
359
360        let mut map = DynamicSyscallMap::new(syscalls).unwrap();
361
362        assert_eq!(map.get(murmur3_32("custom_1")), Some("custom_1"));
363        assert_eq!(map.get(murmur3_32("custom_2")), Some("custom_2"));
364        assert_eq!(map.get(murmur3_32("custom_3")), Some("custom_3"));
365
366        // Add more at runtime
367        map.add("custom_4".to_string()).unwrap();
368        assert_eq!(map.get(murmur3_32("custom_4")), Some("custom_4"));
369    }
370
371    #[test]
372    fn test_convert_static_to_dynamic() {
373        // Create a test static map
374        const TEST_SYSCALLS: &[&str; 3] = &["abort", "sol_log_", "sol_panic_"];
375        const TEST_ENTRIES: &[(u32, &str); 3] = &compute_syscall_entries_const(TEST_SYSCALLS);
376        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
377
378        // Convert to dynamic
379        let dynamic = DynamicSyscallMap::from(&TEST_MAP);
380
381        // Verify all static syscalls are present
382        for &name in TEST_SYSCALLS.iter() {
383            let hash = murmur3_32(name);
384            assert_eq!(dynamic.get(hash), Some(name), "Failed to find syscall: {}", name);
385        }
386
387        // Verify we can add new syscalls to it
388        let mut dynamic_mut = dynamic;
389        dynamic_mut.add("my_custom_syscall".to_string()).unwrap();
390
391        assert_eq!(
392            dynamic_mut.get(murmur3_32("my_custom_syscall")),
393            Some("my_custom_syscall")
394        );
395
396        // Original static syscalls still work
397        assert_eq!(dynamic_mut.get(murmur3_32("abort")), Some("abort"));
398
399        // Count should be original + 1
400        assert_eq!(dynamic_mut.len(), TEST_SYSCALLS.len() + 1);
401    }
402}