use core::ffi::c_void;
use core::hash::Hasher;
use core::num::NonZeroUsize;
use core::ptr;
use std::os::raw::c_int;
use crate::abi::allocator;
use crate::abi::types::xmlChar;
const DICT_INIT_SIZE: usize = 64;
const MAX_LOAD_NUM: usize = 3;
const MAX_LOAD_DEN: usize = 4;
struct DictEntry {
ref_count: usize,
data: *mut u8,
len: usize,
hash: u64,
}
struct SimpleHasher(u64);
impl Hasher for SimpleHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 ^= b as u64;
self.0 = self.0.wrapping_mul(0x100000001b3);
}
}
}
type DictTable = hashbrown::HashTable<(u64, usize)>;
pub struct Dict {
table: DictTable,
entries: Vec<DictEntry>,
active_count: usize,
limit: usize,
parent: Option<DictRef>,
is_sub: bool,
opaque_id: usize,
}
#[derive(Clone)]
struct DictRef {
ptr: *mut Dict,
}
unsafe impl Send for DictRef {}
unsafe impl Sync for DictRef {}
static NEXT_DICT_ID: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(1);
fn next_dict_id() -> usize {
NEXT_DICT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
}
fn fnv1a_hash(data: &[u8]) -> u64 {
let mut hasher = SimpleHasher(0xcbf29ce484222325);
hasher.write(data);
hasher.finish()
}
fn hash_xml_str(s: *const xmlChar, len: usize) -> u64 {
if s.is_null() {
return 0;
}
let slice = unsafe { core::slice::from_raw_parts(s, len) };
fnv1a_hash(slice)
}
pub fn dict_create() -> *mut Dict {
let dict = Box::new(Dict {
table: DictTable::new(),
entries: Vec::with_capacity(DICT_INIT_SIZE),
active_count: 0,
limit: 0,
parent: None,
is_sub: false,
opaque_id: next_dict_id(),
});
Box::into_raw(dict)
}
pub unsafe fn dict_create_sub(parent: *mut Dict) -> *mut Dict {
if parent.is_null() {
return dict_create();
}
let sub = Box::new(Dict {
table: DictTable::new(),
entries: Vec::with_capacity(DICT_INIT_SIZE),
active_count: 0,
limit: 0,
parent: Some(DictRef { ptr: parent }),
is_sub: true,
opaque_id: next_dict_id(),
});
Box::into_raw(sub)
}
pub unsafe fn dict_lookup(dict: *mut Dict, name: *const xmlChar, len: c_int) -> *const xmlChar {
if dict.is_null() || name.is_null() {
return ptr::null();
}
let dict_ref = unsafe { &mut *dict };
let s_len = if len < 0 {
unsafe { crate::abi::exports_xml2::xmlStrlen(name) as usize }
} else {
len as usize
};
if s_len == 0 {
return ptr::null();
}
let hash = hash_xml_str(name, s_len);
if let Some(found) = dict_ref.find_entry(hash, name, s_len) {
let entry = &dict_ref.entries[found];
return entry.data as *const xmlChar;
}
if let Some(ref parent_ref) = dict_ref.parent {
let parent = unsafe { &*parent_ref.ptr };
if let Some(found) = parent.find_entry(hash, name, s_len) {
let entry = &parent.entries[found];
let entry_ref = &parent.entries[found];
let entry_ptr = &parent.entries[found] as *const DictEntry as *mut DictEntry;
unsafe {
(*entry_ptr).ref_count += 1;
}
return entry.data as *const xmlChar;
}
}
if dict_ref.limit > 0 && dict_ref.active_count >= dict_ref.limit {
return ptr::null();
}
let data_copy = unsafe { allocator::xmlMalloc(s_len + 1) as *mut u8 };
if data_copy.is_null() {
return ptr::null();
}
unsafe {
ptr::copy_nonoverlapping(name as *const u8, data_copy, s_len);
*data_copy.add(s_len) = 0;
}
let entry_idx = dict_ref.entries.len();
dict_ref.entries.push(DictEntry {
ref_count: 1,
data: data_copy,
len: s_len,
hash,
});
dict_ref
.table
.insert_unique(hash, (hash, entry_idx), |(h, _)| *h);
dict_ref.active_count += 1;
data_copy as *const xmlChar
}
pub unsafe fn dict_exists(dict: *mut Dict, name: *const xmlChar, len: c_int) -> *const xmlChar {
if dict.is_null() || name.is_null() {
return ptr::null();
}
let dict_ref = unsafe { &*dict };
let s_len = if len < 0 {
unsafe { crate::abi::exports_xml2::xmlStrlen(name) as usize }
} else {
len as usize
};
if s_len == 0 {
return ptr::null();
}
let hash = hash_xml_str(name, s_len);
if let Some(found) = dict_ref.find_entry(hash, name, s_len) {
let entry = &dict_ref.entries[found];
return entry.data as *const xmlChar;
}
if let Some(ref parent_ref) = dict_ref.parent {
let parent = unsafe { &*parent_ref.ptr };
if let Some(found) = parent.find_entry(hash, name, s_len) {
let entry = &parent.entries[found];
return entry.data as *const xmlChar;
}
}
ptr::null()
}
pub fn dict_size(dict: *const Dict) -> c_int {
if dict.is_null() {
return -1;
}
let dict_ref = unsafe { &*dict };
dict_ref.active_count as c_int
}
pub unsafe fn dict_free(dict: *mut Dict) {
if dict.is_null() {
return;
}
let dict_ref = unsafe { &mut *dict };
if dict_ref.is_sub {
} else {
for entry in dict_ref.entries.iter() {
if !entry.data.is_null() && entry.ref_count > 0 {
allocator::xmlFree(entry.data as *mut c_void);
}
}
}
drop(Box::from_raw(dict));
}
pub fn dict_set_limit(dict: *mut Dict, limit: usize) -> usize {
if dict.is_null() {
return 0;
}
let dict_ref = unsafe { &mut *dict };
let prev = dict_ref.limit;
dict_ref.limit = limit;
prev
}
pub fn dict_get_usage(dict: *mut Dict) -> usize {
if dict.is_null() {
return 0;
}
let dict_ref = unsafe { &*dict };
dict_ref.active_count
}
impl Dict {
fn find_entry(&self, hash: u64, name: *const xmlChar, len: usize) -> Option<usize> {
let name_slice = unsafe { core::slice::from_raw_parts(name as *const u8, len) };
self.table
.find(hash, |(entry_hash, entry_idx)| {
if *entry_hash != hash {
return false;
}
if *entry_idx >= self.entries.len() {
return false;
}
let entry = &self.entries[*entry_idx];
if entry.len != len {
return false;
}
if entry.data.is_null() {
return false;
}
let entry_slice = unsafe { core::slice::from_raw_parts(entry.data, entry.len) };
entry_slice == name_slice
})
.map(|entry| entry.1)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn xml_str(s: &str) -> *const xmlChar {
let bytes = s.as_bytes();
let buf = unsafe { allocator::xmlMalloc(bytes.len() + 1) } as *mut u8;
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
*buf.add(bytes.len()) = 0;
}
buf as *const xmlChar
}
fn free_xml_str(s: *const xmlChar) {
if !s.is_null() {
unsafe { allocator::xmlFree(s as *mut c_void) };
}
}
#[test]
fn test_dict_create_free() {
unsafe {
let dict = dict_create();
assert!(!dict.is_null());
dict_free(dict);
}
}
#[test]
fn test_dict_lookup() {
unsafe {
let dict = dict_create();
let name = xml_str("hello");
let result = dict_lookup(dict, name, -1);
assert!(!result.is_null());
let result2 = dict_lookup(dict, name, -1);
assert_eq!(result, result2);
let name2 = xml_str("world");
let result3 = dict_lookup(dict, name2, -1);
assert!(!result3.is_null());
assert_ne!(result, result3);
free_xml_str(name);
free_xml_str(name2);
dict_free(dict);
}
}
#[test]
fn test_dict_exists() {
unsafe {
let dict = dict_create();
let name = xml_str("test_string");
let result = dict_exists(dict, name, -1);
assert!(result.is_null());
let added = dict_lookup(dict, name, -1);
assert!(!added.is_null());
let found = dict_exists(dict, name, -1);
assert!(!found.is_null());
assert_eq!(found, added);
free_xml_str(name);
dict_free(dict);
}
}
#[test]
fn test_dict_size() {
unsafe {
let dict = dict_create();
assert_eq!(dict_size(dict), 0);
let name1 = xml_str("a");
let name2 = xml_str("b");
let name3 = xml_str("c");
dict_lookup(dict, name1, -1);
assert_eq!(dict_size(dict), 1);
dict_lookup(dict, name2, -1);
assert_eq!(dict_size(dict), 2);
dict_lookup(dict, name3, -1);
assert_eq!(dict_size(dict), 3);
dict_lookup(dict, name1, -1);
assert_eq!(dict_size(dict), 3);
free_xml_str(name1);
free_xml_str(name2);
free_xml_str(name3);
dict_free(dict);
}
}
#[test]
fn test_dict_set_limit() {
unsafe {
let dict = dict_create();
assert_eq!(dict_set_limit(dict, 2), 0);
let name1 = xml_str("x");
let name2 = xml_str("y");
let name3 = xml_str("z");
let r1 = dict_lookup(dict, name1, -1);
assert!(!r1.is_null());
let r2 = dict_lookup(dict, name2, -1);
assert!(!r2.is_null());
let r3 = dict_lookup(dict, name3, -1);
assert!(r3.is_null());
assert_eq!(dict_get_usage(dict), 2);
free_xml_str(name1);
free_xml_str(name2);
free_xml_str(name3);
dict_free(dict);
}
}
#[test]
fn test_dict_create_sub() {
unsafe {
let parent = dict_create();
let name = xml_str("shared");
let r1 = dict_lookup(parent, name, -1);
assert!(!r1.is_null());
let sub = dict_create_sub(parent);
assert!(!sub.is_null());
let r2 = dict_lookup(sub, name, -1);
assert!(!r2.is_null());
assert_eq!(r1, r2);
dict_free(sub);
dict_free(parent);
free_xml_str(name);
}
}
#[test]
fn test_dict_null_handling() {
unsafe {
assert!(dict_lookup(ptr::null_mut(), ptr::null(), -1).is_null());
assert!(dict_exists(ptr::null_mut(), ptr::null(), -1).is_null());
assert_eq!(dict_size(ptr::null()), -1);
assert_eq!(dict_set_limit(ptr::null_mut(), 10), 0);
assert_eq!(dict_get_usage(ptr::null_mut()), 0);
dict_free(ptr::null_mut()); }
}
}