suricata
util-hash.c
Go to the documentation of this file.
1 /* Copyright (C) 2007-2026 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Victor Julien <victor@inliniac.net>
22  *
23  * Chained hash table implementation
24  *
25  * The 'Free' pointer can be used to have the API free your
26  * hashed data. If it's NULL it's the callers responsibility
27  */
28 
29 #include "suricata-common.h"
30 #include "util-hash.h"
31 #include "util-unittest.h"
32 #include "util-memcmp.h"
33 #include "util-debug.h"
34 
35 HashTable* HashTableInit(uint32_t size, uint32_t (*Hash)(struct HashTable_ *, void *, uint16_t), char (*Compare)(void *, uint16_t, void *, uint16_t), void (*Free)(void *)) {
36 
37  HashTable *ht = NULL;
38 
39  if (size == 0) {
40  goto error;
41  }
42 
43  if (Hash == NULL) {
44  //printf("ERROR: HashTableInit no Hash function\n");
45  goto error;
46  }
47 
48  /* setup the filter */
49  ht = SCCalloc(1, sizeof(HashTable));
50  if (unlikely(ht == NULL))
51  goto error;
52  ht->array_size = size;
53  ht->Hash = Hash;
54  ht->Free = Free;
55 
56  if (Compare != NULL)
57  ht->Compare = Compare;
58  else
60 
61  /* setup the bitarray */
62  ht->array = SCCalloc(ht->array_size, sizeof(HashTableBucket *));
63  if (ht->array == NULL)
64  goto error;
65 
66  return ht;
67 
68 error:
69  if (ht != NULL) {
70  if (ht->array != NULL)
71  SCFree(ht->array);
72 
73  SCFree(ht);
74  }
75  return NULL;
76 }
77 
79  uint32_t (*Hash)(struct HashTable_ *, void *, uint16_t),
80  char (*Compare)(void *, uint16_t, void *, uint16_t), void (*Free)(void *),
81  const uint32_t seed)
82 {
83  HashTable *ht = HashTableInit(size, Hash, Compare, Free);
84  if (ht != NULL) {
85  ht->seed = seed;
86  }
87  return ht;
88 }
89 
90 /**
91  * \brief Free a HashTableBucket and return the next bucket
92  * \param ht Pointer to the HashTable
93  * \param htb Pointer to the HashTableBucket to free
94  * \return HashTableBucket* Pointer to the next HashTableBucket or NULL
95  */
96 static HashTableBucket *HashTableBucketFree(HashTable *ht, HashTableBucket *htb)
97 {
98  HashTableBucket *next_hashbucket = htb->next;
99  if (ht->Free != NULL)
100  ht->Free(htb->data);
101  SCFree(htb);
102  return next_hashbucket;
103 }
104 
105 /**
106  * \brief Free a HashTable and all its contents
107  * \details This function will free all the buckets and the array of buckets.
108  * \note If the Free function is set, it will be called for each data item in the hash table.
109  * \param ht Pointer to the HashTable to free
110  * \return void
111  */
113 {
114  if (ht == NULL)
115  return;
116 
117  /* free the buckets */
118  for (uint32_t i = 0; i < ht->array_size; i++) {
119  HashTableBucket *hashbucket = ht->array[i];
120  while (hashbucket != NULL) {
121  hashbucket = HashTableBucketFree(ht, hashbucket);
122  }
123  }
124 
125  /* free the array */
126  if (ht->array != NULL)
127  SCFree(ht->array);
128 
129  SCFree(ht);
130 }
131 
132 int HashTableAdd(HashTable *ht, void *data, uint16_t datalen)
133 {
134  if (ht == NULL || data == NULL)
135  return -1;
136 
137  uint32_t hash = ht->Hash(ht, data, datalen);
138 
139  HashTableBucket *hb = SCCalloc(1, sizeof(HashTableBucket));
140  if (unlikely(hb == NULL))
141  goto error;
142  hb->data = data;
143  hb->size = datalen;
144  hb->next = NULL;
145 
146  if (hash >= ht->array_size) {
147  SCLogWarning("attempt to insert element out of hash array\n");
148  goto error;
149  }
150 
151  if (ht->array[hash] == NULL) {
152  ht->array[hash] = hb;
153  } else {
154  hb->next = ht->array[hash];
155  ht->array[hash] = hb;
156  }
157 
158 #ifdef UNITTESTS
159  ht->count++;
160 #endif
161 
162  return 0;
163 
164 error:
165  if (hb != NULL)
166  SCFree(hb);
167  return -1;
168 }
169 /**
170  * \brief Remove an item from the hash table
171  * \details This function will search for the item in the hash table and remove it if found
172  * \note If the Free function is set, it will be called for the data item being removed.
173  * \param ht Pointer to the HashTable
174  * \param data Pointer to the data to remove
175  * \param datalen Length of the data to remove
176  * \return int 0 on success, -1 if the item was not found or an error occurred
177  */
178 int HashTableRemove(HashTable *ht, void *data, uint16_t datalen)
179 {
180  uint32_t hash = ht->Hash(ht, data, datalen);
181 
182  HashTableBucket **hashbucket = &(ht->array[hash]);
183  while (*hashbucket != NULL) {
184  if (ht->Compare((*hashbucket)->data, (*hashbucket)->size, data, datalen)) {
185  *hashbucket = HashTableBucketFree(ht, *hashbucket);
186  return 0;
187  }
188  hashbucket = &((*hashbucket)->next);
189  }
190 
191  return -1;
192 }
193 
194 void *HashTableLookup(HashTable *ht, void *data, uint16_t datalen)
195 {
196  uint32_t hash = 0;
197 
198  if (ht == NULL)
199  return NULL;
200 
201  hash = ht->Hash(ht, data, datalen);
202 
203  if (hash >= ht->array_size) {
204  SCLogWarning("attempt to access element out of hash array\n");
205  return NULL;
206  }
207 
208  if (ht->array[hash] == NULL)
209  return NULL;
210 
211  HashTableBucket *hashbucket = ht->array[hash];
212  do {
213  if (ht->Compare(hashbucket->data, hashbucket->size, data, datalen) == 1)
214  return hashbucket->data;
215 
216  hashbucket = hashbucket->next;
217  } while (hashbucket != NULL);
218 
219  return NULL;
220 }
221 
222 // CallbackFn is an iterator, first argument is the data, second is user auxilary data
223 void HashTableIterate(HashTable *ht, void (*CallbackFn)(void *, void *), void *aux)
224 {
225  if (ht == NULL || CallbackFn == NULL)
226  return;
227 
228  for (uint32_t i = 0; i < ht->array_size; i++) {
229  HashTableBucket *hashbucket = ht->array[i];
230  while (hashbucket != NULL) {
231  CallbackFn(hashbucket->data, aux);
232  hashbucket = hashbucket->next;
233  }
234  }
235 }
236 
237 uint32_t HashTableGenericHash(HashTable *ht, void *data, uint16_t datalen)
238 {
239  uint8_t *d = (uint8_t *)data;
240  uint32_t i;
241  uint32_t hash = 0;
242 
243  for (i = 0; i < datalen; i++) {
244  if (i == 0) hash += (((uint32_t)*d++));
245  else if (i == 1) hash += (((uint32_t)*d++) * datalen);
246  else hash *= (((uint32_t)*d++) * i) + datalen + i;
247  }
248 
249  hash *= datalen;
250  hash %= ht->array_size;
251  return hash;
252 }
253 
254 char HashTableDefaultCompare(void *data1, uint16_t len1, void *data2, uint16_t len2)
255 {
256  if (len1 != len2)
257  return 0;
258 
259  if (SCMemcmp(data1,data2,len1) != 0)
260  return 0;
261 
262  return 1;
263 }
264 
265 /*
266  * ONLY TESTS BELOW THIS COMMENT
267  */
268 
269 #ifdef UNITTESTS
270 static int HashTableTestInit01 (void)
271 {
272  HashTable *ht = HashTableInit(1024, HashTableGenericHash, NULL, NULL);
273  if (ht == NULL)
274  return 0;
275 
276  HashTableFree(ht);
277  return 1;
278 }
279 
280 /* no hash function, so it should fail */
281 static int HashTableTestInit02 (void)
282 {
283  HashTable *ht = HashTableInit(1024, NULL, NULL, NULL);
284  if (ht == NULL)
285  return 1;
286 
287  HashTableFree(ht);
288  return 0;
289 }
290 
291 static int HashTableTestInit03 (void)
292 {
293  int result = 0;
294  HashTable *ht = HashTableInit(1024, HashTableGenericHash, NULL, NULL);
295  if (ht == NULL)
296  return 0;
297 
298  if (ht->Hash == HashTableGenericHash)
299  result = 1;
300 
301  HashTableFree(ht);
302  return result;
303 }
304 
305 static int HashTableTestInit04 (void)
306 {
307  HashTable *ht = HashTableInit(0, HashTableGenericHash, NULL, NULL);
308  if (ht == NULL)
309  return 1;
310 
311  HashTableFree(ht);
312  return 0;
313 }
314 
315 static int HashTableTestInit05 (void)
316 {
317  int result = 0;
318  HashTable *ht = HashTableInit(1024, HashTableGenericHash, NULL, NULL);
319  if (ht == NULL)
320  return 0;
321 
322  if (ht->Compare == HashTableDefaultCompare)
323  result = 1;
324 
325  HashTableFree(ht);
326  return result;
327 }
328 
329 static char HashTableDefaultCompareTest(void *data1, uint16_t len1, void *data2, uint16_t len2)
330 {
331  if (len1 != len2)
332  return 0;
333 
334  if (SCMemcmp(data1,data2,len1) != 0)
335  return 0;
336 
337  return 1;
338 }
339 
340 static int HashTableTestInit06 (void)
341 {
342  int result = 0;
343  HashTable *ht = HashTableInit(1024, HashTableGenericHash, HashTableDefaultCompareTest, NULL);
344  if (ht == NULL)
345  return 0;
346 
347  if (ht->Compare == HashTableDefaultCompareTest)
348  result = 1;
349 
350  HashTableFree(ht);
351  return result;
352 }
353 
354 static int HashTableTestAdd01 (void)
355 {
356  int result = 0;
357  HashTable *ht = HashTableInit(32, HashTableGenericHash, NULL, NULL);
358  if (ht == NULL)
359  goto end;
360 
361  int r = HashTableAdd(ht, (char *)"test", 0);
362  if (r != 0)
363  goto end;
364 
365  /* all is good! */
366  result = 1;
367 end:
368  if (ht != NULL) HashTableFree(ht);
369  return result;
370 }
371 
372 static int HashTableTestAdd02 (void)
373 {
374  int result = 0;
375  HashTable *ht = HashTableInit(32, HashTableGenericHash, NULL, NULL);
376  if (ht == NULL)
377  goto end;
378 
379  int r = HashTableAdd(ht, NULL, 4);
380  if (r == 0)
381  goto end;
382 
383  /* all is good! */
384  result = 1;
385 end:
386  if (ht != NULL) HashTableFree(ht);
387  return result;
388 }
389 
390 static int HashTableTestFull01 (void)
391 {
392  int result = 0;
393  HashTable *ht = HashTableInit(32, HashTableGenericHash, NULL, NULL);
394  if (ht == NULL)
395  goto end;
396 
397  int r = HashTableAdd(ht, (char *)"test", 4);
398  if (r != 0)
399  goto end;
400 
401  char *rp = HashTableLookup(ht, (char *)"test", 4);
402  if (rp == NULL)
403  goto end;
404 
405  r = HashTableRemove(ht, (char *)"test", 4);
406  if (r != 0)
407  goto end;
408 
409  /* all is good! */
410  result = 1;
411 end:
412  if (ht != NULL) HashTableFree(ht);
413  return result;
414 }
415 
416 static int HashTableTestFull02 (void)
417 {
418  int result = 0;
419  HashTable *ht = HashTableInit(32, HashTableGenericHash, NULL, NULL);
420  if (ht == NULL)
421  goto end;
422 
423  int r = HashTableAdd(ht, (char *)"test", 4);
424  if (r != 0)
425  goto end;
426 
427  char *rp = HashTableLookup(ht, (char *)"test", 4);
428  if (rp == NULL)
429  goto end;
430 
431  r = HashTableRemove(ht, (char *)"test2", 5);
432  if (r == 0)
433  goto end;
434 
435  /* all is good! */
436  result = 1;
437 end:
438  if (ht != NULL) HashTableFree(ht);
439  return result;
440 }
441 
442 static int HashTableTestCollisionBug(void)
443 {
444  HashTable *ht = HashTableInit(32, HashTableGenericHash, NULL, NULL);
445  FAIL_IF_NULL(ht);
446 
447  FAIL_IF_NOT(HashTableGenericHash(ht, (void *)"abc", 3) ==
448  HashTableGenericHash(ht, (void *)"iln", 3));
449 
450  // Add two strings that collide in the same bucket
451  FAIL_IF_NOT(HashTableAdd(ht, (char *)"abc", 3) == 0);
452  FAIL_IF_NOT(HashTableAdd(ht, (char *)"iln", 3) == 0);
453 
454  // Verify both keys are present
455  FAIL_IF_NULL(HashTableLookup(ht, (char *)"abc", 3));
456  FAIL_IF_NULL(HashTableLookup(ht, (char *)"iln", 3));
457 
458  // Remove first key once
459  FAIL_IF_NOT(HashTableRemove(ht, (char *)"abc", 3) == 0);
460 
461  // Verify first key is gone, second key remains
462  FAIL_IF_NOT_NULL(HashTableLookup(ht, (char *)"abc", 3));
463  FAIL_IF_NULL(HashTableLookup(ht, (char *)"iln", 3));
464 
465  // Remove first key again (should not affect "iln")
466  FAIL_IF(HashTableRemove(ht, (char *)"abc", 3) == 0);
467 
468  // Verify second key is still present (correct behavior)
469  FAIL_IF_NULL(HashTableLookup(ht, (char *)"iln", 3));
470 
471  HashTableFree(ht);
472  PASS;
473 }
474 #endif
475 
477 {
478 #ifdef UNITTESTS
479  UtRegisterTest("HashTableTestInit01", HashTableTestInit01);
480  UtRegisterTest("HashTableTestInit02", HashTableTestInit02);
481  UtRegisterTest("HashTableTestInit03", HashTableTestInit03);
482  UtRegisterTest("HashTableTestInit04", HashTableTestInit04);
483  UtRegisterTest("HashTableTestInit05", HashTableTestInit05);
484  UtRegisterTest("HashTableTestInit06", HashTableTestInit06);
485 
486  UtRegisterTest("HashTableTestAdd01", HashTableTestAdd01);
487  UtRegisterTest("HashTableTestAdd02", HashTableTestAdd02);
488 
489  UtRegisterTest("HashTableTestFull01", HashTableTestFull01);
490  UtRegisterTest("HashTableTestFull02", HashTableTestFull02);
491  UtRegisterTest("HashTableTestCollisionBug", HashTableTestCollisionBug);
492 #endif
493 }
FAIL_IF_NULL
#define FAIL_IF_NULL(expr)
Fail a test if expression evaluates to NULL.
Definition: util-unittest.h:89
HashTableDefaultCompare
char HashTableDefaultCompare(void *data1, uint16_t len1, void *data2, uint16_t len2)
Definition: util-hash.c:254
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
UtRegisterTest
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
Definition: util-unittest.c:103
HashTableIterate
void HashTableIterate(HashTable *ht, void(*CallbackFn)(void *, void *), void *aux)
Definition: util-hash.c:223
util-hash.h
HashTable_
Definition: util-hash.h:35
util-unittest.h
FAIL_IF_NOT
#define FAIL_IF_NOT(expr)
Fail a test if expression evaluates to false.
Definition: util-unittest.h:82
HashTableBucket_::size
uint16_t size
Definition: util-hash.h:30
HashTableFree
void HashTableFree(HashTable *ht)
Free a HashTable and all its contents.
Definition: util-hash.c:112
HashTable_::array_size
uint32_t array_size
Definition: util-hash.h:37
util-memcmp.h
HashTableBucket_
Definition: util-hash.h:28
FAIL_IF_NOT_NULL
#define FAIL_IF_NOT_NULL(expr)
Fail a test if expression evaluates to non-NULL.
Definition: util-unittest.h:96
util-debug.h
PASS
#define PASS
Pass the test.
Definition: util-unittest.h:105
HashTableInitWithSeed
HashTable * HashTableInitWithSeed(uint32_t size, uint32_t(*Hash)(struct HashTable_ *, void *, uint16_t), char(*Compare)(void *, uint16_t, void *, uint16_t), void(*Free)(void *), const uint32_t seed)
Definition: util-hash.c:78
HashTable_::Compare
char(* Compare)(void *, uint16_t, void *, uint16_t)
Definition: util-hash.h:43
HashTableRegisterTests
void HashTableRegisterTests(void)
Definition: util-hash.c:476
HashTableLookup
void * HashTableLookup(HashTable *ht, void *data, uint16_t datalen)
Definition: util-hash.c:194
HashTableRemove
int HashTableRemove(HashTable *ht, void *data, uint16_t datalen)
Remove an item from the hash table.
Definition: util-hash.c:178
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:262
HashTableAdd
int HashTableAdd(HashTable *ht, void *data, uint16_t datalen)
Definition: util-hash.c:132
HashTable_::array
HashTableBucket ** array
Definition: util-hash.h:36
HashTableBucket_::next
struct HashTableBucket_ * next
Definition: util-hash.h:31
FAIL_IF
#define FAIL_IF(expr)
Fail a test if expression evaluates to true.
Definition: util-unittest.h:71
suricata-common.h
HashTable_::count
uint32_t count
Definition: util-hash.h:40
SCFree
#define SCFree(p)
Definition: util-mem.h:61
HashTableInit
HashTable * HashTableInit(uint32_t size, uint32_t(*Hash)(struct HashTable_ *, void *, uint16_t), char(*Compare)(void *, uint16_t, void *, uint16_t), void(*Free)(void *))
Definition: util-hash.c:35
HashTableBucket_::data
void * data
Definition: util-hash.h:29
HashTableGenericHash
uint32_t HashTableGenericHash(HashTable *ht, void *data, uint16_t datalen)
Definition: util-hash.c:237
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
SCMemcmp
#define SCMemcmp(a, b, c)
Definition: util-memcmp.h:290
HashTable_::Free
void(* Free)(void *)
Definition: util-hash.h:44
HashTable_::Hash
uint32_t(* Hash)(struct HashTable_ *, void *, uint16_t)
Definition: util-hash.h:42
HashTable_::seed
uint32_t seed
Definition: util-hash.h:38