xref: /dragonfly/crypto/libressl/crypto/lhash/lhash.c (revision 72c33676)
1 /* $OpenBSD: lhash.c,v 1.18 2016/11/08 20:20:06 miod Exp $ */
2 /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
3  * All rights reserved.
4  *
5  * This package is an SSL implementation written
6  * by Eric Young (eay@cryptsoft.com).
7  * The implementation was written so as to conform with Netscapes SSL.
8  *
9  * This library is free for commercial and non-commercial use as long as
10  * the following conditions are aheared to.  The following conditions
11  * apply to all code found in this distribution, be it the RC4, RSA,
12  * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
13  * included with this distribution is covered by the same copyright terms
14  * except that the holder is Tim Hudson (tjh@cryptsoft.com).
15  *
16  * Copyright remains Eric Young's, and as such any Copyright notices in
17  * the code are not to be removed.
18  * If this package is used in a product, Eric Young should be given attribution
19  * as the author of the parts of the library used.
20  * This can be in the form of a textual message at program startup or
21  * in documentation (online or textual) provided with the package.
22  *
23  * Redistribution and use in source and binary forms, with or without
24  * modification, are permitted provided that the following conditions
25  * are met:
26  * 1. Redistributions of source code must retain the copyright
27  *    notice, this list of conditions and the following disclaimer.
28  * 2. Redistributions in binary form must reproduce the above copyright
29  *    notice, this list of conditions and the following disclaimer in the
30  *    documentation and/or other materials provided with the distribution.
31  * 3. All advertising materials mentioning features or use of this software
32  *    must display the following acknowledgement:
33  *    "This product includes cryptographic software written by
34  *     Eric Young (eay@cryptsoft.com)"
35  *    The word 'cryptographic' can be left out if the rouines from the library
36  *    being used are not cryptographic related :-).
37  * 4. If you include any Windows specific code (or a derivative thereof) from
38  *    the apps directory (application code) you must include an acknowledgement:
39  *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
40  *
41  * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
42  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
43  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
44  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
45  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
46  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
47  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
49  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
50  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  *
53  * The licence and distribution terms for any publically available version or
54  * derivative of this code cannot be changed.  i.e. this code cannot simply be
55  * copied and put under another distribution licence
56  * [including the GNU Public Licence.]
57  */
58 
59 /* Code for dynamic hash table routines
60  * Author - Eric Young v 2.0
61  *
62  * 2.2 eay - added #include "crypto.h" so the memory leak checking code is
63  *	     present. eay 18-Jun-98
64  *
65  * 2.1 eay - Added an 'error in last operation' flag. eay 6-May-98
66  *
67  * 2.0 eay - Fixed a bug that occurred when using lh_delete
68  *	     from inside lh_doall().  As entries were deleted,
69  *	     the 'table' was 'contract()ed', making some entries
70  *	     jump from the end of the table to the start, there by
71  *	     skipping the lh_doall() processing. eay - 4/12/95
72  *
73  * 1.9 eay - Fixed a memory leak in lh_free, the LHASH_NODEs
74  *	     were not being free()ed. 21/11/95
75  *
76  * 1.8 eay - Put the stats routines into a separate file, lh_stats.c
77  *	     19/09/95
78  *
79  * 1.7 eay - Removed the fputs() for realloc failures - the code
80  *           should silently tolerate them.  I have also fixed things
81  *           lint complained about 04/05/95
82  *
83  * 1.6 eay - Fixed an invalid pointers in contract/expand 27/07/92
84  *
85  * 1.5 eay - Fixed a misuse of realloc in expand 02/03/1992
86  *
87  * 1.4 eay - Fixed lh_doall so the function can call lh_delete 28/05/91
88  *
89  * 1.3 eay - Fixed a few lint problems 19/3/1991
90  *
91  * 1.2 eay - Fixed lh_doall problem 13/3/1991
92  *
93  * 1.1 eay - Added lh_doall
94  *
95  * 1.0 eay - First version
96  */
97 #include <stdio.h>
98 #include <string.h>
99 #include <stdlib.h>
100 
101 #include <openssl/opensslconf.h>
102 
103 #include <openssl/crypto.h>
104 #include <openssl/lhash.h>
105 
106 #undef MIN_NODES
107 #define MIN_NODES	16
108 #define UP_LOAD		(2*LH_LOAD_MULT) /* load times 256  (default 2) */
109 #define DOWN_LOAD	(LH_LOAD_MULT)   /* load times 256  (default 1) */
110 
111 static void expand(_LHASH *lh);
112 static void contract(_LHASH *lh);
113 static LHASH_NODE **getrn(_LHASH *lh, const void *data, unsigned long *rhash);
114 
115 _LHASH *
116 lh_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)
117 {
118 	_LHASH *ret;
119 	int i;
120 
121 	if ((ret = malloc(sizeof(_LHASH))) == NULL)
122 		goto err0;
123 	if ((ret->b = reallocarray(NULL, MIN_NODES, sizeof(LHASH_NODE *))) == NULL)
124 		goto err1;
125 	for (i = 0; i < MIN_NODES; i++)
126 		ret->b[i] = NULL;
127 	ret->comp = ((c == NULL) ? (LHASH_COMP_FN_TYPE)strcmp : c);
128 	ret->hash = ((h == NULL) ? (LHASH_HASH_FN_TYPE)lh_strhash : h);
129 	ret->num_nodes = MIN_NODES / 2;
130 	ret->num_alloc_nodes = MIN_NODES;
131 	ret->p = 0;
132 	ret->pmax = MIN_NODES / 2;
133 	ret->up_load = UP_LOAD;
134 	ret->down_load = DOWN_LOAD;
135 	ret->num_items = 0;
136 
137 	ret->num_expands = 0;
138 	ret->num_expand_reallocs = 0;
139 	ret->num_contracts = 0;
140 	ret->num_contract_reallocs = 0;
141 	ret->num_hash_calls = 0;
142 	ret->num_comp_calls = 0;
143 	ret->num_insert = 0;
144 	ret->num_replace = 0;
145 	ret->num_delete = 0;
146 	ret->num_no_delete = 0;
147 	ret->num_retrieve = 0;
148 	ret->num_retrieve_miss = 0;
149 	ret->num_hash_comps = 0;
150 
151 	ret->error = 0;
152 	return (ret);
153 
154 err1:
155 	free(ret);
156 err0:
157 	return (NULL);
158 }
159 
160 void
161 lh_free(_LHASH *lh)
162 {
163 	unsigned int i;
164 	LHASH_NODE *n, *nn;
165 
166 	if (lh == NULL)
167 		return;
168 
169 	for (i = 0; i < lh->num_nodes; i++) {
170 		n = lh->b[i];
171 		while (n != NULL) {
172 			nn = n->next;
173 			free(n);
174 			n = nn;
175 		}
176 	}
177 	free(lh->b);
178 	free(lh);
179 }
180 
181 void *
182 lh_insert(_LHASH *lh, void *data)
183 {
184 	unsigned long hash;
185 	LHASH_NODE *nn, **rn;
186 	void *ret;
187 
188 	lh->error = 0;
189 	if (lh->up_load <= (lh->num_items * LH_LOAD_MULT / lh->num_nodes))
190 		expand(lh);
191 
192 	rn = getrn(lh, data, &hash);
193 
194 	if (*rn == NULL) {
195 		if ((nn = malloc(sizeof(LHASH_NODE))) == NULL) {
196 			lh->error++;
197 			return (NULL);
198 		}
199 		nn->data = data;
200 		nn->next = NULL;
201 #ifndef OPENSSL_NO_HASH_COMP
202 		nn->hash = hash;
203 #endif
204 		*rn = nn;
205 		ret = NULL;
206 		lh->num_insert++;
207 		lh->num_items++;
208 	}
209 	else /* replace same key */
210 	{
211 		ret = (*rn)->data;
212 		(*rn)->data = data;
213 		lh->num_replace++;
214 	}
215 	return (ret);
216 }
217 
218 void *
219 lh_delete(_LHASH *lh, const void *data)
220 {
221 	unsigned long hash;
222 	LHASH_NODE *nn, **rn;
223 	void *ret;
224 
225 	lh->error = 0;
226 	rn = getrn(lh, data, &hash);
227 
228 	if (*rn == NULL) {
229 		lh->num_no_delete++;
230 		return (NULL);
231 	} else {
232 		nn= *rn;
233 		*rn = nn->next;
234 		ret = nn->data;
235 		free(nn);
236 		lh->num_delete++;
237 	}
238 
239 	lh->num_items--;
240 	if ((lh->num_nodes > MIN_NODES) &&
241 	    (lh->down_load >= (lh->num_items * LH_LOAD_MULT / lh->num_nodes)))
242 		contract(lh);
243 
244 	return (ret);
245 }
246 
247 void *
248 lh_retrieve(_LHASH *lh, const void *data)
249 {
250 	unsigned long hash;
251 	LHASH_NODE **rn;
252 	void *ret;
253 
254 	lh->error = 0;
255 	rn = getrn(lh, data, &hash);
256 
257 	if (*rn == NULL) {
258 		lh->num_retrieve_miss++;
259 		return (NULL);
260 	} else {
261 		ret = (*rn)->data;
262 		lh->num_retrieve++;
263 	}
264 	return (ret);
265 }
266 
267 static void
268 doall_util_fn(_LHASH *lh, int use_arg, LHASH_DOALL_FN_TYPE func,
269     LHASH_DOALL_ARG_FN_TYPE func_arg, void *arg)
270 {
271 	int i;
272 	LHASH_NODE *a, *n;
273 
274 	if (lh == NULL)
275 		return;
276 
277 	/* reverse the order so we search from 'top to bottom'
278 	 * We were having memory leaks otherwise */
279 	for (i = lh->num_nodes - 1; i >= 0; i--) {
280 		a = lh->b[i];
281 		while (a != NULL) {
282 			/* 28/05/91 - eay - n added so items can be deleted
283 			 * via lh_doall */
284 			/* 22/05/08 - ben - eh? since a is not passed,
285 			 * this should not be needed */
286 			n = a->next;
287 			if (use_arg)
288 				func_arg(a->data, arg);
289 			else
290 				func(a->data);
291 			a = n;
292 		}
293 	}
294 }
295 
296 void
297 lh_doall(_LHASH *lh, LHASH_DOALL_FN_TYPE func)
298 {
299 	doall_util_fn(lh, 0, func, (LHASH_DOALL_ARG_FN_TYPE)0, NULL);
300 }
301 
302 void
303 lh_doall_arg(_LHASH *lh, LHASH_DOALL_ARG_FN_TYPE func, void *arg)
304 {
305 	doall_util_fn(lh, 1, (LHASH_DOALL_FN_TYPE)0, func, arg);
306 }
307 
308 static void
309 expand(_LHASH *lh)
310 {
311 	LHASH_NODE **n, **n1, **n2, *np;
312 	unsigned int p, i, j;
313 	unsigned long hash, nni;
314 
315 	lh->num_nodes++;
316 	lh->num_expands++;
317 	p = (int)lh->p++;
318 	n1 = &(lh->b[p]);
319 	n2 = &(lh->b[p + (int)lh->pmax]);
320 	*n2 = NULL;        /* 27/07/92 - eay - undefined pointer bug */
321 	nni = lh->num_alloc_nodes;
322 
323 	for (np = *n1; np != NULL; ) {
324 #ifndef OPENSSL_NO_HASH_COMP
325 		hash = np->hash;
326 #else
327 		hash = lh->hash(np->data);
328 		lh->num_hash_calls++;
329 #endif
330 		if ((hash % nni) != p) { /* move it */
331 			*n1 = (*n1)->next;
332 			np->next= *n2;
333 			*n2 = np;
334 		} else
335 			n1 = &((*n1)->next);
336 		np= *n1;
337 	}
338 
339 	if ((lh->p) >= lh->pmax) {
340 		j = (int)lh->num_alloc_nodes * 2;
341 		n = reallocarray(lh->b, j, sizeof(LHASH_NODE *));
342 		if (n == NULL) {
343 /*			fputs("realloc error in lhash", stderr); */
344 			lh->error++;
345 			lh->p = 0;
346 			return;
347 		}
348 		/* else */
349 		for (i = (int)lh->num_alloc_nodes; i < j; i++)/* 26/02/92 eay */
350 			n[i] = NULL;			  /* 02/03/92 eay */
351 		lh->pmax = lh->num_alloc_nodes;
352 		lh->num_alloc_nodes = j;
353 		lh->num_expand_reallocs++;
354 		lh->p = 0;
355 		lh->b = n;
356 	}
357 }
358 
359 static void
360 contract(_LHASH *lh)
361 {
362 	LHASH_NODE **n, *n1, *np;
363 
364 	np = lh->b[lh->p + lh->pmax - 1];
365 	lh->b[lh->p+lh->pmax - 1] = NULL; /* 24/07-92 - eay - weird but :-( */
366 	if (lh->p == 0) {
367 		n = reallocarray(lh->b, lh->pmax, sizeof(LHASH_NODE *));
368 		if (n == NULL) {
369 /*			fputs("realloc error in lhash", stderr); */
370 			lh->error++;
371 			return;
372 		}
373 		lh->num_contract_reallocs++;
374 		lh->num_alloc_nodes /= 2;
375 		lh->pmax /= 2;
376 		lh->p = lh->pmax - 1;
377 		lh->b = n;
378 	} else
379 		lh->p--;
380 
381 	lh->num_nodes--;
382 	lh->num_contracts++;
383 
384 	n1 = lh->b[(int)lh->p];
385 	if (n1 == NULL)
386 		lh->b[(int)lh->p] = np;
387 	else {
388 		while (n1->next != NULL)
389 			n1 = n1->next;
390 		n1->next = np;
391 	}
392 }
393 
394 static LHASH_NODE **getrn(_LHASH *lh, const void *data, unsigned long *rhash)
395 {
396 	LHASH_NODE **ret, *n1;
397 	unsigned long hash, nn;
398 	LHASH_COMP_FN_TYPE cf;
399 
400 	hash = (*(lh->hash))(data);
401 	lh->num_hash_calls++;
402 	*rhash = hash;
403 
404 	nn = hash % lh->pmax;
405 	if (nn < lh->p)
406 		nn = hash % lh->num_alloc_nodes;
407 
408 	cf = lh->comp;
409 	ret = &(lh->b[(int)nn]);
410 	for (n1 = *ret; n1 != NULL; n1 = n1->next) {
411 #ifndef OPENSSL_NO_HASH_COMP
412 		lh->num_hash_comps++;
413 		if (n1->hash != hash) {
414 			ret = &(n1->next);
415 			continue;
416 		}
417 #endif
418 		lh->num_comp_calls++;
419 		if (cf(n1->data, data) == 0)
420 			break;
421 		ret = &(n1->next);
422 	}
423 	return (ret);
424 }
425 
426 /* The following hash seems to work very well on normal text strings
427  * no collisions on /usr/dict/words and it distributes on %2^n quite
428  * well, not as good as MD5, but still good.
429  */
430 unsigned long
431 lh_strhash(const char *c)
432 {
433 	unsigned long ret = 0;
434 	unsigned long n, v;
435 	unsigned int r;
436 
437 	if (c == NULL || *c == '\0')
438 		return ret;
439 
440 	n = 0x100;
441 	while (*c) {
442 		v = n | *c;
443 		n += 0x100;
444 		if ((r = ((v >> 2) ^ v) & 0x0f) != 0)
445 			ret = (ret << r) | (ret >> (32 - r));
446 		ret &= 0xFFFFFFFFUL;
447 		ret ^= v * v;
448 		c++;
449 	}
450 	return (ret >> 16) ^ ret;
451 }
452 
453 unsigned long
454 lh_num_items(const _LHASH *lh)
455 {
456 	return lh ? lh->num_items : 0;
457 }
458