/*
 * ASCEND: @(#)cache.c	1.0 (95/10/05 00:55:30)
 *
 *      Copyright (c) 1994 Ascend Communications, Inc.
 *      All rights reserved.
 *
 *	Permission to copy all or part of this material for any purpose is
 *	granted provided that the above copyright notice and this paragraph
 *	are duplicated in all copies.  THIS SOFTWARE IS PROVIDED ``AS IS''
 *	AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 *	LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *	FOR A PARTICULAR PURPOSE.
 */

#include	<sys/types.h>
#include	<ctype.h>
#if defined(BSDI)
#	include	<memory.h>
#	include	<stdlib.h>
#else
#	include	<malloc.h>
#endif
#include	<string.h>
#include	<sys/time.h>

#if defined(AUTOTEST)
#	include	<stdio.h>
#	include	<unistd.h>
#endif

#include	"cache.h"

static CONST HASHVAL *
	_hlist_search P__((HASHLIST **list, CONST HASHKEY *key, int len));
static HASHLIST *
	_hlist_locate P__((HASHLIST **list, CONST HASHKEY *key, int len));
static void _hlist_expire P__((HASHLIST **list));
static int _key_expired P__((HASHLIST *elem));
static int _idle_expired P__((HASHLIST *elem));
static time_t _calc_expiration P__((time_t duration));

#if defined(AUTOTEST)
	static CACHE	cache;	/* allocate storage for the cache */
	int	count[BUCKETS];	/* allocater storage for statistics */
#	define log_err	printf	/* debug output */
#else
	extern int log_err P__((char *msg));
#endif


	/*
	 * Initialise the given cache.
	 */
void
cache_init(cache, buckets)
	CACHE	*cache;
	int		buckets;
{
	int	i;

	cache->buckets = buckets;
	for( i = 0; i < buckets; i++ ) {
		cache->table[i] = (HASHLIST *)0;
#if defined(AUTOTEST)
		count[i] = 0;
#endif
	}
}

	/*
	 * Search the given cache for the indicated key.
	 *
	 * Return a pointer to the associated value or (HASHVAL *)0
	 * if the key is not found
	 */
CONST HASHVAL *
cache_search(cache, key, len)
	CACHE		*cache;
	CONST HASHKEY	*key;
	int		len;
{
	int bucket;

	if( len > MAX_KEY_LEN ) {
		return (CONST HASHVAL *)0;
	}

	bucket = hash(key, len, cache->buckets);
	if( cache->table[bucket] ) {
		return _hlist_search(&cache->table[bucket], key, len);
	}
	else {
		return (CONST HASHVAL *)0;
	}
}

	/*
	 * Insert the given info into the given cache.
	 *
	 * Checks for duplicate records.
	 *
	 * Return TRUE if inserted, else FALSE.
	 */
int
cache_insert(cache, key, key_len, val, val_len, duration, idle)
	CACHE		*cache;
	CONST HASHKEY	*key;
	int		key_len;
	CONST HASHVAL	*val;
	int		val_len;
	time_t		duration;
	time_t		idle;
{
	HASHLIST *elem;
	int bucket;
	CONST HASHVAL *res;

	if( (key_len > MAX_KEY_LEN) || (val_len > MAX_VAL_LEN) ) {
		return FALSE;
	}

	bucket = hash(key, key_len, cache->buckets);
	res = _hlist_search(&cache->table[bucket], key, key_len);

	if( res && (res != ELEM_DELETED) ) {
		return FALSE;	/* already in table, delete first */
	}

	elem = (HASHLIST *)malloc(sizeof(*elem));
	if( !elem ) {
		return FALSE;
	}
	else {
		elem->next = cache->table[bucket];
		memcpy(elem->key, key, (size_t)key_len);
		memcpy(elem->val, val, (size_t)val_len);
		elem->time = _calc_expiration(duration);
		if( idle ) {
			elem->idle = _calc_expiration(idle);
			elem->idling = TRUE;
		}
		else {
			elem->idling = FALSE;
		}
		cache->table[bucket] = elem;
#if defined(AUTOTEST)
		count[bucket]++;
#endif
#if(0)
		printf("cache_insert(key<%d><%s>, val<%d><%s>)\n",
			key_len, elem->key, val_len, elem->val);
#endif
		return TRUE;
	}
}

	/*
	 * Delete the given info from the given cache.
	 *
	 * Return TRUE if deleted, else FALSE.
	 */
