use core::ffi::c_void;
use core::ptr;
use std::os::raw::{c_char, c_int};
use crate::abi::allocator;
use crate::abi::callbacks::xmlHashCopier;
use crate::abi::callbacks::xmlHashDeallocator;
use crate::abi::callbacks::xmlHashScanner;
use crate::abi::callbacks::xmlHashScannerFull;
use crate::abi::types::xmlChar;
const HASH_INIT_SIZE: usize = 16;
const MAX_LOAD_FACTOR: f64 = 0.75;
struct HashEntry {
key1: *const xmlChar,
key2: *const xmlChar,
key3: *const xmlChar,
payload: *mut c_void,
}
pub struct HashTable {
buckets: Vec<Vec<HashEntry>>,
count: usize,
deallocator: Option<xmlHashDeallocator>,
copier: Option<xmlHashCopier>,
dict_owned: bool,
}
fn hash_xml_str(s: *const xmlChar) -> u64 {
if s.is_null() {
return 0;
}
let mut hash: u64 = 0xcbf29ce484222325;
let mut i = 0;
loop {
let c = unsafe { *s.add(i) };
if c == 0 {
break;
}
hash ^= c as u64;
hash = hash.wrapping_mul(0x100000001b3);
i += 1;
}
hash
}
fn combined_hash(key1: *const xmlChar, key2: *const xmlChar, key3: *const xmlChar) -> u64 {
let mut h = hash_xml_str(key1);
h = h.wrapping_mul(31).wrapping_add(hash_xml_str(key2));
h = h.wrapping_mul(31).wrapping_add(hash_xml_str(key3));
h
}
pub fn hash_create(size: c_int) -> *mut HashTable {
let table = Box::new(HashTable {
buckets: (0..if size <= 0 {
HASH_INIT_SIZE
} else {
size as usize
})
.map(|_| Vec::new())
.collect(),
count: 0,
deallocator: None,
copier: None,
dict_owned: false,
});
Box::into_raw(table)
}
pub fn hash_create_dict(size: c_int, _dict: *mut c_void) -> *mut HashTable {
let table = hash_create(size);
if !table.is_null() {
unsafe { (*table).dict_owned = true };
}
table
}
pub unsafe fn hash_free(table: *mut HashTable, f: Option<xmlHashDeallocator>) {
if table.is_null() {
return;
}
let t = unsafe { &mut *table };
for bucket in t.buckets.iter_mut() {
for entry in bucket.drain(..) {
if let Some(dealloc) = f {
dealloc(entry.payload, entry.key1 as *mut u8);
}
if !t.dict_owned {
if !entry.key1.is_null() {
allocator::xmlFree(entry.key1 as *mut c_void);
}
if !entry.key2.is_null() {
allocator::xmlFree(entry.key2 as *mut c_void);
}
if !entry.key3.is_null() {
allocator::xmlFree(entry.key3 as *mut c_void);
}
}
}
}
drop(Box::from_raw(table));
}
pub unsafe fn hash_add_entry(
table: *mut HashTable,
name: *const xmlChar,
userdata: *mut c_void,
) -> c_int {
hash_add_entry3(table, name, ptr::null(), ptr::null(), userdata)
}
pub unsafe fn hash_add_entry2(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
userdata: *mut c_void,
) -> c_int {
hash_add_entry3(table, name, name2, ptr::null(), userdata)
}
pub unsafe fn hash_add_entry3(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
name3: *const xmlChar,
userdata: *mut c_void,
) -> c_int {
if table.is_null() {
return -1;
}
let t = unsafe { &mut *table };
if !hash_find_entry(table, name, name2, name3).is_null() {
return -1;
}
let hash = combined_hash(name, name2, name3);
let bucket_idx = (hash as usize) % t.buckets.len();
let k1 = if name.is_null() {
ptr::null()
} else {
unsafe { allocator::xmlMemStrdup(name as *const c_char) as *const xmlChar }
};
let k2 = if name2.is_null() {
ptr::null()
} else {
unsafe { allocator::xmlMemStrdup(name2 as *const c_char) as *const xmlChar }
};
let k3 = if name3.is_null() {
ptr::null()
} else {
unsafe { allocator::xmlMemStrdup(name3 as *const c_char) as *const xmlChar }
};
t.buckets[bucket_idx].push(HashEntry {
key1: k1,
key2: k2,
key3: k3,
payload: userdata,
});
t.count += 1;
0
}
pub unsafe fn hash_update_entry(
table: *mut HashTable,
name: *const xmlChar,
userdata: *mut c_void,
f: Option<xmlHashDeallocator>,
) -> c_int {
hash_update_entry3(table, name, ptr::null(), ptr::null(), userdata, f)
}
pub unsafe fn hash_update_entry2(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
userdata: *mut c_void,
f: Option<xmlHashDeallocator>,
) -> c_int {
hash_update_entry3(table, name, name2, ptr::null(), userdata, f)
}
pub unsafe fn hash_update_entry3(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
name3: *const xmlChar,
userdata: *mut c_void,
f: Option<xmlHashDeallocator>,
) -> c_int {
if table.is_null() {
return -1;
}
let t = unsafe { &mut *table };
let hash = combined_hash(name, name2, name3);
let bucket_idx = (hash as usize) % t.buckets.len();
for entry in t.buckets[bucket_idx].iter_mut() {
if keys_equal(entry.key1, name)
&& keys_equal(entry.key2, name2)
&& keys_equal(entry.key3, name3)
{
if let Some(dealloc) = f {
dealloc(entry.payload, entry.key1 as *mut u8);
}
entry.payload = userdata;
return 0;
}
}
hash_add_entry3(table, name, name2, name3, userdata)
}
pub unsafe fn hash_lookup(table: *mut HashTable, name: *const xmlChar) -> *mut c_void {
hash_lookup3(table, name, ptr::null(), ptr::null())
}
pub unsafe fn hash_lookup2(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
) -> *mut c_void {
hash_lookup3(table, name, name2, ptr::null())
}
pub unsafe fn hash_lookup3(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
name3: *const xmlChar,
) -> *mut c_void {
hash_find_entry(table, name, name2, name3)
}
unsafe fn hash_find_entry(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
name3: *const xmlChar,
) -> *mut c_void {
if table.is_null() {
return ptr::null_mut();
}
let t = unsafe { &*table };
let hash = combined_hash(name, name2, name3);
let bucket_idx = (hash as usize) % t.buckets.len();
for entry in &t.buckets[bucket_idx] {
if keys_equal(entry.key1, name)
&& keys_equal(entry.key2, name2)
&& keys_equal(entry.key3, name3)
{
return entry.payload;
}
}
ptr::null_mut()
}
pub fn hash_size(table: *mut HashTable) -> c_int {
if table.is_null() {
return -1;
}
unsafe { (*table).count as c_int }
}
pub unsafe fn hash_remove_entry(
table: *mut HashTable,
name: *const xmlChar,
f: Option<xmlHashDeallocator>,
) -> c_int {
hash_remove_entry3(table, name, ptr::null(), ptr::null(), f)
}
pub unsafe fn hash_remove_entry2(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
f: Option<xmlHashDeallocator>,
) -> c_int {
hash_remove_entry3(table, name, name2, ptr::null(), f)
}
pub unsafe fn hash_remove_entry3(
table: *mut HashTable,
name: *const xmlChar,
name2: *const xmlChar,
name3: *const xmlChar,
f: Option<xmlHashDeallocator>,
) -> c_int {
if table.is_null() {
return -1;
}
let t = unsafe { &mut *table };
let hash = combined_hash(name, name2, name3);
let bucket_idx = (hash as usize) % t.buckets.len();
let bucket = &mut t.buckets[bucket_idx];
let pos = bucket.iter().position(|entry| {
keys_equal(entry.key1, name)
&& keys_equal(entry.key2, name2)
&& keys_equal(entry.key3, name3)
});
if let Some(idx) = pos {
let entry = bucket.remove(idx);
if let Some(dealloc) = f {
dealloc(entry.payload, entry.key1 as *mut u8);
}
if !t.dict_owned {
if !entry.key1.is_null() {
allocator::xmlFree(entry.key1 as *mut c_void);
}
if !entry.key2.is_null() {
allocator::xmlFree(entry.key2 as *mut c_void);
}
if !entry.key3.is_null() {
allocator::xmlFree(entry.key3 as *mut c_void);
}
}
t.count -= 1;
0
} else {
-1
}
}
pub unsafe fn hash_scan(table: *mut HashTable, f: Option<xmlHashScanner>, data: *mut c_void) {
if table.is_null() || f.is_none() {
return;
}
let f = f.unwrap();
let t = unsafe { &*table };
for bucket in &t.buckets {
for entry in bucket {
f(entry.payload, data, entry.key1 as *const xmlChar);
}
}
}
pub unsafe fn hash_scan_full(
table: *mut HashTable,
f: Option<xmlHashScannerFull>,
data: *mut c_void,
) {
if table.is_null() || f.is_none() {
return;
}
let f = f.unwrap();
let t = unsafe { &*table };
for bucket in &t.buckets {
for entry in bucket {
f(entry.payload, data, entry.key1, entry.key2, entry.key3);
}
}
}
pub unsafe fn hash_copy(table: *mut HashTable, f: Option<xmlHashCopier>) -> *mut HashTable {
if table.is_null() {
return ptr::null_mut();
}
let t = unsafe { &*table };
let new_table = hash_create(t.buckets.len() as c_int);
if new_table.is_null() {
return ptr::null_mut();
}
let new_t = unsafe { &mut *new_table };
new_t.deallocator = t.deallocator;
new_t.copier = t.copier;
new_t.dict_owned = t.dict_owned;
for bucket in &t.buckets {
for entry in bucket {
let payload = match f {
Some(copier) => copier(entry.payload, entry.key1 as *const xmlChar),
None => entry.payload,
};
let k1 = if entry.key1.is_null() {
ptr::null()
} else {
allocator::xmlMemStrdup(entry.key1 as *const c_char) as *const xmlChar
};
let k2 = if entry.key2.is_null() {
ptr::null()
} else {
allocator::xmlMemStrdup(entry.key2 as *const c_char) as *const xmlChar
};
let k3 = if entry.key3.is_null() {
ptr::null()
} else {
allocator::xmlMemStrdup(entry.key3 as *const c_char) as *const xmlChar
};
let hash = combined_hash(k1, k2, k3);
let bucket_idx = (hash as usize) % new_t.buckets.len();
new_t.buckets[bucket_idx].push(HashEntry {
key1: k1,
key2: k2,
key3: k3,
payload,
});
new_t.count += 1;
}
}
new_table
}
fn keys_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
if a.is_null() && b.is_null() {
return true;
}
if a.is_null() || b.is_null() {
return false;
}
let mut i = 0;
loop {
let ca = unsafe { *a.add(i) };
let cb = unsafe { *b.add(i) };
if ca != cb {
return false;
}
if ca == 0 {
return true;
}
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c_str(s: &str) -> *const xmlChar {
let bytes = s.as_bytes();
let buf = unsafe { allocator::xmlMalloc(bytes.len() + 1) as *mut u8 };
if !buf.is_null() {
unsafe {
ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
*buf.add(bytes.len()) = 0;
}
}
buf as *const xmlChar
}
#[test]
fn test_hash_create_free() {
unsafe {
let table = hash_create(16);
assert!(!table.is_null());
hash_free(table, None);
}
}
#[test]
fn test_hash_add_lookup() {
unsafe {
let table = hash_create(16);
let key = c_str("key1");
let value = &mut 42 as *mut c_int as *mut c_void;
let result = hash_add_entry(table, key, value);
assert_eq!(result, 0);
let found = hash_lookup(table, key);
assert_eq!(found, value);
assert_eq!(*(found as *mut c_int), 42);
let result2 = hash_add_entry(table, key, &mut 99 as *mut c_int as *mut c_void);
assert_eq!(result2, -1);
hash_free(table, None);
}
}
#[test]
fn test_hash_lookup_not_found() {
unsafe {
let table = hash_create(16);
let found = hash_lookup(table, c_str("nonexistent"));
assert!(found.is_null());
hash_free(table, None);
}
}
#[test]
fn test_hash_remove_entry() {
unsafe {
let table = hash_create(16);
let key = c_str("remove_me");
let value = &mut 42 as *mut c_int as *mut c_void;
hash_add_entry(table, key, value);
assert_eq!(hash_size(table), 1);
let result = hash_remove_entry(table, key, None);
assert_eq!(result, 0);
assert_eq!(hash_size(table), 0);
let result2 = hash_remove_entry(table, key, None);
assert_eq!(result2, -1);
hash_free(table, None);
}
}
#[test]
fn test_hash_update_entry() {
unsafe {
let table = hash_create(16);
let key = c_str("update_key");
let val1 = &mut 1 as *mut c_int as *mut c_void;
let val2 = &mut 2 as *mut c_int as *mut c_void;
hash_update_entry(table, key, val1, None);
assert_eq!(hash_lookup(table, key), val1);
hash_update_entry(table, key, val2, None);
assert_eq!(hash_lookup(table, key), val2);
hash_free(table, None);
}
}
#[test]
fn test_hash_two_key_lookup() {
unsafe {
let table = hash_create(16);
let key1 = c_str("ns");
let key2 = c_str("local");
let value = &mut 42 as *mut c_int as *mut c_void;
hash_add_entry2(table, key1, key2, value);
let found = hash_lookup2(table, key1, key2);
assert_eq!(found, value);
let not_found = hash_lookup2(table, key1, c_str("wrong"));
assert!(not_found.is_null());
hash_free(table, None);
}
}
#[test]
fn test_hash_three_key_lookup() {
unsafe {
let table = hash_create(16);
let k1 = c_str("a");
let k2 = c_str("b");
let k3 = c_str("c");
let value = &mut 42 as *mut c_int as *mut c_void;
hash_add_entry3(table, k1, k2, k3, value);
let found = hash_lookup3(table, k1, k2, k3);
assert_eq!(found, value);
hash_free(table, None);
}
}
#[test]
fn test_hash_size() {
unsafe {
let table = hash_create(16);
assert_eq!(hash_size(table), 0);
hash_add_entry(table, c_str("a"), ptr::null_mut());
assert_eq!(hash_size(table), 1);
hash_add_entry(table, c_str("b"), ptr::null_mut());
assert_eq!(hash_size(table), 2);
hash_add_entry(table, c_str("c"), ptr::null_mut());
assert_eq!(hash_size(table), 3);
hash_free(table, None);
}
}
#[test]
fn test_hash_null_handling() {
unsafe {
assert!(hash_lookup(ptr::null_mut(), ptr::null()).is_null());
assert_eq!(hash_size(ptr::null_mut()), -1);
hash_free(ptr::null_mut(), None); }
}
}