/*
 *  circ_buf.c - circular buffer module - implementation
 *  
 *  nc6 - an advanced netcat clone
 *  Copyright (C) 2001-2006 Mauro Tortonesi <mauro _at_ deepspace6.net>
 *  Copyright (C) 2002-2006 Chris Leishman <chris _at_ leishman.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */  
#include "system.h"
#include "circ_buf.h"
#include "misc.h"

#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>

RCSID("@(#) $Header: /ds6/cvs/nc6/src/circ_buf.c,v 1.26 2006/01/19 22:46:23 chris Exp $");



#ifndef NDEBUG
static void cb_assert(const circ_buf_t *cb)
{
	if (cb == NULL ||
	    cb->buf == NULL ||
	    cb->ptr == NULL ||
	    cb->buf_size < cb->data_size) 
	{
		fatal_internal("circular buffer assertion failed");
	}
}
#else
#define cb_assert(CB)	do {} while(0)
#endif



void cb_init(circ_buf_t *cb, size_t size)
{
	assert(cb != NULL);
	assert(size > 0);
	
	memset(cb, 0, sizeof(circ_buf_t));
	
	cb->buf = (uint8_t *)xmalloc(size);
	cb->ptr = cb->buf;
	cb->data_size = 0;
	cb->buf_size  = size;

	cb_assert(cb);
}



void cb_destroy(circ_buf_t *cb)
{
	cb_assert(cb);

	free(cb->buf);
	cb->buf = NULL;
}



void cb_resize(circ_buf_t *cb, size_t size)
{
	uint8_t *new_buf;

	cb_assert(cb);
	assert(size > 0);

	/* create a new buffer and copy the existing data into it */
	new_buf = (uint8_t *)xmalloc(size);
	cb_extract(cb, new_buf, size);

	/* replace buffer */
	free(cb->buf);
	cb->buf = new_buf;

	/* adjust pointers and sizes */
	cb->ptr = cb->buf;
	cb->buf_size = size;
	if (cb->data_size > size)
		cb->data_size = size;
}



ssize_t cb_read(circ_buf_t *cb, int fd, size_t nbytes)
{
	ssize_t rr;
	int count;
	struct iovec iov[2];
	size_t len;

	cb_assert(cb);
	assert(fd >= 0);
	
	/* buffer is full, return an error condition */
	if (cb_is_full(cb)) return -1;

	/* set nbytes appropriately */
	if (nbytes == 0 || (size_t)nbytes > cb_space(cb))
		nbytes = cb_space(cb);
	
	/* prepare for writing to buffer */
	if (cb->ptr == cb->buf) {
		/* space only at end of buf */
		iov->iov_base = cb->ptr + cb->data_size;
		iov->iov_len  = nbytes;
		count = 1;
	} else if (cb->ptr + cb->data_size >= cb->buf + cb->buf_size) {
		/* space only before cb->ptr */
		iov->iov_base = cb->ptr + cb->data_size - cb->buf_size;
		iov->iov_len  = nbytes;
		count = 1;
	} else {
		/* space at end and begining of buf */
		iov[0].iov_base = cb->ptr + cb->data_size;
		len = (cb->buf_size - cb->data_size) - (cb->ptr - cb->buf);

		if (len >= nbytes) {
			/* first space provides enough */
			iov[0].iov_len = nbytes;
			count = 1;
		} else {
			/* need to use both free spaces */
			iov[0].iov_len = len;
			iov[1].iov_base = cb->buf;
			iov[1].iov_len = nbytes - len;
			count = 2;
		}
	}		

	/* do the actual read */
	do { 
		errno = 0;
		rr = readv(fd, iov, count);
	} while (errno == EINTR);

	/* if rr < 0 an error has occured, 
	 * if rr = 0 nothing needs to be changed.
	 * update internal stuff only if rr > 0 */
	if (rr > 0) {
		cb->data_size += rr;
		
		/* sanity check */
		cb_assert(cb);
	}

	return rr;
}



ssize_t cb_recv(circ_buf_t *cb, int fd, size_t nbytes,
                struct sockaddr *from, size_t *fromlen)
{
	ssize_t rr;
	int count;
	struct iovec iov[2];
	struct msghdr msg;
	size_t len;

	cb_assert(cb);
	assert(fd >= 0);

	/* buffer is full, return an error condition */
	if (cb_is_full(cb)) return -1;

	/* set nbytes appropriately */
	if (nbytes == 0 || nbytes > cb_space(cb))
		nbytes = cb_space(cb);
	
