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
184WINPR_ATTR_NODISCARD
185static inline BOOL HashTable_Equals(wHashTable* table, const wKeyValuePair* pair, const void* key)
186{
187 WINPR_ASSERT(table);
188 WINPR_ASSERT(pair);
189 WINPR_ASSERT(key);
190 return table->key.fnObjectEquals(key, pair->key);
191}
192
193WINPR_ATTR_NODISCARD
194static inline wKeyValuePair* HashTable_Get(wHashTable* table, const void* key)
195{
196 UINT32 hashValue = 0;
197 wKeyValuePair* pair = nullptr;
198
199 WINPR_ASSERT(table);
200 if (!key)
201 return nullptr;
202
203 hashValue = table->hash(key) % table->numOfBuckets;
204 pair = table->bucketArray[hashValue];
205
206 while (pair && !HashTable_Equals(table, pair, key))
207 pair = pair->next;
208
209 return pair;
210}
211
212static inline void disposeKey(wHashTable* table, void* key)
213{
214 WINPR_ASSERT(table);
215 if (table->key.fnObjectFree)
216 table->key.fnObjectFree(key);
217}
218
219static inline void disposeValue(wHashTable* table, void* value)
220{
221 WINPR_ASSERT(table);
222 if (table->value.fnObjectFree)
223 table->value.fnObjectFree(value);
224}
225
226static inline void disposePair(wHashTable* table, wKeyValuePair* pair)
227{
228 WINPR_ASSERT(table);
229 if (!pair)
230 return;
231 disposeKey(table, pair->key);
232 disposeValue(table, pair->value);
233 free(pair);
234}
235
236static inline void setKey(wHashTable* table, wKeyValuePair* pair, const void* key)
237{
238 WINPR_ASSERT(table);
239 if (!pair)
240 return;
241 disposeKey(table, pair->key);
242 if (table->key.fnObjectNew)
243 pair->key = table->key.fnObjectNew(key);
244 else
245 {
246 union
247 {
248 const void* cpv;
249 void* pv;
250 } cnv;
251 cnv.cpv = key;
252 pair->key = cnv.pv;
253 }
254}
255
256static inline void setValue(wHashTable* table, wKeyValuePair* pair, const void* value)
257{
258 WINPR_ASSERT(table);
259 if (!pair)
260 return;
261 disposeValue(table, pair->value);
262 if (table->value.fnObjectNew)
263 pair->value = table->value.fnObjectNew(value);
264 else
265 {
266 union
267 {
268 const void* cpv;
269 void* pv;
270 } cnv;
271 cnv.cpv = value;
272 pair->value = cnv.pv;
273 }
274}
275
289size_t HashTable_Count(wHashTable* table)
290{
291 WINPR_ASSERT(table);
292 return table->numOfElements;
293}
294
302#if defined(WITH_WINPR_DEPRECATED)
303int HashTable_Add(wHashTable* table, const void* key, const void* value)
304{
305 if (!HashTable_Insert(table, key, value))
306 return -1;
307 return 0;
308}
309#endif
310
311BOOL HashTable_Insert(wHashTable* table, const void* key, const void* value)
312{
313 BOOL rc = FALSE;
314 UINT32 hashValue = 0;
315 wKeyValuePair* pair = nullptr;
316 wKeyValuePair* newPair = nullptr;
317
318 WINPR_ASSERT(table);
319 if (!key || !value)
320 return FALSE;
321
322 if (table->synchronized)
323 EnterCriticalSection(&table->lock);
324
325 hashValue = table->hash(key) % table->numOfBuckets;
326 pair = table->bucketArray[hashValue];
327
328 while (pair && !HashTable_Equals(table, pair, key))
329 pair = pair->next;
330
331 if (pair)
332 {
333 if (pair->markedForRemove)
334 {
335 /* this entry was set to be removed but will be recycled instead */
336 table->pendingRemoves--;
337 pair->markedForRemove = FALSE;
338 table->numOfElements++;
339 }
340
341 if (pair->key != key)
342 {
343 setKey(table, pair, key);
344 }
345
346 if (pair->value != value)
347 {
348 setValue(table, pair, value);
349 }
350 rc = TRUE;
351 }
352 else
353 {
354 newPair = (wKeyValuePair*)calloc(1, sizeof(wKeyValuePair));
355
356 if (newPair)
357 {
358 setKey(table, newPair, key);
359 setValue(table, newPair, value);
360 newPair->next = table->bucketArray[hashValue];
361 newPair->markedForRemove = FALSE;
362 table->bucketArray[hashValue] = newPair;
363 table->numOfElements++;
364
365 if (!table->foreachRecursionLevel && table->upperRehashThreshold > table->idealRatio)
366 {
367 float elementToBucketRatio =
368 (float)table->numOfElements / (float)table->numOfBuckets;
369
370 if (elementToBucketRatio > table->upperRehashThreshold)
371 HashTable_Rehash(table, 0);
372 }
373 rc = TRUE;
374 }
375 }
376
377 if (table->synchronized)
378 LeaveCriticalSection(&table->lock);
379
380 return rc;
381}
382
387BOOL HashTable_Remove(wHashTable* table, const void* key)
388{
389 UINT32 hashValue = 0;
390 BOOL status = TRUE;
391 wKeyValuePair* pair = nullptr;
392 wKeyValuePair* previousPair = nullptr;
393
394 WINPR_ASSERT(table);
395 if (!key)
396 return FALSE;
397
398 if (table->synchronized)
399 EnterCriticalSection(&table->lock);
400
401 hashValue = table->hash(key) % table->numOfBuckets;
402 pair = table->bucketArray[hashValue];
403
404 while (pair && !HashTable_Equals(table, pair, key))
405 {
406 previousPair = pair;
407 pair = pair->next;
408 }
409
410 if (!pair)
411 {
412 status = FALSE;
413 goto out;
414 }
415
416 if (table->foreachRecursionLevel)
417 {
418 /* if we are running a HashTable_Foreach, just mark the entry for removal */
419 pair->markedForRemove = TRUE;
420 table->pendingRemoves++;
421 table->numOfElements--;
422 goto out;
423 }
424
425 if (previousPair)
426 previousPair->next = pair->next;
427 else
428 table->bucketArray[hashValue] = pair->next;
429
430 disposePair(table, pair);
431 table->numOfElements--;
432
433 if (!table->foreachRecursionLevel && table->lowerRehashThreshold > 0.0f)
434 {
435 float elementToBucketRatio = (float)table->numOfElements / (float)table->numOfBuckets;
436
437 if (elementToBucketRatio < table->lowerRehashThreshold)
438 HashTable_Rehash(table, 0);
439 }
440
441out:
442 if (table->synchronized)
443 LeaveCriticalSection(&table->lock);
444
445 return status;
446}
447
452void* HashTable_GetItemValue(wHashTable* table, const void* key)
453{
454 void* value = nullptr;
455 wKeyValuePair* pair = nullptr;
456
457 WINPR_ASSERT(table);
458 if (!key)
459 return nullptr;
460
461 if (table->synchronized)
462 EnterCriticalSection(&table->lock);
463
464 pair = HashTable_Get(table, key);
465
466 if (pair && !pair->markedForRemove)
467 value = pair->value;
468
469 if (table->synchronized)
470 LeaveCriticalSection(&table->lock);
471
472 return value;
473}
474
479BOOL HashTable_SetItemValue(wHashTable* table, const void* key, const void* value)
480{
481 BOOL status = TRUE;
482 wKeyValuePair* pair = nullptr;
483
484 WINPR_ASSERT(table);
485 if (!key)
486 return FALSE;
487
488 if (table->synchronized)
489 EnterCriticalSection(&table->lock);
490
491 pair = HashTable_Get(table, key);
492
493 if (!pair || pair->markedForRemove)
494 status = FALSE;
495 else
496 {
497 setValue(table, pair, value);
498 }
499
500 if (table->synchronized)
501 LeaveCriticalSection(&table->lock);
502
503 return status;
504}
505
510void HashTable_Clear(wHashTable* table)
511{
512 wKeyValuePair* nextPair = nullptr;
513
514 WINPR_ASSERT(table);
515
516 if (table->synchronized)
517 EnterCriticalSection(&table->lock);
518
519 for (size_t index = 0; index < table->numOfBuckets; index++)
520 {
521 wKeyValuePair* pair = table->bucketArray[index];
522
523 while (pair)
524 {
525 nextPair = pair->next;
526
527 if (table->foreachRecursionLevel)
528 {
529 /* if we're in a foreach we just mark the entry for removal */
530 pair->markedForRemove = TRUE;
531 table->pendingRemoves++;
532 }
533 else
534 {
535 disposePair(table, pair);
536 pair = nextPair;
537 }
538 }
539
540 table->bucketArray[index] = nullptr;
541 }
542
543 table->numOfElements = 0;
544 if (table->foreachRecursionLevel == 0)
545 HashTable_Rehash(table, 5);
546
547 if (table->synchronized)
548 LeaveCriticalSection(&table->lock);
549}
550
555size_t HashTable_GetKeys(wHashTable* table, ULONG_PTR** ppKeys)
556{
557 size_t iKey = 0;
558 size_t count = 0;
559 ULONG_PTR* pKeys = nullptr;
560 wKeyValuePair* nextPair = nullptr;
561
562 WINPR_ASSERT(table);
563
564 if (table->synchronized)
565 EnterCriticalSection(&table->lock);
566
567 iKey = 0;
568 count = table->numOfElements;
569 if (ppKeys)
570 *ppKeys = nullptr;
571
572 if (count < 1)
573 {
574 if (table->synchronized)
575 LeaveCriticalSection(&table->lock);
576
577 return 0;
578 }
579
580 pKeys = (ULONG_PTR*)calloc(count, sizeof(ULONG_PTR));
581
582 if (!pKeys)
583 {
584 if (table->synchronized)
585 LeaveCriticalSection(&table->lock);
586
587 return 0;
588 }
589
590 for (size_t index = 0; index < table->numOfBuckets; index++)
591 {
592 wKeyValuePair* pair = table->bucketArray[index];
593
594 while (pair)
595 {
596 nextPair = pair->next;
597 if (!pair->markedForRemove)
598 pKeys[iKey++] = (ULONG_PTR)pair->key;
599 pair = nextPair;
600 }
601 }
602
603 if (table->synchronized)
604 LeaveCriticalSection(&table->lock);
605
606 if (ppKeys)
607 *ppKeys = pKeys;
608 else
609 free(pKeys);
610 return count;
611}
612
613BOOL HashTable_Foreach(wHashTable* table, HASH_TABLE_FOREACH_FN fn, VOID* arg)
614{
615 BOOL ret = TRUE;
616
617 WINPR_ASSERT(table);
618 WINPR_ASSERT(fn);
619
620 if (table->synchronized)
621 EnterCriticalSection(&table->lock);
622
623 table->foreachRecursionLevel++;
624 for (size_t index = 0; index < table->numOfBuckets; index++)
625 {
626 for (wKeyValuePair* pair = table->bucketArray[index]; pair; pair = pair->next)
627 {
628 if (!pair->markedForRemove && !fn(pair->key, pair->value, arg))
629 {
630 ret = FALSE;
631 goto out;
632 }
633 }
634 }
635 table->foreachRecursionLevel--;
636
637 if (!table->foreachRecursionLevel && table->pendingRemoves)
638 {
639 /* if we're the last recursive foreach call, let's do the cleanup if needed */
640 wKeyValuePair** prevPtr = nullptr;
641 for (size_t index = 0; index < table->numOfBuckets; index++)
642 {
643 wKeyValuePair* nextPair = nullptr;
644 prevPtr = &table->bucketArray[index];
645 for (wKeyValuePair* pair = table->bucketArray[index]; pair;)
646 {
647 nextPair = pair->next;
648
649 if (pair->markedForRemove)
650 {
651 disposePair(table, pair);
652 *prevPtr = nextPair;
653 }
654 else
655 {
656 prevPtr = &pair->next;
657 }
658 pair = nextPair;
659 }
660 }
661 table->pendingRemoves = 0;
662 }
663
664out:
665 if (table->synchronized)
666 LeaveCriticalSection(&table->lock);
667 return ret;
668}
669
674BOOL HashTable_Contains(wHashTable* table, const void* key)
675{
676 BOOL status = 0;
677 wKeyValuePair* pair = nullptr;
678
679 WINPR_ASSERT(table);
680 if (!key)
681 return FALSE;
682
683 if (table->synchronized)
684 EnterCriticalSection(&table->lock);
685
686 pair = HashTable_Get(table, key);
687 status = (pair && !pair->markedForRemove);
688
689 if (table->synchronized)
690 LeaveCriticalSection(&table->lock);
691
692 return status;
693}
694
699BOOL HashTable_ContainsKey(wHashTable* table, const void* key)
700{
701 BOOL status = 0;
702 wKeyValuePair* pair = nullptr;
703
704 WINPR_ASSERT(table);
705 if (!key)
706 return FALSE;
707
708 if (table->synchronized)
709 EnterCriticalSection(&table->lock);
710
711 pair = HashTable_Get(table, key);
712 status = (pair && !pair->markedForRemove);
713
714 if (table->synchronized)
715 LeaveCriticalSection(&table->lock);
716
717 return status;
718}
719
724BOOL HashTable_ContainsValue(wHashTable* table, const void* value)
725{
726 BOOL status = FALSE;
727
728 WINPR_ASSERT(table);
729 if (!value)
730 return FALSE;
731
732 if (table->synchronized)
733 EnterCriticalSection(&table->lock);
734
735 for (size_t index = 0; index < table->numOfBuckets; index++)
736 {
737 wKeyValuePair* pair = table->bucketArray[index];
738
739 while (pair)
740 {
741 if (!pair->markedForRemove && HashTable_Equals(table, pair, value))
742 {
743 status = TRUE;
744 break;
745 }
746
747 pair = pair->next;
748 }
749
750 if (status)
751 break;
752 }
753
754 if (table->synchronized)
755 LeaveCriticalSection(&table->lock);
756
757 return status;
758}
759
764wHashTable* HashTable_New(BOOL synchronized)
765{
766 wHashTable* table = (wHashTable*)calloc(1, sizeof(wHashTable));
767
768 if (!table)
769 goto fail;
770
771 table->synchronized = synchronized;
772 if (!InitializeCriticalSectionAndSpinCount(&(table->lock), 4000))
773 goto fail;
774 table->numOfBuckets = 64;
775 table->numOfElements = 0;
776 table->bucketArray = (wKeyValuePair**)calloc(table->numOfBuckets, sizeof(wKeyValuePair*));
777
778 if (!table->bucketArray)
779 goto fail;
780
781 table->idealRatio = 3.0f;
782 table->lowerRehashThreshold = 0.0f;
783 table->upperRehashThreshold = 15.0f;
784 table->hash = HashTable_PointerHash;
785 table->key.fnObjectEquals = HashTable_PointerCompare;
786 table->value.fnObjectEquals = HashTable_PointerCompare;
787
788 return table;
789fail:
790 WINPR_PRAGMA_DIAG_PUSH
791 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
792 HashTable_Free(table);
793 WINPR_PRAGMA_DIAG_POP
794 return nullptr;
795}
796
797void HashTable_Free(wHashTable* table)
798{
799 wKeyValuePair* pair = nullptr;
800 wKeyValuePair* nextPair = nullptr;
801
802 if (!table)
803 return;
804
805 if (table->bucketArray)
806 {
807 for (size_t index = 0; index < table->numOfBuckets; index++)
808 {
809 pair = table->bucketArray[index];
810
811 while (pair)
812 {
813 nextPair = pair->next;
814
815 disposePair(table, pair);
816 pair = nextPair;
817 }
818 }
819 free((void*)table->bucketArray);
820 }
821 DeleteCriticalSection(&(table->lock));
822
823 free(table);
824}
825
826void HashTable_Lock(wHashTable* table)
827{
828 WINPR_ASSERT(table);
829 EnterCriticalSection(&table->lock);
830}
831
832void HashTable_Unlock(wHashTable* table)
833{
834 WINPR_ASSERT(table);
835 LeaveCriticalSection(&table->lock);
836}
837
838wObject* HashTable_KeyObject(wHashTable* table)
839{
840 WINPR_ASSERT(table);
841 return &table->key;
842}
843
844wObject* HashTable_ValueObject(wHashTable* table)
845{
846 WINPR_ASSERT(table);
847 return &table->value;
848}
849
850BOOL HashTable_SetHashFunction(wHashTable* table, HASH_TABLE_HASH_FN fn)
851{
852 WINPR_ASSERT(table);
853 table->hash = fn;
854 return fn != nullptr;
855}
856
857BOOL HashTable_SetupForStringData(wHashTable* table, BOOL stringValues)
858{
859 wObject* obj = nullptr;
860
861 if (!HashTable_SetHashFunction(table, HashTable_StringHash))
862 return FALSE;
863
864 obj = HashTable_KeyObject(table);
865 obj->fnObjectEquals = HashTable_StringCompare;
866 obj->fnObjectNew = HashTable_StringClone;
867 obj->fnObjectFree = HashTable_StringFree;
868
869 if (stringValues)
870 {
871 obj = HashTable_ValueObject(table);
872 obj->fnObjectEquals = HashTable_StringCompare;
873 obj->fnObjectNew = HashTable_StringClone;
874 obj->fnObjectFree = HashTable_StringFree;
875 }
876 return TRUE;
877}
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59
WINPR_ATTR_NODISCARD OBJECT_EQUALS_FN fnObjectEquals
Definition collections.h:61
WINPR_ATTR_NODISCARD OBJECT_NEW_FN fnObjectNew
Definition collections.h:54