Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}