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