FreeRDP
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 
35 typedef struct s_wKeyValuePair wKeyValuePair;
36 
37 struct s_wKeyValuePair
38 {
39  void* key;
40  void* value;
41 
42  wKeyValuePair* next;
43  BOOL markedForRemove;
44 };
45 
46 struct s_wHashTable
47 {
48  BOOL synchronized;
49  CRITICAL_SECTION lock;
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 
66 BOOL HashTable_PointerCompare(const void* pointer1, const void* pointer2)
67 {
68  return (pointer1 == pointer2);
69 }
70 
71 UINT32 HashTable_PointerHash(const void* pointer)
72 {
73  return ((UINT32)(UINT_PTR)pointer) >> 4;
74 }
75 
76 BOOL 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 
84 UINT32 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 
97 void* HashTable_StringClone(const void* str)
98 {
99  return winpr_ObjectStringClone(str);
100 }
101 
102 void HashTable_StringFree(void* str)
103 {
104  winpr_ObjectStringFree(str);
105 }
106 
107 static 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 
120 static 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 
139 static 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(table->bucketArray);
178  table->bucketArray = newBucketArray;
179  table->numOfBuckets = numOfBuckets;
180 }
181 
182 static 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 
190 static 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 
208 static INLINE void disposeKey(wHashTable* table, void* key)
209 {
210  WINPR_ASSERT(table);
211  if (table->key.fnObjectFree)
212  table->key.fnObjectFree(key);
213 }
214 
215 static INLINE void disposeValue(wHashTable* table, void* value)
216 {
217  WINPR_ASSERT(table);
218  if (table->value.fnObjectFree)
219  table->value.fnObjectFree(value);
220 }
221 
222 static 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 
232 static 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 
252 static 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 
285 size_t HashTable_Count(wHashTable* table)
286 {
287  WINPR_ASSERT(table);
288  return table->numOfElements;
289 }
290 
298 #if defined(WITH_WINPR_DEPRECATED)
299 int 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 
307 BOOL 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 
383 BOOL 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 
437 out:
438  if (table->synchronized)
439  LeaveCriticalSection(&table->lock);
440 
441  return status;
442 }
443 
448 void* 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 
475 BOOL 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 
506 void 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 
551 size_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 
609 BOOL 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 
660 out:
661  if (table->synchronized)
662  LeaveCriticalSection(&table->lock);
663  return ret;
664 }
665 
670 BOOL 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 
695 BOOL 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 
720 BOOL 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 
760 wHashTable* 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;
784 fail:
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 
792 void 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(table->bucketArray);
815  }
816  DeleteCriticalSection(&(table->lock));
817 
818  free(table);
819 }
820 
821 void HashTable_Lock(wHashTable* table)
822 {
823  WINPR_ASSERT(table);
824  EnterCriticalSection(&table->lock);
825 }
826 
827 void HashTable_Unlock(wHashTable* table)
828 {
829  WINPR_ASSERT(table);
830  LeaveCriticalSection(&table->lock);
831 }
832 
833 wObject* HashTable_KeyObject(wHashTable* table)
834 {
835  WINPR_ASSERT(table);
836  return &table->key;
837 }
838 
839 wObject* HashTable_ValueObject(wHashTable* table)
840 {
841  WINPR_ASSERT(table);
842  return &table->value;
843 }
844 
845 BOOL HashTable_SetHashFunction(wHashTable* table, HASH_TABLE_HASH_FN fn)
846 {
847  WINPR_ASSERT(table);
848  table->hash = fn;
849  return fn != NULL;
850 }
851 
852 BOOL 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