/src/suricata/src/util-rohash.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2024 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 read only hash table implementation, meaning that |
24 | | * after the initial fill no changes are allowed. |
25 | | * |
26 | | * Loading takes 2 stages. |
27 | | * - stage1 maps data |
28 | | * - stage2 fills blob |
29 | | * |
30 | | * \todo a bloomfilter in the ROHashTableOffsets could possibly prevent |
31 | | * a lot of cache misses when validating a potential match |
32 | | * |
33 | | * \todo maybe add a user ctx to be returned instead, something like a |
34 | | * 4/8 byte ptr or simply a flag |
35 | | */ |
36 | | |
37 | | #include "suricata-common.h" |
38 | | #include "util-hash.h" |
39 | | #include "util-unittest.h" |
40 | | #include "util-memcmp.h" |
41 | | #include "util-hash-lookup3.h" |
42 | | #include "util-rohash.h" |
43 | | #include "util-debug.h" |
44 | | |
45 | | /** item_size data beyond this header */ |
46 | | typedef struct ROHashTableItem_ { |
47 | | uint32_t pos; /**< position relative to other values with same hash */ |
48 | | TAILQ_ENTRY(ROHashTableItem_) next; |
49 | | } ROHashTableItem; |
50 | | |
51 | | /** offset table */ |
52 | | typedef struct ROHashTableOffsets_ { |
53 | | uint32_t cnt; /**< number of items for this hash */ |
54 | | uint32_t offset; /**< position in the blob of the first item */ |
55 | | } ROHashTableOffsets; |
56 | | |
57 | | /** \brief initialize a new rohash |
58 | | * |
59 | | * \param hash_bits hash size as 2^hash_bits, so power of 2, max 31 |
60 | | * \param item_size size of the data to store |
61 | | * |
62 | | * \retval table ptr or NULL on error |
63 | | */ |
64 | | ROHashTable *ROHashInit(uint8_t hash_bits, uint16_t item_size) |
65 | 1.30k | { |
66 | 1.30k | if (item_size % 4 != 0 || item_size == 0) { |
67 | 0 | SCLogError("data size must be multiple of 4"); |
68 | 0 | return NULL; |
69 | 0 | } |
70 | 1.30k | if (hash_bits < 4 || hash_bits > 31) { |
71 | 0 | SCLogError("invalid hash_bits setting, valid range is 4-31"); |
72 | 0 | return NULL; |
73 | 0 | } |
74 | | |
75 | 1.30k | uint32_t size = hashsize(hash_bits) * sizeof(ROHashTableOffsets); |
76 | | |
77 | 1.30k | ROHashTable *table = SCCalloc(1, sizeof(ROHashTable) + size); |
78 | 1.30k | if (unlikely(table == NULL)) { |
79 | 0 | SCLogError("failed to alloc memory"); |
80 | 0 | return NULL; |
81 | 0 | } |
82 | | |
83 | 1.30k | table->items = 0; |
84 | 1.30k | table->item_size = item_size; |
85 | 1.30k | table->hash_bits = hash_bits; |
86 | 1.30k | TAILQ_INIT(&table->head); |
87 | | |
88 | 1.30k | return table; |
89 | 1.30k | } |
90 | | |
91 | | void ROHashFree(ROHashTable *table) |
92 | 1.30k | { |
93 | 1.30k | if (table != NULL) { |
94 | 1.30k | if (table->data != NULL) { |
95 | 0 | SCFree(table->data); |
96 | 0 | } |
97 | | |
98 | 1.30k | SCFree(table); |
99 | 1.30k | } |
100 | 1.30k | } |
101 | | |
102 | | uint32_t ROHashMemorySize(ROHashTable *table) |
103 | 0 | { |
104 | 0 | uint32_t r1 = hashsize(table->hash_bits) * sizeof(ROHashTableOffsets); |
105 | 0 | uint32_t r2 = table->items * table->item_size; |
106 | 0 | return (uint32_t)(r1 + r2 + sizeof(ROHashTable)); |
107 | 0 | } |
108 | | |
109 | | /** |
110 | | * \retval NULL not found |
111 | | * \retval ptr found |
112 | | */ |
113 | | void *ROHashLookup(ROHashTable *table, void *data, uint16_t size) |
114 | 0 | { |
115 | 0 | if (data == NULL || size != table->item_size) { |
116 | 0 | SCReturnPtr(NULL, "void"); |
117 | 0 | } |
118 | | |
119 | 0 | const uint32_t hash = hashword(data, table->item_size / 4, 0) & hashmask(table->hash_bits); |
120 | | |
121 | | /* get offsets start */ |
122 | 0 | const ROHashTableOffsets *os = (ROHashTableOffsets *)((uint8_t *)table + sizeof(ROHashTable)); |
123 | 0 | const ROHashTableOffsets *o = &os[hash]; |
124 | | |
125 | | /* no matches */ |
126 | 0 | if (o->cnt == 0) { |
127 | 0 | SCReturnPtr(NULL, "void"); |
128 | 0 | } |
129 | | |
130 | 0 | for (uint32_t u = 0; u < o->cnt; u++) { |
131 | 0 | uint32_t offset = (o->offset + u) * table->item_size; |
132 | |
|
133 | 0 | if (SCMemcmp(table->data + offset, data, table->item_size) == 0) { |
134 | 0 | SCReturnPtr(table->data + offset, "void"); |
135 | 0 | } |
136 | 0 | } |
137 | 0 | SCReturnPtr(NULL, "void"); |
138 | 0 | } |
139 | | |
140 | | /** \brief Add a new value to the hash |
141 | | * |
142 | | * \note can only be done when table isn't in a locked state yet |
143 | | * |
144 | | * \param table the hash table |
145 | | * \param value value to add |
146 | | * \param size value size. *MUST* match table item_size |
147 | | * |
148 | | * \retval 0 error |
149 | | * \retval 1 ok |
150 | | */ |
151 | | int ROHashInitQueueValue(ROHashTable *table, void *value, uint16_t size) |
152 | 0 | { |
153 | 0 | if (table->locked) { |
154 | 0 | SCLogError("can't add value to locked table"); |
155 | 0 | return 0; |
156 | 0 | } |
157 | 0 | if (table->item_size != size) { |
158 | 0 | SCLogError("wrong size for data %u != %u", size, table->item_size); |
159 | 0 | return 0; |
160 | 0 | } |
161 | | |
162 | 0 | ROHashTableItem *item = SCCalloc(1, sizeof(ROHashTableItem) + table->item_size); |
163 | 0 | if (item != NULL) { |
164 | 0 | memcpy((void *)item + sizeof(ROHashTableItem), value, table->item_size); |
165 | 0 | TAILQ_INSERT_TAIL(&table->head, item, next); |
166 | 0 | return 1; |
167 | 0 | } |
168 | | |
169 | 0 | return 0; |
170 | 0 | } |
171 | | |
172 | | /** \brief create final hash data structure |
173 | | * |
174 | | * \param table the hash table |
175 | | * |
176 | | * \retval 0 error |
177 | | * \retval 1 ok |
178 | | * |
179 | | * \note after this call the nothing can be added to the hash anymore. |
180 | | */ |
181 | | int ROHashInitFinalize(ROHashTable *table) |
182 | 34 | { |
183 | 34 | if (table->locked) { |
184 | 0 | SCLogError("table already locked"); |
185 | 0 | return 0; |
186 | 0 | } |
187 | | |
188 | 34 | ROHashTableItem *item = NULL; |
189 | 34 | ROHashTableOffsets *os = (ROHashTableOffsets *)((uint8_t *)table + sizeof(ROHashTable)); |
190 | | |
191 | | /* count items per hash value */ |
192 | 34 | TAILQ_FOREACH(item, &table->head, next) { |
193 | 0 | uint32_t hash = |
194 | 0 | hashword((uint32_t *)((uint8_t *)item + sizeof(*item)), table->item_size / 4, 0) & |
195 | 0 | hashmask(table->hash_bits); |
196 | 0 | ROHashTableOffsets *o = &os[hash]; |
197 | |
|
198 | 0 | item->pos = o->cnt; |
199 | 0 | o->cnt++; |
200 | 0 | table->items++; |
201 | 0 | } |
202 | | |
203 | 34 | if (table->items == 0) { |
204 | 34 | SCLogError("no items"); |
205 | 34 | return 0; |
206 | 34 | } |
207 | | |
208 | | /* get the data block */ |
209 | 0 | uint32_t newsize = table->items * table->item_size; |
210 | 0 | table->data = SCCalloc(1, newsize); |
211 | 0 | if (table->data == NULL) { |
212 | 0 | SCLogError("failed to alloc memory"); |
213 | 0 | return 0; |
214 | 0 | } |
215 | | |
216 | | /* calc offsets into the block per hash value */ |
217 | 0 | uint32_t total = 0; |
218 | 0 | for (uint32_t x = 0; x < hashsize(table->hash_bits); x++) { |
219 | 0 | ROHashTableOffsets *o = &os[x]; |
220 | |
|
221 | 0 | if (o->cnt == 0) |
222 | 0 | continue; |
223 | | |
224 | 0 | o->offset = total; |
225 | 0 | total += o->cnt; |
226 | 0 | } |
227 | | |
228 | | /* copy each value into the data block */ |
229 | 0 | TAILQ_FOREACH(item, &table->head, next) { |
230 | 0 | uint32_t hash = |
231 | 0 | hashword((uint32_t *)((uint8_t *)item + sizeof(*item)), table->item_size / 4, 0) & |
232 | 0 | hashmask(table->hash_bits); |
233 | |
|
234 | 0 | ROHashTableOffsets *o = &os[hash]; |
235 | 0 | uint32_t offset = (o->offset + item->pos) * table->item_size; |
236 | |
|
237 | 0 | memcpy(table->data + offset, (uint8_t *)item + sizeof(*item), table->item_size); |
238 | 0 | } |
239 | | |
240 | | /* clean up temp items */ |
241 | 0 | while ((item = TAILQ_FIRST(&table->head))) { |
242 | 0 | TAILQ_REMOVE(&table->head, item, next); |
243 | 0 | SCFree(item); |
244 | 0 | } |
245 | |
|
246 | 0 | table->locked = 1; |
247 | 0 | return 1; |
248 | 0 | } |