Skip to main content

syscall_map/
dynamic_map.rs

1use crate::{SyscallMap, murmur3_32};
2
3/// Runtime-mutable syscall map that owns its data
4/// This allows for dynamic updates at runtime
5pub struct DynamicSyscallMap {
6    // Store entries as (hash, name) pairs, kept sorted by hash
7    pub entries: Vec<(u32, String)>,
8}
9
10impl DynamicSyscallMap {
11    /// Create a new dynamic syscall map from owned strings
12    pub fn new(syscalls: Vec<String>) -> Result<Self, String> {
13        let mut entries: Vec<(u32, String)> = syscalls
14            .into_iter()
15            .map(|name| (murmur3_32(&name), name))
16            .collect();
17
18        entries.sort_by_key(|(hash, _)| *hash);
19
20        // Check for conflicts
21        for i in 0..entries.len().saturating_sub(1) {
22            if entries[i].0 == entries[i + 1].0 {
23                return Err(format!(
24                    "Hash conflict detected between syscalls '{}' and '{}'",
25                    entries[i].1,
26                    entries[i + 1].1
27                ));
28            }
29        }
30
31        Ok(Self { entries })
32    }
33
34    /// Create from string slices (convenience method)
35    pub fn from_names(names: &[&str]) -> Result<Self, String> {
36        Self::new(names.iter().map(|&s| s.to_string()).collect())
37    }
38
39    /// Look up a syscall by hash
40    pub fn get(&self, hash: u32) -> Option<&str> {
41        match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
42            Ok(idx) => Some(&self.entries[idx].1),
43            Err(_) => None,
44        }
45    }
46
47    /// Add a new syscall at runtime
48    pub fn add(&mut self, name: String) -> Result<(), String> {
49        let hash = murmur3_32(&name);
50
51        // Check if it already exists or would conflict
52        match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
53            Ok(_) => Err(format!(
54                "Hash conflict: '{}' conflicts with existing syscall",
55                name
56            )),
57            Err(pos) => {
58                self.entries.insert(pos, (hash, name));
59                Ok(())
60            }
61        }
62    }
63
64    pub fn len(&self) -> usize {
65        self.entries.len()
66    }
67
68    pub fn is_empty(&self) -> bool {
69        self.entries.is_empty()
70    }
71}
72
73/// Convert a static SyscallMap to a dynamic one
74impl<'a> From<&SyscallMap<'a>> for DynamicSyscallMap {
75    fn from(static_map: &SyscallMap<'a>) -> Self {
76        let entries = static_map
77            .entries
78            .iter()
79            .map(|(hash, name)| (*hash, name.to_string()))
80            .collect();
81
82        Self { entries }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_dynamic_mutable_map() {
92        // Example: Create a fully dynamic, mutable syscall map
93        let mut map = DynamicSyscallMap::from_names(&["initial_syscall"]).unwrap();
94
95        // Initial lookup works
96        assert_eq!(
97            map.get(murmur3_32("initial_syscall")),
98            Some("initial_syscall")
99        );
100
101        // Add new syscalls at runtime
102        map.add("runtime_syscall_1".to_string()).unwrap();
103        map.add("runtime_syscall_2".to_string()).unwrap();
104
105        // All lookups work
106        assert_eq!(
107            map.get(murmur3_32("initial_syscall")),
108            Some("initial_syscall")
109        );
110        assert_eq!(
111            map.get(murmur3_32("runtime_syscall_1")),
112            Some("runtime_syscall_1")
113        );
114        assert_eq!(
115            map.get(murmur3_32("runtime_syscall_2")),
116            Some("runtime_syscall_2")
117        );
118
119        // Non-existent syscall returns None
120        assert_eq!(map.get(0xDEADBEEF), None);
121
122        // Verify count
123        assert_eq!(map.len(), 3);
124    }
125
126    #[test]
127    fn test_dynamic_map_with_owned_strings() {
128        // Create from owned strings directly
129        let syscalls = vec![
130            String::from("custom_1"),
131            String::from("custom_2"),
132            String::from("custom_3"),
133        ];
134
135        let mut map = DynamicSyscallMap::new(syscalls).unwrap();
136
137        assert_eq!(map.get(murmur3_32("custom_1")), Some("custom_1"));
138        assert_eq!(map.get(murmur3_32("custom_2")), Some("custom_2"));
139        assert_eq!(map.get(murmur3_32("custom_3")), Some("custom_3"));
140
141        // Add more at runtime
142        map.add("custom_4".to_string()).unwrap();
143        assert_eq!(map.get(murmur3_32("custom_4")), Some("custom_4"));
144    }
145
146    #[test]
147    fn test_convert_static_to_dynamic() {
148        use crate::compute_syscall_entries_const;
149
150        // Create a test static map
151        const TEST_SYSCALLS: &[&str; 3] = &["abort", "sol_log_", "sol_panic_"];
152        const TEST_ENTRIES: &[(u32, &str); 3] = &compute_syscall_entries_const(TEST_SYSCALLS);
153        const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
154
155        // Convert to dynamic
156        let dynamic = DynamicSyscallMap::from(&TEST_MAP);
157
158        // Verify all static syscalls are present
159        for &name in TEST_SYSCALLS.iter() {
160            let hash = murmur3_32(name);
161            assert_eq!(
162                dynamic.get(hash),
163                Some(name),
164                "Failed to find syscall: {}",
165                name
166            );
167        }
168
169        // Verify we can add new syscalls to it
170        let mut dynamic_mut = dynamic;
171        dynamic_mut.add("my_custom_syscall".to_string()).unwrap();
172
173        assert_eq!(
174            dynamic_mut.get(murmur3_32("my_custom_syscall")),
175            Some("my_custom_syscall")
176        );
177
178        // Original static syscalls still work
179        assert_eq!(dynamic_mut.get(murmur3_32("abort")), Some("abort"));
180
181        // Count should be original + 1
182        assert_eq!(dynamic_mut.len(), TEST_SYSCALLS.len() + 1);
183    }
184
185    #[test]
186    fn test_dynamic_map_add_duplicate() {
187        let mut map = DynamicSyscallMap::from_names(&["existing"]).unwrap();
188
189        // Try to add the same syscall again
190        let result = map.add("existing".to_string());
191        assert!(result.is_err());
192        assert!(result.unwrap_err().contains("Hash conflict"));
193    }
194
195    #[test]
196    fn test_dynamic_map_len_and_is_empty() {
197        let empty_map = DynamicSyscallMap::from_names(&[]).unwrap();
198        assert!(empty_map.is_empty());
199        assert_eq!(empty_map.len(), 0);
200
201        let map = DynamicSyscallMap::from_names(&["syscall1", "syscall2"]).unwrap();
202        assert!(!map.is_empty());
203        assert_eq!(map.len(), 2);
204    }
205}