blob: 246fb34df9ed60e4f57054ea98322cb8d532ed14 [file] [log] [blame]
Radek Krejci5aeea3a2018-09-05 13:29:36 +02001/**
2 * @file hash_table.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
Michal Vaskoae130f52023-04-20 14:25:16 +02004 * @author Michal Vasko <mvasko@cesnet.cz>
5 * @brief libyang generic hash table implementation
Radek Krejci5aeea3a2018-09-05 13:29:36 +02006 *
Michal Vaskoae130f52023-04-20 14:25:16 +02007 * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
Radek Krejci5aeea3a2018-09-05 13:29:36 +02008 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
Radek Krejci535ea9f2020-05-29 16:01:05 +020016#include "hash_table.h"
Radek Krejcie7b95092019-05-15 11:03:07 +020017
Michal Vasko69730152020-10-09 16:30:07 +020018#include <assert.h>
19#include <pthread.h>
Radek Krejci5aeea3a2018-09-05 13:29:36 +020020#include <stdint.h>
21#include <stdlib.h>
Michal Vasko69730152020-10-09 16:30:07 +020022#include <string.h>
Radek Krejci5aeea3a2018-09-05 13:29:36 +020023
Radek Krejci535ea9f2020-05-29 16:01:05 +020024#include "common.h"
Michal Vasko69730152020-10-09 16:30:07 +020025#include "compat.h"
Radek Krejci535ea9f2020-05-29 16:01:05 +020026#include "dict.h"
Radek Krejci857189e2020-09-01 13:26:36 +020027#include "log.h"
Radek Krejci5aeea3a2018-09-05 13:29:36 +020028
Michal Vaskoae130f52023-04-20 14:25:16 +020029LIBYANG_API_DEF uint32_t
30lyht_hash_multi(uint32_t hash, const char *key_part, size_t len)
Radek Krejci5aeea3a2018-09-05 13:29:36 +020031{
32 uint32_t i;
33
aPiecek4f07c3e2021-06-11 10:53:07 +020034 if (key_part && len) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +020035 for (i = 0; i < len; ++i) {
36 hash += key_part[i];
37 hash += (hash << 10);
38 hash ^= (hash >> 6);
39 }
40 } else {
41 hash += (hash << 3);
42 hash ^= (hash >> 11);
43 hash += (hash << 15);
44 }
45
46 return hash;
47}
48
Michal Vaskoae130f52023-04-20 14:25:16 +020049LIBYANG_API_DEF uint32_t
50lyht_hash(const char *key, size_t len)
Radek Krejcif2dc4c52018-11-08 09:04:13 +010051{
52 uint32_t hash;
53
Michal Vaskoae130f52023-04-20 14:25:16 +020054 hash = lyht_hash_multi(0, key, len);
55 return lyht_hash_multi(hash, NULL, len);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020056}
57
Michal Vasko8efac242023-03-30 08:24:56 +020058struct ly_ht_rec *
Radek Krejci5aeea3a2018-09-05 13:29:36 +020059lyht_get_rec(unsigned char *recs, uint16_t rec_size, uint32_t idx)
60{
Michal Vasko8efac242023-03-30 08:24:56 +020061 return (struct ly_ht_rec *)&recs[idx * rec_size];
Radek Krejci5aeea3a2018-09-05 13:29:36 +020062}
63
Michal Vaskoae130f52023-04-20 14:25:16 +020064LIBYANG_API_DEF struct ly_ht *
Michal Vasko62524a92021-02-26 10:08:50 +010065lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void *cb_data, uint16_t resize)
Radek Krejci5aeea3a2018-09-05 13:29:36 +020066{
Michal Vasko8efac242023-03-30 08:24:56 +020067 struct ly_ht *ht;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020068
69 /* check that 2^x == size (power of 2) */
70 assert(size && !(size & (size - 1)));
71 assert(val_equal && val_size);
72 assert(resize == 0 || resize == 1);
73
74 if (size < LYHT_MIN_SIZE) {
75 size = LYHT_MIN_SIZE;
76 }
77
78 ht = malloc(sizeof *ht);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020079 LY_CHECK_ERR_RET(!ht, LOGMEM(NULL), NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020080
81 ht->used = 0;
82 ht->size = size;
Michal Vaskodc95d9c2021-04-12 15:11:48 +020083 ht->invalid = 0;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020084 ht->val_equal = val_equal;
85 ht->cb_data = cb_data;
Radek Krejci1deb5be2020-08-26 16:43:36 +020086 ht->resize = resize;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020087
Michal Vasko8efac242023-03-30 08:24:56 +020088 ht->rec_size = (sizeof(struct ly_ht_rec) - 1) + val_size;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020089 /* allocate the records correctly */
90 ht->recs = calloc(size, ht->rec_size);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020091 LY_CHECK_ERR_RET(!ht->recs, free(ht); LOGMEM(NULL), NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020092
93 return ht;
94}
95
Michal Vaskoae130f52023-04-20 14:25:16 +020096LIBYANG_API_DEF lyht_value_equal_cb
Michal Vasko8efac242023-03-30 08:24:56 +020097lyht_set_cb(struct ly_ht *ht, lyht_value_equal_cb new_val_equal)
Radek Krejci5aeea3a2018-09-05 13:29:36 +020098{
Michal Vasko62524a92021-02-26 10:08:50 +010099 lyht_value_equal_cb prev;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200100
101 prev = ht->val_equal;
102 ht->val_equal = new_val_equal;
103 return prev;
104}
105
Michal Vaskoae130f52023-04-20 14:25:16 +0200106LIBYANG_API_DEF void *
Michal Vasko8efac242023-03-30 08:24:56 +0200107lyht_set_cb_data(struct ly_ht *ht, void *new_cb_data)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200108{
109 void *prev;
110
111 prev = ht->cb_data;
112 ht->cb_data = new_cb_data;
113 return prev;
114}
115
Michal Vaskoae130f52023-04-20 14:25:16 +0200116LIBYANG_API_DEF struct ly_ht *
Michal Vasko8efac242023-03-30 08:24:56 +0200117lyht_dup(const struct ly_ht *orig)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200118{
Michal Vasko8efac242023-03-30 08:24:56 +0200119 struct ly_ht *ht;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200120
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200121 LY_CHECK_ARG_RET(NULL, orig, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200122
Michal Vasko8efac242023-03-30 08:24:56 +0200123 ht = lyht_new(orig->size, orig->rec_size - (sizeof(struct ly_ht_rec) - 1), orig->val_equal, orig->cb_data, orig->resize ? 1 : 0);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200124 if (!ht) {
125 return NULL;
126 }
127
Radek Krejcid78fd462021-06-02 21:29:20 +0200128 memcpy(ht->recs, orig->recs, (size_t)orig->used * (size_t)orig->rec_size);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200129 ht->used = orig->used;
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200130 ht->invalid = orig->invalid;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200131 return ht;
132}
133
Michal Vaskoae130f52023-04-20 14:25:16 +0200134LIBYANG_API_DEF void
Michal Vasko8efac242023-03-30 08:24:56 +0200135lyht_free(struct ly_ht *ht, void (*val_free)(void *val_p))
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200136{
Michal Vasko8efac242023-03-30 08:24:56 +0200137 struct ly_ht_rec *rec;
Michal Vasko77b7f90a2023-01-31 15:42:41 +0100138 uint32_t i;
139
140 if (!ht) {
141 return;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200142 }
Michal Vasko77b7f90a2023-01-31 15:42:41 +0100143
144 if (val_free) {
145 for (i = 0; i < ht->size; ++i) {
146 rec = lyht_get_rec(ht->recs, ht->rec_size, i);
147 if (rec->hits > 0) {
148 val_free(&rec->val);
149 }
150 }
151 }
152 free(ht->recs);
153 free(ht);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200154}
155
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200156/**
157 * @brief Resize a hash table.
158 *
159 * @param[in] ht Hash table to resize.
160 * @param[in] operation Operation to perform. 1 to enlarge, -1 to shrink, 0 to only rehash all records.
161 * @return LY_ERR value.
162 */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200163static LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200164lyht_resize(struct ly_ht *ht, int operation)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200165{
Michal Vasko8efac242023-03-30 08:24:56 +0200166 struct ly_ht_rec *rec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200167 unsigned char *old_recs;
168 uint32_t i, old_size;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200169
170 old_recs = ht->recs;
171 old_size = ht->size;
172
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200173 if (operation > 0) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200174 /* double the size */
175 ht->size <<= 1;
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200176 } else if (operation < 0) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200177 /* half the size */
178 ht->size >>= 1;
179 }
180
181 ht->recs = calloc(ht->size, ht->rec_size);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200182 LY_CHECK_ERR_RET(!ht->recs, LOGMEM(NULL); ht->recs = old_recs; ht->size = old_size, LY_EMEM);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200183
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200184 /* reset used and invalid, it will increase again */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200185 ht->used = 0;
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200186 ht->invalid = 0;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200187
188 /* add all the old records into the new records array */
189 for (i = 0; i < old_size; ++i) {
190 rec = lyht_get_rec(old_recs, ht->rec_size, i);
191 if (rec->hits > 0) {
Radek Krejci1deb5be2020-08-26 16:43:36 +0200192 LY_ERR ret = lyht_insert(ht, rec->val, rec->hash, NULL);
Michal Vasko26bbb272022-08-02 14:54:33 +0200193
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200194 assert(!ret);
195 (void)ret;
196 }
197 }
198
199 /* final touches */
200 free(old_recs);
201 return LY_SUCCESS;
202}
203
Michal Vaskoda859032020-07-14 12:20:14 +0200204/**
205 * @brief Search for the first match.
206 *
207 * @param[in] ht Hash table to search in.
208 * @param[in] hash Hash to find.
209 * @param[out] rec_p Optional found record.
210 * @return LY_SUCCESS hash found, returned its record,
211 * @return LY_ENOTFOUND hash not found, returned the record where it would be inserted.
212 */
213static LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200214lyht_find_first(struct ly_ht *ht, uint32_t hash, struct ly_ht_rec **rec_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200215{
Michal Vasko8efac242023-03-30 08:24:56 +0200216 struct ly_ht_rec *rec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200217 uint32_t i, idx;
218
219 if (rec_p) {
220 *rec_p = NULL;
221 }
222
223 idx = i = hash & (ht->size - 1);
224 rec = lyht_get_rec(ht->recs, ht->rec_size, idx);
225
226 /* skip through overflow and deleted records */
227 while ((rec->hits != 0) && ((rec->hits == -1) || ((rec->hash & (ht->size - 1)) != idx))) {
228 if ((rec->hits == -1) && rec_p && !(*rec_p)) {
229 /* remember this record for return */
230 *rec_p = rec;
231 }
232 i = (i + 1) % ht->size;
233 if (i == idx) {
234 /* we went through all the records (very unlikely, but possible when many records are invalid),
235 * just return not found */
236 assert(!rec_p || *rec_p);
Michal Vaskoda859032020-07-14 12:20:14 +0200237 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200238 }
239 rec = lyht_get_rec(ht->recs, ht->rec_size, i);
240 }
241 if (rec->hits == 0) {
242 /* we could not find the value */
243 if (rec_p && !*rec_p) {
244 *rec_p = rec;
245 }
Michal Vaskoda859032020-07-14 12:20:14 +0200246 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200247 }
248
249 /* we have found a record with equal (shortened) hash */
250 if (rec_p) {
251 *rec_p = rec;
252 }
Michal Vaskoda859032020-07-14 12:20:14 +0200253 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200254}
255
256/**
257 * @brief Search for the next collision.
258 *
259 * @param[in] ht Hash table to search in.
260 * @param[in,out] last Last returned collision record.
261 * @param[in] first First collision record (hits > 1).
Michal Vaskoda859032020-07-14 12:20:14 +0200262 * @return LY_SUCCESS when hash collision found, \p last points to this next collision,
263 * @return LY_ENOTFOUND when hash collision not found, \p last points to the record where it would be inserted.
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200264 */
Michal Vaskoda859032020-07-14 12:20:14 +0200265static LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200266lyht_find_collision(struct ly_ht *ht, struct ly_ht_rec **last, struct ly_ht_rec *first)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200267{
Michal Vasko8efac242023-03-30 08:24:56 +0200268 struct ly_ht_rec *empty = NULL;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200269 uint32_t i, idx;
270
271 assert(last && *last);
272
273 idx = (*last)->hash & (ht->size - 1);
274 i = (((unsigned char *)*last) - ht->recs) / ht->rec_size;
275
276 do {
277 i = (i + 1) % ht->size;
278 *last = lyht_get_rec(ht->recs, ht->rec_size, i);
279 if (*last == first) {
280 /* we went through all the records (very unlikely, but possible when many records are invalid),
281 * just return an invalid record */
282 assert(empty);
283 *last = empty;
Michal Vaskoda859032020-07-14 12:20:14 +0200284 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200285 }
286
287 if (((*last)->hits == -1) && !empty) {
288 empty = *last;
289 }
290 } while (((*last)->hits != 0) && (((*last)->hits == -1) || (((*last)->hash & (ht->size - 1)) != idx)));
291
292 if ((*last)->hits > 0) {
293 /* we found a collision */
294 assert((*last)->hits == 1);
Michal Vaskoda859032020-07-14 12:20:14 +0200295 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200296 }
297
298 /* no next collision found, return the record where it would be inserted */
299 if (empty) {
300 *last = empty;
301 } /* else (*last)->hits == 0, it is already correct */
Michal Vaskoda859032020-07-14 12:20:14 +0200302 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200303}
304
Michal Vaskoda859032020-07-14 12:20:14 +0200305/**
306 * @brief Search for a record with specific value and hash.
307 *
308 * @param[in] ht Hash table to search in.
309 * @param[in] val_p Pointer to the value to find.
310 * @param[in] hash Hash to find.
Radek Krejci857189e2020-09-01 13:26:36 +0200311 * @param[in] mod Whether the operation modifies the hash table (insert or remove) or not (find).
Michal Vaskoda859032020-07-14 12:20:14 +0200312 * @param[out] crec_p Optional found first record.
313 * @param[out] col Optional collision number of @p rec_p, 0 for no collision.
314 * @param[out] rec_p Found exact matching record, may be a collision of @p crec_p.
315 * @return LY_ENOTFOUND if no record found,
316 * @return LY_SUCCESS if record was found.
317 */
318static LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200319lyht_find_rec(struct ly_ht *ht, void *val_p, uint32_t hash, ly_bool mod, struct ly_ht_rec **crec_p, uint32_t *col,
320 struct ly_ht_rec **rec_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200321{
Michal Vasko8efac242023-03-30 08:24:56 +0200322 struct ly_ht_rec *rec, *crec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200323 uint32_t i, c;
Michal Vaskoda859032020-07-14 12:20:14 +0200324 LY_ERR r;
325
326 if (crec_p) {
327 *crec_p = NULL;
328 }
329 if (col) {
330 *col = 0;
331 }
332 *rec_p = NULL;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200333
334 if (lyht_find_first(ht, hash, &rec)) {
335 /* not found */
Michal Vaskoda859032020-07-14 12:20:14 +0200336 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200337 }
Michal Vaskoda859032020-07-14 12:20:14 +0200338 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200339 /* even the value matches */
Michal Vaskoda859032020-07-14 12:20:14 +0200340 if (crec_p) {
341 *crec_p = rec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200342 }
Michal Vaskoda859032020-07-14 12:20:14 +0200343 if (col) {
344 *col = 0;
345 }
346 *rec_p = rec;
347 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200348 }
349
350 /* some collisions, we need to go through them, too */
351 crec = rec;
Michal Vaskoda859032020-07-14 12:20:14 +0200352 c = crec->hits;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200353 for (i = 1; i < c; ++i) {
354 r = lyht_find_collision(ht, &rec, crec);
355 assert(!r);
356 (void)r;
357
358 /* compare values */
Michal Vaskoda859032020-07-14 12:20:14 +0200359 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) {
360 if (crec_p) {
361 *crec_p = crec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200362 }
Michal Vaskoda859032020-07-14 12:20:14 +0200363 if (col) {
364 *col = i;
365 }
366 *rec_p = rec;
367 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200368 }
369 }
370
371 /* not found even in collisions */
Michal Vaskoda859032020-07-14 12:20:14 +0200372 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200373}
374
Michal Vaskoae130f52023-04-20 14:25:16 +0200375LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200376lyht_find(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p)
Michal Vaskoda859032020-07-14 12:20:14 +0200377{
Michal Vasko8efac242023-03-30 08:24:56 +0200378 struct ly_ht_rec *rec;
Michal Vaskoda859032020-07-14 12:20:14 +0200379
380 lyht_find_rec(ht, val_p, hash, 0, NULL, NULL, &rec);
381
382 if (rec && match_p) {
383 *match_p = rec->val;
384 }
385 return rec ? LY_SUCCESS : LY_ENOTFOUND;
386}
387
Michal Vaskoae130f52023-04-20 14:25:16 +0200388LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200389lyht_find_next_with_collision_cb(struct ly_ht *ht, void *val_p, uint32_t hash,
Michal Vasko6374de22022-09-05 15:48:48 +0200390 lyht_value_equal_cb collision_val_equal, void **match_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200391{
Michal Vasko8efac242023-03-30 08:24:56 +0200392 struct ly_ht_rec *rec, *crec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200393 uint32_t i, c;
Michal Vaskoda859032020-07-14 12:20:14 +0200394 LY_ERR r;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200395
Michal Vasko6374de22022-09-05 15:48:48 +0200396 /* find the record of the previously found value */
Michal Vaskoda859032020-07-14 12:20:14 +0200397 if (lyht_find_rec(ht, val_p, hash, 1, &crec, &i, &rec)) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200398 /* not found, cannot happen */
Michal Vaskoda859032020-07-14 12:20:14 +0200399 LOGINT_RET(NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200400 }
401
Michal Vasko6374de22022-09-05 15:48:48 +0200402 /* go through collisions and find the next one after the previous one */
Michal Vaskoda859032020-07-14 12:20:14 +0200403 c = crec->hits;
404 for (++i; i < c; ++i) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200405 r = lyht_find_collision(ht, &rec, crec);
406 assert(!r);
407 (void)r;
408
Michal Vasko6374de22022-09-05 15:48:48 +0200409 if (rec->hash != hash) {
410 continue;
411 }
412
413 if (collision_val_equal) {
414 if (collision_val_equal(val_p, &rec->val, 0, ht->cb_data)) {
415 /* even the value matches */
416 if (match_p) {
417 *match_p = rec->val;
418 }
419 return LY_SUCCESS;
420 }
421 } else if (ht->val_equal(val_p, &rec->val, 0, ht->cb_data)) {
Michal Vaskoda859032020-07-14 12:20:14 +0200422 /* even the value matches */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200423 if (match_p) {
424 *match_p = rec->val;
425 }
Michal Vaskoda859032020-07-14 12:20:14 +0200426 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200427 }
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200428 }
429
430 /* the last equal value was already returned */
Michal Vaskoda859032020-07-14 12:20:14 +0200431 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200432}
433
Michal Vaskoae130f52023-04-20 14:25:16 +0200434LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200435lyht_find_next(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p)
Michal Vasko6374de22022-09-05 15:48:48 +0200436{
437 return lyht_find_next_with_collision_cb(ht, val_p, hash, NULL, match_p);
438}
439
Michal Vaskoae130f52023-04-20 14:25:16 +0200440LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200441lyht_insert_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal,
Michal Vasko62524a92021-02-26 10:08:50 +0100442 void **match_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200443{
Radek Krejci1deb5be2020-08-26 16:43:36 +0200444 LY_ERR r, ret = LY_SUCCESS;
Michal Vasko8efac242023-03-30 08:24:56 +0200445 struct ly_ht_rec *rec, *crec = NULL;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200446 int32_t i;
Michal Vasko62524a92021-02-26 10:08:50 +0100447 lyht_value_equal_cb old_val_equal = NULL;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200448
449 if (!lyht_find_first(ht, hash, &rec)) {
450 /* we found matching shortened hash */
451 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
452 /* even the value matches */
453 if (match_p) {
454 *match_p = (void *)&rec->val;
455 }
456 return LY_EEXIST;
457 }
458
459 /* some collisions, we need to go through them, too */
460 crec = rec;
461 for (i = 1; i < crec->hits; ++i) {
462 r = lyht_find_collision(ht, &rec, crec);
463 assert(!r);
464
465 /* compare values */
466 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
467 if (match_p) {
468 *match_p = (void *)&rec->val;
469 }
470 return LY_EEXIST;
471 }
472 }
473
474 /* value not found, get the record where it will be inserted */
475 r = lyht_find_collision(ht, &rec, crec);
476 assert(r);
477 }
478
479 /* insert it into the returned record */
480 assert(rec->hits < 1);
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200481 if (rec->hits < 0) {
482 --ht->invalid;
483 }
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200484 rec->hash = hash;
485 rec->hits = 1;
Michal Vasko8efac242023-03-30 08:24:56 +0200486 memcpy(&rec->val, val_p, ht->rec_size - (sizeof(struct ly_ht_rec) - 1));
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200487 if (match_p) {
488 *match_p = (void *)&rec->val;
489 }
490
491 if (crec) {
492 /* there was a collision, increase hits */
493 if (crec->hits == INT32_MAX) {
494 LOGINT(NULL);
495 }
496 ++crec->hits;
497 }
498
499 /* check size & enlarge if needed */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200500 ++ht->used;
501 if (ht->resize) {
Radek Krejcif13b87b2020-12-01 22:02:17 +0100502 r = (ht->used * LYHT_HUNDRED_PERCENTAGE) / ht->size;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200503 if ((ht->resize == 1) && (r >= LYHT_FIRST_SHRINK_PERCENTAGE)) {
504 /* enable shrinking */
505 ht->resize = 2;
506 }
507 if ((ht->resize == 2) && (r >= LYHT_ENLARGE_PERCENTAGE)) {
508 if (resize_val_equal) {
509 old_val_equal = lyht_set_cb(ht, resize_val_equal);
510 }
511
512 /* enlarge */
513 ret = lyht_resize(ht, 1);
514 /* if hash_table was resized, we need to find new matching value */
Michal Vasko69730152020-10-09 16:30:07 +0200515 if ((ret == LY_SUCCESS) && match_p) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200516 lyht_find(ht, val_p, hash, match_p);
517 }
518
519 if (resize_val_equal) {
520 lyht_set_cb(ht, old_val_equal);
521 }
522 }
523 }
524 return ret;
525}
526
Michal Vaskoae130f52023-04-20 14:25:16 +0200527LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200528lyht_insert(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200529{
530 return lyht_insert_with_resize_cb(ht, val_p, hash, NULL, match_p);
531}
532
Michal Vaskoae130f52023-04-20 14:25:16 +0200533LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200534lyht_remove_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200535{
Michal Vasko8efac242023-03-30 08:24:56 +0200536 struct ly_ht_rec *rec, *crec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200537 int32_t i;
Radek Krejci857189e2020-09-01 13:26:36 +0200538 ly_bool first_matched = 0;
Radek Krejci1deb5be2020-08-26 16:43:36 +0200539 LY_ERR r, ret = LY_SUCCESS;
stewegd8e2fc92023-05-31 09:52:56 +0200540 lyht_value_equal_cb old_val_equal = NULL;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200541
Michal Vasko4a4c7ed2020-07-17 09:30:12 +0200542 LY_CHECK_ERR_RET(lyht_find_first(ht, hash, &rec), LOGARG(NULL, hash), LY_ENOTFOUND); /* hash not found */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200543
544 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
545 /* even the value matches */
546 first_matched = 1;
547 }
548
549 /* we always need to go through collisions */
550 crec = rec;
551 for (i = 1; i < crec->hits; ++i) {
552 r = lyht_find_collision(ht, &rec, crec);
553 assert(!r);
554
555 /* compare values */
556 if (!first_matched && (rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
557 break;
558 }
559 }
560
561 if (i < crec->hits) {
562 /* one of collisions matched, reduce collision count, remove the record */
563 assert(!first_matched);
564 --crec->hits;
565 rec->hits = -1;
566 } else if (first_matched) {
567 /* the first record matches */
568 if (crec != rec) {
569 /* ... so put the last collision in its place */
570 rec->hits = crec->hits - 1;
571 memcpy(crec, rec, ht->rec_size);
572 }
573 rec->hits = -1;
574 } else {
575 /* value not found even in collisions */
Michal Vasko4a4c7ed2020-07-17 09:30:12 +0200576 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200577 }
578
579 /* check size & shrink if needed */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200580 --ht->used;
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200581 ++ht->invalid;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200582 if (ht->resize == 2) {
Radek Krejcif13b87b2020-12-01 22:02:17 +0100583 r = (ht->used * LYHT_HUNDRED_PERCENTAGE) / ht->size;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200584 if ((r < LYHT_SHRINK_PERCENTAGE) && (ht->size > LYHT_MIN_SIZE)) {
Michal Vasko5bcc33b2020-10-06 15:33:44 +0200585 if (resize_val_equal) {
586 old_val_equal = lyht_set_cb(ht, resize_val_equal);
587 }
588
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200589 /* shrink */
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200590 ret = lyht_resize(ht, -1);
Michal Vasko5bcc33b2020-10-06 15:33:44 +0200591
592 if (resize_val_equal) {
593 lyht_set_cb(ht, old_val_equal);
594 }
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200595 }
596 }
597
Michal Vaskodc95d9c2021-04-12 15:11:48 +0200598 /* rehash all records if needed */
599 r = ((ht->size - ht->used - ht->invalid) * 100) / ht->size;
600 if (r < LYHT_REHASH_PERCENTAGE) {
601 if (resize_val_equal) {
602 old_val_equal = lyht_set_cb(ht, resize_val_equal);
603 }
604
605 /* rehash */
606 ret = lyht_resize(ht, 0);
607
608 if (resize_val_equal) {
609 lyht_set_cb(ht, old_val_equal);
610 }
611 }
612
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200613 return ret;
614}
Michal Vasko5bcc33b2020-10-06 15:33:44 +0200615
Michal Vaskoae130f52023-04-20 14:25:16 +0200616LIBYANG_API_DEF LY_ERR
Michal Vasko8efac242023-03-30 08:24:56 +0200617lyht_remove(struct ly_ht *ht, void *val_p, uint32_t hash)
Michal Vasko5bcc33b2020-10-06 15:33:44 +0200618{
619 return lyht_remove_with_resize_cb(ht, val_p, hash, NULL);
620}
Michal Vasko626196f2022-08-05 12:49:52 +0200621
Michal Vaskoae130f52023-04-20 14:25:16 +0200622LIBYANG_API_DEF uint32_t
Michal Vasko626196f2022-08-05 12:49:52 +0200623lyht_get_fixed_size(uint32_t item_count)
624{
625 uint32_t i, size = 0;
626
627 /* detect number of upper zero bits in the items' counter value ... */
628 for (i = (sizeof item_count * CHAR_BIT) - 1; i > 0; i--) {
629 size = item_count << i;
630 size = size >> i;
631 if (size == item_count) {
632 break;
633 }
634 }
635 assert(i);
636
637 /* ... and then we convert it to the position of the highest non-zero bit ... */
638 i = (sizeof item_count * CHAR_BIT) - i;
639
640 /* ... and by using it to shift 1 to the left we get the closest sufficient hash table size */
641 size = 1 << i;
642
643 return size;
644}