/*
 * RINGBUF - general support for ring buffers
 *
 * Author:
 * Emile van Bergen, emile@evbergen.xs4all.nl
 *
 * Permission to redistribute an original or modified version of this program
 * in source, intermediate or object code form is hereby granted exclusively
 * under the terms of the GNU General Public License, version 2. Please see the
 * file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
 *
 * History:
 * 2000/11/28 - EvB - Created
 * 2001/07/02 - EvB - Added ring_peekdata
 * 2001/07/03 - EvB - Added ring_strncmp as a counting function
 */

char ringbuf_id[] = "RINGBUF - Copyright (C) 2000 Emile van Bergen.";


/*
 * INCLUDES & DEFINES
 */


#include <stdlib.h>	/* for malloc/free */
#include <unistd.h>	/* for read/write */
#include <sys/uio.h>	/* for readv/writev */
#include <errno.h>	/* for errno */
#include <limits.h>	/* for *_MAX */
#include <string.h>	/* for memcpy/str(c)spn */

#include <ringbuf.h>

#define DEBUGLEVEL 0	/* emit code for DEBUGx functions up to level 4 */
#include <debug.h>


#define SSIZE_T_MAX	(sizeof(ssize_t) == sizeof(long) ? LONG_MAX : INT_MAX)


/*
 * FUNCTIONS
 */


/* Creation / deletion */


RING *ring_new(ssize_t size)
{
	RING *ret;

	/* Limit size to half the number the variable can hold,
	   because of the additions we perform later on. */
	if (size < 1 || size > (SSIZE_T_MAX >> 1) - 1) {
		errno = EINVAL;
		return 0;
	}

	ret = (RING *)malloc(sizeof(RING));
	if (!ret) return 0;

	ret->buf = (char *)malloc(size);
	if (!ret->buf) { free(ret); return 0; }

	ret->size = size;
	ret->r = ret->w = 0;

	return ret;
}


void ring_del(RING *r)
{
	if (!r) return;

	if (r->buf) free(r->buf);
	free(r);
}


/* Debugging */


void ring_debug(RING *r)
{
	if (r->w < r->r)
		msg(F_MISC, L_DEBUG, 
		    "   %*s%*s\n", r->w + 1, "w", r->r - r->w, "r");
	else if (r->r < r->w)
		msg(F_MISC, L_DEBUG, 
		    "   %*s%*s\n", r->r + 1, "r", r->w - r->r, "w");
	else
		msg(F_MISC, L_DEBUG, "   %*s\n", r->w + 1, "x");
	
	msg(F_MISC, L_DEBUG, "- [%s]\n", dbg_cvtstr(r->buf, r->size));
}


/* Getting / putting */


ssize_t ring_get(RING *r, void *data, ssize_t len)
{
	ssize_t maxlen;

	/* Fall through for messages that fit and don't wrap around. */

	maxlen = ring_maxget(r);
	if (maxlen <= len) {

		/* Message fits */
		if (r->r + maxlen <= r->size) {

			/* Message ends before end of buffer */
			memcpy(data, r->buf + r->r, maxlen);
			r->r += maxlen;
			return maxlen;
		}

		/* Message doesn't end before end of buffer */
		memcpy(data, r->buf + r->r, r->size - r->r);
		memcpy((char *)data + (r->size - r->r), r->buf,
		       maxlen - (r->size - r->r)); 
		r->r = maxlen - (r->size - r->r);
		return maxlen;
	}

	/* Message doesn't fit */
	if (r->r + len <= r->size) {

		/* Fitting part ends before end of buffer */
		memcpy(data, r->buf + r->r, len);
		r->r += len;
		return len;
	}

	/* Fitting part doesn't end before end of buffer */
	memcpy(data, r->buf + r->r, r->size - r->r);
	memcpy((char *)data + (r->size - r->r), r->buf,
	       len - (r->size - r->r));
	r->r = len - (r->size - r->r);
	return len;
}


ssize_t ring_peekdata(RING *r, void *data, ssize_t len)
{
	ssize_t maxlen;

	maxlen = ring_maxget(r);
	if (maxlen <= len) {

		/* Message fits */
		if (r->r + maxlen <= r->size) {

			/* Message ends before end of buffer */
			memcpy(data, r->buf + r->r, maxlen);
			return maxlen;
		}

		/* Message doesn't end before end of buffer */
		memcpy(data, r->buf + r->r, r->size - r->r);
		memcpy((char *)data + (r->size - r->r), r->buf,
		       maxlen - (r->size - r->r)); 
		return maxlen;
	}

	/* Message doesn't fit */
	if (r->r + len <= r->size) {

		/* Fitting part ends before end of buffer */
		memcpy(data, r->buf + r->r, len);
		return len;
	}

	/* Fitting part doesn't end before end of buffer */
	memcpy(data, r->buf + r->r, r->size - r->r);
	memcpy((char *)data + (r->size - r->r), r->buf,
	       len - (r->size - r->r));
	return len;
}


