1use core::ffi::c_void;
20use core::ptr;
21use std::os::raw::{c_char, c_int};
22
23use crate::abi::allocator;
24use crate::abi::callbacks::xmlHashCopier;
25use crate::abi::callbacks::xmlHashDeallocator;
26use crate::abi::callbacks::xmlHashScanner;
27use crate::abi::callbacks::xmlHashScannerFull;
28
29use crate::abi::types::xmlChar;
30
31const HASH_INIT_SIZE: usize = 16;
37
38const MAX_LOAD_FACTOR: f64 = 0.75;
40
41struct HashEntry {
47 key1: *const xmlChar,
49 key2: *const xmlChar,
50 key3: *const xmlChar,
51 payload: *mut c_void,
53}
54
55pub struct HashTable {
57 buckets: Vec<Vec<HashEntry>>,
59 count: usize,
61 deallocator: Option<xmlHashDeallocator>,
63 copier: Option<xmlHashCopier>,
65 dict_owned: bool,
67}
68
69fn hash_xml_str(s: *const xmlChar) -> u64 {
75 if s.is_null() {
76 return 0;
77 }
78 let mut hash: u64 = 0xcbf29ce484222325;
79 let mut i = 0;
80 loop {
81 let c = unsafe { *s.add(i) };
83 if c == 0 {
84 break;
85 }
86 hash ^= c as u64;
87 hash = hash.wrapping_mul(0x100000001b3);
88 i += 1;
89 }
90 hash
91}
92
93fn combined_hash(key1: *const xmlChar, key2: *const xmlChar, key3: *const xmlChar) -> u64 {
95 let mut h = hash_xml_str(key1);
96 h = h.wrapping_mul(31).wrapping_add(hash_xml_str(key2));
97 h = h.wrapping_mul(31).wrapping_add(hash_xml_str(key3));
98 h
99}
100
101pub fn hash_create(size: c_int) -> *mut HashTable {
116 let table = Box::new(HashTable {
117 buckets: (0..if size <= 0 {
118 HASH_INIT_SIZE
119 } else {
120 size as usize
121 })
122 .map(|_| Vec::new())
123 .collect(),
124 count: 0,
125 deallocator: None,
126 copier: None,
127 dict_owned: false,
128 });
129
130 Box::into_raw(table)
131}
132
133pub fn hash_create_dict(size: c_int, _dict: *mut c_void) -> *mut HashTable {
145 let table = hash_create(size);
146 if !table.is_null() {
147 unsafe { (*table).dict_owned = true };
148 }
149 table
150}
151
152pub unsafe fn hash_free(table: *mut HashTable, f: Option<xmlHashDeallocator>) {
167 if table.is_null() {
168 return;
169 }
170
171 let t = unsafe { &mut *table };
172
173 for bucket in t.buckets.iter_mut() {
174 for entry in bucket.drain(..) {
175 if let Some(dealloc) = f {
176 dealloc(entry.payload, entry.key1 as *mut u8);
177 }
178 if !t.dict_owned {
179 if !entry.key1.is_null() {
180 allocator::xmlFree(entry.key1 as *mut c_void);
181 }
182 if !entry.key2.is_null() {
183 allocator::xmlFree(entry.key2 as *mut c_void);
184 }
185 if !entry.key3.is_null() {
186 allocator::xmlFree(entry.key3 as *mut c_void);
187 }
188 }
189 }
190 }
191
192 drop(Box::from_raw(table));
193}
194
195pub unsafe fn hash_add_entry(
210 table: *mut HashTable,
211 name: *const xmlChar,
212 userdata: *mut c_void,
213) -> c_int {
214 hash_add_entry3(table, name, ptr::null(), ptr::null(), userdata)
215}
216
217pub unsafe fn hash_add_entry2(
230 table: *mut HashTable,
231 name: *const xmlChar,
232 name2: *const xmlChar,
233 userdata: *mut c_void,
234) -> c_int {
235 hash_add_entry3(table, name, name2, ptr::null(), userdata)
236}
237
238pub unsafe fn hash_add_entry3(
255 table: *mut HashTable,
256 name: *const xmlChar,
257 name2: *const xmlChar,
258 name3: *const xmlChar,
259 userdata: *mut c_void,
260) -> c_int {
261 if table.is_null() {
262 return -1;
263 }
264
265 let t = unsafe { &mut *table };
266
267 if !hash_find_entry(table, name, name2, name3).is_null() {
269 return -1;
270 }
271
272 let hash = combined_hash(name, name2, name3);
273 let bucket_idx = (hash as usize) % t.buckets.len();
274
275 let k1 = if name.is_null() {
277 ptr::null()
278 } else {
279 unsafe { allocator::xmlMemStrdup(name as *const c_char) as *const xmlChar }
280 };
281 let k2 = if name2.is_null() {
282 ptr::null()
283 } else {
284 unsafe { allocator::xmlMemStrdup(name2 as *const c_char) as *const xmlChar }
285 };
286 let k3 = if name3.is_null() {
287 ptr::null()
288 } else {
289 unsafe { allocator::xmlMemStrdup(name3 as *const c_char) as *const xmlChar }
290 };
291
292 t.buckets[bucket_idx].push(HashEntry {
293 key1: k1,
294 key2: k2,
295 key3: k3,
296 payload: userdata,
297 });
298 t.count += 1;
299
300 0
301}
302
303pub unsafe fn hash_update_entry(
321 table: *mut HashTable,
322 name: *const xmlChar,
323 userdata: *mut c_void,
324 f: Option<xmlHashDeallocator>,
325) -> c_int {
326 hash_update_entry3(table, name, ptr::null(), ptr::null(), userdata, f)
327}
328
329pub unsafe fn hash_update_entry2(
331 table: *mut HashTable,
332 name: *const xmlChar,
333 name2: *const xmlChar,
334 userdata: *mut c_void,
335 f: Option<xmlHashDeallocator>,
336) -> c_int {
337 hash_update_entry3(table, name, name2, ptr::null(), userdata, f)
338}
339
340pub unsafe fn hash_update_entry3(
354 table: *mut HashTable,
355 name: *const xmlChar,
356 name2: *const xmlChar,
357 name3: *const xmlChar,
358 userdata: *mut c_void,
359 f: Option<xmlHashDeallocator>,
360) -> c_int {
361 if table.is_null() {
362 return -1;
363 }
364
365 let t = unsafe { &mut *table };
366
367 let hash = combined_hash(name, name2, name3);
368 let bucket_idx = (hash as usize) % t.buckets.len();
369
370 for entry in t.buckets[bucket_idx].iter_mut() {
372 if keys_equal(entry.key1, name)
373 && keys_equal(entry.key2, name2)
374 && keys_equal(entry.key3, name3)
375 {
376 if let Some(dealloc) = f {
378 dealloc(entry.payload, entry.key1 as *mut u8);
379 }
380 entry.payload = userdata;
381 return 0;
382 }
383 }
384
385 hash_add_entry3(table, name, name2, name3, userdata)
387}
388
389pub unsafe fn hash_lookup(table: *mut HashTable, name: *const xmlChar) -> *mut c_void {
404 hash_lookup3(table, name, ptr::null(), ptr::null())
405}
406
407pub unsafe fn hash_lookup2(
409 table: *mut HashTable,
410 name: *const xmlChar,
411 name2: *const xmlChar,
412) -> *mut c_void {
413 hash_lookup3(table, name, name2, ptr::null())
414}
415
416pub unsafe fn hash_lookup3(
432 table: *mut HashTable,
433 name: *const xmlChar,
434 name2: *const xmlChar,
435 name3: *const xmlChar,
436) -> *mut c_void {
437 hash_find_entry(table, name, name2, name3)
438}
439
440unsafe fn hash_find_entry(
442 table: *mut HashTable,
443 name: *const xmlChar,
444 name2: *const xmlChar,
445 name3: *const xmlChar,
446) -> *mut c_void {
447 if table.is_null() {
448 return ptr::null_mut();
449 }
450
451 let t = unsafe { &*table };
452
453 let hash = combined_hash(name, name2, name3);
454 let bucket_idx = (hash as usize) % t.buckets.len();
455
456 for entry in &t.buckets[bucket_idx] {
457 if keys_equal(entry.key1, name)
458 && keys_equal(entry.key2, name2)
459 && keys_equal(entry.key3, name3)
460 {
461 return entry.payload;
462 }
463 }
464
465 ptr::null_mut()
466}
467
468pub fn hash_size(table: *mut HashTable) -> c_int {
476 if table.is_null() {
477 return -1;
478 }
479 unsafe { (*table).count as c_int }
480}
481
482pub unsafe fn hash_remove_entry(
499 table: *mut HashTable,
500 name: *const xmlChar,
501 f: Option<xmlHashDeallocator>,
502) -> c_int {
503 hash_remove_entry3(table, name, ptr::null(), ptr::null(), f)
504}
505
506pub unsafe fn hash_remove_entry2(
508 table: *mut HashTable,
509 name: *const xmlChar,
510 name2: *const xmlChar,
511 f: Option<xmlHashDeallocator>,
512) -> c_int {
513 hash_remove_entry3(table, name, name2, ptr::null(), f)
514}
515
516pub unsafe fn hash_remove_entry3(
534 table: *mut HashTable,
535 name: *const xmlChar,
536 name2: *const xmlChar,
537 name3: *const xmlChar,
538 f: Option<xmlHashDeallocator>,
539) -> c_int {
540 if table.is_null() {
541 return -1;
542 }
543
544 let t = unsafe { &mut *table };
545
546 let hash = combined_hash(name, name2, name3);
547 let bucket_idx = (hash as usize) % t.buckets.len();
548
549 let bucket = &mut t.buckets[bucket_idx];
550 let pos = bucket.iter().position(|entry| {
551 keys_equal(entry.key1, name)
552 && keys_equal(entry.key2, name2)
553 && keys_equal(entry.key3, name3)
554 });
555
556 if let Some(idx) = pos {
557 let entry = bucket.remove(idx);
558 if let Some(dealloc) = f {
559 dealloc(entry.payload, entry.key1 as *mut u8);
560 }
561 if !t.dict_owned {
562 if !entry.key1.is_null() {
563 allocator::xmlFree(entry.key1 as *mut c_void);
564 }
565 if !entry.key2.is_null() {
566 allocator::xmlFree(entry.key2 as *mut c_void);
567 }
568 if !entry.key3.is_null() {
569 allocator::xmlFree(entry.key3 as *mut c_void);
570 }
571 }
572 t.count -= 1;
573 0
574 } else {
575 -1
576 }
577}
578
579pub unsafe fn hash_scan(table: *mut HashTable, f: Option<xmlHashScanner>, data: *mut c_void) {
593 if table.is_null() || f.is_none() {
594 return;
595 }
596 let f = f.unwrap();
597
598 let t = unsafe { &*table };
599 for bucket in &t.buckets {
600 for entry in bucket {
601 f(entry.payload, data, entry.key1 as *const xmlChar);
602 }
603 }
604}
605
606pub unsafe fn hash_scan_full(
620 table: *mut HashTable,
621 f: Option<xmlHashScannerFull>,
622 data: *mut c_void,
623) {
624 if table.is_null() || f.is_none() {
625 return;
626 }
627 let f = f.unwrap();
628
629 let t = unsafe { &*table };
630 for bucket in &t.buckets {
631 for entry in bucket {
632 f(entry.payload, data, entry.key1, entry.key2, entry.key3);
633 }
634 }
635}
636
637pub unsafe fn hash_copy(table: *mut HashTable, f: Option<xmlHashCopier>) -> *mut HashTable {
655 if table.is_null() {
656 return ptr::null_mut();
657 }
658
659 let t = unsafe { &*table };
660 let new_table = hash_create(t.buckets.len() as c_int);
661 if new_table.is_null() {
662 return ptr::null_mut();
663 }
664
665 let new_t = unsafe { &mut *new_table };
666 new_t.deallocator = t.deallocator;
667 new_t.copier = t.copier;
668 new_t.dict_owned = t.dict_owned;
669
670 for bucket in &t.buckets {
671 for entry in bucket {
672 let payload = match f {
673 Some(copier) => copier(entry.payload, entry.key1 as *const xmlChar),
674 None => entry.payload,
675 };
676
677 let k1 = if entry.key1.is_null() {
678 ptr::null()
679 } else {
680 allocator::xmlMemStrdup(entry.key1 as *const c_char) as *const xmlChar
681 };
682 let k2 = if entry.key2.is_null() {
683 ptr::null()
684 } else {
685 allocator::xmlMemStrdup(entry.key2 as *const c_char) as *const xmlChar
686 };
687 let k3 = if entry.key3.is_null() {
688 ptr::null()
689 } else {
690 allocator::xmlMemStrdup(entry.key3 as *const c_char) as *const xmlChar
691 };
692
693 let hash = combined_hash(k1, k2, k3);
694 let bucket_idx = (hash as usize) % new_t.buckets.len();
695
696 new_t.buckets[bucket_idx].push(HashEntry {
697 key1: k1,
698 key2: k2,
699 key3: k3,
700 payload,
701 });
702 new_t.count += 1;
703 }
704 }
705
706 new_table
707}
708
709fn keys_equal(a: *const xmlChar, b: *const xmlChar) -> bool {
715 if a.is_null() && b.is_null() {
716 return true;
717 }
718 if a.is_null() || b.is_null() {
719 return false;
720 }
721 let mut i = 0;
723 loop {
724 let ca = unsafe { *a.add(i) };
726 let cb = unsafe { *b.add(i) };
727 if ca != cb {
728 return false;
729 }
730 if ca == 0 {
731 return true;
732 }
733 i += 1;
734 }
735}
736
737#[cfg(test)]
742mod tests {
743 use super::*;
744
745 fn c_str(s: &str) -> *const xmlChar {
746 let bytes = s.as_bytes();
747 let buf = unsafe { allocator::xmlMalloc(bytes.len() + 1) as *mut u8 };
748 if !buf.is_null() {
749 unsafe {
750 ptr::copy_nonoverlapping(bytes.as_ptr(), buf, bytes.len());
751 *buf.add(bytes.len()) = 0;
752 }
753 }
754 buf as *const xmlChar
755 }
756
757 #[test]
758 fn test_hash_create_free() {
759 unsafe {
760 let table = hash_create(16);
761 assert!(!table.is_null());
762 hash_free(table, None);
763 }
764 }
765
766 #[test]
767 fn test_hash_add_lookup() {
768 unsafe {
769 let table = hash_create(16);
770 let key = c_str("key1");
771 let value = &mut 42 as *mut c_int as *mut c_void;
772
773 let result = hash_add_entry(table, key, value);
774 assert_eq!(result, 0);
775
776 let found = hash_lookup(table, key);
777 assert_eq!(found, value);
778 assert_eq!(*(found as *mut c_int), 42);
779
780 let result2 = hash_add_entry(table, key, &mut 99 as *mut c_int as *mut c_void);
782 assert_eq!(result2, -1);
783
784 hash_free(table, None);
785 }
786 }
787
788 #[test]
789 fn test_hash_lookup_not_found() {
790 unsafe {
791 let table = hash_create(16);
792 let found = hash_lookup(table, c_str("nonexistent"));
793 assert!(found.is_null());
794 hash_free(table, None);
795 }
796 }
797
798 #[test]
799 fn test_hash_remove_entry() {
800 unsafe {
801 let table = hash_create(16);
802 let key = c_str("remove_me");
803 let value = &mut 42 as *mut c_int as *mut c_void;
804
805 hash_add_entry(table, key, value);
806 assert_eq!(hash_size(table), 1);
807
808 let result = hash_remove_entry(table, key, None);
809 assert_eq!(result, 0);
810 assert_eq!(hash_size(table), 0);
811
812 let result2 = hash_remove_entry(table, key, None);
814 assert_eq!(result2, -1);
815
816 hash_free(table, None);
817 }
818 }
819
820 #[test]
821 fn test_hash_update_entry() {
822 unsafe {
823 let table = hash_create(16);
824 let key = c_str("update_key");
825 let val1 = &mut 1 as *mut c_int as *mut c_void;
826 let val2 = &mut 2 as *mut c_int as *mut c_void;
827
828 hash_update_entry(table, key, val1, None);
829 assert_eq!(hash_lookup(table, key), val1);
830
831 hash_update_entry(table, key, val2, None);
832 assert_eq!(hash_lookup(table, key), val2);
833
834 hash_free(table, None);
835 }
836 }
837
838 #[test]
839 fn test_hash_two_key_lookup() {
840 unsafe {
841 let table = hash_create(16);
842 let key1 = c_str("ns");
843 let key2 = c_str("local");
844 let value = &mut 42 as *mut c_int as *mut c_void;
845
846 hash_add_entry2(table, key1, key2, value);
847 let found = hash_lookup2(table, key1, key2);
848 assert_eq!(found, value);
849
850 let not_found = hash_lookup2(table, key1, c_str("wrong"));
852 assert!(not_found.is_null());
853
854 hash_free(table, None);
855 }
856 }
857
858 #[test]
859 fn test_hash_three_key_lookup() {
860 unsafe {
861 let table = hash_create(16);
862 let k1 = c_str("a");
863 let k2 = c_str("b");
864 let k3 = c_str("c");
865 let value = &mut 42 as *mut c_int as *mut c_void;
866
867 hash_add_entry3(table, k1, k2, k3, value);
868 let found = hash_lookup3(table, k1, k2, k3);
869 assert_eq!(found, value);
870
871 hash_free(table, None);
872 }
873 }
874
875 #[test]
876 fn test_hash_size() {
877 unsafe {
878 let table = hash_create(16);
879 assert_eq!(hash_size(table), 0);
880
881 hash_add_entry(table, c_str("a"), ptr::null_mut());
882 assert_eq!(hash_size(table), 1);
883
884 hash_add_entry(table, c_str("b"), ptr::null_mut());
885 assert_eq!(hash_size(table), 2);
886
887 hash_add_entry(table, c_str("c"), ptr::null_mut());
888 assert_eq!(hash_size(table), 3);
889
890 hash_free(table, None);
891 }
892 }
893
894 #[test]
895 fn test_hash_null_handling() {
896 unsafe {
897 assert!(hash_lookup(ptr::null_mut(), ptr::null()).is_null());
898 assert_eq!(hash_size(ptr::null_mut()), -1);
899 hash_free(ptr::null_mut(), None); }
901 }
902}