sbpf_common/
syscalls_map.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,
73                    entries[i + 1].1
74                ));
75            }
76        }
77
78        Ok(Self { entries })
79    }
80
81    /// Create from string slices (convenience method)
82    pub fn from_names(names: &[&str]) -> Result<Self, String> {
83        Self::new(names.iter().map(|&s| s.to_string()).collect())
84    }
85
86    /// Look up a syscall by hash
87    pub fn get(&self, hash: u32) -> Option<&str> {
88        match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
89            Ok(idx) => Some(&self.entries[idx].1),
90            Err(_) => None,
91        }
92    }
93
94    /// Add a new syscall at runtime
95    pub fn add(&mut self, name: String) -> Result<(), String> {
96        let hash = murmur3_32(&name);
97
98        // Check if it already exists or would conflict
99        match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
100            Ok(_) => Err(format!(
101                "Hash conflict: '{}' conflicts with existing syscall",
102                name
103            )),
104            Err(pos) => {
105                self.entries.insert(pos, (hash, name));
106                Ok(())
107            }
108        }
109    }
110
111    pub fn len(&self) -> usize {
112        self.entries.len()
113    }
114
115    pub fn is_empty(&self) -> bool {
116        self.entries.is_empty()
117    }
118}
119
120/// Convert a static SyscallMap to a dynamic one
121impl<'a> From<&SyscallMap<'a>> for DynamicSyscallMap {
122    fn from(static_map: &SyscallMap<'a>) -> Self {
123        let entries = static_map
124            .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],
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>>(syscalls: &'a [T]) -> Vec<(u32, &'a str)> {
169    let mut entries: Vec<(u32, &'a str)> = syscalls
170        .iter()
171        .map(|name| (murmur3_32(name.as_ref()), name.as_ref()))
172        .collect();
173
174    entries.sort_by_key(|(hash, _)| *hash);
175
176    // Check for conflicts
177    for i in 0..entries.len().saturating_sub(1) {
178        if entries[i].0 == entries[i + 1].0 {
179            panic!(
180                "Hash conflict detected between syscalls '{}' and '{}'",
181                entries[i].1,
182                entries[i + 1].1
183            );
184        }
185    }
186
187    entries
188}
189
190pub const fn murmur3_32(buf: &str) -> u32 {
191    const fn pre_mix(buf: [u8; 4]) -> u32 {
192        u32::from_le_bytes(buf)
193            .wrapping_mul(0xcc9e2d51)
194            .rotate_left(15)
195            .wrapping_mul(0x1b873593)
196    }
197
198    let mut hash = 0;
199    let mut i = 0;
200    let buf = buf.as_bytes();
201
202    while i < buf.len() / 4 {
203        let buf = [buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], buf[i * 4 + 3]];
204        hash ^= pre_mix(buf);
205        hash = hash.rotate_left(13);
206        hash = hash.wrapping_mul(5).wrapping_add(0xe6546b64);
207
208        i += 1;
209    }
210
211    match buf.len() % 4 {
212        0 => {}
213        1 => {
214            hash = hash ^ pre_mix([buf[i * 4], 0, 0, 0]);
215        }
216        2 => {
217            hash = hash ^ pre_mix([buf[i * 4], buf[i * 4 + 1], 0, 0]);
218        }
219        3 => {
220            hash = hash ^ pre_mix([buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], 0]);
221        }
222        _ => { /* unreachable!() */ }
223    }
224
225    hash = hash ^ buf.len() as u32;
226    hash = hash ^ (hash.wrapping_shr(16));
227    hash = hash.wrapping_mul(0x85ebca6b);
228    hash = hash ^ (hash.wrapping_shr(13));
229    hash = hash.wrapping_mul(0xc2b2ae35);
230    hash = hash ^ (hash.wrapping_shr(16));
231
232    hash
233}
234
235#[cfg(test)]
236mod tests {
237    use {
238        super::*,
239        crate::syscalls::{REGISTERED_SYSCALLS, SYSCALLS},
240    };
241
242    #[test]
243    fn test_syscall_lookup() {
244        // Test that all syscalls can be found
245        for &name in REGISTERED_SYSCALLS.iter() {
246            let hash = murmur3_32(name);
247            assert_eq!(
248                SYSCALLS.get(hash),
249                Some(name),
250                "Failed to find syscall: {}",
251                name
252            );
253        }
254    }
255
256    #[test]
257    fn test_const_evaluation() {
258        // Verify const evaluation works at compile time
259        const ABORT_HASH: u32 = murmur3_32("abort");
260        const SOL_LOG_HASH: u32 = murmur3_32("sol_log_");
261
262        // Verify the hashes are computed correctly and can look up syscalls
263        assert_eq!(SYSCALLS.get(ABORT_HASH), Some("abort"));
264        assert_eq!(SYSCALLS.get(SOL_LOG_HASH), Some("sol_log_"));
265    }
266
267    #[test]
268    fn test_nonexistent_syscall() {
269        // Test that non-existent syscalls return None
270        assert_eq!(SYSCALLS.get(0xDEADBEEF), None);
271    }
272
273    #[test]
274    fn test_dynamic_syscalls() {
275        // Example: Create a dynamic syscall map with owned strings
276        // The caller owns the strings (e.g., from user input, config file, etc.)
277        let owned_syscalls: Vec<String> = vec![
278            String::from("my_custom_syscall"),
279            String::from("another_syscall"),
280        ];
281
282        // Compute entries - they borrow from owned_syscalls
283        let entries = compute_syscall_entries(&owned_syscalls);
284
285        // Create the map - it borrows from entries
286        let map = SyscallMap::from_entries(&entries);
287
288        // Verify lookups work
289        let hash1 = murmur3_32("my_custom_syscall");
290        let hash2 = murmur3_32("another_syscall");
291
292        assert_eq!(map.get(hash1), Some("my_custom_syscall"));
293        assert_eq!(map.get(hash2), Some("another_syscall"));
294
295        // The lifetimes ensure owned_syscalls outlives both entries and map
296    }
297
298    #[test]
299    fn test_dynamic_syscalls_with_str_slices() {
300        // Also works with &str slices
301        let syscalls: Vec<&str> = vec!["syscall_a", "syscall_b", "syscall_c"];
302
303        let entries = compute_syscall_entries(&syscalls);
304        let map = SyscallMap::from_entries(&entries);
305
306        assert_eq!(map.get(murmur3_32("syscall_a")), Some("syscall_a"));
307        assert_eq!(map.get(murmur3_32("syscall_b")), Some("syscall_b"));
308        assert_eq!(map.get(murmur3_32("syscall_c")), Some("syscall_c"));
309    }
310
311    #[test]
312    fn test_static_custom_map() {
313        // Example: Create a static custom syscall map at compile time
314        const CUSTOM_SYSCALLS: &[&str; 2] = &["test1", "test2"];
315        const CUSTOM_ENTRIES: &[(u32, &str); 2] = &compute_syscall_entries_const(CUSTOM_SYSCALLS);
316        const CUSTOM_MAP: SyscallMap<'static> = SyscallMap::from_entries(CUSTOM_ENTRIES);
317
318        assert_eq!(CUSTOM_MAP.get(murmur3_32("test1")), Some("test1"));
319        assert_eq!(CUSTOM_MAP.get(murmur3_32("test2")), Some("test2"));
320    }
321
322    #[test]
323    fn test_dynamic_mutable_map() {
324        // Example: Create a fully dynamic, mutable syscall map
325        let mut map = DynamicSyscallMap::from_names(&["initial_syscall"]).unwrap();
326
327        // Initial lookup works
328        assert_eq!(
329            map.get(murmur3_32("initial_syscall")),
330            Some("initial_syscall")
331        );
332
333        // Add new syscalls at runtime
334        map.add("runtime_syscall_1".to_string()).unwrap();
335        map.add("runtime_syscall_2".to_string()).unwrap();
336
337        // All lookups work
338        assert_eq!(
339            map.get(murmur3_32("initial_syscall")),
340            Some("initial_syscall")
341        );
342        assert_eq!(
343            map.get(murmur3_32("runtime_syscall_1")),
344            Some("runtime_syscall_1")
345        );
346        assert_eq!(
347            map.get(murmur3_32("runtime_syscall_2")),
348            Some("runtime_syscall_2")
349        );
350
351        // Non-existent syscall returns None
352        assert_eq!(map.get(0xDEADBEEF), None);
353
354        // Verify count
355        assert_eq!(map.len(), 3);
356    }
357
358    #[test]
359    fn test_dynamic_map_with_owned_strings() {
360        // Create from owned strings directly
361        let syscalls = vec![
362            String::from("custom_1"),
363            String::from("custom_2"),
364            String::from("custom_3"),
365        ];
366
367        let mut map = DynamicSyscallMap::new(syscalls).unwrap();
368
369        assert_eq!(map.get(murmur3_32("custom_1")), Some("custom_1"));
370        assert_eq!(map.get(murmur3_32("custom_2")), Some("custom_2"));
371        assert_eq!(map.get(murmur3_32("custom_3")), Some("custom_3"));
372
373        // Add more at runtime
374        map.add("custom_4".to_string()).unwrap();
375        assert_eq!(map.get(murmur3_32("custom_4")), Some("custom_4"));
376    }
377
378    #[test]
379    fn test_convert_static_to_dynamic() {
380        // Start with the static syscall map
381        let dynamic = DynamicSyscallMap::from(&SYSCALLS);
382
383        // Verify all static syscalls are present
384        for &name in REGISTERED_SYSCALLS.iter() {
385            let hash = murmur3_32(name);
386            assert_eq!(
387                dynamic.get(hash),
388                Some(name),
389                "Failed to find syscall: {}",
390                name
391            );
392        }
393
394        // Verify we can add new syscalls to it
395        let mut dynamic_mut = dynamic;
396        dynamic_mut.add("my_custom_syscall".to_string()).unwrap();
397
398        assert_eq!(
399            dynamic_mut.get(murmur3_32("my_custom_syscall")),
400            Some("my_custom_syscall")
401        );
402
403        // Original static syscalls still work
404        assert_eq!(dynamic_mut.get(murmur3_32("abort")), Some("abort"));
405
406        // Count should be original + 1
407        assert_eq!(dynamic_mut.len(), REGISTERED_SYSCALLS.len() + 1);
408    }
409
410    #[test]
411    fn test_syscall_map_len_and_is_empty() {
412        // Test static map
413        assert!(SYSCALLS.len() > 0);
414        assert!(!SYSCALLS.is_empty());
415
416        // Test map with single element
417        const SINGLE_ENTRIES: &[(u32, &str)] = &[(123, "test")];
418        const SINGLE_MAP: SyscallMap = SyscallMap::from_entries(SINGLE_ENTRIES);
419        assert_eq!(SINGLE_MAP.len(), 1);
420        assert!(!SINGLE_MAP.is_empty());
421    }
422
423    #[test]
424    fn test_dynamic_map_len_and_is_empty() {
425        // Test empty dynamic map
426        let empty_map = DynamicSyscallMap::new(vec![]).unwrap();
427        assert_eq!(empty_map.len(), 0);
428        assert!(empty_map.is_empty());
429
430        // Test non-empty dynamic map
431        let map = DynamicSyscallMap::from_names(&["test"]).unwrap();
432        assert_eq!(map.len(), 1);
433        assert!(!map.is_empty());
434    }
435
436    #[test]
437    fn test_dynamic_map_add_duplicate() {
438        let mut map = DynamicSyscallMap::from_names(&["existing"]).unwrap();
439        let result = map.add("existing".to_string());
440        assert!(result.is_err());
441    }
442
443    #[test]
444    fn test_dynamic_map_hash_conflict_in_creation() {
445        let syscalls = vec![String::from("test"), String::from("test")];
446        let result = DynamicSyscallMap::new(syscalls);
447        // Should error due to duplicate hash
448        assert!(result.is_err());
449        if let Err(msg) = result {
450            assert!(msg.contains("Hash conflict"));
451            assert!(msg.contains("test"));
452        }
453    }
454}