use crate::{SyscallMap, murmur3_32};
pub struct DynamicSyscallMap {
pub entries: Vec<(u32, String)>,
}
impl DynamicSyscallMap {
pub fn new(syscalls: Vec<String>) -> Result<Self, String> {
let mut entries: Vec<(u32, String)> = syscalls
.into_iter()
.map(|name| (murmur3_32(&name), name))
.collect();
entries.sort_by_key(|(hash, _)| *hash);
for i in 0..entries.len().saturating_sub(1) {
if entries[i].0 == entries[i + 1].0 {
return Err(format!(
"Hash conflict detected between syscalls '{}' and '{}'",
entries[i].1,
entries[i + 1].1
));
}
}
Ok(Self { entries })
}
pub fn from_names(names: &[&str]) -> Result<Self, String> {
Self::new(names.iter().map(|&s| s.to_string()).collect())
}
pub fn get(&self, hash: u32) -> Option<&str> {
match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
Ok(idx) => Some(&self.entries[idx].1),
Err(_) => None,
}
}
pub fn add(&mut self, name: String) -> Result<(), String> {
let hash = murmur3_32(&name);
match self.entries.binary_search_by_key(&hash, |(h, _)| *h) {
Ok(_) => Err(format!(
"Hash conflict: '{}' conflicts with existing syscall",
name
)),
Err(pos) => {
self.entries.insert(pos, (hash, name));
Ok(())
}
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl<'a> From<&SyscallMap<'a>> for DynamicSyscallMap {
fn from(static_map: &SyscallMap<'a>) -> Self {
let entries = static_map
.entries
.iter()
.map(|(hash, name)| (*hash, name.to_string()))
.collect();
Self { entries }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dynamic_mutable_map() {
let mut map = DynamicSyscallMap::from_names(&["initial_syscall"]).unwrap();
assert_eq!(
map.get(murmur3_32("initial_syscall")),
Some("initial_syscall")
);
map.add("runtime_syscall_1".to_string()).unwrap();
map.add("runtime_syscall_2".to_string()).unwrap();
assert_eq!(
map.get(murmur3_32("initial_syscall")),
Some("initial_syscall")
);
assert_eq!(
map.get(murmur3_32("runtime_syscall_1")),
Some("runtime_syscall_1")
);
assert_eq!(
map.get(murmur3_32("runtime_syscall_2")),
Some("runtime_syscall_2")
);
assert_eq!(map.get(0xDEADBEEF), None);
assert_eq!(map.len(), 3);
}
#[test]
fn test_dynamic_map_with_owned_strings() {
let syscalls = vec![
String::from("custom_1"),
String::from("custom_2"),
String::from("custom_3"),
];
let mut map = DynamicSyscallMap::new(syscalls).unwrap();
assert_eq!(map.get(murmur3_32("custom_1")), Some("custom_1"));
assert_eq!(map.get(murmur3_32("custom_2")), Some("custom_2"));
assert_eq!(map.get(murmur3_32("custom_3")), Some("custom_3"));
map.add("custom_4".to_string()).unwrap();
assert_eq!(map.get(murmur3_32("custom_4")), Some("custom_4"));
}
#[test]
fn test_convert_static_to_dynamic() {
use crate::compute_syscall_entries_const;
const TEST_SYSCALLS: &[&str; 3] = &["abort", "sol_log_", "sol_panic_"];
const TEST_ENTRIES: &[(u32, &str); 3] = &compute_syscall_entries_const(TEST_SYSCALLS);
const TEST_MAP: SyscallMap<'static> = SyscallMap::from_entries(TEST_ENTRIES);
let dynamic = DynamicSyscallMap::from(&TEST_MAP);
for &name in TEST_SYSCALLS.iter() {
let hash = murmur3_32(name);
assert_eq!(
dynamic.get(hash),
Some(name),
"Failed to find syscall: {}",
name
);
}
let mut dynamic_mut = dynamic;
dynamic_mut.add("my_custom_syscall".to_string()).unwrap();
assert_eq!(
dynamic_mut.get(murmur3_32("my_custom_syscall")),
Some("my_custom_syscall")
);
assert_eq!(dynamic_mut.get(murmur3_32("abort")), Some("abort"));
assert_eq!(dynamic_mut.len(), TEST_SYSCALLS.len() + 1);
}
#[test]
fn test_dynamic_map_add_duplicate() {
let mut map = DynamicSyscallMap::from_names(&["existing"]).unwrap();
let result = map.add("existing".to_string());
assert!(result.is_err());
assert!(result.unwrap_err().contains("Hash conflict"));
}
#[test]
fn test_dynamic_map_len_and_is_empty() {
let empty_map = DynamicSyscallMap::from_names(&[]).unwrap();
assert!(empty_map.is_empty());
assert_eq!(empty_map.len(), 0);
let map = DynamicSyscallMap::from_names(&["syscall1", "syscall2"]).unwrap();
assert!(!map.is_empty());
assert_eq!(map.len(), 2);
}
}