int
cache_delete(cache, key, len)
	CACHE		*cache;
	CONST HASHKEY	*key;
	int		len;
{
	int bucket;
	HASHLIST *cur;
	HASHLIST *prev = (HASHLIST *)0;
	HASHLIST **list;
	int result = FALSE;

	if( len > MAX_KEY_LEN ) {
		return FALSE;
	}

	bucket = hash(key, len, cache->buckets);
	list = &cache->table[bucket];

	for( cur = *list; cur; prev = cur, cur = cur->next ) {
		if( memcmp(key, cur->key, len) == 0 ) {
			if( prev ) {
				prev->next = cur->next;
			}
			else {
				*list = cur->next;
			}
			free(cur);
			result = TRUE;
			break;
		}
	}
	return result;
}

	/*
	 * Update the idle time for this entry
	 *
	 * Return TRUE if elem has idling enabled, else FALSE.
	 */
int
cache_idle_update(cache, key, len, idle)
	CACHE		*cache;
	CONST HASHKEY	*key;
	int		len;
	time_t		idle;
{
	int bucket;
	HASHLIST *elem;

	if( len > MAX_KEY_LEN ) {
		return FALSE;
	}

	bucket = hash(key, len, cache->buckets);
	elem = _hlist_locate(&cache->table[bucket], key, len);
	elem->idle = _calc_expiration(idle);
	return elem->idling;
}

	/*
	 * Search the given cache for expired keys and delete them.
	 */
void
cache_expire(cache)
	CACHE		*cache;
{
	int ix;

	for(ix = 0; ix < cache->buckets; ix++ ) {
		_hlist_expire(&cache->table[ix]);
	}
}

	/*
	 * Search the given hash list for the indicated key.
	 *
	 * Return a pointer to the associated elem or (HASHLIST *)0
	 * if the key is not found.
	 */
static HASHLIST *
_hlist_locate(list, key, len)
	HASHLIST	**list;
	CONST HASHKEY	*key;
	int		len;
{
	HASHLIST *cur;
	HASHLIST *prev;
	HASHLIST *retval = (HASHLIST *)0;
	prev = (HASHLIST *)0;
	for( cur = *list; cur; prev = cur, cur = cur->next ) {
		if( memcmp(key, cur->key, len) == 0 ) {
			retval = cur;
			break;
		}
	}

	return retval;
}

	/*
	 * Search the given hash list for the indicated key.
	 *
	 * Return a pointer to the associated value or (HASHVAL *)0
	 * if the key is not found.
	 *
	 * For efficiency, delete the search entry if it has expired
	 * and return ELEM_DELETED.
	 *
	 * Does NOT update the idle timer.
	 */
static CONST HASHVAL *
_hlist_search(list, key, len)
	HASHLIST	**list;
	CONST HASHKEY	*key;
	int		len;
{
	HASHLIST *cur;
	HASHLIST *prev;
	HASHVAL *retval = (HASHVAL *)0;
	prev = (HASHLIST *)0;
	for( cur = *list; cur; prev = cur, cur = cur->next ) {
		if( memcmp(key, cur->key, len) == 0 ) {
			retval = cur->val;
			break;
		}
	}
	if( retval ) {
		if( (_key_expired(cur) == TRUE)
		 || ((cur->idling == TRUE) && (_idle_expired(cur) == TRUE)) ) {
			/* delete it now */
			if( prev ) {
				prev->next = cur->next;
			}
			else {
				*list = cur->next;
			}
			retval = ELEM_DELETED;
			free(cur);
		}
	}
	return (CONST HASHVAL *)retval;
}

	/*
	 * Search for and delete all expired keys
	 */
static void
_hlist_expire(list)
	HASHLIST	**list;
{
	HASHLIST *cur;
	HASHLIST *prev;
	prev = (HASHLIST *)0;
	for( cur = *list; cur; prev = cur, cur = cur->next ) {
		if( _key_expired(cur) ) {
			if( prev ) {
				prev->next = cur->next;
			}
			else {
				*list = cur->next;
			}
			free(cur);
		}
	}
}

	/*
	 * Return TRUE if the key has expired
	 */
