qLibc
qhasharr.c
Go to the documentation of this file.
1/******************************************************************************
2 * qLibc
3 *
4 * Copyright (c) 2010-2026 Seungyoung Kim.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *****************************************************************************/
28
29/**
30 * @file qhasharr.c Static(array) hash-table implementation.
31 *
32 * qhasharr implements a hash table that maps keys to values and stores them in
33 * fixed-size static memory such as shared memory and memory-mapped files.
34 * The creator qhasharr() initializes static memory and divides it into small
35 * slots. The default slot size factors are defined in Q_HASHARR_NAMESIZE and
36 * Q_HASHARR_DATASIZE, and they are applied at compile time.
37 *
38 * The value part of an element will be stored across several slots if its size
39 * exceeds the slot size. The key part of an element will be truncated if its
40 * length exceeds the limit, and an MD5 hash value will also be stored with the
41 * key. To look up a particular key, we first find an element that has the same
42 * hash value. If the key was not truncated, we simply compare the key values.
43 * If the key was truncated because its length exceeded the limit, we compare
44 * both the MD5 hash and the stored portion of the key to verify that the key
45 * is the same. Please be aware that, in theory, it is possible to pick the
46 * wrong element if a key exceeds the limit and has the same length and MD5 hash
47 * as the lookup key. In practice, however, this possibility is extremely low.
48 *
49 * qhasharr intentionally does not provide thread-safe handling and lets users
50 * determine whether to provide a locking mechanism, depending on the use case.
51 * When race conditions are expected, you should provide shared-resource
52 * control using a mutex or semaphore to make sure data is updated by one
53 * instance at a time.
54 *
55 * @code
56 * [Data Structure Diagram]
57 *
58 * +--[Static Flat Memory Area]-----------------------------------------------+
59 * | +-[Header]---------+ +-[Slot 0]---+ +-[Slot 1]---+ +-[Slot N]---+ |
60 * | |Private table data| |KEY A|DATA A| |KEY B|DATA B| .... |KEY N|DATA N| |
61 * | +------------------+ +------------+ +------------+ +------------+ |
62 * +--------------------------------------------------------------------------+
63 *
64 * The diagram below shows how a large value is stored.
65 * +--[Static Flat Memory Area------------------------------------------------+
66 * | +--------+ +-[Slot 0]---+ +-[Slot 1]---+ +-[Slot 2]---+ +-[Slot 3]-----+ |
67 * | |TBL INFO| |KEY A|DATA A| |DATA A cont.| |KEY B|DATA B| |DATA A cont. | |
68 * | +--------+ +------------+ +------------+ +------------+ +--------------+ |
69 * | ^~~link~~^ ^~~~~~~~~~link~~~~~~~~~^ |
70 * +--------------------------------------------------------------------------+
71 * @endcode
72 *
73 * @code
74 * // initialize hash-table.
75 * char memory[1000 * 10];
76 * qhasharr_t *tbl = qhasharr(memory, sizeof(memory));
77 * if(tbl == NULL) return;
78 *
79 * // insert elements (key duplication is not allowed)
80 * tbl->putstr(tbl, "e1", "a");
81 * tbl->putstr(tbl, "e2", "b");
82 * tbl->putstr(tbl, "e3", "c");
83 *
84 * // debug print out
85 * tbl->debug(tbl, stdout);
86 *
87 * char *e2 = tbl->getstr(tbl, "e2");
88 * if(e2 != NULL) {
89 * printf("getstr('e2') : %s\n", e2);
90 * free(e2);
91 * }
92 *
93 * // Release reference object.
94 * tbl->free(tbl);
95 * @endcode
96 *
97 * An example for using hash table over shared memory.
98 *
99 * @code
100 * [CREATOR SIDE]
101 * int maxslots = 1000;
102 * int memsize = qhasharr_calculate_memsize(maxslots);
103 *
104 * // create shared memory
105 * int shmid = qshm_init("/tmp/some_id_file", 'q', memsize, true);
106 * if(shmid < 0) return -1; // creation failed
107 * void *memory = qshm_get(shmid);
108 *
109 * // initialize hash-table
110 * qhasharr_t *tbl = qhasharr(memory, memsize);
111 * if(hasharr == NULL) return -1;
112 *
113 * (...your codes with your own locking mechanism...)
114 *
115 * // Release reference object
116 * tbl->free(tbl);
117 *
118 * // destroy shared memory
119 * qshm_free(shmid);
120 *
121 * [USER SIDE]
122 * int shmid = qshm_getid("/tmp/some_id_file", 'q');
123 *
124 * // get shared memory
125 * void *memory = qshm_get(shmid);
126 *
127 * // map existing memory into table
128 * qhasharr_t *tbl = qhasharr(memory, 0);
129 *
130 * (...your codes with your own locking mechanism...)
131 *
132 * // Release reference object
133 * tbl->free(tbl);
134 * @endcode
135 */
136
137#include <stdio.h>
138#include <stdlib.h>
139#include <stdbool.h>
140#include <stdint.h>
141#include <inttypes.h>
142#include <stdarg.h>
143#include <string.h>
144#include <errno.h>
145#include <assert.h>
146#include "qinternal.h"
147#include "utilities/qhash.h"
148#include "containers/qhasharr.h"
149
150#define COLLISION_MARK (-1)
151#define EXTBLOCK_MARK (-2)
152
153#ifndef _DOXYGEN_SKIP
154
155static qhasharr_slot_t* get_slots(qhasharr_t *tbl);
156static int find_avail(qhasharr_t *tbl, int startidx);
157static int get_idx(qhasharr_t *tbl, const void *name, size_t namesize,
158 uint32_t hash);
159static void *get_data(qhasharr_t *tbl, int idx, size_t *size);
160static bool put_data(qhasharr_t *tbl, int idx, uint32_t hash, const void *name,
161 size_t namesize, const void *data, size_t datasize,
162 int count);
163static bool copy_slot(qhasharr_t *tbl, int idx1, int idx2);
164static bool remove_slot(qhasharr_t *tbl, int idx);
165static bool remove_data(qhasharr_t *tbl, int idx);
166
167#endif
168
169/**
170 * Get how much memory is needed for N slots.
171 *
172 * @param max a number of maximum internal slots
173 *
174 * @return memory size needed
175 *
176 * @note
177 * This can be used for calculating minimum memory size for N slots.
178 */
180 size_t memsize = sizeof(qhasharr_data_t)
181 + (sizeof(qhasharr_slot_t) * (max));
182 return memsize;
183}
184
185/**
186 * Initialize a static hash table.
187 *
188 * @param memory pointer to the data buffer
189 * @param memsize size of the data buffer, or 0 to use existing data
190 *
191 * @return qhasharr_t pointer on success, or NULL on failure.
192 * @retval errno will be set on error.
193 * - EINVAL : Assigned memory is too small. It must be large enough to hold
194 * at least one slot.
195 *
196 * @code
197 * // Initialize a hash table with 100 slots.
198 * // One element can use several slots.
199 * char memory[112 * 100];
200 *
201 * // Initialize a new table.
202 * qhasharr_t *tbl = qhasharr(memory, sizeof(memory));
203 *
204 * // Use an existing table.
205 * qhasharr_t *tbl2 = qhasharr(memory, 0);
206 * @endcode
207 */
208qhasharr_t *qhasharr(void *memory, size_t memsize) {
209 // Structure memory.
210 qhasharr_data_t *tbldata = (qhasharr_data_t *) memory;
211
212 // Initialize data if memsize is set or use existing data.
213 if (memsize > 0) {
214 // calculate max
215 int maxslots = (memsize - sizeof(qhasharr_data_t))
216 / sizeof(qhasharr_slot_t);
217 if (maxslots < 1 || memsize <= sizeof(qhasharr_t)) {
218 errno = EINVAL;
219 return NULL;
220 }
221
222 // Set memory.
223 memset((void *) tbldata, 0, memsize);
224 tbldata->maxslots = maxslots;
225 tbldata->usedslots = 0;
226 tbldata->num = 0;
227 }
228
229 // Create the table object.
230 qhasharr_t *tbl = (qhasharr_t *) malloc(sizeof(qhasharr_t));
231 if (tbl == NULL) {
232 errno = ENOMEM;
233 return NULL;
234 }
235 memset((void *) tbl, 0, sizeof(qhasharr_t));
236
237 // assign methods
238 tbl->put = qhasharr_put;
239 tbl->putstr = qhasharr_putstr;
240 tbl->putstrf = qhasharr_putstrf;
241 tbl->put_by_obj = qhasharr_put_by_obj;
242
243 tbl->get = qhasharr_get;
244 tbl->getstr = qhasharr_getstr;
245 tbl->get_by_obj = qhasharr_get_by_obj;
246
247 tbl->remove = qhasharr_remove;
248 tbl->remove_by_obj = qhasharr_remove_by_obj;
249 tbl->remove_by_idx = qhasharr_remove_by_idx;
250
251 tbl->getnext = qhasharr_getnext;
252
253 tbl->size = qhasharr_size;
254 tbl->clear = qhasharr_clear;
255 tbl->debug = qhasharr_debug;
256
257 tbl->free = qhasharr_free;
258
259 tbl->data = tbldata;
260
261 return tbl;
262}
263
264/**
265 * qhasharr->put(): Put an object into this table.
266 *
267 * @param tbl qhasharr_t container pointer.
268 * @param key key string
269 * @param value value object data
270 * @param size size of value
271 *
272 * @return true on success, otherwise false
273 * @retval errno will be set in error condition.
274 * - ENOBUFS : Table doesn't have enough space to store the object.
275 * - EINVAL : Invalid argument.
276 * - EFAULT : Unexpected error. Data structure is inconsistent.
277 */
278bool qhasharr_put(qhasharr_t *tbl, const char *name, const void *data,
279 size_t datasize) {
280 return qhasharr_put_by_obj(tbl, name, (name) ? strlen(name) + 1 : 0, data,
281 datasize);
282}
283
284/**
285 * qhasharr->putstr(): Put a string into this table
286 *
287 * @param tbl qhasharr_t container pointer.
288 * @param name key string.
289 * @param data value string.
290 *
291 * @return true on success, otherwise false
292 * @retval errno will be set in error condition.
293 * - ENOBUFS : Table doesn't have enough space to store the object.
294 * - EINVAL : Invalid argument.
295 * - EFAULT : Unexpected error. Data structure is inconsistent.
296 */
297bool qhasharr_putstr(qhasharr_t *tbl, const char *name, const char *data) {
298 return qhasharr_put_by_obj(tbl, name, (name) ? strlen(name) + 1 : 0,
299 data, (data) ? strlen(data) + 1 : 0);
300}
301
302/**
303 * qhasharr->putstrf(): Put a formatted string into this table.
304 *
305 * @param tbl qhasharr_t container pointer.
306 * @param name key string
307 * @param format formatted string data.
308 *
309 * @return true on success, otherwise false.
310 * @retval errno will be set in error condition.
311 * - ENOBUFS : Table doesn't have enough space to store the object.
312 * - ENOMEM : System memory allocation failure.
313 * - EINVAL : Invalid argument.
314 * - EFAULT : Unexpected error. Data structure is inconsistent.
315 */
316bool qhasharr_putstrf(qhasharr_t *tbl, const char *name, const char *format, ...) {
317 char *str;
318 DYNAMIC_VSPRINTF(str, format);
319 if (str == NULL) {
320 errno = ENOMEM;
321 return false;
322 }
323
324 bool ret = qhasharr_putstr(tbl, name, str);
325 free(str);
326 return ret;
327}
328
329/**
330 * qhasharr->put_by_obj(): Put an object into this table by key object.
331 *
332 * @param tbl qhasharr_t container pointer.
333 * @param name key data
334 * @param namesize size of key
335 * @param data data
336 * @param datasize size of data
337 *
338 * @return true on success, otherwise false
339 * @retval errno will be set in error condition.
340 * - ENOBUFS : Table doesn't have enough space to store the object.
341 * - EINVAL : Invalid argument.
342 * - EFAULT : Unexpected error. Data structure is inconsistent.
343 */
344bool qhasharr_put_by_obj(qhasharr_t *tbl, const void *name, size_t namesize,
345 const void *data, size_t datasize) {
346 if (tbl == NULL || name == NULL || namesize == 0 || data == NULL
347 || datasize == 0) {
348 errno = EINVAL;
349 return false;
350 }
351
352 qhasharr_data_t *tbldata = tbl->data;
353 qhasharr_slot_t *tblslots = get_slots(tbl);
354
355 // check full
356 if (tbldata->usedslots >= tbldata->maxslots) {
357 errno = ENOBUFS;
358 return false;
359 }
360
361 // get hash integer
362 uint32_t hash = qhashmurmur3_32(name, namesize) % tbldata->maxslots;
363
364 // check, is slot empty
365 if (tblslots[hash].count == 0) { // empty slot
366 // put data
367 if (put_data(tbl, hash, hash, name, namesize, data, datasize,
368 1) == false) {
369 return false;
370 }
371 } else if (tblslots[hash].count > 0) { // same key or hash collision
372 // check same key;
373 int idx = get_idx(tbl, name, namesize, hash);
374 if (idx >= 0) { // same key
375 // remove and recall
376 qhasharr_remove_by_idx(tbl, idx);
377 return qhasharr_put_by_obj(tbl, name, namesize, data, datasize);
378 } else { // no same key but hash collision
379 // find empty slot
380 int idx = find_avail(tbl, hash);
381 if (idx < 0) {
382 errno = ENOBUFS;
383 return false;
384 }
385
386 // put data. -1 is used for collision resolution (idx != hash);
387 if (put_data(tbl, idx, hash, name, namesize, data, datasize,
388 COLLISION_MARK) == false) {
389 return false;
390 }
391
392 // increase counter from leading slot
393 tblslots[hash].count++;
394 }
395 } else {
396 // collision key or extended block
397
398 // find empty slot
399 int idx = find_avail(tbl, hash + 1);
400 if (idx < 0) {
401 errno = ENOBUFS;
402 return false;
403 }
404
405 // move the slot
406 copy_slot(tbl, idx, hash);
407 remove_slot(tbl, hash);
408
409 // adjust the link chain
410 if (tblslots[idx].link != -1) {
411 tblslots[tblslots[idx].link].hash = idx;
412 }
413 if (tblslots[idx].count == EXTBLOCK_MARK) {
414 tblslots[tblslots[idx].hash].link = idx;
415 }
416
417 // store data
418 if (put_data(tbl, hash, hash, name, namesize, data, datasize,
419 1) == false) {
420 return false;
421 }
422 }
423
424 return true;
425}
426
427/**
428 * qhasharr->get(): Get an object from this table
429 *
430 * @param tbl qhasharr_t container pointer.
431 * @param name key string
432 * @param datasize if not NULL, returned object size will be stored
433 *
434 * @return newly allocated object on success, or NULL if not found.
435 * @retval errno will be set in error condition.
436 * - ENOENT : No such key found.
437 * - EINVAL : Invalid argument.
438 * - ENOMEM : Memory allocation failed.
439 *
440 * @note
441 * The returned object must be freed after use.
442 */
443void *qhasharr_get(qhasharr_t *tbl, const char *name, size_t *datasize) {
444 return qhasharr_get_by_obj(tbl, name, (name) ? strlen(name) + 1 : 0, datasize);
445}
446
447/**
448 * qhasharr->getstr(): Finds an object with the given name and returns it as a
449 * string.
450 *
451 * @param tbl qhasharr_t container pointer.
452 * @param name key string
453 *
454 * @return string pointer on success, or NULL if not found.
455 * @retval errno will be set in error condition.
456 * - ENOENT : No such key found.
457 * - EINVAL : Invalid argument.
458 * - ENOMEM : Memory allocation failed.
459 *
460 * @note
461 * The returned object must be freed after use.
462 */
463char *qhasharr_getstr(qhasharr_t *tbl, const char *name) {
464 return (char *) qhasharr_get(tbl, name, NULL);
465}
466
467/**
468 * qhasharr->get_by_obj(): Get an object from this table by key object.
469 *
470 * @param tbl qhasharr_t container pointer.
471 * @param name key data
472 * @param namesize size of key
473 * @param datasize if not NULL, returned object size will be stored
474 *
475 * @return newly allocated object on success, or NULL if not found.
476 * @retval errno will be set in error condition.
477 * - ENOENT : No such key found.
478 * - EINVAL : Invalid argument.
479 * - ENOMEM : Memory allocation failed.
480 *
481 * @note
482 * The returned object must be freed after use.
483 */
484void *qhasharr_get_by_obj(qhasharr_t *tbl, const void *name, size_t namesize,
485 size_t *datasize) {
486 if (tbl == NULL || name == NULL || namesize == 0) {
487 errno = EINVAL;
488 return NULL;
489 }
490
491 qhasharr_data_t *tbldata = tbl->data;
492
493 // get hash integer
494 uint32_t hash = qhashmurmur3_32(name, namesize) % tbldata->maxslots;
495 int idx = get_idx(tbl, name, namesize, hash);
496 if (idx < 0) {
497 errno = ENOENT;
498 return NULL;
499 }
500
501 return get_data(tbl, idx, datasize);
502}
503
504/**
505 * qhasharr->remove(): Remove an object from this table.
506 *
507 * @param tbl qhasharr_t container pointer.
508 * @param name key string
509 *
510 * @return true on success; otherwise (if not found), returns false.
511 * @retval errno will be set in error condition.
512 * - ENOENT : No such key found.
513 * - EINVAL : Invalid argument.
514 * - EFAULT : Unexpected error. Data structure is inconsistent.
515 */
516bool qhasharr_remove(qhasharr_t *tbl, const char *name) {
517 return qhasharr_remove_by_obj(tbl, name, (name) ? strlen(name) + 1 : 0);
518}
519
520/**
521 * qhasharr->remove_by_obj(): Remove an object from this table by key object
522 *
523 * @param tbl qhasharr_t container pointer.
524 * @param name key data
525 * @param namesize size of key
526 *
527 * @return true on success; otherwise (if not found), returns false.
528 * @retval errno will be set in error condition.
529 * - ENOENT : No such key found.
530 * - EINVAL : Invalid argument.
531 * - EFAULT : Unexpected error. Data structure is inconsistent.
532 */
533bool qhasharr_remove_by_obj(qhasharr_t *tbl, const char *name, size_t namesize) {
534 if (tbl == NULL || name == NULL || namesize == 0) {
535 errno = EINVAL;
536 return false;
537 }
538
539 qhasharr_data_t *tbldata = tbl->data;
540
541 // get hash integer
542 uint32_t hash = qhashmurmur3_32(name, namesize) % tbldata->maxslots;
543 int idx = get_idx(tbl, name, namesize, hash);
544 if (idx < 0) {
545 errno = ENOENT;
546 return false;
547 }
548
549 return qhasharr_remove_by_idx(tbl, idx);
550}
551
552/**
553 * qhasharr->remove_by_idx(): Remove an object from this table by index number.
554 *
555 * @param tbl qhasharr_t container pointer.
556 * @param idx slot index number
557 *
558 * @return true on success; otherwise (if not found), returns false.
559 * @retval errno will be set in error condition.
560 * - ENOENT : Index is not pointing a valid object.
561 * - EINVAL : Invalid argument.
562 * - EFAULT : Unexpected error. Data structure is inconsistent.
563 *
564 * @code
565 * int idx = 0;
566 * qhasharr_obj_t obj;
567 * while(tbl->getnext(tbl, &obj, &idx) == true) {
568 * if (condition_to_remove) {
569 * idx--; // adjust index by -1
570 * remove_by_idx(idx);
571 * }
572 * free(obj.name);
573 * free(obj.data);
574 * }
575 * @endcode
576 *
577 * @note
578 * This function is to remove an object in getnext() traversal loop without
579 * knowing the object name but index value. When key names are longer than
580 * defined size, the keys are stored with truncation with their fingerprint,
581 * so this method provides a way to remove those keys.
582 * getnext() returns actual index + 1(pointing next slot of current finding),
583 * so you need to adjust it by -1 for a valid index. After you remove an
584 * object with this method, rewind idx by -1 before calling getnext()
585 * because collision objects can be moved back to removed index again, so
586 * by adjusting index by -1, getnext() can continue search from the removed
587 * slot index again. Please see the example above.
588 */
589bool qhasharr_remove_by_idx(qhasharr_t *tbl, int idx) {
590 if (idx < 0) {
591 errno = EINVAL;
592 return false;
593 }
594
595 qhasharr_data_t *tbldata = tbl->data;
596 qhasharr_slot_t *tblslots = get_slots(tbl);
597
598 if (tblslots[idx].count == 1) {
599 // just remove
600 remove_data(tbl, idx);
601 } else if (tblslots[idx].count > 1) { // leading slot and has collision
602 // find the collision key
603 int idx2;
604 for (idx2 = idx + 1;; idx2++) {
605 if (idx2 >= tbldata->maxslots)
606 idx2 = 0;
607 if (idx2 == idx) {
608 errno = EFAULT;
609 return false;
610 }
611 if (tblslots[idx2].count == COLLISION_MARK
612 && tblslots[idx2].hash == tblslots[idx].hash) {
613 break;
614 }
615 }
616
617 // move to leading slot
618 int backupcount = tblslots[idx].count;
619 remove_data(tbl, idx); // remove leading data
620 copy_slot(tbl, idx, idx2); // copy slot
621 remove_slot(tbl, idx2); // remove moved slot
622
623 tblslots[idx].count = backupcount - 1; // adjust collision counter
624 if (tblslots[idx].link != -1) {
625 tblslots[tblslots[idx].link].hash = idx;
626 }
627
628 } else if (tblslots[idx].count == COLLISION_MARK) { // collision key
629 // decrease counter from leading slot
630 if (tblslots[tblslots[idx].hash].count <= 1) {
631 errno = EFAULT;
632 return false;
633 }
634 tblslots[tblslots[idx].hash].count--;
635
636 // remove data
637 remove_data(tbl, idx);
638 } else {
639 errno = ENOENT;
640 return false;
641 }
642
643 return true;
644}
645
646/**
647 * qhasharr->getnext(): Get next element.
648 *
649 * @param tbl qhasharr_t container pointer.
650 * @param idx index pointer
651 *
652 * @return key name string if successful, otherwise(end of table) returns NULL
653 * @retval errno will be set in error condition.
654 * - ENOENT : No next element.
655 * - EINVAL : Invalid argument.
656 * - ENOMEM : Memory allocation failed.
657 *
658 * @code
659 * int idx = 0;
660 * qhasharr_obj_t obj;
661 * while(tbl->getnext(tbl, &obj, &idx) == true) {
662 * printf("NAMESIZE=%zu, DATASIZE=%zu\n",
663 * obj.namesize, obj.datasize);
664 * free(obj.name); // key name
665 * free(obj.data); // value data
666 * }
667 * @endcode
668 *
669 * @note
670 * Please be aware a key name will be returned with truncated length
671 * because key name gets truncated if it doesn't fit into slot size,
672 * Q_HASHARR_NAMESIZE.
673 */
674bool qhasharr_getnext(qhasharr_t *tbl, qhasharr_obj_t *obj, int *idx) {
675 if (tbl == NULL || obj == NULL || idx == NULL) {
676 errno = EINVAL;
677 return NULL;
678 }
679
680 qhasharr_data_t *tbldata = tbl->data;
681 qhasharr_slot_t *tblslots = get_slots(tbl);
682
683 for (; *idx < tbldata->maxslots; (*idx)++) {
684 if (tblslots[*idx].count == 0 || tblslots[*idx].count == EXTBLOCK_MARK) {
685 continue;
686 }
687
688 size_t namesize = tblslots[*idx].data.pair.namesize;
689 if (namesize > Q_HASHARR_NAMESIZE)
690 namesize = Q_HASHARR_NAMESIZE;
691
692 obj->name = malloc(namesize + 1);
693 if (obj->name == NULL) {
694 errno = ENOMEM;
695 return false;
696 }
697 memcpy(obj->name, tblslots[*idx].data.pair.name, namesize);
698 memcpy(obj->name + namesize, "", 1); // for truncated case
699 obj->namesize = namesize;
700
701 obj->data = get_data(tbl, *idx, &obj->datasize);
702 if (obj->data == NULL) {
703 free(obj->name);
704 errno = ENOMEM;
705 return false;
706 }
707
708 *idx += 1;
709 return true;
710 }
711
712 errno = ENOENT;
713 return false;
714}
715
716/**
717 * qhasharr->size(): Returns the number of objects in this table.
718 *
719 * @param tbl qhasharr_t container pointer.
720 * @param maxslots if not NULL, total number of slots will be stored.
721 * @param usedslots if not NULL, total number of used slots will be stored.
722 *
723 * @return a number of elements stored.
724 */
725int qhasharr_size(qhasharr_t *tbl, int *maxslots, int *usedslots) {
726 if (tbl == NULL) {
727 errno = EINVAL;
728 return -1;
729 }
730
731 qhasharr_data_t *tbldata = tbl->data;
732
733 if (maxslots != NULL)
734 *maxslots = tbldata->maxslots;
735 if (usedslots != NULL)
736 *usedslots = tbldata->usedslots;
737
738 return tbldata->num;
739}
740
741/**
742 * qhasharr->clear(): Clears this table so that it contains no keys.
743 *
744 * @param tbl qhasharr_t container pointer.
745 *
746 * @return true on success, otherwise false.
747 */
748void qhasharr_clear(qhasharr_t *tbl) {
749 if (tbl == NULL) {
750 errno = EINVAL;
751 return;
752 }
753
754 qhasharr_data_t *tbldata = tbl->data;
755 qhasharr_slot_t *tblslots = get_slots(tbl);
756
757 if (tbldata->usedslots == 0)
758 return;
759
760 tbldata->usedslots = 0;
761 tbldata->num = 0;
762
763 // clear memory
764 memset((void *) tblslots, '\0',
765 (tbldata->maxslots * sizeof(qhasharr_slot_t)));
766}
767
768/**
769 * qhasharr->debug(): Print the hash table for debugging purposes.
770 *
771 * @param tbl qhasharr_t container pointer.
772 * @param out output stream such as stdout or stderr.
773 *
774 * @return true on success, otherwise false
775 * @retval errno will be set in error condition.
776 * - EIO : Invalid output stream.
777 */
778bool qhasharr_debug(qhasharr_t *tbl, FILE *out) {
779 if (tbl == NULL) {
780 errno = EINVAL;
781 return false;
782 }
783
784 if (out == NULL) {
785 errno = EIO;
786 return false;
787 }
788
789 qhasharr_slot_t *tblslots = get_slots(tbl);
790
791 int idx = 0;
792 qhasharr_obj_t obj;
793 while (tbl->getnext(tbl, &obj, &idx) == true) {
794 uint16_t namesize = tblslots[idx - 1].data.pair.namesize;
795 _q_textout(out, obj.name, obj.namesize, MAX_HUMANOUT);
796 fprintf(out, "%s(%d)=", (namesize > Q_HASHARR_NAMESIZE) ? "..." : "",
797 namesize);
798 _q_textout(out, obj.data, obj.datasize, MAX_HUMANOUT);
799 fprintf(out, " (%zu)\n", obj.datasize);
800
801 free(obj.name);
802 free(obj.data);
803 }
804
805#ifdef BUILD_DEBUG
806 qhasharr_data_t *tbldata = tbl->data;
807 fprintf(out, "%d elements (slot %d used/%d total)\n",
808 tbldata->num, tbldata->usedslots, tbldata->maxslots);
809 for (idx = 0; idx < tbldata->maxslots; idx++) {
810 if (tblslots[idx].count == 0) continue;
811
812 fprintf(out, "slot=%d,type=", idx);
813 if (tblslots[idx].count == EXTBLOCK_MARK) {
814 fprintf(out, "EXTEND");
815 fprintf(out, ",prev=%d", tblslots[idx].hash);
816 fprintf(out, ",next=%d", tblslots[idx].link);
817 fprintf(out, ",datasize=%d", tblslots[idx].datasize);
818 fprintf(out, ",data=");
819 _q_textout(out,
820 tblslots[idx].data.ext.data,
821 tblslots[idx].datasize,
822 MAX_HUMANOUT);
823 } else {
824 fprintf(out, "%s", (tblslots[idx].count == COLLISION_MARK)?"COLISN":"NORMAL");
825 fprintf(out, ",next=%d", tblslots[idx].link);
826 fprintf(out, ",count=%d", tblslots[idx].count);
827 fprintf(out, ",hash=%u", tblslots[idx].hash);
828 fprintf(out, ",namesize=%d", tblslots[idx].data.pair.namesize);
829 fprintf(out, ",datasize=%d", tblslots[idx].datasize);
830 fprintf(out, ",name=");
831 _q_textout(out,
832 tblslots[idx].data.pair.name,
833 (tblslots[idx].data.pair.namesize>Q_HASHARR_NAMESIZE)
834 ? Q_HASHARR_NAMESIZE
835 : tblslots[idx].data.pair.namesize,
836 MAX_HUMANOUT);
837 fprintf(out, ",data=");
838 _q_textout(out,
839 tblslots[idx].data.pair.data,
840 tblslots[idx].datasize,
841 MAX_HUMANOUT);
842 }
843 fprintf(out, "\n");
844 }
845#endif
846
847 return true;
848}
849
850/**
851 * qhasharr->free(): Free table reference object.
852 *
853 * @param tbl qhashtbl_t container pointer.
854 *
855 * @note
856 * This does not free the data memory but only the memory of
857 * qhasharr struct. User provided data memory must be freed
858 * by user.
859 */
860void qhasharr_free(qhasharr_t *tbl) {
861 free(tbl);
862}
863
864#ifndef _DOXYGEN_SKIP
865
866static qhasharr_slot_t* get_slots(qhasharr_t *tbl) {
867 return (qhasharr_slot_t*) ((char*) (tbl->data) + sizeof(qhasharr_data_t));
868}
869
870// find empty slot : return empty slow number, otherwise returns -1.
871static int find_avail(qhasharr_t *tbl, int startidx) {
872 qhasharr_data_t *tbldata = tbl->data;
873 qhasharr_slot_t *tblslots = get_slots(tbl);
874
875 if (startidx >= tbldata->maxslots)
876 startidx = 0;
877
878 int idx = startidx;
879 while (true) {
880 if (tblslots[idx].count == 0)
881 return idx;
882
883 idx++;
884 if (idx >= tbldata->maxslots)
885 idx = 0;
886 if (idx == startidx)
887 break;
888 }
889
890 return -1;
891}
892
893static int get_idx(qhasharr_t *tbl, const void *name, size_t namesize,
894 uint32_t hash) {
895 qhasharr_data_t *tbldata = tbl->data;
896 qhasharr_slot_t *tblslots = get_slots(tbl);
897
898 if (tblslots[hash].count > 0) {
899 int count, idx;
900 for (count = 0, idx = hash; count < tblslots[hash].count;) {
901 if (tblslots[idx].hash == hash
902 && (tblslots[idx].count > 0 || tblslots[idx].count == COLLISION_MARK)) {
903 // same hash
904 count++;
905
906 // is same key?
907 // first check key length
908 if (namesize == tblslots[idx].data.pair.namesize) {
909 if (namesize <= Q_HASHARR_NAMESIZE) {
910 // original key is stored
911 if (!memcmp(name, tblslots[idx].data.pair.name,
912 namesize)) {
913 return idx;
914 }
915 } else {
916 // key is truncated, compare MD5 also.
917 unsigned char namemd5[16];
918 qhashmd5(name, namesize, namemd5);
919 if (!memcmp(name, tblslots[idx].data.pair.name,
920 Q_HASHARR_NAMESIZE)
921 && !memcmp(namemd5,
922 tblslots[idx].data.pair.namemd5,
923 16)) {
924 return idx;
925 }
926 }
927 }
928 }
929
930 // increase idx
931 idx++;
932 if (idx >= tbldata->maxslots)
933 idx = 0;
934
935 // check loop
936 if (idx == hash)
937 break;
938
939 continue;
940 }
941 }
942
943 return -1;
944}
945
946static void *get_data(qhasharr_t *tbl, int idx, size_t *size) {
947 if (idx < 0) {
948 errno = ENOENT;
949 return NULL;
950 }
951
952 qhasharr_slot_t *tblslots = get_slots(tbl);
953
954 int newidx;
955 size_t datasize;
956 for (newidx = idx, datasize = 0;; newidx = tblslots[newidx].link) {
957 datasize += tblslots[newidx].datasize;
958 if (tblslots[newidx].link == -1)
959 break;
960 }
961
962 void *data, *dp;
963 if ((data = malloc(datasize)) == NULL) {
964 errno = ENOMEM;
965 return NULL;
966 }
967
968 for (newidx = idx, dp = data;; newidx = tblslots[newidx].link) {
969 if (tblslots[newidx].count == EXTBLOCK_MARK) {
970 // extended data block
971 memcpy(dp, (void *) tblslots[newidx].data.ext.data,
972 tblslots[newidx].datasize);
973 } else {
974 // key/value pair data block
975 memcpy(dp, (void *) tblslots[newidx].data.pair.data,
976 tblslots[newidx].datasize);
977 }
978
979 dp += tblslots[newidx].datasize;
980 if (tblslots[newidx].link == -1)
981 break;
982 }
983
984 if (size != NULL)
985 *size = datasize;
986 return data;
987}
988
989static bool put_data(qhasharr_t *tbl, int idx, uint32_t hash, const void *name,
990 size_t namesize, const void *data, size_t datasize,
991 int count) {
992 qhasharr_data_t *tbldata = tbl->data;
993 qhasharr_slot_t *tblslots = get_slots(tbl);
994
995 assert(tblslots[idx].count == 0);
996
997 unsigned char namemd5[16];
998 qhashmd5(name, namesize, namemd5);
999
1000 // store name
1001 tblslots[idx].count = count;
1002 tblslots[idx].hash = hash;
1003 memcpy(tblslots[idx].data.pair.name, name,
1004 (namesize < Q_HASHARR_NAMESIZE) ? namesize : Q_HASHARR_NAMESIZE);
1005 memcpy((char *) tblslots[idx].data.pair.namemd5, (char *) namemd5, 16);
1006 tblslots[idx].data.pair.namesize = namesize;
1007 tblslots[idx].link = -1;
1008
1009 // store data
1010 int newidx;
1011 size_t savesize;
1012 for (newidx = idx, savesize = 0; savesize < datasize;) {
1013 if (savesize > 0) { // find next empty slot
1014 int tmpidx = find_avail(tbl, newidx + 1);
1015 if (tmpidx < 0) {
1016 remove_data(tbl, idx);
1017 errno = ENOBUFS;
1018 return false;
1019 }
1020
1021 // clear & set
1022 memset((void *) (&tblslots[tmpidx]), '\0', sizeof(qhasharr_slot_t));
1023
1024 tblslots[tmpidx].count = EXTBLOCK_MARK; // extended data block
1025 tblslots[tmpidx].hash = newidx; // previous link
1026 tblslots[tmpidx].link = -1; // end block mark
1027 tblslots[tmpidx].datasize = 0;
1028 tblslots[newidx].link = tmpidx; // link chain
1029
1030 newidx = tmpidx;
1031 }
1032
1033 // copy data
1034 size_t copysize = datasize - savesize;
1035 if (tblslots[newidx].count == EXTBLOCK_MARK) {
1036 // extended value
1037 if (copysize > sizeof(struct Q_HASHARR_SLOT_EXT)) {
1038 copysize = sizeof(struct Q_HASHARR_SLOT_EXT);
1039 }
1040 memcpy(tblslots[newidx].data.ext.data, data + savesize, copysize);
1041 } else {
1042 // first slot
1043 if (copysize > Q_HASHARR_DATASIZE) {
1044 copysize = Q_HASHARR_DATASIZE;
1045 }
1046 memcpy(tblslots[newidx].data.pair.data, data + savesize, copysize);
1047
1048 // increase stored key counter
1049 tbldata->num++;
1050 }
1051 tblslots[newidx].datasize = copysize;
1052 savesize += copysize;
1053
1054 // increase used slot counter
1055 tbldata->usedslots++;
1056 }
1057
1058 return true;
1059}
1060
1061static bool copy_slot(qhasharr_t *tbl, int idx1, int idx2) {
1062 qhasharr_slot_t *tblslots = get_slots(tbl);
1063
1064 if (tblslots[idx1].count != 0 || tblslots[idx2].count == 0) {
1065 errno = EFAULT;
1066 return false;
1067 }
1068
1069 memcpy((void *) (&tblslots[idx1]), (void *) (&tblslots[idx2]),
1070 sizeof(qhasharr_slot_t));
1071
1072 return true;
1073}
1074
1075static bool remove_slot(qhasharr_t *tbl, int idx) {
1076 qhasharr_slot_t *tblslots = get_slots(tbl);
1077 assert(tblslots[idx].count != 0);
1078
1079 tblslots[idx].count = 0;
1080 return true;
1081}
1082
1083static bool remove_data(qhasharr_t *tbl, int idx) {
1084 qhasharr_data_t *tbldata = tbl->data;
1085 qhasharr_slot_t *tblslots = get_slots(tbl);
1086 assert(tblslots[idx].count != 0);
1087
1088 while (true) {
1089 int link = tblslots[idx].link;
1090 remove_slot(tbl, idx);
1091 tbldata->usedslots--;
1092
1093 if (link == -1)
1094 break;
1095
1096 idx = link;
1097 }
1098
1099 // decrease stored key counter
1100 tbldata->num--;
1101
1102 return true;
1103}
1104
1105#endif /* _DOXYGEN_SKIP */
bool qhashmd5(const void *data, size_t nbytes, void *retbuf)
Calculate 128-bit(16-bytes) MD5 hash.
Definition qhash.c:67
uint32_t qhashmurmur3_32(const void *data, size_t nbytes)
Get 32-bit Murmur3 hash.
Definition qhash.c:265
void qhasharr_clear(qhasharr_t *tbl)
qhasharr->clear(): Clears this table so that it contains no keys.
Definition qhasharr.c:748
int qhasharr_size(qhasharr_t *tbl, int *maxslots, int *usedslots)
qhasharr->size(): Returns the number of objects in this table.
Definition qhasharr.c:725
bool qhasharr_put_by_obj(qhasharr_t *tbl, const void *name, size_t namesize, const void *data, size_t datasize)
qhasharr->put_by_obj(): Put an object into this table by key object.
Definition qhasharr.c:344
bool qhasharr_remove_by_idx(qhasharr_t *tbl, int idx)
qhasharr->remove_by_idx(): Remove an object from this table by index number.
Definition qhasharr.c:589
void * qhasharr_get(qhasharr_t *tbl, const char *name, size_t *datasize)
qhasharr->get(): Get an object from this table
Definition qhasharr.c:443
bool qhasharr_debug(qhasharr_t *tbl, FILE *out)
qhasharr->debug(): Print the hash table for debugging purposes.
Definition qhasharr.c:778
bool qhasharr_put(qhasharr_t *tbl, const char *name, const void *data, size_t datasize)
qhasharr->put(): Put an object into this table.
Definition qhasharr.c:278
qhasharr_t * qhasharr(void *memory, size_t memsize)
Initialize a static hash table.
Definition qhasharr.c:208
size_t qhasharr_calculate_memsize(int max)
Get how much memory is needed for N slots.
Definition qhasharr.c:179
char * qhasharr_getstr(qhasharr_t *tbl, const char *name)
qhasharr->getstr(): Finds an object with the given name and returns it as a string.
Definition qhasharr.c:463
bool qhasharr_getnext(qhasharr_t *tbl, qhasharr_obj_t *obj, int *idx)
qhasharr->getnext(): Get next element.
Definition qhasharr.c:674
void * qhasharr_get_by_obj(qhasharr_t *tbl, const void *name, size_t namesize, size_t *datasize)
qhasharr->get_by_obj(): Get an object from this table by key object.
Definition qhasharr.c:484
bool qhasharr_putstrf(qhasharr_t *tbl, const char *name, const char *format,...)
qhasharr->putstrf(): Put a formatted string into this table.
Definition qhasharr.c:316
void qhasharr_free(qhasharr_t *tbl)
qhasharr->free(): Free table reference object.
Definition qhasharr.c:860
bool qhasharr_remove(qhasharr_t *tbl, const char *name)
qhasharr->remove(): Remove an object from this table.
Definition qhasharr.c:516
bool qhasharr_putstr(qhasharr_t *tbl, const char *name, const char *data)
qhasharr->putstr(): Put a string into this table
Definition qhasharr.c:297
bool qhasharr_remove_by_obj(qhasharr_t *tbl, const char *name, size_t namesize)
qhasharr->remove_by_obj(): Remove an object from this table by key object
Definition qhasharr.c:533