xref: /freebsd/crypto/krb5/src/util/support/hashtab.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1*7f2fe78bSCy Schubert /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2*7f2fe78bSCy Schubert /* util/support/hash.c - hash table implementation */
3*7f2fe78bSCy Schubert /*
4*7f2fe78bSCy Schubert  * Copyright (C) 2018 by the Massachusetts Institute of Technology.
5*7f2fe78bSCy Schubert  * All rights reserved.
6*7f2fe78bSCy Schubert  *
7*7f2fe78bSCy Schubert  * Redistribution and use in source and binary forms, with or without
8*7f2fe78bSCy Schubert  * modification, are permitted provided that the following conditions
9*7f2fe78bSCy Schubert  * are met:
10*7f2fe78bSCy Schubert  *
11*7f2fe78bSCy Schubert  * * Redistributions of source code must retain the above copyright
12*7f2fe78bSCy Schubert  *   notice, this list of conditions and the following disclaimer.
13*7f2fe78bSCy Schubert  *
14*7f2fe78bSCy Schubert  * * Redistributions in binary form must reproduce the above copyright
15*7f2fe78bSCy Schubert  *   notice, this list of conditions and the following disclaimer in
16*7f2fe78bSCy Schubert  *   the documentation and/or other materials provided with the
17*7f2fe78bSCy Schubert  *   distribution.
18*7f2fe78bSCy Schubert  *
19*7f2fe78bSCy Schubert  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20*7f2fe78bSCy Schubert  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21*7f2fe78bSCy Schubert  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22*7f2fe78bSCy Schubert  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23*7f2fe78bSCy Schubert  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24*7f2fe78bSCy Schubert  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25*7f2fe78bSCy Schubert  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26*7f2fe78bSCy Schubert  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27*7f2fe78bSCy Schubert  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28*7f2fe78bSCy Schubert  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29*7f2fe78bSCy Schubert  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30*7f2fe78bSCy Schubert  * OF THE POSSIBILITY OF SUCH DAMAGE.
31*7f2fe78bSCy Schubert  */
32*7f2fe78bSCy Schubert 
33*7f2fe78bSCy Schubert #include "k5-platform.h"
34*7f2fe78bSCy Schubert #include "k5-hashtab.h"
35*7f2fe78bSCy Schubert #include "k5-queue.h"
36*7f2fe78bSCy Schubert 
37*7f2fe78bSCy Schubert struct entry {
38*7f2fe78bSCy Schubert     const void *key;
39*7f2fe78bSCy Schubert     size_t klen;
40*7f2fe78bSCy Schubert     void *val;
41*7f2fe78bSCy Schubert     K5_SLIST_ENTRY(entry) next;
42*7f2fe78bSCy Schubert };
43*7f2fe78bSCy Schubert 
44*7f2fe78bSCy Schubert struct k5_hashtab {
45*7f2fe78bSCy Schubert     uint64_t k0;
46*7f2fe78bSCy Schubert     uint64_t k1;
47*7f2fe78bSCy Schubert     size_t nbuckets;
48*7f2fe78bSCy Schubert     size_t nentries;
49*7f2fe78bSCy Schubert     K5_SLIST_HEAD(bucket_list, entry) *buckets;
50*7f2fe78bSCy Schubert };
51*7f2fe78bSCy Schubert 
52*7f2fe78bSCy Schubert /* Return x rotated to the left by r bits. */
53*7f2fe78bSCy Schubert static inline uint64_t
rotl64(uint64_t x,int r)54*7f2fe78bSCy Schubert rotl64(uint64_t x, int r)
55*7f2fe78bSCy Schubert {
56*7f2fe78bSCy Schubert     return (x << r) | (x >> (64 - r));
57*7f2fe78bSCy Schubert }
58*7f2fe78bSCy Schubert 
59*7f2fe78bSCy Schubert static inline void
sipround(uint64_t * v0,uint64_t * v1,uint64_t * v2,uint64_t * v3)60*7f2fe78bSCy Schubert sipround(uint64_t *v0, uint64_t *v1, uint64_t *v2, uint64_t *v3)
61*7f2fe78bSCy Schubert {
62*7f2fe78bSCy Schubert     *v0 += *v1;
63*7f2fe78bSCy Schubert     *v2 += *v3;
64*7f2fe78bSCy Schubert     *v1 = rotl64(*v1, 13) ^ *v0;
65*7f2fe78bSCy Schubert     *v3 = rotl64(*v3, 16) ^ *v2;
66*7f2fe78bSCy Schubert     *v0 = rotl64(*v0, 32);
67*7f2fe78bSCy Schubert     *v2 += *v1;
68*7f2fe78bSCy Schubert     *v0 += *v3;
69*7f2fe78bSCy Schubert     *v1 = rotl64(*v1, 17) ^ *v2;
70*7f2fe78bSCy Schubert     *v3 = rotl64(*v3, 21) ^ *v0;
71*7f2fe78bSCy Schubert     *v2 = rotl64(*v2, 32);
72*7f2fe78bSCy Schubert }
73*7f2fe78bSCy Schubert 
74*7f2fe78bSCy Schubert /* SipHash-2-4 from https://131002.net/siphash/siphash.pdf (Jean-Philippe
75*7f2fe78bSCy Schubert  * Aumasson and Daniel J. Bernstein) */
76*7f2fe78bSCy Schubert static uint64_t
siphash24(const uint8_t * data,size_t len,uint64_t k0,uint64_t k1)77*7f2fe78bSCy Schubert siphash24(const uint8_t *data, size_t len, uint64_t k0, uint64_t k1)
78*7f2fe78bSCy Schubert {
79*7f2fe78bSCy Schubert     uint64_t v0 = k0 ^ 0x736F6D6570736575;
80*7f2fe78bSCy Schubert     uint64_t v1 = k1 ^ 0x646F72616E646F6D;
81*7f2fe78bSCy Schubert     uint64_t v2 = k0 ^ 0x6C7967656E657261;
82*7f2fe78bSCy Schubert     uint64_t v3 = k1 ^ 0x7465646279746573;
83*7f2fe78bSCy Schubert     uint64_t mi;
84*7f2fe78bSCy Schubert     const uint8_t *p, *end = data + (len - len % 8);
85*7f2fe78bSCy Schubert     uint8_t last[8] = { 0 };
86*7f2fe78bSCy Schubert 
87*7f2fe78bSCy Schubert     /* Process each full 8-byte chunk of data. */
88*7f2fe78bSCy Schubert     for (p = data; p < end; p += 8) {
89*7f2fe78bSCy Schubert         mi = load_64_le(p);
90*7f2fe78bSCy Schubert         v3 ^= mi;
91*7f2fe78bSCy Schubert         sipround(&v0, &v1, &v2, &v3);
92*7f2fe78bSCy Schubert         sipround(&v0, &v1, &v2, &v3);
93*7f2fe78bSCy Schubert         v0 ^= mi;
94*7f2fe78bSCy Schubert     }
95*7f2fe78bSCy Schubert 
96*7f2fe78bSCy Schubert     /* Process the last 0-7 bytes followed by the length mod 256. */
97*7f2fe78bSCy Schubert     memcpy(last, end, len % 8);
98*7f2fe78bSCy Schubert     last[7] = len & 0xFF;
99*7f2fe78bSCy Schubert     mi = load_64_le(last);
100*7f2fe78bSCy Schubert     v3 ^= mi;
101*7f2fe78bSCy Schubert     sipround(&v0, &v1, &v2, &v3);
102*7f2fe78bSCy Schubert     sipround(&v0, &v1, &v2, &v3);
103*7f2fe78bSCy Schubert     v0 ^= mi;
104*7f2fe78bSCy Schubert 
105*7f2fe78bSCy Schubert     /* Finalize. */
106*7f2fe78bSCy Schubert     v2 ^= 0xFF;
107*7f2fe78bSCy Schubert     sipround(&v0, &v1, &v2, &v3);
108*7f2fe78bSCy Schubert     sipround(&v0, &v1, &v2, &v3);
109*7f2fe78bSCy Schubert     sipround(&v0, &v1, &v2, &v3);
110*7f2fe78bSCy Schubert     sipround(&v0, &v1, &v2, &v3);
111*7f2fe78bSCy Schubert     return v0 ^ v1 ^ v2 ^ v3;
112*7f2fe78bSCy Schubert }
113*7f2fe78bSCy Schubert 
114*7f2fe78bSCy Schubert uint64_t
k5_siphash24(const uint8_t * data,size_t len,const uint8_t seed[K5_HASH_SEED_LEN])115*7f2fe78bSCy Schubert k5_siphash24(const uint8_t *data, size_t len,
116*7f2fe78bSCy Schubert              const uint8_t seed[K5_HASH_SEED_LEN])
117*7f2fe78bSCy Schubert {
118*7f2fe78bSCy Schubert     uint64_t k0 = load_64_le(seed), k1 = load_64_le(seed + 8);
119*7f2fe78bSCy Schubert 
120*7f2fe78bSCy Schubert     return siphash24(data, len, k0, k1);
121*7f2fe78bSCy Schubert }
122*7f2fe78bSCy Schubert 
123*7f2fe78bSCy Schubert int
k5_hashtab_create(const uint8_t seed[K5_HASH_SEED_LEN],size_t initial_buckets,struct k5_hashtab ** ht_out)124*7f2fe78bSCy Schubert k5_hashtab_create(const uint8_t seed[K5_HASH_SEED_LEN], size_t initial_buckets,
125*7f2fe78bSCy Schubert                   struct k5_hashtab **ht_out)
126*7f2fe78bSCy Schubert {
127*7f2fe78bSCy Schubert     struct k5_hashtab *ht;
128*7f2fe78bSCy Schubert 
129*7f2fe78bSCy Schubert     *ht_out = NULL;
130*7f2fe78bSCy Schubert 
131*7f2fe78bSCy Schubert     ht = malloc(sizeof(*ht));
132*7f2fe78bSCy Schubert     if (ht == NULL)
133*7f2fe78bSCy Schubert         return ENOMEM;
134*7f2fe78bSCy Schubert 
135*7f2fe78bSCy Schubert     if (seed != NULL) {
136*7f2fe78bSCy Schubert         ht->k0 = load_64_le(seed);
137*7f2fe78bSCy Schubert         ht->k1 = load_64_le(seed + 8);
138*7f2fe78bSCy Schubert     } else {
139*7f2fe78bSCy Schubert         ht->k0 = ht->k1 = 0;
140*7f2fe78bSCy Schubert     }
141*7f2fe78bSCy Schubert     ht->nbuckets = (initial_buckets > 0) ? initial_buckets : 64;
142*7f2fe78bSCy Schubert     ht->nentries = 0;
143*7f2fe78bSCy Schubert     ht->buckets = calloc(ht->nbuckets, sizeof(*ht->buckets));
144*7f2fe78bSCy Schubert     if (ht->buckets == NULL) {
145*7f2fe78bSCy Schubert         free(ht);
146*7f2fe78bSCy Schubert         return ENOMEM;
147*7f2fe78bSCy Schubert     }
148*7f2fe78bSCy Schubert 
149*7f2fe78bSCy Schubert     *ht_out = ht;
150*7f2fe78bSCy Schubert     return 0;
151*7f2fe78bSCy Schubert }
152*7f2fe78bSCy Schubert 
153*7f2fe78bSCy Schubert void
k5_hashtab_free(struct k5_hashtab * ht)154*7f2fe78bSCy Schubert k5_hashtab_free(struct k5_hashtab *ht)
155*7f2fe78bSCy Schubert {
156*7f2fe78bSCy Schubert     size_t i;
157*7f2fe78bSCy Schubert     struct entry *ent;
158*7f2fe78bSCy Schubert 
159*7f2fe78bSCy Schubert     for (i = 0; i < ht->nbuckets; i++) {
160*7f2fe78bSCy Schubert         while (!K5_SLIST_EMPTY(&ht->buckets[i])) {
161*7f2fe78bSCy Schubert             ent = K5_SLIST_FIRST(&ht->buckets[i]);
162*7f2fe78bSCy Schubert             K5_SLIST_REMOVE_HEAD(&ht->buckets[i], next);
163*7f2fe78bSCy Schubert             free(ent);
164*7f2fe78bSCy Schubert         }
165*7f2fe78bSCy Schubert     }
166*7f2fe78bSCy Schubert     free(ht->buckets);
167*7f2fe78bSCy Schubert     free(ht);
168*7f2fe78bSCy Schubert }
169*7f2fe78bSCy Schubert 
170*7f2fe78bSCy Schubert static int
resize_table(struct k5_hashtab * ht)171*7f2fe78bSCy Schubert resize_table(struct k5_hashtab *ht)
172*7f2fe78bSCy Schubert {
173*7f2fe78bSCy Schubert     size_t i, j, newsize = ht->nbuckets * 2;
174*7f2fe78bSCy Schubert     struct bucket_list *newbuckets;
175*7f2fe78bSCy Schubert     struct entry *ent;
176*7f2fe78bSCy Schubert 
177*7f2fe78bSCy Schubert     newbuckets = calloc(newsize, sizeof(*newbuckets));
178*7f2fe78bSCy Schubert     if (newbuckets == NULL)
179*7f2fe78bSCy Schubert         return ENOMEM;
180*7f2fe78bSCy Schubert 
181*7f2fe78bSCy Schubert     /* Rehash all the entries into the new buckets. */
182*7f2fe78bSCy Schubert     for (i = 0; i < ht->nbuckets; i++) {
183*7f2fe78bSCy Schubert         while (!K5_SLIST_EMPTY(&ht->buckets[i])) {
184*7f2fe78bSCy Schubert             ent = K5_SLIST_FIRST(&ht->buckets[i]);
185*7f2fe78bSCy Schubert             j = siphash24(ent->key, ent->klen, ht->k0, ht->k1) % newsize;
186*7f2fe78bSCy Schubert             K5_SLIST_REMOVE_HEAD(&ht->buckets[i], next);
187*7f2fe78bSCy Schubert             K5_SLIST_INSERT_HEAD(&newbuckets[j], ent, next);
188*7f2fe78bSCy Schubert         }
189*7f2fe78bSCy Schubert     }
190*7f2fe78bSCy Schubert 
191*7f2fe78bSCy Schubert     free(ht->buckets);
192*7f2fe78bSCy Schubert     ht->buckets = newbuckets;
193*7f2fe78bSCy Schubert     ht->nbuckets = newsize;
194*7f2fe78bSCy Schubert     return 0;
195*7f2fe78bSCy Schubert }
196*7f2fe78bSCy Schubert 
197*7f2fe78bSCy Schubert int
k5_hashtab_add(struct k5_hashtab * ht,const void * key,size_t klen,void * val)198*7f2fe78bSCy Schubert k5_hashtab_add(struct k5_hashtab *ht, const void *key, size_t klen, void *val)
199*7f2fe78bSCy Schubert {
200*7f2fe78bSCy Schubert     size_t i;
201*7f2fe78bSCy Schubert     struct entry *ent;
202*7f2fe78bSCy Schubert 
203*7f2fe78bSCy Schubert     if (ht->nentries == ht->nbuckets) {
204*7f2fe78bSCy Schubert         if (resize_table(ht) != 0)
205*7f2fe78bSCy Schubert             return ENOMEM;
206*7f2fe78bSCy Schubert     }
207*7f2fe78bSCy Schubert 
208*7f2fe78bSCy Schubert     ent = malloc(sizeof(*ent));
209*7f2fe78bSCy Schubert     if (ent == NULL)
210*7f2fe78bSCy Schubert         return ENOMEM;
211*7f2fe78bSCy Schubert     ent->key = key;
212*7f2fe78bSCy Schubert     ent->klen = klen;
213*7f2fe78bSCy Schubert     ent->val = val;
214*7f2fe78bSCy Schubert 
215*7f2fe78bSCy Schubert     i = siphash24(key, klen, ht->k0, ht->k1) % ht->nbuckets;
216*7f2fe78bSCy Schubert     K5_SLIST_INSERT_HEAD(&ht->buckets[i], ent, next);
217*7f2fe78bSCy Schubert 
218*7f2fe78bSCy Schubert     ht->nentries++;
219*7f2fe78bSCy Schubert     return 0;
220*7f2fe78bSCy Schubert }
221*7f2fe78bSCy Schubert 
222*7f2fe78bSCy Schubert int
k5_hashtab_remove(struct k5_hashtab * ht,const void * key,size_t klen)223*7f2fe78bSCy Schubert k5_hashtab_remove(struct k5_hashtab *ht, const void *key, size_t klen)
224*7f2fe78bSCy Schubert {
225*7f2fe78bSCy Schubert     size_t i;
226*7f2fe78bSCy Schubert     struct entry *ent;
227*7f2fe78bSCy Schubert 
228*7f2fe78bSCy Schubert     i = siphash24(key, klen, ht->k0, ht->k1) % ht->nbuckets;
229*7f2fe78bSCy Schubert     K5_SLIST_FOREACH(ent, &ht->buckets[i], next) {
230*7f2fe78bSCy Schubert         if (ent->klen == klen && memcmp(ent->key, key, klen) == 0) {
231*7f2fe78bSCy Schubert             K5_SLIST_REMOVE(&ht->buckets[i], ent, entry, next);
232*7f2fe78bSCy Schubert             free(ent);
233*7f2fe78bSCy Schubert             ht->nentries--;
234*7f2fe78bSCy Schubert             return 1;
235*7f2fe78bSCy Schubert         }
236*7f2fe78bSCy Schubert     }
237*7f2fe78bSCy Schubert     return 0;
238*7f2fe78bSCy Schubert }
239*7f2fe78bSCy Schubert 
240*7f2fe78bSCy Schubert void *
k5_hashtab_get(struct k5_hashtab * ht,const void * key,size_t klen)241*7f2fe78bSCy Schubert k5_hashtab_get(struct k5_hashtab *ht, const void *key, size_t klen)
242*7f2fe78bSCy Schubert {
243*7f2fe78bSCy Schubert     size_t i;
244*7f2fe78bSCy Schubert     struct entry *ent;
245*7f2fe78bSCy Schubert 
246*7f2fe78bSCy Schubert     i = siphash24(key, klen, ht->k0, ht->k1) % ht->nbuckets;
247*7f2fe78bSCy Schubert     K5_SLIST_FOREACH(ent, &ht->buckets[i], next) {
248*7f2fe78bSCy Schubert         if (ent->klen == klen && memcmp(ent->key, key, klen) == 0)
249*7f2fe78bSCy Schubert             return ent->val;
250*7f2fe78bSCy Schubert     }
251*7f2fe78bSCy Schubert     return NULL;
252*7f2fe78bSCy Schubert }
253