/*
 * Copyright 1997 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that both the above copyright notice and this
 * permission notice appear in all copies, that both the above
 * copyright notice and this permission notice appear in all
 * supporting documentation, and that the name of M.I.T. not be used
 * in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  M.I.T. makes
 * no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 * 
 * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
 * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
 * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	$Id: hostcache.c,v 1.5 1998/02/06 12:13:48 eivind Exp $
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/socket.h>

#include <net/hostcache.h>
#include <net/route.h>

MALLOC_DEFINE(M_HOSTCACHE, "hostcache", "per-host cache structure");

static	struct hctable hctable[AF_MAX];
static	int hc_timeout_interval = 120;
static	int hc_maxidle = 1800;

static	int cmpsa(const struct sockaddr *sa1, const struct sockaddr *sa2);
static	void hc_timeout(void *xhct);
static	void maybe_bump_hash(struct hctable *hct);

int
hc_init(int af, struct hccallback *hccb, int init_nelem, int primes)
{
	struct hctable *hct;
	struct hchead *heads;
	u_long nelem;

	hct = &hctable[af];
	nelem = init_nelem;
	if (hct->hct_nentries)
		return 0;

	if (primes) {
		heads = phashinit(init_nelem, M_HOSTCACHE, &nelem);
	} else {
		int i;
		MALLOC(heads, struct hchead *, nelem * sizeof *heads,
		       M_HOSTCACHE, M_WAITOK);
		for (i = 0; i < nelem; i++) {
			LIST_INIT(&heads[i]);
		}
	}
	
	hct->hct_heads = heads;
	hct->hct_nentries = nelem;
	hct->hct_primes = primes;
	timeout(hc_timeout, hct, hc_timeout_interval * hz);
	return 0;
}

struct hcentry *
hc_get(struct sockaddr *sa)
{
	u_long hash;
	struct hcentry *hc;
	struct hctable *hct;
	int s;

	hct = &hctable[sa->sa_family];
	if (hct->hct_nentries == 0)
		return 0;
	hash = hct->hct_cb->hccb_hash(sa, hct->hct_nentries);
	hc = hct->hct_heads[hash].lh_first;
	for (; hc; hc = hc->hc_link.le_next) {
		if (cmpsa(hc->hc_host, sa) == 0)
			break;
	}
	if (hc == 0)
		return 0;
	s = splnet();
	if (hc->hc_rt && (hc->hc_rt->rt_flags & RTF_UP) == 0) {
		RTFREE(hc->hc_rt);
		hc->hc_rt = 0;
	}
	if (hc->hc_rt == 0) {
		hc->hc_rt = rtalloc1(hc->hc_host, 1, 0);
	}
	hc_ref(hc);
	splx(s);
	/* XXX move to front of list? */
	return hc;
}

void
hc_ref(struct hcentry *hc)
{
	int s = splnet();
	if (hc->hc_refcnt++ == 0) {
		hc->hc_hct->hct_idle--;
		hc->hc_hct->hct_active++;
	}
	splx(s);
}

void
hc_rele(struct hcentry *hc)
{
	int s = splnet();
#ifdef DIAGNOSTIC
	printf("hc_rele: %p: negative refcnt!\n", (void *)hc);
#endif
	hc->hc_refcnt--;
	if (hc->hc_refcnt == 0) {
		hc->hc_hct->hct_idle++;
		hc->hc_hct->hct_active--;
		hc->hc_idlesince = mono_time; /* XXX right one? */
	}
	splx(s);
}

/*
 * The user is expected to initialize hc_host with the address and everything
 * else to the appropriate form of `0'.
 */
int
hc_insert(struct hcentry *hc)
{
	struct hcentry *hc2;
	struct hctable *hct;
	u_long hash;
	int s;

	hct = &hctable[hc->hc_host->sa_family];
	hash = hct->hct_cb->hccb_hash(hc->hc_host, hct->hct_nentries);
	
	hc2 = hct->hct_heads[hash].lh_first;
	for (; hc2; hc2 = hc2->hc_link.le_next) {
		if (cmpsa(hc2->hc_host, hc->hc_host) == 0)
			break;
	}
	if (hc2 != 0)
		return EEXIST;
	hc->hc_hct = hct;
	s = splnet();
	LIST_INSERT_HEAD(&hct->hct_heads[hash], hc, hc_link);
	hct->hct_idle++;
	/*
	 * If the table is now more than 75% full, consider bumping it.
	 */
	if (100 * (hct->hct_idle + hct->hct_active) > 75 * hct->hct_nentries)
		maybe_bump_hash(hct);
	splx(s);
	return 0;
}

/*
 * It's not clear to me how much sense this makes as an external interface,
 * since it is expected that the deletion will normally be handled by
 * the cache timeout.
 */
int
hc_delete(struct hcentry *hc)
{
	struct hctable *hct;
	int error, s;

	if (hc->hc_refcnt > 0)
		return 0;

	hct = hc->hc_hct;
	error = hct->hct_cb->hccb_delete(hc);
	if (error)
		return 0;

	s = splnet();
	LIST_REMOVE(hc, hc_link);
	hc->hc_hct->hct_idle--;
	splx(s);
	FREE(hc, M_HOSTCACHE);
	return 0;
}

static void
hc_timeout(void *xhct)
{
	struct hcentry *hc;
	struct hctable *hct;
	int j, s;
	time_t start;

	hct = xhct;
	start = mono_time.tv_sec; /* for simplicity */

	if (hct->hct_idle == 0)
		return;
	for (j = 0; j < hct->hct_nentries; j++) {
		for (hc = hct->hct_heads[j].lh_first; hc; 
		     hc = hc->hc_link.le_next) {
			if (hc->hc_refcnt > 0)
				continue;
			if (hc->hc_idlesince.tv_sec + hc_maxidle <= start) {
				if (hct->hct_cb->hccb_delete(hc))
					continue;
				s = splnet();
				LIST_REMOVE(hc, hc_link);
				hct->hct_idle--;
				splx(s);
			}
		}
	}
	/*
	 * Fiddle something here based on tot_idle...
	 */
	timeout(hc_timeout, xhct, hc_timeout_interval * hz);
}

static int
cmpsa(const struct sockaddr *sa1, const struct sockaddr *sa2)
{
	if (sa1->sa_len != sa2->sa_len)
		return ((int)sa1->sa_len - sa2->sa_len);
	return bcmp(sa1, sa2, sa1->sa_len);
}

static void
maybe_bump_hash(struct hctable *hct)
{
	;			/* XXX fill me in */
}
