FreeRDP
Loading...
Searching...
No Matches
HashTable.c
1
20#include <winpr/config.h>
21
22#include <winpr/crt.h>
23#include <winpr/assert.h>
24
25#include <winpr/collections.h>
26
35typedef struct s_wKeyValuePair wKeyValuePair;
36
37struct s_wKeyValuePair
38{
39 void* key;
40 void* value;
41
42 wKeyValuePair* next;
43 BOOL markedForRemove;
44};
45
46struct s_wHashTable
47{
48 BOOL synchronized;
50
51 size_t numOfBuckets;
52 size_t numOfElements;
53 float idealRatio;
54 float lowerRehashThreshold;
55 float upperRehashThreshold;
56 wKeyValuePair** bucketArray;
57
58 HASH_TABLE_HASH_FN hash;
59 wObject key;
60 wObject value;
61
62 DWORD foreachRecursionLevel;
63 DWORD pendingRemoves;
64};
65
66BOOL HashTable_PointerCompare(const void* pointer1, const void* pointer2)
67{
68 return (pointer1 == pointer2);
69}
70
71UINT32 HashTable_PointerHash(const void* pointer)
72{
73 return ((UINT32)(UINT_PTR)pointer) >> 4;
74}
75
76BOOL HashTable_StringCompare(const void* string1, const void* string2)
77{
78 if (!string1 || !string2)
79 return (string1 == string2);
80
81 return (strcmp((const char*)string1, (const char*)string2) == 0);
82}
83
84UINT32 HashTable_StringHash(const void* key)
85{
86 UINT32 c = 0;
87 UINT32 hash = 5381;
88 const BYTE* str = (const BYTE*)key;
89
90 /* djb2 algorithm */
91 while ((c = *str++) != '\0')
92 hash = (hash * 33) + c;
93
94 return hash;
95}
96
97void* HashTable_StringClone(const void* str)
98{
99 return winpr_ObjectStringClone(str);
100}
101
102void HashTable_StringFree(void* str)
103{
104 winpr_ObjectStringFree(str);
105}
106
107WINPR_ATTR_NODISCARD
108static inline BOOL HashTable_IsProbablePrime(size_t oddNumber)
109{
110 for (size_t i = 3; i < 51; i += 2)
111 {
112 if (oddNumber == i)
113 return TRUE;
114 else if (oddNumber % i == 0)
115 return FALSE;
116 }
117
118 return TRUE; /* maybe */
119}
120
121WINPR_ATTR_NODISCARD
122static inline size_t HashTable_CalculateIdealNumOfBuckets(wHashTable* table)
123{
124 WINPR_ASSERT(table);
125
126 const float numOfElements = (float)table->numOfElements;
127 const float tmp = (numOfElements / table->idealRatio);
128 size_t idealNumOfBuckets = (size_t)tmp;
129
130 if (idealNumOfBuckets < 5)
131 idealNumOfBuckets = 5;
132 else
133 idealNumOfBuckets |= 0x01;
134
135 while (!HashTable_IsProbablePrime(idealNumOfBuckets))
136 idealNumOfBuckets += 2;
137
138 return idealNumOfBuckets;
139}
140
141static inline void HashTable_Rehash(wHashTable* table, size_t numOfBuckets)
142{
143 UINT32 hashValue = 0;
144 wKeyValuePair* nextPair = nullptr;
145 wKeyValuePair** newBucketArray = nullptr;
146
147 WINPR_ASSERT(table);
148 if (numOfBuckets == 0)
149 numOfBuckets = HashTable_CalculateIdealNumOfBuckets(table);
150
151 if (numOfBuckets == table->numOfBuckets)
152 return; /* already the right size! */
153
154 newBucketArray = (wKeyValuePair**)calloc(numOfBuckets, sizeof(wKeyValuePair*));
155
156 if (!newBucketArray)
157 {
158 /*
159 * Couldn't allocate memory for the new array.
160 * This isn't a fatal error; we just can't perform the rehash.
161 */
162 return;
163 }
164
165 for (size_t index = 0; index < table->numOfBuckets; index++)
166 {
167 wKeyValuePair* pair = table->bucketArray[index];
168
169 while (pair)
170 {
171 nextPair = pair->next;
172 hashValue = table->hash(pair->key) % numOfBuckets;
173 pair->next = newBucketArray[hashValue];
174 newBucketArray[hashValue] = pair;
175 pair = nextPair;
176 }
177 }
178
179 free((void*)table->bucketArray);
180 table->bucketArray = newBucketArray;
181 table->numOfBuckets = numOfBuckets;
182}
183
184static inline BOOL HashTable_Equals(wHashTable* table, const wKeyValuePair* pair, const void* key)
185{
186 WINPR_ASSERT(table);
187 WINPR_ASSERT(pair);
188 WINPR_ASSERT(key);
189 return table->key.fnObjectEquals(key, pair->key);
190}
191
192static inline wKeyValuePair* HashTable_Get(wHashTable* table, const void* key)
193{
194 UINT32 hashValue = 0;
195 wKeyValuePair* pair = nullptr;
196
197 WINPR_ASSERT(table);
198 if (!key)
199 return nullptr;
200
201 hashValue = table->hash(key) % table->numOfBuckets;
202 pair = table->bucketArray[hashValue];
203
204 while (pair && !HashTable_Equals(table, pair, key))
205 pair = pair->next;
206
207 return pair;
208}
209
210static inline void disposeKey(wHashTable* table, void* key)
211{
212 WINPR_ASSERT(table);
213 if (table->key.fnObjectFree)
214 table->key.fnObjectFree(key);
215}
216
217static inline void disposeValue(wHashTable* table, void* value)
218{
219 WINPR_ASSERT(table);
220 if (table->value.fnObjectFree)
221 table->value.fnObjectFree(value);
222}
223
224static inline void disposePair(wHashTable* table, wKeyValuePair* pair)
225{
226 WINPR_ASSERT(table);
227 if (!pair)
228 return;
229 disposeKey(table, pair->key);
230 disposeValue(table, pair->value);
231 free(pair);
232}
233
234static inline void setKey(wHashTable* table, wKeyValuePair* pair, const void* key)
235{
236 WINPR_ASSERT(table);
237 if (!pair)
238 return;
239 disposeKey(table, pair->key);
240 if (table->key.fnObjectNew)
241 pair->key = table->key.fnObjectNew(key);
242 else
243 {
244 union
245 {
246 const void* cpv;
247 void* pv;
248 } cnv;
249 cnv.cpv = key;
250 pair->key = cnv.pv;
251 }
252}
253
254static inline void setValue(wHashTable* table, wKeyValuePair* pair, const void* value)
255{
256 WINPR_ASSERT(table);
257 if (!pair)
258 return;
259 disposeValue(table, pair->value);
260 if (table->value.fnObjectNew)
261 pair->value = table->value.fnObjectNew(value);
262 else
263 {
264 union
265 {
266 const void* cpv;
267 void* pv;
268 } cnv;
269 cnv.cpv = value;
270 pair->value = cnv.pv;
271 }
272}
273
287size_t HashTable_Count(wHashTable* table)
288{
289 WINPR_ASSERT(table);
290 return table->numOfElements;
291}
292
300#if defined(WITH_WINPR_DEPRECATED)
301int HashTable_Add(wHashTable* table, const void* key, const void* value)
302{
303 if (!HashTable_Insert(table, key, value))
304 return -1;
305 return 0;
306}
307#endif
308
309BOOL HashTable_Insert(wHashTable* table, const void* key, const void* value)
310{
311 BOOL rc = FALSE;
312 UINT32 hashValue = 0;
313 wKeyValuePair* pair = nullptr;
314 wKeyValuePair* newPair = nullptr;
315
316 WINPR_ASSERT(table);
317 if (!key || !value)
318 return FALSE;
319
320 if (table->synchronized)
321 EnterCriticalSection(&table->lock);
322
323 hashValue = table->hash(key) % table->numOfBuckets;
324 pair = table->bucketArray[hashValue];
325
326 while (pair && !HashTable_Equals(table, pair, key))
327 pair = pair->next;
328
329 if (pair)
330 {
331 if (pair->markedForRemove)
332 {
333 /* this entry was set to be removed but will be recycled instead */
334 table->pendingRemoves--;
335 pair->markedForRemove = FALSE;
336 table->numOfElements++;
337 }
338
339 if (pair->key != key)
340 {
341 setKey(table, pair, key);
342 }
343
344 if (pair->value != value)
345 {
346 setValue(table, pair, value);
347 }
348 rc = TRUE;
349 }
350 else
351 {
352 newPair = (wKeyValuePair*)calloc(1, sizeof(wKeyValuePair));
353
354 if (newPair)
355 {
356 setKey(table, newPair, key);
357 setValue(table, newPair, value);
358 newPair->next = table->bucketArray[hashValue];
359 newPair->markedForRemove = FALSE;
360 table->bucketArray[hashValue] = newPair;
361 table->numOfElements++;
362
363 if (!table->foreachRecursionLevel && table->upperRehashThreshold > table->idealRatio)
364 {
365 float elementToBucketRatio =
366 (float)table->numOfElements / (float)table->numOfBuckets;
367
368 if (elementToBucketRatio > table->upperRehashThreshold)
369 HashTable_Rehash(table, 0);
370 }
371 rc = TRUE;
372 }
373 }
374
375 if (table->synchronized)
376 LeaveCriticalSection(&table->lock);
377
378 return rc;
379}
380
385BOOL HashTable_Remove(wHashTable* table, const void* key)
386{
387 UINT32 hashValue = 0;
388 BOOL status = TRUE;
389 wKeyValuePair* pair = nullptr;
390 wKeyValuePair* previousPair = nullptr;
391
392 WINPR_ASSERT(table);
393 if (!key)
394 return FALSE;
395
396 if (table->synchronized)
397 EnterCriticalSection(&table->lock);
398
399 hashValue = table->hash(key) % table->numOfBuckets;
400 pair = table->bucketArray[hashValue];
401
402 while (pair && !HashTable_Equals(table, pair, key))
403 {
404 previousPair = pair;
405 pair = pair->next;
406 }
407
408 if (!pair)
409 {
410 status = FALSE;
411 goto out;
412 }
413
414 if (table->foreachRecursionLevel)
415 {
416 /* if we are running a HashTable_Foreach, just mark the entry for removal */
417 pair->markedForRemove = TRUE;
418 table->pendingRemoves++;
419 table->numOfElements--;
420 goto out;
421 }
422
423 if (previousPair)
424 previousPair->next = pair->next;
425 else
426 table->bucketArray[hashValue] = pair->next;
427
428 disposePair(table, pair);
429 table->numOfElements--;
430
431 if (!table->foreachRecursionLevel && table->lowerRehashThreshold > 0.0f)
432 {
433 float elementToBucketRatio = (float)table->numOfElements / (float)table->numOfBuckets;
434
435 if (elementToBucketRatio < table->lowerRehashThreshold)
436 HashTable_Rehash(table, 0);
437 }
438
439out:
440 if (table->synchronized)
441 LeaveCriticalSection(&table->lock);
442
443 return status;
444}
445
450void* HashTable_GetItemValue(wHashTable* table, const void* key)
451{
452 void* value = nullptr;
453 wKeyValuePair* pair = nullptr;
454
455 WINPR_ASSERT(table);
456 if (!key)
457 return nullptr;
458
459 if (table->synchronized)
460 EnterCriticalSection(&table->lock);
461
462 pair = HashTable_Get(table, key);
463
464 if (pair && !pair->markedForRemove)
465 value = pair->value;
466
467 if (table->synchronized)
468 LeaveCriticalSection(&table->lock);
469
470 return value;
471}
472
477BOOL HashTable_SetItemValue(wHashTable* table, const void* key, const void* value)
478{
479 BOOL status = TRUE;
480 wKeyValuePair* pair = nullptr;
481
482 WINPR_ASSERT(table);
483 if (!key)
484 return FALSE;
485
486 if (table->synchronized)
487 EnterCriticalSection(&table->lock);
488
489 pair = HashTable_Get(table, key);
490
491 if (!pair || pair->markedForRemove)
492 status = FALSE;
493 else
494 {
495 setValue(table, pair, value);
496 }
497
498 if (table->synchronized)
499 LeaveCriticalSection(&table->lock);
500
501 return status;
502}
503
508void HashTable_Clear(wHashTable* table)
509{
510 wKeyValuePair* nextPair = nullptr;
511
512 WINPR_ASSERT(table);
513
514 if (table->synchronized)
515 EnterCriticalSection(&table->lock);
516
517 for (size_t index = 0; index < table->numOfBuckets; index++)
518 {
519 wKeyValuePair* pair = table->bucketArray[index];
520
521 while (pair)
522 {
523 nextPair = pair->next;
524
525 if (table->foreachRecursionLevel)
526 {
527 /* if we're in a foreach we just mark the entry for removal */
528 pair->markedForRemove = TRUE;
529 table->pendingRemoves++;
530 }
531 else
532 {
533 disposePair(table, pair);
534 pair = nextPair;
535 }
536 }
537
538 table->bucketArray[index] = nullptr;
539 }
540
541 table->numOfElements = 0;
542 if (table->foreachRecursionLevel == 0)
543 HashTable_Rehash(table, 5);
544
545 if (table->synchronized)
546 LeaveCriticalSection(&table->lock);
547}
548
553size_t HashTable_GetKeys(wHashTable* table, ULONG_PTR** ppKeys)
554{
555 size_t iKey = 0;
556 size_t count = 0;
557 ULONG_PTR* pKeys = nullptr;
558 wKeyValuePair* nextPair = nullptr;
559
560 WINPR_ASSERT(table);
561
562 if (table->synchronized)
563 EnterCriticalSection(&table->lock);
564
565 iKey = 0;
566 count = table->numOfElements;
567 if (ppKeys)
568 *ppKeys = nullptr;
569
570 if (count < 1)
571 {
572 if (table->synchronized)
573 LeaveCriticalSection(&table->lock);
574
575 return 0;
576 }
577
578 pKeys = (ULONG_PTR*)calloc(count, sizeof(ULONG_PTR));
579
580 if (!pKeys)
581 {
582 if (table->synchronized)
583 LeaveCriticalSection(&table->lock);
584
585 return 0;
586 }
587
588 for (size_t index = 0; index < table->numOfBuckets; index++)
589 {
590 wKeyValuePair* pair = table->bucketArray[index];
591
592 while (pair)
593 {
594 nextPair = pair->next;
595 if (!pair->markedForRemove)
596 pKeys[iKey++] = (ULONG_PTR)pair->key;
597 pair = nextPair;
598 }
599 }
600
601 if (table->synchronized)
602 LeaveCriticalSection(&table->lock);
603
604 if (ppKeys)
605 *ppKeys = pKeys;
606 else
607 free(pKeys);
608 return count;
609}
610
611BOOL HashTable_Foreach(wHashTable* table, HASH_TABLE_FOREACH_FN fn, VOID* arg)
612{
613 BOOL ret = TRUE;
614
615 WINPR_ASSERT(table);
616 WINPR_ASSERT(fn);
617
618 if (table->synchronized)
619 EnterCriticalSection(&table->lock);
620
621 table->foreachRecursionLevel++;
622 for (size_t index = 0; index < table->numOfBuckets; index++)
623 {
624 for (wKeyValuePair* pair = table->bucketArray[index]; pair; pair = pair->next)
625 {
626 if (!pair->markedForRemove && !fn(pair->key, pair->value, arg))
627 {
628 ret = FALSE;
629 goto out;
630 }
631 }
632 }
633 table->foreachRecursionLevel--;
634
635 if (!table->foreachRecursionLevel && table->pendingRemoves)
636 {
637 /* if we're the last recursive foreach call, let's do the cleanup if needed */
638 wKeyValuePair** prevPtr = nullptr;
639 for (size_t index = 0; index < table->numOfBuckets; index++)
640 {
641 wKeyValuePair* nextPair = nullptr;
642 prevPtr = &table->bucketArray[index];
643 for (wKeyValuePair* pair = table->bucketArray[index]; pair;)
644 {
645 nextPair = pair->next;
646
647 if (pair->markedForRemove)
648 {
649 disposePair(table, pair);
650 *prevPtr = nextPair;
651 }
652 else
653 {
654 prevPtr = &pair->next;
655 }
656 pair = nextPair;
657 }
658 }
659 table->pendingRemoves = 0;
660 }
661
662out:
663 if (table->synchronized)
664 LeaveCriticalSection(&table->lock);
665 return ret;
666}
667
672BOOL HashTable_Contains(wHashTable* table, const void* key)
673{
674 BOOL status = 0;
675 wKeyValuePair* pair = nullptr;
676
677 WINPR_ASSERT(table);
678 if (!key)
679 return FALSE;
680
681 if (table->synchronized)
682 EnterCriticalSection(&table->lock);
683
684 pair = HashTable_Get(table, key);
685 status = (pair && !pair->markedForRemove);
686
687 if (table->synchronized)
688 LeaveCriticalSection(&table->lock);
689
690 return status;
691}
692
697BOOL HashTable_ContainsKey(wHashTable* table, const void* key)
698{
699 BOOL status = 0;
700 wKeyValuePair* pair = nullptr;
701
702 WINPR_ASSERT(table);
703 if (!key)
704 return FALSE;
705
706 if (table->synchronized)
707 EnterCriticalSection(&table->lock);
708
709 pair = HashTable_Get(table, key);
710 status = (pair && !pair->markedForRemove);
711
712 if (table->synchronized)
713 LeaveCriticalSection(&table->lock);
714
715 return status;
716}
717
722BOOL HashTable_ContainsValue(wHashTable* table, const void* value)
723{
724 BOOL status = FALSE;
725
726 WINPR_ASSERT(table);
727 if (!value)
728 return FALSE;
729
730 if (table->synchronized)
731 EnterCriticalSection(&table->lock);
732
733 for (size_t index = 0; index < table->numOfBuckets; index++)
734 {
735 wKeyValuePair* pair = table->bucketArray[index];
736
737 while (pair)
738 {
739 if (!pair->markedForRemove && HashTable_Equals(table, pair, value))
740 {
741 status = TRUE;
742 break;
743 }
744
745 pair = pair->next;
746 }
747
748 if (status)
749 break;
750 }
751
752 if (table->synchronized)
753 LeaveCriticalSection(&table->lock);
754
755 return status;
756}
757
762wHashTable* HashTable_New(BOOL synchronized)
763{
764 wHashTable* table = (wHashTable*)calloc(1, sizeof(wHashTable));
765
766 if (!table)
767 goto fail;
768
769 table->synchronized = synchronized;
770 if (!InitializeCriticalSectionAndSpinCount(&(table->lock), 4000))
771 goto fail;
772 table->numOfBuckets = 64;
773 table->numOfElements = 0;
774 table->bucketArray = (wKeyValuePair**)calloc(table->numOfBuckets, sizeof(wKeyValuePair*));
775
776 if (!table->bucketArray)
777 goto fail;
778
779 table->idealRatio = 3.0f;
780 table->lowerRehashThreshold = 0.0f;
781 table->upperRehashThreshold = 15.0f;
782 table->hash = HashTable_PointerHash;
783 table->key.fnObjectEquals = HashTable_PointerCompare;
784 table->value.fnObjectEquals = HashTable_PointerCompare;
785
786 return table;
787fail:
788 WINPR_PRAGMA_DIAG_PUSH
789 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
790 HashTable_Free(table);
791 WINPR_PRAGMA_DIAG_POP
792 return nullptr;
793}
794
795void HashTable_Free(wHashTable* table)
796{
797 wKeyValuePair* pair = nullptr;
798 wKeyValuePair* nextPair = nullptr;
799
800 if (!table)
801 return;
802
803 if (table->bucketArray)
804 {
805 for (size_t index = 0; index < table->numOfBuckets; index++)
806 {
807 pair = table->bucketArray[index];
808
809 while (pair)
810 {
811 nextPair = pair->next;
812
813 disposePair(table, pair);
814 pair = nextPair;
815 }
816 }
817 free((void*)table->bucketArray);
818 }
819 DeleteCriticalSection(&(table->lock));
820
821 free(table);
822}
823
824void HashTable_Lock(wHashTable* table)
825{
826 WINPR_ASSERT(table);
827 EnterCriticalSection(&table->lock);
828}
829
830void HashTable_Unlock(wHashTable* table)
831{
832 WINPR_ASSERT(table);
833 LeaveCriticalSection(&table->lock);
834}
835
836wObject* HashTable_KeyObject(wHashTable* table)
837{
838 WINPR_ASSERT(table);
839 return &table->key;
840}
841
842wObject* HashTable_ValueObject(wHashTable* table)
843{
844 WINPR_ASSERT(table);
845 return &table->value;
846}
847
848BOOL HashTable_SetHashFunction(wHashTable* table, HASH_TABLE_HASH_FN fn)
849{
850 WINPR_ASSERT(table);
851 table->hash = fn;
852 return fn != nullptr;
853}
854
855BOOL HashTable_SetupForStringData(wHashTable* table, BOOL stringValues)
856{
857 wObject* obj = nullptr;
858
859 if (!HashTable_SetHashFunction(table, HashTable_StringHash))
860 return FALSE;
861
862 obj = HashTable_KeyObject(table);
863 obj->fnObjectEquals = HashTable_StringCompare;
864 obj->fnObjectNew = HashTable_StringClone;
865 obj->fnObjectFree = HashTable_StringFree;
866
867 if (stringValues)
868 {
869 obj = HashTable_ValueObject(table);
870 obj->fnObjectEquals = HashTable_StringCompare;
871 obj->fnObjectNew = HashTable_StringClone;
872 obj->fnObjectFree = HashTable_StringFree;
873 }
874 return TRUE;
875}
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:58
OBJECT_EQUALS_FN fnObjectEquals
Definition collections.h:59
OBJECT_NEW_FN fnObjectNew
Definition collections.h:53