#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>

#include "ntp_assert.h"
#include "ntp_syslog.h"
#include "ntp_stdlib.h"
#include "ntp_lists.h"
#include "recvbuff.h"
#include "iosignal.h"

#if (RECV_INC & (RECV_INC-1))
# error RECV_INC not a power of 2!
#endif
#if (RECV_BATCH & (RECV_BATCH - 1))
#error RECV_BATCH not a power of 2!
#endif
#if (RECV_BATCH < RECV_INC)
#error RECV_BATCH must be >= RECV_INC!
#endif

/*
 * Memory allocation
 */
static u_long volatile full_recvbufs;	/* recvbufs on full_recv_fifo */
static u_long volatile free_recvbufs;	/* recvbufs on free_recv_list */
static u_long volatile total_recvbufs;	/* total recvbufs currently in use */
static u_long volatile lowater_adds;	/* number of times we have added memory */
static u_long volatile buffer_shortfall;/* number of missed free receive buffers
					   between replenishments */
static u_long limit_recvbufs;		/* maximum total of receive buffers */
static u_long emerg_recvbufs;		/* emergency/urgent buffers to keep */

static DECL_FIFO_ANCHOR(recvbuf_t) full_recv_fifo;
static recvbuf_t *		   free_recv_list;
	
#if defined(SYS_WINNT)

/*
 * For Windows we need to set up a lock to manipulate the
 * recv buffers to prevent corruption. We keep it lock for as
 * short a time as possible
 */
static CRITICAL_SECTION RecvLock;
static CRITICAL_SECTION FreeLock;
# define LOCK_R()	EnterCriticalSection(&RecvLock)
# define UNLOCK_R()	LeaveCriticalSection(&RecvLock)
# define LOCK_F()	EnterCriticalSection(&FreeLock)
# define UNLOCK_F()	LeaveCriticalSection(&FreeLock)
#else
# define LOCK_R()	do {} while (FALSE)
# define UNLOCK_R()	do {} while (FALSE)
# define LOCK_F()	do {} while (FALSE)
# define UNLOCK_F()	do {} while (FALSE)
#endif

#ifdef DEBUG
static void uninit_recvbuff(void);
#endif


u_long
free_recvbuffs (void)
{
	return free_recvbufs;
}

u_long
full_recvbuffs (void)
{
	return full_recvbufs;
}

u_long
total_recvbuffs (void)
{
	return total_recvbufs;
}

u_long
lowater_additions(void)
{
	return lowater_adds;
}

static inline void 
initialise_buffer(recvbuf_t *buff)
{
	ZERO(*buff);
}

static void
create_buffers(
	size_t			nbufs
)
{
	static const u_int	chunk =
#   ifndef DEBUG
					RECV_INC;
#   else
	/* Allocate each buffer individually so they can be free()d
	 * during ntpd shutdown on DEBUG builds to keep them out of heap
	 * leak reports.
	 */
					1;
#   endif
	static int/*BOOL*/	doneonce;
	recvbuf_t *		bufp;
	u_int			i;
	size_t			abuf;

	/*[bug 3666]: followup -- reset shortfalls in all cases */
	abuf = nbufs + buffer_shortfall;
	buffer_shortfall = 0;

	if (limit_recvbufs <= total_recvbufs) {
		if (!doneonce) {
			msyslog(LOG_CRIT, "Unable to allocate receive"
					  " buffer, %lu/%lu",
				total_recvbufs, limit_recvbufs);
			doneonce = TRUE;
		}
		return;
	}

	if (abuf < nbufs || abuf > RECV_BATCH) {
		abuf = RECV_BATCH;	/* clamp on overflow */
	} else {
		abuf += (~abuf + 1) & (RECV_INC - 1);	/* round up */
	}
	if (abuf > (limit_recvbufs - total_recvbufs)) {
		abuf = limit_recvbufs - total_recvbufs;
	}
	abuf += (~abuf + 1) & (chunk - 1);		/* round up */
	
	while (abuf) {
		bufp = calloc(chunk, sizeof(*bufp));
		if (!bufp) {
			msyslog(LOG_CRIT, "Out of memory, allocating "
					  "%u recvbufs, %lu bytes",
				chunk, (u_long)sizeof(*bufp) * chunk);
			limit_recvbufs = total_recvbufs;
			break;
		}
		for (i = chunk; i; --i,++bufp) {
			LINK_SLIST(free_recv_list, bufp, link);
		}
		free_recvbufs += chunk;
		total_recvbufs += chunk;
		abuf -= chunk;
	}
	++lowater_adds;
}

void
init_recvbuff(int nbufs)
{

	/*
	 * Init buffer free list and stat counters
	 */
	free_recvbufs = total_recvbufs = 0;
	full_recvbufs = lowater_adds = 0;

	limit_recvbufs = RECV_TOOMANY;
	emerg_recvbufs = RECV_CLOCK;

	create_buffers(nbufs);

#   if defined(SYS_WINNT)
	InitializeCriticalSection(&RecvLock);
	InitializeCriticalSection(&FreeLock);
#   endif

#   ifdef DEBUG
	atexit(&uninit_recvbuff);
#   endif
}


