* don't search for null-lengthened strings.
[citadel.git] / libcitadel / lib / hash.c
1 #include <stdint.h>
2 #include <stdlib.h>
3 #include <string.h>
4 //dbg
5 #include <stdio.h>
6 #include "libcitadel.h"
7 #include "lookup3.h"
8
9 typedef struct Payload Payload;
10
11 struct Payload {
12         /**
13          * \brief Hash Payload storage Structure; filled in linear.
14          */
15         void *Data; /**< the Data belonging to this storage */
16         DeleteHashDataFunc Destructor; /**< if we want to destroy Data, do it with this function. */
17 };
18
19 struct HashKey {
20         /**
21          * \brief Hash key element; sorted by Keye
22          */
23         long Key;         /**< Numeric Hashkey comperator for hash sorting */
24         long Position;    /**< Pointer to a Payload struct in the Payload Aray */
25         char *HashKey;    /**< the Plaintext Hashkey */
26         long HKLen;       /**< length of the Plaintext Hashkey */
27         Payload *PL;      /**< pointer to our payload for sorting */
28 };
29
30 struct HashList {
31         /**
32          * \brief Hash structure; holds arrays of Hashkey and Payload. 
33          */
34         Payload **Members;     /**< Our Payload members. This fills up linear */
35         HashKey **LookupTable; /**< Hash Lookup table. Elements point to members, and are sorted by their hashvalue */
36         char **MyKeys;         /**< this keeps the members for a call of GetHashKeys */
37         long nMembersUsed;     /**< how many pointers inside of the array are used? */
38         long MemberSize;       /**< how big is Members and LookupTable? */
39         long tainted;          /**< if 0, we're hashed, else s.b. else sorted us in his own way. */
40 };
41
42 struct HashPos {
43         /**
44          * \brief Anonymous Hash Iterator Object. used for traversing the whole array from outside 
45          */
46         long Position;
47 };
48
49 /**
50  * \brief verify the contents of a hash list; here for debugging purposes.
51  * \param Hash your Hashlist structure
52  * \param First Functionpointer to allow you to print your payload
53  * \param Second Functionpointer to allow you to print your payload
54  * \returns 0
55  */
56 int PrintHash(HashList *Hash, PrintHashContent First, PrintHashContent Second)
57 {
58         const char *foo;
59         const char *bar;
60         const char *bla = "";
61         long key;
62         long i;
63         if (Hash->MyKeys != NULL)
64                 free (Hash->MyKeys);
65
66         Hash->MyKeys = (char**) malloc(sizeof(char*) * Hash->nMembersUsed);
67 #ifdef DEBUG
68         printf("----------------------------------\n");
69 #endif
70         for (i=0; i < Hash->nMembersUsed; i++) {
71                 
72                 if (Hash->LookupTable[i] == NULL)
73                 {
74                         foo = "";
75                         bar = "";
76                         key = 0;
77                 }
78                 else 
79                 {
80                         key = Hash->LookupTable[i]->Key;
81                         foo = Hash->LookupTable[i]->HashKey;
82                         if (First != NULL)
83                                 bar = First(Hash->Members[Hash->LookupTable[i]->Position]->Data);
84                         if (Second != NULL)
85                                 bla = Second(Hash->Members[Hash->LookupTable[i]->Position]->Data);
86                 }
87 #ifdef DEBUG
88                 printf (" ---- Hashkey[%ld][%ld]: '%s' Value: '%s' ; %s\n", i, key, foo, bar, bla);
89 #endif
90         }
91 #ifdef DEBUG
92         printf("----------------------------------\n");
93 #endif
94         return 0;
95 }
96
97
98 /**
99  * \brief instanciate a new hashlist
100  * \returns the newly allocated list. 
101  */
102 HashList *NewHash(void) 
103 {
104         HashList *NewList;
105         NewList = malloc (sizeof(HashList));
106         memset(NewList, 0, sizeof(HashList));
107
108         NewList->Members = malloc(sizeof(Payload*) * 100);
109         memset(NewList->Members, 0, sizeof(Payload*) * 100);
110
111         NewList->LookupTable = malloc(sizeof(HashKey*) * 100);
112         memset(NewList->LookupTable, 0, sizeof(HashKey*) * 100);
113
114         NewList->MemberSize = 100;
115         NewList->tainted = 0;
116
117         return NewList;
118 }
119
120 /**
121  * \brief private destructor for one hash element.
122  * \param Data an element to free using the user provided destructor, or just plain free
123  */
124 static void DeleteHashPayload (Payload *Data)
125 {
126         /** do we have a destructor for our payload? */
127         if (Data->Destructor)
128                 Data->Destructor(Data->Data);
129         else
130                 free(Data->Data);
131 }
132
133 /**
134  * \brief destroy a hashlist and all of its members
135  * \param Hash Hash to destroy. Is NULL'ed so you are shure its done.
136  */
137 void DeleteHash(HashList **Hash)
138 {
139         int i;
140         HashList *FreeMe;
141
142         FreeMe = *Hash;
143         if (FreeMe == NULL)
144                 return;
145         for (i=0; i < FreeMe->nMembersUsed; i++)
146         {
147                 /** get rid of our payload */
148                 if (FreeMe->Members[i] != NULL)
149                 {
150                         DeleteHashPayload(FreeMe->Members[i]);
151                         free(FreeMe->Members[i]);
152                 }
153                 /** delete our hashing data */
154                 if (FreeMe->LookupTable[i] != NULL)
155                 {
156                         free(FreeMe->LookupTable[i]->HashKey);
157                         free(FreeMe->LookupTable[i]);
158                 }
159         }
160         /** now, free our arrays... */
161         free(FreeMe->LookupTable);
162         free(FreeMe->Members);
163         /** did s.b. want an array of our keys? free them. */
164         if (FreeMe->MyKeys != NULL)
165                 free(FreeMe->MyKeys);
166         /** buye bye cruel world. */    
167         free (FreeMe);
168         *Hash = NULL;
169 }
170
171
172 /**
173  * \brief private function to add a new item to / replace an existing in -  the hashlist
174  * if the hash list is full, its re-alloced with double size.
175  * \parame Hash our hashlist to manipulate
176  * \param HashPos where should we insert / replace?
177  * \param HashKeyStr the Hash-String
178  * \param HKLen length of HashKeyStr
179  * \param Data your Payload to add
180  * \param Destructor Functionpointer to free Data. if NULL, default free() is used.
181  */
182 static void InsertHashItem(HashList *Hash, 
183                            long HashPos, 
184                            long HashBinKey, 
185                            char *HashKeyStr, 
186                            long HKLen, 
187                            void *Data,
188                            DeleteHashDataFunc Destructor)
189 {
190         Payload *NewPayloadItem;
191         HashKey *NewHashKey;
192
193         if (Hash->nMembersUsed >= Hash->MemberSize)
194         {
195                 /* Ok, Our space is used up. Double the available space. */
196                 Payload **NewPayloadArea;
197                 HashKey **NewTable;
198
199                 /** double our payload area */
200                 NewPayloadArea = (Payload**) malloc(sizeof(Payload*) * Hash->MemberSize * 2);
201                 memset(&NewPayloadArea[Hash->MemberSize], 0, sizeof(Payload*) * Hash->MemberSize);
202                 memcpy(NewPayloadArea, Hash->Members, sizeof(Payload*) * Hash->MemberSize);
203                 free(Hash->Members);
204                 Hash->Members = NewPayloadArea;
205
206                 /** double our hashtable area */
207                 NewTable = malloc(sizeof(HashKey*) * Hash->MemberSize * 2);
208                 memset(&NewTable[Hash->MemberSize], 0, sizeof(HashKey*) * Hash->MemberSize);
209                 memcpy(NewTable, Hash->LookupTable, sizeof(HashKey*) * Hash->MemberSize);
210                 free(Hash->LookupTable);
211                 Hash->LookupTable = NewTable;
212
213                 Hash->MemberSize *= 2;
214         }
215         /** Arrange the payload */
216         NewPayloadItem = (Payload*) malloc (sizeof(Payload));
217         NewPayloadItem->Data = Data;
218         NewPayloadItem->Destructor = Destructor;
219         /** Arrange the hashkey */
220         NewHashKey = (HashKey*) malloc (sizeof(HashKey));
221         NewHashKey->HashKey = (char *) malloc (HKLen + 1);
222         NewHashKey->HKLen = HKLen;
223         memcpy (NewHashKey->HashKey, HashKeyStr, HKLen + 1);
224         NewHashKey->Key = HashBinKey;
225         NewHashKey->PL = NewPayloadItem;
226         /** our payload is queued at the end... */
227         NewHashKey->Position = Hash->nMembersUsed;
228         /** but if we should be sorted into a specific place... */
229         if ((Hash->nMembersUsed != 0) && 
230             (HashPos != Hash->nMembersUsed) ) {
231                 long InsertAt;
232                 long ItemsAfter;
233
234                 ItemsAfter = Hash->nMembersUsed - HashPos;
235                 InsertAt = HashPos;
236                 /** make space were we can fill us in */
237                 if (ItemsAfter > 0)
238                 {
239                         memmove(&Hash->LookupTable[InsertAt + 1],
240                                 &Hash->LookupTable[InsertAt],
241                                 ItemsAfter * sizeof(HashKey*));
242                 } 
243         }
244         
245         Hash->Members[Hash->nMembersUsed] = NewPayloadItem;
246         Hash->LookupTable[HashPos] = NewHashKey;
247         Hash->nMembersUsed++;
248 }
249
250 /**
251  * \brief if the user has tainted the hash, but wants to insert / search items by their key
252  *  we need to search linear through the array. You have been warned that this will take more time!
253  * \param Hash Our Hash to manipulate
254  * \param HashBinKey the Hash-Number to lookup. 
255  * \returns the position (most closely) matching HashBinKey (-> Caller needs to compare! )
256  */
257 static long FindInTaintedHash(HashList *Hash, long HashBinKey)
258 {
259         long SearchPos;
260
261         for (SearchPos = 0; SearchPos < Hash->nMembersUsed; SearchPos ++) {
262                 if (Hash->LookupTable[SearchPos]->Key == HashBinKey){
263                         return SearchPos;
264                 }
265         }
266         return SearchPos;
267 }
268
269 /**
270  * \brief Private function to lookup the Item / the closest position to put it in
271  * \param Hash Our Hash to manipulate
272  * \param HashBinKey the Hash-Number to lookup. 
273  * \returns the position (most closely) matching HashBinKey (-> Caller needs to compare! )
274  */
275 static long FindInHash(HashList *Hash, long HashBinKey)
276 {
277         long SearchPos;
278         long StepWidth;
279
280         if (Hash->tainted)
281                 return FindInTaintedHash(Hash, HashBinKey);
282
283         SearchPos = Hash->nMembersUsed / 2;
284         StepWidth = SearchPos / 2;
285         while ((SearchPos > 0) && 
286                (SearchPos < Hash->nMembersUsed)) 
287         {
288                 /** Did we find it? */
289                 if (Hash->LookupTable[SearchPos]->Key == HashBinKey){
290                         return SearchPos;
291                 }
292                 /** are we Aproximating in big steps? */
293                 if (StepWidth > 1){
294                         if (Hash->LookupTable[SearchPos]->Key > HashBinKey)
295                                 SearchPos -= StepWidth;
296                         else
297                                 SearchPos += StepWidth;
298                         StepWidth /= 2;                 
299                 }
300                 else { /** We are right next to our target, within 4 positions */
301                         if (Hash->LookupTable[SearchPos]->Key > HashBinKey) {
302                                 if ((SearchPos > 0) && 
303                                     (Hash->LookupTable[SearchPos - 1]->Key < HashBinKey))
304                                         return SearchPos;
305                                 SearchPos --;
306                         }
307                         else {
308                                 if ((SearchPos + 1 < Hash->nMembersUsed) && 
309                                     (Hash->LookupTable[SearchPos + 1]->Key > HashBinKey))
310                                         return SearchPos;
311                                 SearchPos ++;
312                         }
313                         StepWidth--;
314                 }
315         }
316         return SearchPos;
317 }
318
319 /**
320  * \brief private abstract wrapper around the hashing algorithm
321  * \param HKey the hash string
322  * \param HKLen length of HKey
323  * \returns the calculated hash value
324  */
325 inline static long CalcHashKey (char *HKey, long HKLen)
326 {
327         return hashlittle(HKey, HKLen, 9283457);
328 }
329
330
331 /**
332  * \brief Add a new / Replace an existing item in the Hash
333  * \param HashList the list to manipulate
334  * \param HKey the hash-string to store Data under
335  * \param HKeyLen Length of HKey
336  * \param Data the payload you want to associate with HKey
337  * \param DeleteIt if not free() should be used to delete Data set to NULL, else DeleteIt is used.
338  */
339 void Put(HashList *Hash, char *HKey, long HKLen, void *Data, DeleteHashDataFunc DeleteIt)
340 {
341         long HashBinKey;
342         long HashAt;
343
344         /** first, find out were we could fit in... */
345         HashBinKey = CalcHashKey(HKey, HKLen);
346         HashAt = FindInHash(Hash, HashBinKey);
347
348         /** oh, we're brand new... */
349         if (Hash->LookupTable[HashAt] == NULL){
350                 InsertHashItem(Hash, HashAt, HashBinKey, HKey, HKLen, Data, DeleteIt);
351         }/** Insert After? */
352         else if (Hash->LookupTable[HashAt]->Key > HashBinKey) {
353                 InsertHashItem(Hash, HashAt, HashBinKey, HKey, HKLen, Data, DeleteIt);
354         }/** Insert before? */
355         else if (Hash->LookupTable[HashAt]->Key < HashBinKey) {
356                 InsertHashItem(Hash, HashAt + 1, HashBinKey, HKey, HKLen, Data, DeleteIt);              
357         }
358         else { /** Ok, we have a colision. replace it. */
359                 long PayloadPos;
360
361                 PayloadPos = Hash->LookupTable[HashAt]->Position;
362                 DeleteHashPayload(Hash->Members[PayloadPos]);
363                 Hash->Members[PayloadPos]->Data = Data;
364                 Hash->Members[PayloadPos]->Destructor = DeleteIt;
365         }
366 }
367
368 /**
369  * \brief Lookup the Data associated with HKey
370  * \param Hash the Hashlist to search in
371  * \param HKey the hashkey to look up
372  * \param HKLen length of HKey
373  * \param Data returns the Data associated with HKey
374  * \returns 0 if not found, 1 if.
375  */
376 int GetHash(HashList *Hash, const char *HKey, long HKLen, void **Data)
377 {
378         long HashBinKey;
379         long HashAt;
380
381         if (HKLen <= 0) {
382                 *Data = NULL;
383                 return  0;
384         }
385         /** first, find out were we could be... */
386         HashBinKey = CalcHashKey((char*)HKey, HKLen);
387         HashAt = FindInHash(Hash, HashBinKey);
388         if ((HashAt < 0) || /**< Not found at the lower edge? */
389             (HashAt >= Hash->nMembersUsed) || /**< Not found at the upper edge? */
390             (Hash->LookupTable[HashAt]->Key != HashBinKey)) { /**< somewhere inbetween but no match? */
391                 *Data = NULL;
392                 return 0;
393         }
394         else { /** GOTCHA! */
395                 long MemberPosition;
396
397                 MemberPosition = Hash->LookupTable[HashAt]->Position;
398                 *Data = Hash->Members[MemberPosition]->Data;
399                 return 1;
400         }
401 }
402
403 /* TODO? */
404 int GetKey(HashList *Hash, char *HKey, long HKLen, void **Payload)
405 {
406         return 0;
407 }
408
409 /**
410  * \brief get the Keys present in this hash, simila to array_keys() in PHP
411  *  Attention: List remains to Hash! don't modify or free it!
412  * \param Hash Your Hashlist to extract the keys from
413  * \param List returns the list of hashkeys stored in Hash
414  */
415 int GetHashKeys(HashList *Hash, char ***List)
416 {
417         long i;
418         if (Hash->MyKeys != NULL)
419                 free (Hash->MyKeys);
420
421         Hash->MyKeys = (char**) malloc(sizeof(char*) * Hash->nMembersUsed);
422         for (i=0; i < Hash->nMembersUsed; i++) {
423         
424                 Hash->MyKeys[i] = Hash->LookupTable[i]->HashKey;
425         }
426         *List = (char**)Hash->MyKeys;
427         return Hash->nMembersUsed;
428 }
429
430 /**
431  * \brief creates a hash-linear iterator object
432  * \returns the hash iterator
433  */
434 HashPos *GetNewHashPos(void)
435 {
436         HashPos *Ret;
437         
438         Ret = (HashPos*)malloc(sizeof(HashPos));
439         Ret->Position = 0;
440         return Ret;
441 }
442
443 /**
444  * \brief frees a linear hash iterator
445  */
446 void DeleteHashPos(HashPos **DelMe)
447 {
448         free(*DelMe);
449         *DelMe = NULL;
450 }
451
452
453 /**
454  * \brief Get the data located where HashPos Iterator points at, and Move HashPos one forward
455  * \param Hash your Hashlist to follow
456  * \param HKLen returns Length of Hashkey Returned
457  * \param HashKey returns the Hashkey corrosponding to HashPos
458  * \param Data returns the Data found at HashPos
459  * \returns whether the item was found or not.
460  */
461 int GetNextHashPos(HashList *Hash, HashPos *At, long *HKLen, char **HashKey, void **Data)
462 {
463         long PayloadPos;
464
465         if (Hash->nMembersUsed <= At->Position)
466                 return 0;
467         *HKLen = Hash->LookupTable[At->Position]->HKLen;
468         *HashKey = Hash->LookupTable[At->Position]->HashKey;
469         PayloadPos = Hash->LookupTable[At->Position]->Position;
470         *Data = Hash->Members[PayloadPos]->Data;
471         At->Position++;
472         return 1;
473 }
474
475 /**
476  * \brief sorting function for sorting the Hash alphabeticaly by their strings
477  * \param Key1 first item
478  * \param Key2 second item
479  */
480 static int SortByKeys(const void *Key1, const void* Key2)
481 {
482         HashKey *HKey1, *HKey2;
483         HKey1 = *(HashKey**) Key1;
484         HKey2 = *(HashKey**) Key2;
485
486         return strcasecmp(HKey1->HashKey, HKey2->HashKey);
487 }
488
489 /**
490  * \brief sorting function to regain hash-sequence and revert tainted status
491  * \param Key1 first item
492  * \param Key2 second item
493  */
494 static int SortByHashKeys(const void *Key1, const void* Key2)
495 {
496         HashKey *HKey1, *HKey2;
497         HKey1 = *(HashKey**) Key1;
498         HKey2 = *(HashKey**) Key2;
499
500         return HKey1->Key > HKey2->Key;
501 }
502
503
504 /**
505  * \brief sort the hash alphabeticaly by their keys.
506  * Caution: This taints the hashlist, so accessing it later 
507  * will be significantly slower! You can un-taint it by SortByHashKeyStr
508  * \param Hash the list to sort
509  */
510 void SortByHashKey(HashList *Hash)
511 {
512         if (Hash->nMembersUsed < 2)
513                 return;
514         qsort(Hash->LookupTable, Hash->nMembersUsed, sizeof(HashKey*), SortByKeys);
515         Hash->tainted = 1;
516 }
517
518 /**
519  * \brief sort the hash by their keys (so it regains untainted state).
520  * this will result in the sequence the hashing allgorithm produces it by default.
521  * \param Hash the list to sort
522  */
523 void SortByHashKeyStr(HashList *Hash)
524 {
525         Hash->tainted = 0;
526         if (Hash->nMembersUsed < 2)
527                 return;
528         qsort(Hash->LookupTable, Hash->nMembersUsed, sizeof(HashKey*), SortByHashKeys);
529 }
530
531
532 /**
533  * \brief gives user sort routines access to the hash payload
534  * \param Searchentry to retrieve Data to
535  * \returns Data belonging to HashVoid
536  */
537 const void *GetSearchPayload(const void *HashVoid)
538 {
539         return (*(HashKey**)HashVoid)->PL->Data;
540 }
541
542 /**
543  * \brief sort the hash by your sort function. see the following sample.
544  * this will result in the sequence the hashing allgorithm produces it by default.
545  * \param Hash the list to sort
546  * \param SortBy Sortfunction; see below how to implement this
547  */
548 void SortByPayload(HashList *Hash, CompareFunc SortBy)
549 {
550         if (Hash->nMembersUsed < 2)
551                 return;
552         qsort(Hash->LookupTable, Hash->nMembersUsed, sizeof(HashKey*), SortBy);
553         Hash->tainted = 1;
554 }
555
556
557
558
559 /**
560  * given you've put char * into your hash as a payload, a sort function might
561  * look like this:
562  * int SortByChar(const void* First, const void* Second)
563  * {
564  *      char *a, *b;
565  *      a = (char*) GetSearchPayload(First);
566  *      b = (char*) GetSearchPayload(Second);
567  *      return strcmp (a, b);
568  * }
569  */