blob: 928a96371a0628ae95b3625b91cbc53f33d1edb3 [file] [log] [blame]
Radek Krejci5aeea3a2018-09-05 13:29:36 +02001/**
2 * @file hash_table.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief libyang dictionary for storing strings and generic hash table
5 *
6 * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
Radek Krejci535ea9f2020-05-29 16:01:05 +020015#include "hash_table.h"
Radek Krejcie7b95092019-05-15 11:03:07 +020016
Radek Krejci5aeea3a2018-09-05 13:29:36 +020017#include <string.h>
18#include <stdint.h>
19#include <stdlib.h>
20#include <pthread.h>
21#include <assert.h>
22
Michal Vaskoc5a22832020-08-20 13:21:33 +020023#include "compat.h"
Radek Krejci535ea9f2020-05-29 16:01:05 +020024#include "common.h"
25#include "dict.h"
Radek Krejci5aeea3a2018-09-05 13:29:36 +020026
27static int
28lydict_val_eq(void *val1_p, void *val2_p, int UNUSED(mod), void *cb_data)
29{
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020030 LY_CHECK_ARG_RET(NULL, val1_p, val2_p, cb_data, 0);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020031
32 const char *str1 = ((struct dict_rec *)val1_p)->value;
33 const char *str2 = ((struct dict_rec *)val2_p)->value;
34
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020035 LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0);
36 LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020037
38 if (strncmp(str1, str2, *(size_t *)cb_data) == 0) {
39 return 1;
40 }
41
42 return 0;
43}
44
45void
46lydict_init(struct dict_table *dict)
47{
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020048 LY_CHECK_ARG_RET(NULL, dict,);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020049
50 dict->hash_tab = lyht_new(1024, sizeof(struct dict_rec), lydict_val_eq, NULL, 1);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020051 LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), );
Radek Krejci5aeea3a2018-09-05 13:29:36 +020052 pthread_mutex_init(&dict->lock, NULL);
53}
54
55void
56lydict_clean(struct dict_table *dict)
57{
58 unsigned int i;
59 struct dict_rec *dict_rec = NULL;
60 struct ht_rec *rec = NULL;
61
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020062 LY_CHECK_ARG_RET(NULL, dict,);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020063
64 for (i = 0; i < dict->hash_tab->size; i++) {
65 /* get ith record */
66 rec = (struct ht_rec *)&dict->hash_tab->recs[i * dict->hash_tab->rec_size];
67 if (rec->hits == 1) {
68 /*
69 * this should not happen, all records inserted into
70 * dictionary are supposed to be removed using lydict_remove()
71 * before calling lydict_clean()
72 */
73 dict_rec = (struct dict_rec *)rec->val;
74 LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %d", dict_rec->value, dict_rec->refcount);
75 /* if record wasn't removed before free string allocated for that record */
76#ifdef NDEBUG
77 free(dict_rec->value);
78#endif
79 }
80 }
81
82 /* free table and destroy mutex */
83 lyht_free(dict->hash_tab);
84 pthread_mutex_destroy(&dict->lock);
85}
86
87/*
Radek Krejci5aeea3a2018-09-05 13:29:36 +020088 * Usage:
89 * - init hash to 0
90 * - repeatedly call dict_hash_multi(), provide hash from the last call
91 * - call dict_hash_multi() with key_part = NULL to finish the hash
92 */
93uint32_t
94dict_hash_multi(uint32_t hash, const char *key_part, size_t len)
95{
96 uint32_t i;
97
98 if (key_part) {
99 for (i = 0; i < len; ++i) {
100 hash += key_part[i];
101 hash += (hash << 10);
102 hash ^= (hash >> 6);
103 }
104 } else {
105 hash += (hash << 3);
106 hash ^= (hash >> 11);
107 hash += (hash << 15);
108 }
109
110 return hash;
111}
112
Radek Krejcif2dc4c52018-11-08 09:04:13 +0100113/*
114 * Bob Jenkin's one-at-a-time hash
115 * http://www.burtleburtle.net/bob/hash/doobs.html
116 *
117 * Spooky hash is faster, but it works only for little endian architectures.
118 */
119uint32_t
120dict_hash(const char *key, size_t len)
121{
122 uint32_t hash;
123
124 hash = dict_hash_multi(0, key, len);
125 return dict_hash_multi(hash, NULL, len);
126}
127
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200128API void
Michal Vasko52927e22020-03-16 17:26:14 +0100129lydict_remove(const struct ly_ctx *ctx, const char *value)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200130{
131 size_t len;
132 int ret;
133 uint32_t hash;
134 struct dict_rec rec, *match = NULL;
135 char *val_p;
136
Michal Vasko0f6b3e22018-09-07 12:18:12 +0200137 if (!value) {
Michal Vasko0b3a4172018-09-07 12:20:18 +0200138 return;
Michal Vasko0f6b3e22018-09-07 12:18:12 +0200139 }
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200140
141 len = strlen(value);
142 hash = dict_hash(value, len);
143
144 /* create record for lyht_find call */
145 rec.value = (char *)value;
146 rec.refcount = 0;
147
Michal Vasko52927e22020-03-16 17:26:14 +0100148 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200149 /* set len as data for compare callback */
150 lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len);
151 /* check if value is already inserted */
152 ret = lyht_find(ctx->dict.hash_tab, &rec, hash, (void **)&match);
153
154 if (ret == 0) {
155 LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), finish);
156
157 /* if value is already in dictionary, decrement reference counter */
158 match->refcount--;
159 if (match->refcount == 0) {
160 /*
161 * remove record
162 * save pointer to stored string before lyht_remove to
163 * free it after it is removed from hash table
164 */
165 val_p = match->value;
166 ret = lyht_remove(ctx->dict.hash_tab, &rec, hash);
167 free(val_p);
168 LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), finish);
169 }
170 }
171
172finish:
Michal Vasko52927e22020-03-16 17:26:14 +0100173 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200174}
175
176static char *
Michal Vasko52927e22020-03-16 17:26:14 +0100177dict_insert(const struct ly_ctx *ctx, char *value, size_t len, int zerocopy)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200178{
179 struct dict_rec *match = NULL, rec;
180 int ret = 0;
181 uint32_t hash;
182
183 hash = dict_hash(value, len);
184 /* set len as data for compare callback */
185 lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len);
186 /* create record for lyht_insert */
187 rec.value = value;
188 rec.refcount = 1;
189
190 LOGDBG(LY_LDGDICT, "inserting \"%s\"", rec.value);
191 ret = lyht_insert(ctx->dict.hash_tab, (void *)&rec, hash, (void **)&match);
Michal Vaskofcc96b92018-09-12 11:12:01 +0200192 if (ret == LY_EEXIST) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200193 match->refcount++;
194 if (zerocopy) {
195 free(value);
196 }
Michal Vaskofcc96b92018-09-12 11:12:01 +0200197 } else if (ret == LY_SUCCESS) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200198 if (!zerocopy) {
199 /*
200 * allocate string for new record
201 * record is already inserted in hash table
202 */
203 match->value = malloc(sizeof *match->value * (len + 1));
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200204 LY_CHECK_ERR_RET(!match->value, LOGMEM(ctx), NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200205 memcpy(match->value, value, len);
206 match->value[len] = '\0';
207 }
208 } else {
209 /* lyht_insert returned error */
210 LOGINT(ctx);
David Sedlák234a91f2019-08-15 13:16:43 +0200211 if (zerocopy) {
212 free(value);
213 }
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200214 return NULL;
215 }
216
217 return match->value;
218}
219
220API const char *
Michal Vasko52927e22020-03-16 17:26:14 +0100221lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200222{
223 const char *result;
224
Radek Krejci0ae092d2018-09-20 16:43:19 +0200225 LY_CHECK_ARG_RET(ctx, ctx, value, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200226
Radek Krejci0ae092d2018-09-20 16:43:19 +0200227 if (!len) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200228 len = strlen(value);
229 }
230
Michal Vasko52927e22020-03-16 17:26:14 +0100231 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200232 result = dict_insert(ctx, (char *)value, len, 0);
Michal Vasko52927e22020-03-16 17:26:14 +0100233 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200234
235 return result;
236}
237
238API const char *
Michal Vasko52927e22020-03-16 17:26:14 +0100239lydict_insert_zc(const struct ly_ctx *ctx, char *value)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200240{
241 const char *result;
242
Radek Krejci0ae092d2018-09-20 16:43:19 +0200243 LY_CHECK_ARG_RET(ctx, ctx, value, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200244
Michal Vasko52927e22020-03-16 17:26:14 +0100245 pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200246 result = dict_insert(ctx, value, strlen(value), 1);
Michal Vasko52927e22020-03-16 17:26:14 +0100247 pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200248
249 return result;
250}
251
Radek Krejci2d7a47b2019-05-16 13:34:10 +0200252struct ht_rec *
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200253lyht_get_rec(unsigned char *recs, uint16_t rec_size, uint32_t idx)
254{
255 return (struct ht_rec *)&recs[idx * rec_size];
256}
257
258struct hash_table *
259lyht_new(uint32_t size, uint16_t val_size, values_equal_cb val_equal, void *cb_data, int resize)
260{
261 struct hash_table *ht;
262
263 /* check that 2^x == size (power of 2) */
264 assert(size && !(size & (size - 1)));
265 assert(val_equal && val_size);
266 assert(resize == 0 || resize == 1);
267
268 if (size < LYHT_MIN_SIZE) {
269 size = LYHT_MIN_SIZE;
270 }
271
272 ht = malloc(sizeof *ht);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200273 LY_CHECK_ERR_RET(!ht, LOGMEM(NULL), NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200274
275 ht->used = 0;
276 ht->size = size;
277 ht->val_equal = val_equal;
278 ht->cb_data = cb_data;
279 ht->resize = (uint16_t)resize;
280
281 ht->rec_size = (sizeof(struct ht_rec) - 1) + val_size;
282 /* allocate the records correctly */
283 ht->recs = calloc(size, ht->rec_size);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200284 LY_CHECK_ERR_RET(!ht->recs, free(ht); LOGMEM(NULL), NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200285
286 return ht;
287}
288
289values_equal_cb
290lyht_set_cb(struct hash_table *ht, values_equal_cb new_val_equal)
291{
292 values_equal_cb prev;
293
294 prev = ht->val_equal;
295 ht->val_equal = new_val_equal;
296 return prev;
297}
298
299void *
300lyht_set_cb_data(struct hash_table *ht, void *new_cb_data)
301{
302 void *prev;
303
304 prev = ht->cb_data;
305 ht->cb_data = new_cb_data;
306 return prev;
307}
308
309struct hash_table *
310lyht_dup(const struct hash_table *orig)
311{
312 struct hash_table *ht;
313
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200314 LY_CHECK_ARG_RET(NULL, orig, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200315
316 ht = lyht_new(orig->size, orig->rec_size - (sizeof(struct ht_rec) - 1), orig->val_equal, orig->cb_data, orig->resize ? 1 : 0);
317 if (!ht) {
318 return NULL;
319 }
320
321 memcpy(ht->recs, orig->recs, orig->used * orig->rec_size);
322 ht->used = orig->used;
323 return ht;
324}
325
326void
327lyht_free(struct hash_table *ht)
328{
329 if (ht) {
330 free(ht->recs);
331 free(ht);
332 }
333}
334
335static LY_ERR
336lyht_resize(struct hash_table *ht, int enlarge)
337{
338 struct ht_rec *rec;
339 unsigned char *old_recs;
340 uint32_t i, old_size;
341 int ret;
342
343 old_recs = ht->recs;
344 old_size = ht->size;
345
346 if (enlarge) {
347 /* double the size */
348 ht->size <<= 1;
349 } else {
350 /* half the size */
351 ht->size >>= 1;
352 }
353
354 ht->recs = calloc(ht->size, ht->rec_size);
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200355 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 +0200356
357 /* reset used, it will increase again */
358 ht->used = 0;
359
360 /* add all the old records into the new records array */
361 for (i = 0; i < old_size; ++i) {
362 rec = lyht_get_rec(old_recs, ht->rec_size, i);
363 if (rec->hits > 0) {
364 ret = lyht_insert(ht, rec->val, rec->hash, NULL);
365 assert(!ret);
366 (void)ret;
367 }
368 }
369
370 /* final touches */
371 free(old_recs);
372 return LY_SUCCESS;
373}
374
Michal Vaskoda859032020-07-14 12:20:14 +0200375/**
376 * @brief Search for the first match.
377 *
378 * @param[in] ht Hash table to search in.
379 * @param[in] hash Hash to find.
380 * @param[out] rec_p Optional found record.
381 * @return LY_SUCCESS hash found, returned its record,
382 * @return LY_ENOTFOUND hash not found, returned the record where it would be inserted.
383 */
384static LY_ERR
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200385lyht_find_first(struct hash_table *ht, uint32_t hash, struct ht_rec **rec_p)
386{
387 struct ht_rec *rec;
388 uint32_t i, idx;
389
390 if (rec_p) {
391 *rec_p = NULL;
392 }
393
394 idx = i = hash & (ht->size - 1);
395 rec = lyht_get_rec(ht->recs, ht->rec_size, idx);
396
397 /* skip through overflow and deleted records */
398 while ((rec->hits != 0) && ((rec->hits == -1) || ((rec->hash & (ht->size - 1)) != idx))) {
399 if ((rec->hits == -1) && rec_p && !(*rec_p)) {
400 /* remember this record for return */
401 *rec_p = rec;
402 }
403 i = (i + 1) % ht->size;
404 if (i == idx) {
405 /* we went through all the records (very unlikely, but possible when many records are invalid),
406 * just return not found */
407 assert(!rec_p || *rec_p);
Michal Vaskoda859032020-07-14 12:20:14 +0200408 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200409 }
410 rec = lyht_get_rec(ht->recs, ht->rec_size, i);
411 }
412 if (rec->hits == 0) {
413 /* we could not find the value */
414 if (rec_p && !*rec_p) {
415 *rec_p = rec;
416 }
Michal Vaskoda859032020-07-14 12:20:14 +0200417 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200418 }
419
420 /* we have found a record with equal (shortened) hash */
421 if (rec_p) {
422 *rec_p = rec;
423 }
Michal Vaskoda859032020-07-14 12:20:14 +0200424 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200425}
426
427/**
428 * @brief Search for the next collision.
429 *
430 * @param[in] ht Hash table to search in.
431 * @param[in,out] last Last returned collision record.
432 * @param[in] first First collision record (hits > 1).
Michal Vaskoda859032020-07-14 12:20:14 +0200433 * @return LY_SUCCESS when hash collision found, \p last points to this next collision,
434 * @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 +0200435 */
Michal Vaskoda859032020-07-14 12:20:14 +0200436static LY_ERR
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200437lyht_find_collision(struct hash_table *ht, struct ht_rec **last, struct ht_rec *first)
438{
439 struct ht_rec *empty = NULL;
440 uint32_t i, idx;
441
442 assert(last && *last);
443
444 idx = (*last)->hash & (ht->size - 1);
445 i = (((unsigned char *)*last) - ht->recs) / ht->rec_size;
446
447 do {
448 i = (i + 1) % ht->size;
449 *last = lyht_get_rec(ht->recs, ht->rec_size, i);
450 if (*last == first) {
451 /* we went through all the records (very unlikely, but possible when many records are invalid),
452 * just return an invalid record */
453 assert(empty);
454 *last = empty;
Michal Vaskoda859032020-07-14 12:20:14 +0200455 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200456 }
457
458 if (((*last)->hits == -1) && !empty) {
459 empty = *last;
460 }
461 } while (((*last)->hits != 0) && (((*last)->hits == -1) || (((*last)->hash & (ht->size - 1)) != idx)));
462
463 if ((*last)->hits > 0) {
464 /* we found a collision */
465 assert((*last)->hits == 1);
Michal Vaskoda859032020-07-14 12:20:14 +0200466 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200467 }
468
469 /* no next collision found, return the record where it would be inserted */
470 if (empty) {
471 *last = empty;
472 } /* else (*last)->hits == 0, it is already correct */
Michal Vaskoda859032020-07-14 12:20:14 +0200473 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200474}
475
Michal Vaskoda859032020-07-14 12:20:14 +0200476/**
477 * @brief Search for a record with specific value and hash.
478 *
479 * @param[in] ht Hash table to search in.
480 * @param[in] val_p Pointer to the value to find.
481 * @param[in] hash Hash to find.
482 * @param[in] mod Operation kind for the val_equal callback.
483 * @param[out] crec_p Optional found first record.
484 * @param[out] col Optional collision number of @p rec_p, 0 for no collision.
485 * @param[out] rec_p Found exact matching record, may be a collision of @p crec_p.
486 * @return LY_ENOTFOUND if no record found,
487 * @return LY_SUCCESS if record was found.
488 */
489static LY_ERR
490lyht_find_rec(struct hash_table *ht, void *val_p, uint32_t hash, int mod, struct ht_rec **crec_p, uint32_t *col,
Radek Krejci0f969882020-08-21 16:56:47 +0200491 struct ht_rec **rec_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200492{
493 struct ht_rec *rec, *crec;
494 uint32_t i, c;
Michal Vaskoda859032020-07-14 12:20:14 +0200495 LY_ERR r;
496
497 if (crec_p) {
498 *crec_p = NULL;
499 }
500 if (col) {
501 *col = 0;
502 }
503 *rec_p = NULL;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200504
505 if (lyht_find_first(ht, hash, &rec)) {
506 /* not found */
Michal Vaskoda859032020-07-14 12:20:14 +0200507 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200508 }
Michal Vaskoda859032020-07-14 12:20:14 +0200509 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200510 /* even the value matches */
Michal Vaskoda859032020-07-14 12:20:14 +0200511 if (crec_p) {
512 *crec_p = rec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200513 }
Michal Vaskoda859032020-07-14 12:20:14 +0200514 if (col) {
515 *col = 0;
516 }
517 *rec_p = rec;
518 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200519 }
520
521 /* some collisions, we need to go through them, too */
522 crec = rec;
Michal Vaskoda859032020-07-14 12:20:14 +0200523 c = crec->hits;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200524 for (i = 1; i < c; ++i) {
525 r = lyht_find_collision(ht, &rec, crec);
526 assert(!r);
527 (void)r;
528
529 /* compare values */
Michal Vaskoda859032020-07-14 12:20:14 +0200530 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) {
531 if (crec_p) {
532 *crec_p = crec;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200533 }
Michal Vaskoda859032020-07-14 12:20:14 +0200534 if (col) {
535 *col = i;
536 }
537 *rec_p = rec;
538 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200539 }
540 }
541
542 /* not found even in collisions */
Michal Vaskoda859032020-07-14 12:20:14 +0200543 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200544}
545
Michal Vaskoda859032020-07-14 12:20:14 +0200546LY_ERR
547lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p)
548{
549 struct ht_rec *rec;
550
551 lyht_find_rec(ht, val_p, hash, 0, NULL, NULL, &rec);
552
553 if (rec && match_p) {
554 *match_p = rec->val;
555 }
556 return rec ? LY_SUCCESS : LY_ENOTFOUND;
557}
558
559LY_ERR
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200560lyht_find_next(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p)
561{
562 struct ht_rec *rec, *crec;
563 uint32_t i, c;
Michal Vaskoda859032020-07-14 12:20:14 +0200564 LY_ERR r;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200565
Michal Vaskoda859032020-07-14 12:20:14 +0200566 /* found the record of the previously found value */
567 if (lyht_find_rec(ht, val_p, hash, 1, &crec, &i, &rec)) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200568 /* not found, cannot happen */
Michal Vaskoda859032020-07-14 12:20:14 +0200569 LOGINT_RET(NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200570 }
571
572 /* go through collisions and find next one after the previous one */
Michal Vaskoda859032020-07-14 12:20:14 +0200573 c = crec->hits;
574 for (++i; i < c; ++i) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200575 r = lyht_find_collision(ht, &rec, crec);
576 assert(!r);
577 (void)r;
578
Michal Vaskoda859032020-07-14 12:20:14 +0200579 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 0, ht->cb_data)) {
580 /* even the value matches */
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200581 if (match_p) {
582 *match_p = rec->val;
583 }
Michal Vaskoda859032020-07-14 12:20:14 +0200584 return LY_SUCCESS;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200585 }
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200586 }
587
588 /* the last equal value was already returned */
Michal Vaskoda859032020-07-14 12:20:14 +0200589 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200590}
591
592LY_ERR
593lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash,
Radek Krejci0f969882020-08-21 16:56:47 +0200594 values_equal_cb resize_val_equal, void **match_p)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200595{
596 struct ht_rec *rec, *crec = NULL;
597 int32_t i;
598 int r, ret;
599 values_equal_cb old_val_equal;
600
601 if (!lyht_find_first(ht, hash, &rec)) {
602 /* we found matching shortened hash */
603 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
604 /* even the value matches */
605 if (match_p) {
606 *match_p = (void *)&rec->val;
607 }
608 return LY_EEXIST;
609 }
610
611 /* some collisions, we need to go through them, too */
612 crec = rec;
613 for (i = 1; i < crec->hits; ++i) {
614 r = lyht_find_collision(ht, &rec, crec);
615 assert(!r);
616
617 /* compare values */
618 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
619 if (match_p) {
620 *match_p = (void *)&rec->val;
621 }
622 return LY_EEXIST;
623 }
624 }
625
626 /* value not found, get the record where it will be inserted */
627 r = lyht_find_collision(ht, &rec, crec);
628 assert(r);
629 }
630
631 /* insert it into the returned record */
632 assert(rec->hits < 1);
633 rec->hash = hash;
634 rec->hits = 1;
635 memcpy(&rec->val, val_p, ht->rec_size - (sizeof(struct ht_rec) - 1));
636 if (match_p) {
637 *match_p = (void *)&rec->val;
638 }
639
640 if (crec) {
641 /* there was a collision, increase hits */
642 if (crec->hits == INT32_MAX) {
643 LOGINT(NULL);
644 }
645 ++crec->hits;
646 }
647
648 /* check size & enlarge if needed */
649 ret = LY_SUCCESS;
650 ++ht->used;
651 if (ht->resize) {
652 r = (ht->used * 100) / ht->size;
653 if ((ht->resize == 1) && (r >= LYHT_FIRST_SHRINK_PERCENTAGE)) {
654 /* enable shrinking */
655 ht->resize = 2;
656 }
657 if ((ht->resize == 2) && (r >= LYHT_ENLARGE_PERCENTAGE)) {
658 if (resize_val_equal) {
659 old_val_equal = lyht_set_cb(ht, resize_val_equal);
660 }
661
662 /* enlarge */
663 ret = lyht_resize(ht, 1);
664 /* if hash_table was resized, we need to find new matching value */
665 if (ret == LY_SUCCESS && match_p) {
666 lyht_find(ht, val_p, hash, match_p);
667 }
668
669 if (resize_val_equal) {
670 lyht_set_cb(ht, old_val_equal);
671 }
672 }
673 }
674 return ret;
675}
676
Radek Krejci0ae092d2018-09-20 16:43:19 +0200677LY_ERR
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200678lyht_insert(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p)
679{
680 return lyht_insert_with_resize_cb(ht, val_p, hash, NULL, match_p);
681}
682
683LY_ERR
684lyht_remove(struct hash_table *ht, void *val_p, uint32_t hash)
685{
686 struct ht_rec *rec, *crec;
687 int32_t i;
688 int first_matched = 0, r, ret;
689
Michal Vasko4a4c7ed2020-07-17 09:30:12 +0200690 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 +0200691
692 if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
693 /* even the value matches */
694 first_matched = 1;
695 }
696
697 /* we always need to go through collisions */
698 crec = rec;
699 for (i = 1; i < crec->hits; ++i) {
700 r = lyht_find_collision(ht, &rec, crec);
701 assert(!r);
702
703 /* compare values */
704 if (!first_matched && (rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) {
705 break;
706 }
707 }
708
709 if (i < crec->hits) {
710 /* one of collisions matched, reduce collision count, remove the record */
711 assert(!first_matched);
712 --crec->hits;
713 rec->hits = -1;
714 } else if (first_matched) {
715 /* the first record matches */
716 if (crec != rec) {
717 /* ... so put the last collision in its place */
718 rec->hits = crec->hits - 1;
719 memcpy(crec, rec, ht->rec_size);
720 }
721 rec->hits = -1;
722 } else {
723 /* value not found even in collisions */
Michal Vasko4a4c7ed2020-07-17 09:30:12 +0200724 return LY_ENOTFOUND;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200725 }
726
727 /* check size & shrink if needed */
728 ret = LY_SUCCESS;
729 --ht->used;
730 if (ht->resize == 2) {
731 r = (ht->used * 100) / ht->size;
732 if ((r < LYHT_SHRINK_PERCENTAGE) && (ht->size > LYHT_MIN_SIZE)) {
733 /* shrink */
734 ret = lyht_resize(ht, 0);
735 }
736 }
737
738 return ret;
739}