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