/src/suricata8/src/util-hash.c
Line | Count | Source |
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 | 461k | 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 | 461k | HashTable *ht = NULL; |
38 | | |
39 | 461k | if (size == 0) { |
40 | 0 | goto error; |
41 | 0 | } |
42 | | |
43 | 461k | if (Hash == NULL) { |
44 | | //printf("ERROR: HashTableInit no Hash function\n"); |
45 | 0 | goto error; |
46 | 0 | } |
47 | | |
48 | | /* setup the filter */ |
49 | 461k | ht = SCCalloc(1, sizeof(HashTable)); |
50 | 461k | if (unlikely(ht == NULL)) |
51 | 0 | goto error; |
52 | 461k | ht->array_size = size; |
53 | 461k | ht->Hash = Hash; |
54 | 461k | ht->Free = Free; |
55 | | |
56 | 461k | if (Compare != NULL) |
57 | 461k | ht->Compare = Compare; |
58 | 0 | else |
59 | 0 | ht->Compare = HashTableDefaultCompare; |
60 | | |
61 | | /* setup the bitarray */ |
62 | 461k | ht->array = SCCalloc(ht->array_size, sizeof(HashTableBucket *)); |
63 | 461k | if (ht->array == NULL) |
64 | 0 | goto error; |
65 | | |
66 | 461k | return ht; |
67 | | |
68 | 0 | error: |
69 | 0 | if (ht != NULL) { |
70 | 0 | if (ht->array != NULL) |
71 | 0 | SCFree(ht->array); |
72 | |
|
73 | 0 | SCFree(ht); |
74 | 0 | } |
75 | 0 | return NULL; |
76 | 461k | } |
77 | | |
78 | | HashTable *HashTableInitWithSeed(uint32_t size, |
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 | 6 | { |
83 | 6 | HashTable *ht = HashTableInit(size, Hash, Compare, Free); |
84 | 6 | if (ht != NULL) { |
85 | 6 | ht->seed = seed; |
86 | 6 | } |
87 | 6 | return ht; |
88 | 6 | } |
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 | 388k | { |
98 | 388k | HashTableBucket *next_hashbucket = htb->next; |
99 | 388k | if (ht->Free != NULL) |
100 | 388k | ht->Free(htb->data); |
101 | 388k | SCFree(htb); |
102 | 388k | return next_hashbucket; |
103 | 388k | } |
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 | | */ |
112 | | void HashTableFree(HashTable *ht) |
113 | 461k | { |
114 | 461k | if (ht == NULL) |
115 | 0 | return; |
116 | | |
117 | | /* free the buckets */ |
118 | 524M | for (uint32_t i = 0; i < ht->array_size; i++) { |
119 | 524M | HashTableBucket *hashbucket = ht->array[i]; |
120 | 524M | while (hashbucket != NULL) { |
121 | 339k | hashbucket = HashTableBucketFree(ht, hashbucket); |
122 | 339k | } |
123 | 524M | } |
124 | | |
125 | | /* free the array */ |
126 | 461k | if (ht->array != NULL) |
127 | 461k | SCFree(ht->array); |
128 | | |
129 | 461k | SCFree(ht); |
130 | 461k | } |
131 | | |
132 | | int HashTableAdd(HashTable *ht, void *data, uint16_t datalen) |
133 | 393k | { |
134 | 393k | if (ht == NULL || data == NULL) |
135 | 0 | return -1; |
136 | | |
137 | 393k | uint32_t hash = ht->Hash(ht, data, datalen); |
138 | | |
139 | 393k | HashTableBucket *hb = SCCalloc(1, sizeof(HashTableBucket)); |
140 | 393k | if (unlikely(hb == NULL)) |
141 | 0 | goto error; |
142 | 393k | hb->data = data; |
143 | 393k | hb->size = datalen; |
144 | 393k | hb->next = NULL; |
145 | | |
146 | 393k | if (hash >= ht->array_size) { |
147 | 0 | SCLogWarning("attempt to insert element out of hash array\n"); |
148 | 0 | goto error; |
149 | 0 | } |
150 | | |
151 | 393k | if (ht->array[hash] == NULL) { |
152 | 166k | ht->array[hash] = hb; |
153 | 227k | } else { |
154 | 227k | hb->next = ht->array[hash]; |
155 | 227k | ht->array[hash] = hb; |
156 | 227k | } |
157 | | |
158 | | #ifdef UNITTESTS |
159 | | ht->count++; |
160 | | #endif |
161 | | |
162 | 393k | return 0; |
163 | | |
164 | 0 | error: |
165 | 0 | if (hb != NULL) |
166 | 0 | SCFree(hb); |
167 | 0 | return -1; |
168 | 393k | } |
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 | 48.8k | { |
180 | 48.8k | uint32_t hash = ht->Hash(ht, data, datalen); |
181 | | |
182 | 48.8k | HashTableBucket **hashbucket = &(ht->array[hash]); |
183 | 140k | while (*hashbucket != NULL) { |
184 | 140k | if (ht->Compare((*hashbucket)->data, (*hashbucket)->size, data, datalen)) { |
185 | 48.8k | *hashbucket = HashTableBucketFree(ht, *hashbucket); |
186 | 48.8k | return 0; |
187 | 48.8k | } |
188 | 91.1k | hashbucket = &((*hashbucket)->next); |
189 | 91.1k | } |
190 | | |
191 | 0 | return -1; |
192 | 48.8k | } |
193 | | |
194 | | void *HashTableLookup(HashTable *ht, void *data, uint16_t datalen) |
195 | 2.20M | { |
196 | 2.20M | uint32_t hash = 0; |
197 | | |
198 | 2.20M | if (ht == NULL) |
199 | 0 | return NULL; |
200 | | |
201 | 2.20M | hash = ht->Hash(ht, data, datalen); |
202 | | |
203 | 2.20M | if (hash >= ht->array_size) { |
204 | 0 | SCLogWarning("attempt to access element out of hash array\n"); |
205 | 0 | return NULL; |
206 | 0 | } |
207 | | |
208 | 2.20M | if (ht->array[hash] == NULL) |
209 | 161k | return NULL; |
210 | | |
211 | 2.04M | HashTableBucket *hashbucket = ht->array[hash]; |
212 | 2.35M | do { |
213 | 2.35M | if (ht->Compare(hashbucket->data, hashbucket->size, data, datalen) == 1) |
214 | 1.94M | return hashbucket->data; |
215 | | |
216 | 411k | hashbucket = hashbucket->next; |
217 | 411k | } while (hashbucket != NULL); |
218 | | |
219 | 100k | return NULL; |
220 | 2.04M | } |
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 | 0 | { |
225 | 0 | if (ht == NULL || CallbackFn == NULL) |
226 | 0 | return; |
227 | | |
228 | 0 | for (uint32_t i = 0; i < ht->array_size; i++) { |
229 | 0 | HashTableBucket *hashbucket = ht->array[i]; |
230 | 0 | while (hashbucket != NULL) { |
231 | 0 | CallbackFn(hashbucket->data, aux); |
232 | 0 | hashbucket = hashbucket->next; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | } |
236 | | |
237 | | uint32_t HashTableGenericHash(HashTable *ht, void *data, uint16_t datalen) |
238 | 0 | { |
239 | 0 | uint8_t *d = (uint8_t *)data; |
240 | 0 | uint32_t i; |
241 | 0 | uint32_t hash = 0; |
242 | |
|
243 | 0 | for (i = 0; i < datalen; i++) { |
244 | 0 | if (i == 0) hash += (((uint32_t)*d++)); |
245 | 0 | else if (i == 1) hash += (((uint32_t)*d++) * datalen); |
246 | 0 | else hash *= (((uint32_t)*d++) * i) + datalen + i; |
247 | 0 | } |
248 | |
|
249 | 0 | hash *= datalen; |
250 | 0 | hash %= ht->array_size; |
251 | 0 | return hash; |
252 | 0 | } |
253 | | |
254 | | char HashTableDefaultCompare(void *data1, uint16_t len1, void *data2, uint16_t len2) |
255 | 0 | { |
256 | 0 | if (len1 != len2) |
257 | 0 | return 0; |
258 | | |
259 | 0 | if (SCMemcmp(data1,data2,len1) != 0) |
260 | 0 | return 0; |
261 | | |
262 | 0 | return 1; |
263 | 0 | } |
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 | | |
476 | | void HashTableRegisterTests(void) |
477 | 0 | { |
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 | 0 | } |