static int
_key_expired(elem)
	HASHLIST	*elem;
{
	time_t curtime;

	curtime = time((time_t *)0);
	if( curtime == -1 ) {
		log_err("_key_expired(): time() err\n");
		return TRUE;
	}
	if( curtime > elem->time ) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

	/*
	 * Return TRUE if the idle timer has expired
	 */
static int
_idle_expired(elem)
	HASHLIST	*elem;
{
	time_t curtime;

	curtime = time((time_t *)0);
	if( curtime == -1 ) {
		log_err("_key_expired(): time() err\n");
		return TRUE;
	}
	if( curtime > elem->idle ) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

	/*
	 * Calculate the time of expiration
	 */
static time_t
_calc_expiration(duration)
	time_t duration;
{
	time_t curtime;

	curtime = time((time_t *)0);
	if( curtime == -1 ) {
		log_err("_calc_expiration(): time() err\n");
		return duration;
	}
	curtime += duration;
	return curtime;
}

#define	CRC_16_POLY	(0xa001)
/*--------------------------------------------------------------------
		NOTE: 17 (_not_ 16) terms [x^16 ... x^0]

		X^16 + X^15 + X^2 + 1
			= 1 1000 0000 0000 0101
			= 18005
			  ^ implicit value (17th bit)

		Inverting the bit order yields
			= 1010 0000 0000 0001 1
			= a0011
			      ^ implicit value (17th  bit)
--------------------------------------------------------------------*/

/**********************************************************


	CRC-16 Byte Look-up Table <initial crc=0>

		P(x) = a001 = 1 + x^2 + x^15 + x^16

	This table was automatically generated by 'gencrc'

	-- Do _NOT_ modify this table manually ! --

	Table generated on Thu Oct  5 16:45:34 1995


**********************************************************/

unsigned short crc_16_lookup[256] = {
	0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 
	0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 
	0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 
	0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 
	0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 
	0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 
	0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 
	0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 
	0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, 
	0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 
	0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 
	0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 
	0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 
	0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 
	0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, 
	0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 
	0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, 
	0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 
	0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 
	0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 
	0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 
	0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 
	0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 
	0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 
	0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 
	0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, 
	0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 
	0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 
	0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 
	0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 
	0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 
	0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
};

/*-----------------------------------------------------------------------
	unsigned short hash(key, len, buckets)


	Calculate the CRC-16 of a key, modulo MODULUS

		uses the x^16 + x^15 + x^2 + 1 CRC-16 algorithm

	Return the 16 bit crc number for a key, mod BUCKETS
	Uses a 256 entry table lookup
-----------------------------------------------------------------------*/
u_short
hash(key, len, buckets)
	CONST HASHKEY	*key;
	int		len;
	int		buckets;
{
	u_short	n, crc;
	int	i;
  
	crc = 0;
	for( i = len; i; i-- ) {
		n = *key++;	
		n ^= crc;
		crc = crc_16_lookup[n & 0xff] ^ (crc >> 8);
	}
	return (unsigned int)crc % (unsigned int)buckets;
}

/**********************************************************************

			Test code

**********************************************************************/

#if defined(AUTOTEST)
int
main(argc, argv)
	int	argc;
	char	**argv;
{
	FILE	*fd;
	HASHVAL	*val;
	int	stat;
	char	buf[101];
	int	least, most;
	int	i;
	char	*words;		/* typically, /usr/dict/words */
	int	len;

	if( argc < 2 ) {
		words = "/usr/dict/words";
	}
	else {
		words = argv[1];
	}

	cache_init(&cache, BUCKETS);
	fd = fopen(words, "r");
	if( !fd ) {
		printf("Error opening '%s'\n", words);
		exit(-1);
	}

	while( fgets(buf, 100, fd) != (char *)0 ) {
		len = strlen(buf);
		if( buf[len-1] == '\n' ) {
			buf[len-1] = '\0';
			len--;
		}
		len = MIN((size_t)MAX_KEY_LEN, strlen(buf));
		buf[len+1] = '\0';
		stat = cache_insert(&cache, buf, len+1, buf, len+1, 1000);
		printf("Insert key <%s>: %s\n",
				buf, (stat == TRUE) ? "GOOD" : "BAD");
	}

	least = most = 0;
	for( i = 0; i < BUCKETS; i++ ) {
		if( count[i] < least ) {
			least = count[i];
		}
		else if( count[i] > most ) {
			most = count[i];
		}
	}
	printf("\n\nShortest List Len=%d, Longest List Len=%d\n\n",
		least, most);
	for( i = 0; i < BUCKETS; i++ ) {
		if( (i % 10) == 0 ) {
			printf("\ncount[%4x]= ", i);
		}
		printf("%4d ", count[i]);
	}
	printf("\n\n");

	rewind(fd);
	while( fgets(buf, 100, fd) != (char *)0 ) {
		char	*msg;

		len = strlen(buf);
		if( buf[len-1] == '\n' ) {
			buf[len-1] = '\0';
			len--;
		}
		len = MIN((size_t)MAX_KEY_LEN, strlen(buf));
		buf[len+1] = '\0';
		val = (HASHVAL *)cache_search(&cache, buf, len+1);
		if( val && (val != ELEM_DELETED) ) {
			msg = "GOOD";
		}
		else if( val == ELEM_DELETED ) {
			msg = "DELETED";
		}
		else {
			msg = "BAD";
		}
		printf("Search key <%s, %s>: %s\n", buf, val, msg);
	}

	rewind(fd);
	while( fgets(buf, 100, fd) != (char *)0 ) {
		len = strlen(buf);
		if( buf[len-1] == '\n' ) {
			buf[len-1] = '\0';
			len--;
		}
		len = MIN((size_t)MAX_KEY_LEN, strlen(buf));
		buf[len+1] = '\0';
		stat = cache_delete(&cache, buf, len+1);
		printf("Delete key <%s>: %s\n",
				buf, (stat == TRUE) ? "GOOD" : "BAD");
	}

	fclose(fd);
	return 0;
}

#endif