	/* prepare for writing to buffer */
	if (cb->ptr == cb->buf) {
		/* space only at end of buf */
		iov->iov_base = cb->ptr + cb->data_size;
		iov->iov_len  = nbytes;
		count = 1;
	} else if (cb->ptr + cb->data_size >= cb->buf + cb->buf_size) {
		/* space only before cb->ptr */
		iov->iov_base = cb->ptr + cb->data_size - cb->buf_size;
		iov->iov_len  = nbytes;
		count = 1;
	} else {
		/* space at end and begining of buf */
		iov[0].iov_base = cb->ptr + cb->data_size;
		len = (cb->buf_size - cb->data_size) - (cb->ptr - cb->buf);

		if (len >= nbytes) {
			/* first space provides enough */
			iov[0].iov_len = nbytes;
			count = 1;
		} else {
			/* need to use both free spaces */
			iov[0].iov_len = len;
			iov[1].iov_base = cb->buf;
			iov[1].iov_len = nbytes - len;
			count = 2;
		}
	}		

	/* setup msg structure */
	memset(&msg, 0, sizeof(msg));
	msg.msg_name    = (void *)from;
	msg.msg_namelen = (from != NULL && fromlen != 0)? *fromlen : 0;
	msg.msg_iov     = iov;
	msg.msg_iovlen  = count;

	/* do the actual recv */
	do {
		errno = 0;
		rr = recvmsg(fd, &msg, 0);
		
		/* copy out updated namelen */
		if (from != NULL && fromlen != 0) 
			*fromlen = msg.msg_namelen;
	} while (errno == EINTR);

	/* if rr < 0 an error has occured,
	 * if rr = 0 nothing needs to be changed.
	 * update internal stuff only if rr > 0 */
	if (rr > 0) {
		cb->data_size += rr;
		
		/* sanity check */
		cb_assert(cb);
	}

	return rr;
}



ssize_t cb_append(circ_buf_t *cb, const uint8_t *buf, size_t len)
{
	ssize_t rr;
	int i, count;
	struct iovec iov[2];
	const uint8_t *tmp;

	cb_assert(cb);
	assert(buf != NULL);
	
	/* buffer is full, return an error condition */
	if (cb_is_full(cb)) return -1;
	
	/* return if len is zero */
	if (len == 0) return 0;
	
	/* setup initial values for tmp and rr */
	tmp = (const uint8_t *)buf;
	rr  = 0;
	
	/* prepare for writing to buffer */
	if (cb->ptr == cb->buf) {
		/* space only at end of buf */
		iov->iov_base = cb->ptr + cb->data_size;
		iov->iov_len  = cb->buf_size - cb->data_size;
		count = 1;
	} else if (cb->ptr + cb->data_size >= cb->buf + cb->buf_size) {
		/* space only before cb->ptr */
		iov->iov_base = cb->ptr + cb->data_size - cb->buf_size;
		iov->iov_len  = cb->buf_size - cb->data_size;
		count = 1;
	} else {
		/* space at end and begining of buf */
		iov[0].iov_base = cb->ptr + cb->data_size;
		iov[0].iov_len  = (cb->buf_size - cb->data_size) 
		                - (cb->ptr - cb->buf);
		iov[1].iov_base = cb->buf;
		iov[1].iov_len  = cb->ptr - cb->buf;
		count = 2;
	}		

	/* do the actual copy */
	for (i = 0; i < count; ++i) {
		size_t chunk_size;
		
		chunk_size = MIN((size_t)iov[i].iov_len, len);
		assert(chunk_size > 0);
		
		memcpy((void *)iov[i].iov_base, (const void *)tmp, chunk_size);

		tmp = tmp + chunk_size;
		len -= chunk_size;
		cb->data_size += chunk_size;
		rr += chunk_size;

		/* sanity check */
		cb_assert(cb);
		
		if (len == 0) break;
	}

	return rr;
}



ssize_t cb_write(circ_buf_t *cb, int fd, size_t nbytes)
{
	ssize_t rr;
	int count;
	struct iovec iov[2];
	size_t len;
	
	cb_assert(cb);
	assert(fd >= 0);
	
	/* buffer is empty, return immediately */
	if (cb_is_empty(cb)) return 0;
	
	/* set nbytes appropriately */
	if (nbytes == 0 || nbytes > cb_used(cb))
		nbytes = cb_used(cb);

	/* prepare for reading from buffer */
	if (cb->ptr + cb->data_size > cb->buf + cb->buf_size) {
		/* data after ptr and at beginning of buffer */
		iov[0].iov_base = cb->ptr;
		len = cb->buf_size - (cb->ptr - cb->buf);

		if (len >= nbytes) {
			/* data after ptr is enough */
			iov[0].iov_len = nbytes;
			count = 1;
		} else {
			iov[0].iov_len  = len;
			iov[1].iov_base = cb->buf;
			iov[1].iov_len  = nbytes - len;
			count = 2;
		}
	} else {
		/* data only after ptr */
		iov[0].iov_base = cb->ptr;
		iov[0].iov_len  = nbytes;
		count = 1;
	}		

	/* do the actual write */
	do { 
		errno = 0;
		rr = writev(fd, iov, count);
	} while (errno == EINTR);

	/* if rr < 0 an error has occured, 
	 * if rr = 0 nothing needs to be changed.
	 * update internal stuff only if rr > 0 */
	if (rr > 0) {
		assert((size_t)rr <= cb->data_size);
		cb->data_size -= rr;
		
		/* update value of cb->ptr */
		cb->ptr += rr;
		if (cb->ptr >= cb->buf + cb->buf_size) 
			cb->ptr -= cb->buf_size;
		
		/* sanity check */
		cb_assert(cb);
	}

	return rr;
}