#ifdef DEBUG
static void
uninit_recvbuff(void)
{
	recvbuf_t *rbunlinked;

	for (;;) {
		UNLINK_FIFO(rbunlinked, full_recv_fifo, link);
		if (rbunlinked == NULL)
			break;
		free(rbunlinked);
	}

	for (;;) {
		UNLINK_HEAD_SLIST(rbunlinked, free_recv_list, link);
		if (rbunlinked == NULL)
			break;
		free(rbunlinked);
	}
#   if defined(SYS_WINNT)
	DeleteCriticalSection(&FreeLock);
	DeleteCriticalSection(&RecvLock);
#   endif
}
#endif	/* DEBUG */


/*
 * freerecvbuf - make a single recvbuf available for reuse
 */
void
freerecvbuf(recvbuf_t *rb)
{
	if (rb) {
		if (--rb->used != 0) {
			msyslog(LOG_ERR, "******** freerecvbuff non-zero usage: %d *******", rb->used);
			rb->used = 0;
		}
		LOCK_F();
		LINK_SLIST(free_recv_list, rb, link);
		++free_recvbufs;
		UNLOCK_F();
	}
}

	
void
add_full_recv_buffer(recvbuf_t *rb)
{
	if (rb == NULL) {
		msyslog(LOG_ERR, "add_full_recv_buffer received NULL buffer");
		return;
	}
	LOCK_R();
	LINK_FIFO(full_recv_fifo, rb, link);
	++full_recvbufs;
	UNLOCK_R();
}


recvbuf_t *
get_free_recv_buffer(
    int /*BOOL*/ urgent
    )
{
	recvbuf_t *buffer = NULL;

	LOCK_F();
	if (free_recvbufs > (urgent ? 0 : emerg_recvbufs)) {
		UNLINK_HEAD_SLIST(buffer, free_recv_list, link);
	}
	
	if (buffer != NULL) {
		if (free_recvbufs)
			--free_recvbufs;
		initialise_buffer(buffer);
		++buffer->used;
	} else {
		++buffer_shortfall;
	}
	UNLOCK_F();

	return buffer;
}


#ifdef HAVE_IO_COMPLETION_PORT
recvbuf_t *
get_free_recv_buffer_alloc(
    int /*BOOL*/ urgent
    )
{
	LOCK_F();	
	if (free_recvbufs <= emerg_recvbufs || buffer_shortfall > 0)
		create_buffers(RECV_INC);
	UNLOCK_F();
	return get_free_recv_buffer(urgent);
}
#endif


recvbuf_t *
get_full_recv_buffer(void)
{
	recvbuf_t *	rbuf;

	/*
	 * make sure there are free buffers when we wander off to do
	 * lengthy packet processing with any buffer we grab from the
	 * full list.
	 * 
	 * fixes malloc() interrupted by SIGIO risk (Bug 889)
	 */
	LOCK_F();	
	if (free_recvbufs <= emerg_recvbufs || buffer_shortfall > 0)
		create_buffers(RECV_INC);
	UNLOCK_F();

	/*
	 * try to grab a full buffer
	 */
	LOCK_R();
	UNLINK_FIFO(rbuf, full_recv_fifo, link);
	if (rbuf != NULL && full_recvbufs)
		--full_recvbufs;
	UNLOCK_R();

	return rbuf;
}


/*
 * purge_recv_buffers_for_fd() - purges any previously-received input
 *				 from a given file descriptor.
 */
void
purge_recv_buffers_for_fd(
	int	fd
	)
{
	recvbuf_t *rbufp;
	recvbuf_t *next;
	recvbuf_t *punlinked;
	recvbuf_t *freelist = NULL;

	/* We want to hold only one lock at a time. So we do a scan on
	 * the full buffer queue, collecting items as we go, and when
	 * done we spool the the collected items to 'freerecvbuf()'.
	 */
	LOCK_R();

	for (rbufp = HEAD_FIFO(full_recv_fifo);
	     rbufp != NULL;
	     rbufp = next)
	{
		next = rbufp->link;
#	    ifdef HAVE_IO_COMPLETION_PORT
		if (rbufp->dstadr == NULL && rbufp->fd == fd)
#	    else
		if (rbufp->fd == fd)
#	    endif
		{
			UNLINK_MID_FIFO(punlinked, full_recv_fifo,
					rbufp, link, recvbuf_t);
			INSIST(punlinked == rbufp);
			if (full_recvbufs)
				--full_recvbufs;
			rbufp->link = freelist;
			freelist = rbufp;
		}
	}

	UNLOCK_R();
	
	while (freelist) {
		next = freelist->link;
		freerecvbuf(freelist);
		freelist = next;
	}
}


/*
 * Checks to see if there are buffers to process
 */
isc_boolean_t has_full_recv_buffer(void)
{
	if (HEAD_FIFO(full_recv_fifo) != NULL)
		return (ISC_TRUE);
	else
		return (ISC_FALSE);
}


#ifdef NTP_DEBUG_LISTS_H
void
check_gen_fifo_consistency(void *fifo)
{
	gen_fifo *pf;
	gen_node *pthis;
	gen_node **pptail;

	pf = fifo;
	REQUIRE((NULL == pf->phead && NULL == pf->pptail) ||
		(NULL != pf->phead && NULL != pf->pptail));

	pptail = &pf->phead;
	for (pthis = pf->phead;
	     pthis != NULL;
	     pthis = pthis->link)
		if (NULL != pthis->link)
			pptail = &pthis->link;

	REQUIRE(NULL == pf->pptail || pptail == pf->pptail);
}
#endif	/* NTP_DEBUG_LISTS_H */