ssize_t ring_put(RING *r, void *data, ssize_t len)
{
	ssize_t maxlen;

	/* Fall through for messages that fit and don't wrap around. */

	maxlen = ring_maxput(r);
	if (len <= maxlen) {

		/* Message fits */
		if (r->w + len <= r->size) {

			/* Message ends before end of buffer */
			memcpy(r->buf + r->w, data, len);
			r->w += len;
			return len;
		}

		/* Message doesn't end before end of buffer */
		memcpy(r->buf + r->w, data, r->size - r->w);
		memcpy(r->buf, (char *)data + (r->size - r->w),
		       len - (r->size - r->w));
		r->w = len - (r->size - r->w);
		return len;
	}

	/* Message doesn't fit */
	if (r->w + maxlen <= r->size) {

		/* Fitting part ends before end of buffer */
		memcpy(r->buf + r->w, data, maxlen);
		r->w += maxlen;
		return maxlen;
	}

	/* Fitting part doesn't end before end of buffer */
	memcpy(r->buf + r->w, data, r->size - r->w);
	memcpy(r->buf, (char *)data + (r->size - r->w),
	       maxlen - (r->size - r->w));
	r->w = maxlen - (r->size - r->w);
	return maxlen;
}


/* Discards a number of bytes; len <= 0 means all
   Returns number of bytes removed from the ring. */

ssize_t ring_discard(RING *r, ssize_t maxlen)
{
	ssize_t len;

	len = ring_maxget(r);
	
	if (len) {

		if (maxlen > 0 && len > maxlen) {

			/* Remove 'maxlen' bytes */

			r->r = (r->r + maxlen) % r->size;
			return maxlen;
		}

		/* Remove 'len' (all) bytes */

		r->r = (r->r + len) % r->size;

		return len;
	}

	/* Return no bytes removed */

	return 0;
}


/* Open file I/O. Tries to transfer as much as possible or the maximum
   specified by max if that's nonzero and the ring holds more than that; 
   returns status. */

int ring_write(RING *r, int fd, ssize_t *xfered, ssize_t max)
{
	ssize_t len, ret;
	struct iovec iov[2];

	len = ring_maxget(r); if (max > 0 && max < len) len = max;
	if (r->r + len <= r->size) {

		/* Available data doesn't wrap around, so write and advance */
		ret = write(fd, r->buf + r->r, len);
		if (ret > 0) r->r += ret;
	}
	else {
		/* Available data does wrap around, so writev the two parts */

		/* define first part, till end of buffer */
		iov[0].iov_base = r->buf + r->r;
		iov[0].iov_len = r->size - r->r;

		/* define rest, from start of buffer */
		iov[1].iov_base = r->buf;
		iov[1].iov_len = len - (r->size - r->r);

		/* do the write and advance get pointer */
		ret = writev(fd, iov, 2);
		if (ret > 0) r->r = (r->r + ret) % r->size;
	}

	/* Return number of bytes transfered */
	if (xfered) *xfered = ret > 0 ? ret : 0;

	/* See if we wrote all we got, wrote less, got EAGAIN or other error */
	if (ret == len) return RING_OK;
	if (ret == -1) return errno == EAGAIN ? RING_EAGAIN : RING_IOERR;
	return RING_EOF;
}


int ring_read(RING *r, int fd, ssize_t *xfered)
{
	ssize_t len, ret;
	struct iovec iov[2];

	len = ring_maxput(r);
	if (r->w + len <= r->size) {

		/* Available space doesn't wrap around, so read and advance */
		ret = read(fd, r->buf + r->w, len); 
		if (ret > 0) r->w += ret;
	}
	else {
		/* Available space wraps around, so readv the two parts */

		/* define first part, till end of buffer */
		iov[0].iov_base = r->buf + r->w;
		iov[0].iov_len = r->size - r->w;

		/* define rest, at start of buffer */
		iov[1].iov_base = r->buf;
		iov[1].iov_len = len - (r->size - r->w);

		/* do the read and advance put pointer */
		ret = readv(fd, iov, 2);
		if (ret > 0) r->w = (r->w + ret) % r->size;
	}

	/* Return number of bytes transfered */
	if (xfered) *xfered = ret > 0 ? ret : 0;

	/* See if we wrote all we got, wrote less, got EAGAIN or other error */
	if (ret == len) return RING_OK;
	if (ret == -1) return errno == EAGAIN ? RING_EAGAIN : RING_IOERR;
	return RING_EOF;
}