ssize_t cb_send(circ_buf_t *cb, int fd, size_t nbytes,
                struct sockaddr *dest, size_t destlen)
{
	ssize_t rr;
	int count;
	struct iovec iov[2];
	struct msghdr msg;
	size_t len;
	
	cb_assert(cb);
	assert(fd >= 0);
	
	/* buffer is empty, return immediately */
	if (cb_is_empty(cb)) return 0;
	
	/* set nbytes appropriately */
	if (nbytes == 0 || nbytes > cb_used(cb))
		nbytes = cb_used(cb);
	
	/* prepare for reading from buffer */
	if (cb->ptr + cb->data_size > cb->buf + cb->buf_size) {
		/* data after ptr and at beginning of buffer */
		iov[0].iov_base = cb->ptr;
		len = cb->buf_size - (cb->ptr - cb->buf);

		if (len >= nbytes) {
			/* data after ptr is enough */
			iov[0].iov_len = nbytes;
			count = 1;
		} else {
			iov[0].iov_len  = len;
			iov[1].iov_base = cb->buf;
			iov[1].iov_len  = nbytes - len;
			count = 2;
		}
	} else {
		/* data only after ptr */
		iov[0].iov_base = cb->ptr;
		iov[0].iov_len  = nbytes;
		count = 1;
	}		
	
	/* setup msg structure */
	memset(&msg, 0, sizeof(msg));
	msg.msg_name    = (void *)dest;
	msg.msg_namelen = destlen;
	msg.msg_iov     = iov;
	msg.msg_iovlen  = count;

	/* do the actual send */
	do { 
		errno = 0;
		rr = sendmsg(fd, &msg, 0);
	} while (errno == EINTR);

	/* if rr < 0 an error has occured, 
	 * if rr = 0 nothing needs to be changed.
	 * update internal stuff only if rr > 0 */
	if (rr > 0) {
		assert((size_t)rr <= cb->data_size);
		cb->data_size -= rr;
		
		/* update value of cb->ptr */
		cb->ptr += rr;
		if (cb->ptr >= cb->buf + cb->buf_size) 
			cb->ptr -= cb->buf_size;
		
		/* sanity check */
		cb_assert(cb);
	}

	return rr;
}



ssize_t cb_extract(circ_buf_t *cb, uint8_t *buf, size_t len)
{
	ssize_t rr;
	int i, count;
	struct iovec iov[2];

	cb_assert(cb);
	assert(buf != NULL);
	
	/* buffer is empty, return immediately */
	if (cb_is_empty(cb)) return 0;

	/* return if len is zero */
	if (len == 0) return 0;

	/* setup initial value for rr */
	rr = 0;
	
	/* prepare for reading from buffer */
	if (cb->ptr + cb->data_size > cb->buf + cb->buf_size) {
		iov[0].iov_base = cb->ptr;
		iov[0].iov_len  = cb->buf_size - (cb->ptr - cb->buf);
		iov[1].iov_base = cb->buf;
		iov[1].iov_len  = cb->data_size - iov[0].iov_len;
		count = 2;
	} else {
		iov[0].iov_base = cb->ptr;
		iov[0].iov_len  = cb->data_size;
		count = 1;
	}		

	/* do the actual copy */
	for (i = 0; i < count; ++i) {
		size_t chunk_size;

		chunk_size = MIN((size_t)iov[i].iov_len, len);
		assert(chunk_size > 0);

		memcpy((void *)buf, (const void *)iov[i].iov_base, chunk_size);

		buf = buf + chunk_size;
		len -= chunk_size;
		rr += chunk_size;

		/* sanity check */
		cb_assert(cb);

		if (len == 0) break;
	}

	/* if rr = 0 nothing needs to be changed,
	 * update internal stuff only if rr > 0 */
	if (rr > 0) {
		assert((size_t)rr <= cb->data_size);
		cb->data_size -= rr;

		/* update value of cb->ptr */
		cb->ptr += rr;
		if (cb->ptr >= cb->buf + cb->buf_size)
			cb->ptr -= cb->buf_size;

		/* sanity check */
		cb_assert(cb);
	}

	return rr;
}



void cb_clear(circ_buf_t *cb)
{
	cb_assert(cb);
	
	cb->ptr = cb->buf;
	cb->data_size = 0;
}


syntax highlighted by Code2HTML, v. 0.9.1