LCOV - code coverage report
Current view: top level - src/container - hashmap.c (source / functions) Hit Total Coverage
Test: passgen-test.info Lines: 146 156 93.6 %
Date: 2024-05-03 06:05:14 Functions: 20 20 100.0 %

          Line data    Source code
       1             : #include "passgen/container/hashmap.h"
       2             : #include "passgen/util/siphash.h"
       3             : #include <stdlib.h>
       4             : #define UNUSED(x) (void) x
       5             : 
       6             : static const size_t hashmap_sizes[] = {
       7             :     3,
       8             :     7,
       9             :     17,
      10             :     37,
      11             :     79,
      12             :     163,
      13             :     331,
      14             :     673,
      15             :     1361,
      16             :     2729,
      17             :     5471,
      18             :     10949,
      19             :     21911,
      20             :     43853,
      21             :     87719,
      22             :     175447,
      23             :     350899,
      24             :     701819,
      25             :     0};
      26             : 
      27             : struct siphash_keys {
      28             :     const char *first;
      29             :     const char *second;
      30             : };
      31             : 
      32             : static const struct siphash_keys utf8_keys = {
      33             :     .first = "someverylongpass",
      34             :     .second = "myotherverylongk",
      35             : };
      36             : 
      37             : static const struct siphash_keys utf32_keys = {
      38             :     .first = "someverylongpass",
      39             :     .second = "myotherverylongk",
      40             : };
      41             : 
      42          95 : void passgen_hashmap_init(
      43             :     passgen_hashmap *map,
      44             :     const passgen_hashmap_context *context) {
      45             :     // zero-out hashmap
      46          95 :     memset(map, 0, sizeof(*map));
      47             : 
      48             :     // set context or default to UTF-8 context
      49          95 :     if(context) {
      50          89 :         map->context = context;
      51             :     } else {
      52           6 :         map->context = &passgen_hashmap_context_utf8;
      53             :     }
      54             : 
      55             :     // initialize hashmap context
      56          95 :     map->context->init(map);
      57          95 : }
      58             : 
      59          19 : void passgen_hashmap_free(passgen_hashmap *map) {
      60             :     // deinitialize context
      61          19 :     map->context->fini(map);
      62             : 
      63             :     // deallocate data
      64          19 :     free(map->data);
      65             : 
      66             :     // set everything to zero
      67          19 :     memset(map, 0, sizeof(*map));
      68          19 : }
      69             : 
      70          20 : void passgen_hashmap_realloc(passgen_hashmap *map, size_t capacity) {
      71             :     // save previous hashmap
      72          20 :     passgen_hashmap old = *map;
      73             : 
      74          20 :     map->data = calloc(capacity, sizeof(passgen_hashmap_entry));
      75          20 :     map->capacity = capacity;
      76          20 :     map->len = 0;
      77             : 
      78             :     // copy data over
      79       87607 :     for(size_t i = 0; i < old.capacity; i++) {
      80       87587 :         if(old.data[i].key) {
      81       13997 :             passgen_hashmap_insert(map, old.data[i].key, old.data[i].value);
      82             :         }
      83             :     }
      84             : 
      85          20 :     free(old.data);
      86          20 : }
      87             : 
      88       38651 : static inline size_t passgen_hashmap_position(
      89             :     const passgen_hashmap *map,
      90             :     const void *key,
      91             :     bool first) {
      92       38651 :     return map->context->hash(map, key, first) % map->capacity;
      93             : }
      94             : 
      95        1859 : static inline bool passgen_hashmap_move(passgen_hashmap *map, size_t pos) {
      96        1859 :     const void *key = map->data[pos].key;
      97        1859 :     bool first = passgen_hashmap_position(map, key, true) == pos;
      98        1859 :     size_t other_pos = passgen_hashmap_position(map, key, !first);
      99        1859 :     if(!map->data[other_pos].key) {
     100        1568 :         map->data[other_pos].key = map->data[pos].key;
     101        1568 :         map->data[other_pos].value = map->data[pos].value;
     102        1568 :         return true;
     103             :     }
     104         291 :     return false;
     105             : }
     106             : 
     107          15 : static inline size_t next_capacity(passgen_hashmap *map) {
     108             :     size_t i;
     109         106 :     for(i = 0; hashmap_sizes[i]; i++) {
     110         106 :         if(hashmap_sizes[i] >= map->capacity) {
     111          15 :             break;
     112             :         }
     113             :     }
     114          15 :     if(hashmap_sizes[i] && hashmap_sizes[i + 1]) {
     115          15 :         return hashmap_sizes[i + 1];
     116             :     } else {
     117           0 :         return 2 * map->capacity - 1;
     118             :     }
     119             : }
     120             : 
     121             : passgen_hashmap_entry
     122       24023 : passgen_hashmap_insert(passgen_hashmap *map, const void *key, void *value) {
     123       24023 :     if(map->capacity == 0) {
     124           5 :         passgen_hashmap_realloc(map, hashmap_sizes[0]);
     125             :     }
     126             : 
     127             :     size_t hash;
     128             : 
     129       24023 :     hash = passgen_hashmap_position(map, key, true);
     130       24023 :     if(!map->data[hash].key) {
     131       22211 :         map->data[hash].key = key;
     132       22211 :         map->data[hash].value = value;
     133       22211 :         map->len += 1;
     134       22211 :         return (passgen_hashmap_entry){NULL, NULL};
     135             :     } else {
     136        1812 :         if(map->context->equal(map, map->data[hash].key, key)) {
     137           0 :             passgen_hashmap_entry entry = map->data[hash];
     138           0 :             map->data[hash].key = key;
     139           0 :             map->data[hash].value = value;
     140           0 :             return entry;
     141        1812 :         } else if(passgen_hashmap_move(map, hash)) {
     142        1536 :             map->data[hash].key = key;
     143        1536 :             map->data[hash].value = value;
     144        1536 :             map->len += 1;
     145        1536 :             return (passgen_hashmap_entry){NULL, NULL};
     146             :         }
     147             :     }
     148             : 
     149         276 :     hash = passgen_hashmap_position(map, key, false);
     150         276 :     if(!map->data[hash].key) {
     151         229 :         map->data[hash].key = key;
     152         229 :         map->data[hash].value = value;
     153         229 :         map->len += 1;
     154         229 :         return (passgen_hashmap_entry){NULL, NULL};
     155             :     } else {
     156          47 :         if(map->context->equal(map, map->data[hash].key, key)) {
     157           0 :             passgen_hashmap_entry entry = map->data[hash];
     158           0 :             map->data[hash].key = key;
     159           0 :             map->data[hash].value = value;
     160           0 :             return entry;
     161          47 :         } else if(passgen_hashmap_move(map, hash)) {
     162          32 :             map->data[hash].key = key;
     163          32 :             map->data[hash].value = value;
     164          32 :             map->len += 1;
     165          32 :             return (passgen_hashmap_entry){NULL, NULL};
     166             :         }
     167             :     }
     168             : 
     169          15 :     passgen_hashmap_realloc(map, next_capacity(map));
     170          15 :     return passgen_hashmap_insert(map, key, value);
     171             : }
     172             : 
     173             : passgen_hashmap_entry
     174           2 : passgen_hashmap_remove(passgen_hashmap *map, const void *key) {
     175             :     size_t hash;
     176             : 
     177             :     // if this key exists
     178           2 :     hash = passgen_hashmap_position(map, key, true);
     179           2 :     if(map->data[hash].key) {
     180             :         // and it matches
     181           2 :         if(map->context->equal(map, map->data[hash].key, key)) {
     182           1 :             passgen_hashmap_entry entry = map->data[hash];
     183           1 :             map->data[hash].key = NULL;
     184           1 :             map->data[hash].value = NULL;
     185           1 :             map->len -= 1;
     186           1 :             return entry;
     187             :         }
     188             :     }
     189             : 
     190             :     // if this key exists
     191           1 :     hash = passgen_hashmap_position(map, key, false);
     192           1 :     if(map->data[hash].key) {
     193             :         // and it matches
     194           1 :         if(map->context->equal(map, map->data[hash].key, key)) {
     195           1 :             passgen_hashmap_entry entry = map->data[hash];
     196           1 :             map->data[hash].key = NULL;
     197           1 :             map->data[hash].value = NULL;
     198           1 :             map->len -= 1;
     199           1 :             return entry;
     200             :         }
     201             :     }
     202             : 
     203           0 :     return (passgen_hashmap_entry){NULL, NULL};
     204             : }
     205             : 
     206             : passgen_hashmap_entry *
     207       10016 : passgen_hashmap_lookup(const passgen_hashmap *map, const void *key) {
     208             :     // make sure the hashmap is not empty
     209       10016 :     if(!map->data) {
     210           1 :         return NULL;
     211             :     }
     212             : 
     213             :     size_t hash;
     214             : 
     215             :     // lookup first possible location
     216       10015 :     hash = passgen_hashmap_position(map, key, true);
     217       20026 :     if(map->data[hash].key &&
     218       10011 :        map->context->equal(map, map->data[hash].key, key)) {
     219        9399 :         return &map->data[hash];
     220             :     }
     221             : 
     222             :     // lookup second possible location
     223         616 :     hash = passgen_hashmap_position(map, key, false);
     224        1225 :     if(map->data[hash].key &&
     225         609 :        map->context->equal(map, map->data[hash].key, key)) {
     226         609 :         return &map->data[hash];
     227             :     }
     228             : 
     229           7 :     return NULL;
     230             : }
     231             : 
     232           8 : int passgen_hashmap_foreach(
     233             :     const passgen_hashmap *map,
     234             :     void *user,
     235             :     int (*func)(void *user, passgen_hashmap_entry *entry)) {
     236           8 :     if(map->len) {
     237       87730 :         for(size_t i = 0; i < map->capacity; i++) {
     238       87728 :             if(map->data[i].key) {
     239       10006 :                 int ret = func(user, &map->data[i]);
     240       10006 :                 if(ret != 0) {
     241           1 :                     return ret;
     242             :                 }
     243             :             }
     244             :         }
     245             :     }
     246             : 
     247           7 :     return 0;
     248             : }
     249             : 
     250          93 : static void utf8_init(passgen_hashmap *map) {
     251          93 :     map->context_data = (void *) &utf8_keys;
     252          93 : }
     253             : 
     254          17 : static void utf8_fini(passgen_hashmap *map) {
     255          17 :     map->context_data = NULL;
     256          17 : }
     257             : 
     258             : static uint64_t
     259       38650 : utf8_hash(const passgen_hashmap *map, const void *key, bool first) {
     260             :     UNUSED(map);
     261             :     uint64_t output;
     262       38650 :     const struct siphash_keys *keys = map->context_data;
     263       38650 :     const char *siphash_key = first ? keys->first : keys->second;
     264       38650 :     passgen_siphash(
     265             :         key,
     266             :         strlen(key),
     267             :         siphash_key,
     268             :         (uint8_t *) &output,
     269             :         sizeof(output));
     270       38650 :     return output;
     271             : }
     272             : 
     273             : static bool
     274       12482 : utf8_equal(const passgen_hashmap *map, const void *lhs, const void *rhs) {
     275             :     UNUSED(map);
     276       12482 :     return strcmp(lhs, rhs) == 0;
     277             : }
     278             : 
     279             : const passgen_hashmap_context passgen_hashmap_context_utf8 = {
     280             :     .hash = utf8_hash,
     281             :     .equal = utf8_equal,
     282             :     .init = utf8_init,
     283             :     .fini = utf8_fini,
     284             : };
     285             : 
     286          21 : static size_t utf32_len(const void *data) {
     287             :     // cast void pointer to array of UTF-32 codepoints
     288          21 :     const int32_t *utf32 = data;
     289             : 
     290             :     // iterate over until we hit zero codepoint
     291          21 :     size_t len = 0;
     292         117 :     while(utf32[len]) {
     293          96 :         len++;
     294             :     }
     295             : 
     296          21 :     return len;
     297             : }
     298             : 
     299             : static uint64_t
     300           9 : utf32_hash(const passgen_hashmap *map, const void *key, bool first) {
     301             :     UNUSED(map);
     302             :     uint64_t output;
     303           9 :     const struct siphash_keys *keys = map->context_data;
     304           9 :     const char *siphash_key = first ? keys->first : keys->second;
     305           9 :     passgen_siphash(
     306             :         key,
     307             :         utf32_len(key),
     308             :         siphash_key,
     309             :         (uint8_t *) &output,
     310             :         sizeof(output));
     311           9 :     return output;
     312             : }
     313             : 
     314             : static bool
     315           6 : utf32_equal(const passgen_hashmap *map, const void *lhs, const void *rhs) {
     316             :     UNUSED(map);
     317             :     // compute lengths of lhs and rhs
     318           6 :     size_t rhs_len = utf32_len(rhs);
     319           6 :     size_t lhs_len = utf32_len(lhs);
     320             : 
     321             :     // if lengths are not the same, the keys cannot be equal
     322           6 :     if(rhs_len != lhs_len) {
     323           1 :         return false;
     324             :     }
     325             : 
     326             :     // compare lhs and rhs
     327           5 :     int ret = memcmp(lhs, rhs, rhs_len * sizeof(uint32_t));
     328             : 
     329             :     // return true if comparison was equal
     330           5 :     return ret == 0;
     331             : }
     332             : 
     333           2 : static void utf32_init(passgen_hashmap *map) {
     334           2 :     map->context_data = (void *) &utf32_keys;
     335           2 : }
     336             : 
     337           2 : static void utf32_fini(passgen_hashmap *map) {
     338           2 :     map->context_data = NULL;
     339           2 : }
     340             : 
     341             : const passgen_hashmap_context passgen_hashmap_context_utf32 = {
     342             :     .hash = utf32_hash,
     343             :     .equal = utf32_equal,
     344             :     .init = utf32_init,
     345             :     .fini = utf32_fini,
     346             : };
     347             : 
     348       10000 : int passgen_hashmap_entry_free(void *user, passgen_hashmap_entry *entry) {
     349             :     (void) user;
     350       10000 :     free((void *) entry->key);
     351       10000 :     free(entry->value);
     352       10000 :     return 0;
     353             : }

Generated by: LCOV version 1.14