/* Peeking functions; all built from a general counting function 
   and a closure. 

   The closure must return a numeric value corresponding to a count of
   characters that matches a certain condition. If the ring contains
   a wrapped message, the closure is only called a second time if the
   first call returned the full length of the first segment.

   The closure cannot specify any error condition. 

   TODO: this could probably be a lot better if implemented with macro's,
   but then we would probably start to require some GCC extensions... */

ssize_t ring_count(RING *r, RINGCNTFUNC func, ...)
{
	va_list ap;
	ssize_t len, ret = 0;

	len = ring_maxget(r);

	if (len) {

		/* There is data available */

		if (r->r + len <= r->size) {

			/* Message ends before end of buffer */

			/* peek at single part, return whatever the
			   closure returns. */
			va_start(ap, func);
			ret = func(r->buf + r->r, len, 0, ap);
			va_end(ap);
		}
		else {

			/* Message doesn't end before end of buffer */

			/* peek at first part, till end of buffer */
			va_start(ap, func);
			ret = func(r->buf + r->r, r->size - r->r, 0, ap);
			va_end(ap);

			if (ret < r->size - r->r) {
				/* Satisfied by first part; return whatever
				   the closure gave us. */
			}
			else {
				/* peek at rest, from start of buffer */
				va_start(ap, func);
				ret += func(r->buf, len - (r->size - r->r), 
					    ret, ap);
				va_end(ap);
			}
		}
	}

	/* No data available, return 0. */
	return ret;
}


/* count functions for ring_str(c)spn - varargs: char *s, int slen. */

ssize_t _ring_cnt_strspn(char *s, ssize_t len, ssize_t seen, va_list ap)
{
	char *set;
	int setlen;
	ssize_t ret;

	set = va_arg(ap, char *);
	setlen = va_arg(ap, int);

	for(ret = 0; ret < len && memchr(set, s[ret], setlen); ret++);

	return ret;
}

ssize_t _ring_cnt_strcspn(char *s, ssize_t len, ssize_t seen, va_list ap)
{
	char *set;
	int setlen;
	ssize_t ret;

	set = va_arg(ap, char *);
	setlen = va_arg(ap, int);

	for(ret = 0; ret < len && memchr(set, s[ret], setlen) == 0; ret++);

	return ret;
}

ssize_t _ring_cnt_strncmp(char *s, ssize_t len, ssize_t seen, va_list ap)
{
	char *arg;
	ssize_t arglen;

	arg = va_arg(ap, char *) + seen;
	arglen = va_arg(ap, ssize_t) - seen;

	/* arglen is smallest - compare arglen, return that or nothing */
	if (arglen <= len)
		return (memcmp(s, arg, arglen) == 0) ? arglen : 0;

	/* arglen is biggest - compare len, return len or noting */
	return (memcmp(s, arg, len) == 0) ? len : 0;
}


#if 0

/* Real versions of the inline peeking functions defined in the header */

ssize_t ring_strlen(RING *r)
{
        return ring_count(r, _ring_cnt_strcspn, "\0", 1);
}

ssize_t ring_strspn(RING *r, char *s, int slen) 
{ 
	return ring_count(r, _ring_cnt_strspn, s, slen); 
}

ssize_t ring_strcspn(RING *r, char *s, int slen) 
{ 
	return ring_count(r, _ring_cnt_strcspn, s, slen); 
}

ssize_t ring_strncmp(RING *r, char *s, int slen) 
{ 
	/* always returns 1 if different - doesn't test bigger or smaller. */
	return ring_count(r, _ring_cnt_strncmp, s, slen) != slen; 
}


/* Testing; put is guaranteed to accept the number of bytes given
   by maxput, and get is guaranteed to return the number of bytes given 
   by maxget. These are also available in the header as inlines. */

ssize_t ring_maxput(RING *r) 
{
        return ((r->size + (r->r - (r->w + 1))) % r->size);
}

ssize_t ring_maxget(RING *r) 
{
        return ((r->size + (r->w - r->r)) % r->size);
}

int ring_isempty(RING *r)
{
	return (r->r == r->w);
}

int ring_capacity(RING *r)
{
	return r->size - 1;
}


/* Peeking function: returns the single character at the specified
   position in the string of available bytes. If a position beyond
   the end of that string is specified, -1 is returned. This function
   is also available inline in the header. */

int ring_peek(RING *r, ssize_t pos)
{
	if (pos >= 0 && pos < ring_maxget(r)) {
		return (r->buf[(r->size + r->r + pos) % r->size]) & 0xff;
	}

	return -1;
}

#endif


syntax highlighted by Code2HTML, v. 0.9.1