/*	$CoreSDI: buffer.c,v 1.12 2001/08/30 21:33:13 claudio Exp $	*/

/*
 *   Copyright (c) 2000, 2001, Core SDI S.A., Argentina
 *   All rights reserved
 *  
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *   3. Neither name of the Core SDI S.A. nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *  
 *   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *   IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *   IN NO EVENT SHALL THE AUTHOR 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.
 */

/*
 * Buffer - Generic buffer functions
 * Author: Claudio Castiglia
 */

#include <sys/types.h>

#ifndef WIN32
#include <sys/uio.h>
#else
#include <io.h>
#endif /* WIN32 */

#ifdef __linux__
#include <netinet/in.h>
#elif !defined(WIN32)
#include <machine/endian.h>
#endif /* __linux__ */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#endif

#include <openssl/evp.h>
#include <zlib.h>

#include "sysdep.h"
#include "buffer.h"


/*
 * Buffer structure:
 *                 rpos           wpos                          size
 *       +----------+--------------+-----------------------------+
 *       |used data |  util data   |        free space           |
 *       +----------+--------------+-----------------------------+
 *	 inutil data   util space
 */

/*
 * _buf_get_free_space():
 */
static int32_t
_buf_get_free_space(BUFFER *buf)
{
	return (buf->buf_size - buf->buf_wpos);
}


/*
 * _buf_get_util_space():
 */
static int32_t
_buf_get_util_space(BUFFER *buf)
{
	return (buf->buf_wpos - buf->buf_rpos);
}


/*
 * _buf_put_raw():
 */
static void
_buf_put_raw(BUFFER *buf, const void *src, int32_t srclen)
{
	memcpy(buf->buf_data + buf->buf_wpos, src, srclen);
	buf->buf_wpos += srclen;
}


/*
 * _buf_get_raw():
 */
static void
_buf_get_raw(BUFFER *buf, void *dst, int32_t dstlen)
{
	memcpy(dst, buf->buf_data + buf->buf_rpos, dstlen);
	buf->buf_rpos += dstlen;
}


/*
 * buf_create():
 *	Create a bufsiz bytes buffer and return a pointer to it,
 *	on error return NULL and set errno.
 */
BUFFER *
buf_create(int32_t bufsiz)
{
	BUFFER *buf;

	if (bufsiz <= 0)
		errno = EINVAL;
	else {
		buf = (BUFFER *) malloc(sizeof(BUFFER));
		if (buf != NULL) {
			buf->buf_data = (u_int8_t *)
			    malloc(sizeof(u_int8_t) * bufsiz);
			if (buf->buf_data != NULL) {
				buf->buf_size = bufsiz;
				buf->buf_rpos = 0;
				buf->buf_wpos = 0;
				return (buf);
			}
			free(buf);
		}
	} 
	return (NULL);
}


/*
 * buf_release():
 *	Release a buffer.
 */
void
buf_release(BUFFER *buf, int flag)
{
	if (buf != NULL) {
		if (flag == BUFFER_CLEAN)
			bzero(buf->buf_data, buf->buf_size);
		free(buf->buf_data);
		free(buf);
	}
}


/*
 * buf_reset():
 *	Reset buffer (mark it as unused).
 */
void
buf_reset(BUFFER *buf)
{
	if (buf != NULL) {
		buf->buf_wpos = 0;
		buf->buf_rpos = 0;
	}
}


/*
 * buf_available():
 *	Return no. of free bytes on buffer, and -1 on error.
 */
int32_t
buf_available(BUFFER *buf)
{
	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}
	return (_buf_get_free_space(buf));
}


/*
 * buf_isempty():
 *	Return a positive value if buffer is empty, 0 if not,
 *	and -1 on error.
 */
int
buf_isempty(BUFFER *buf)
{
	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}
	return (_buf_get_free_space(buf) == buf->buf_size);
}


/*
 * buf_isfull():
 *	Return a positive value if buffer is full, 0 if not,
 *	and -1 on error.
 */
int
buf_isfull(BUFFER *buf)
{
	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}
	return (_buf_get_free_space(buf) == 0); /* XXX: positive value ? */
}


/*
 * buf_util_space():
 *	Return no. of util bytes in the buffer, or -1 on error.
 */
int32_t
buf_util_space(BUFFER *buf)
{
	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}
	return (_buf_get_util_space(buf));
}


/*
 * buf_size():
 *	Return buffer size, or -1 on error.
 */
int32_t
buf_size(BUFFER *buf)
{
	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}
	return (buf->buf_size);
}


/*
 * buf_put_raw():
 *	Attempt to store datasize bytes from data into the buffer;
 *	return the no. of total bytes stored, or -1 on error.
 */
int32_t
buf_put_raw(BUFFER *buf, const void *data, ssize_t datasize)
{
	int32_t free_space, to_store;

	if (buf != NULL) {
		free_space = _buf_get_free_space(buf);
		to_store = (datasize > free_space) ? free_space : datasize;
		if (to_store)
			_buf_put_raw(buf, data, to_store);
		return (to_store);
	}
	errno = EINVAL;
	return (-1);
}
 

/*
 * buf_put_int32():
 *	Put a 32 bit number into the buffer (this number is converted
 *	to network order before being stored);
 *	return 4 on success (4 bytes stored), 0 if there are not
 *	more space available, and -1 on error.
 */
int32_t
buf_put_int32(BUFFER *buf, int32_t value)
{
	if (buf != NULL) {
		if (_buf_get_free_space(buf) >= sizeof(value)) {
			value = htonl(value);
			_buf_put_raw(buf, &value, sizeof(value));
			return (sizeof(value));
		}
		return (0);
	}
	errno = EINVAL;
	return (-1);
}


/*
 * buf_get_raw():
 *	Attempt to retrieve datasize bytes from the buffer;
 *	return the no. of total bytes stored, or -1 if error.
 */
int32_t
buf_get_raw(BUFFER *buf, void *dst, ssize_t dstlen)
{
	int32_t util_space, to_retrieve;

	if (buf != NULL) {
		util_space = _buf_get_util_space(buf);
		to_retrieve = (dstlen > util_space) ? util_space : dstlen;
		if (to_retrieve)
			_buf_get_raw(buf, dst, to_retrieve);
		return (to_retrieve);
	}
	errno = EINVAL;
	return (-1);
}


/*
 * buf_get_int32():
 *	Retrieve a 32 bit int value from the current position on
 *	the buffer; return 4 on success (32 bits = 4 bytes), 0 if can't
 *	exist such value on the buffer, and -1 on error.
 *	NOTE: It is supposed the value on the buffer in network order
 *	      (the retrieved is converted to host order).
 */
int32_t
buf_get_int32(BUFFER *buf, int32_t *value)
{
	if (buf != NULL) {
		if (_buf_get_util_space(buf) >= sizeof(int32_t)) {
			_buf_get_raw(buf, value, sizeof(int32_t));
			*value = ntohl(*value);
			return (sizeof(int32_t));
		}
		return (0);
	}
	errno = EINVAL;
	return (-1);
}


/*
 * buf_compress():
 *	Compress a buffer; after compression, the first 4 bytes on the
 *	destination buffer holds the original data lenght, and the
 *	second 4 bytes the compressed data lenght (both in network order).
 *	Return 0 on success, and -1 on error (if status is not NULL,
 *	it contains the compress2(3) exit code).
 *	NOTE 1: Destination buffer is automatically reset.
 *	NOTE 2: The destination buffer must contain
 *		    4 + 4 + source_buffer_length * (1 + 0.001) + 12
 *		bytes. The first two members holds the uncompressed and
 *		compressed lengths, the rest is needed by the compress2
 *		function (see zlib manual).
 */
int
buf_compress(BUFFER *dst, BUFFER *src, int level, int *status)
{
	if (dst != NULL && src != NULL) {

		if (dst->buf_size > 8) {
			int st;
			uLongf compressed_len;

			compressed_len = dst->buf_size - 8; /* avail. space */
			st = compress2(dst->buf_data + 8, &compressed_len,
			    src->buf_data, src->buf_wpos, level);

			if (status != NULL)
				*status = st;

			switch(st) {
			case Z_OK:
				/* Save orignal and compressed lengths */
				*(int32_t *) dst->buf_data =
				    htonl(src->buf_wpos);
				*(int32_t *) (dst->buf_data + 4) =
				    htonl((int32_t) compressed_len);
				dst->buf_rpos = 0;
				dst->buf_wpos = compressed_len + 8;
				return (0);
			case Z_MEM_ERROR:
			case Z_BUF_ERROR:
				errno = ENOMEM;
				break;
			case Z_STREAM_ERROR:
				errno = EINVAL;
				break;
			}

		} else {
			/* Destination buffer size <= 8 */
			errno = EINVAL;
			if (status != NULL)
				*status = Z_BUF_ERROR;
		}

	} else /* Invalid dst and/or src value */
		errno = EINVAL;

	return (-1);
}


/*
 * buf_uncompress():
 *	Uncompress a buffer; before uncompression, the first 8 bytes
 *	on the source buffer should hold the uncompressed and compressed
 *	data lenghts (in network order); Return 0 on success, -1 on
 *	error, and, if status is not NULL, it contains the uncompress(3)
 *	exit code (see zlib manual).
 *	NOTE: Destination buffer is automatically reset.
 */
int
buf_uncompress(BUFFER *dst, BUFFER *src, int *status)
{
	int st;
	uLongf uncomp_len, comp_len;

	if (dst == NULL || src == NULL) {
		errno = EINVAL;
		return (-1);
	}
	if (src->buf_size <= 8) {
		errno = EINVAL;
		return (-1);
	}

	/* XXX: uLongf greater than or equal to int32_t ever ? */
	uncomp_len = (uLongf) ntohl(*(int32_t *) src->buf_data);
	comp_len = (uLongf) ntohl(*(int32_t *) (src->buf_data + 4));

	/* Destination buffer must have enough space */
	if ((size_t) dst->buf_size < uncomp_len) {
		if (status != NULL)
			*status = Z_BUF_ERROR;
		errno = EINVAL;
		return (-1);
	}

	st = uncompress(dst->buf_data,
	    &uncomp_len, src->buf_data + 8, comp_len);

	if (status != NULL)
		*status = st;

	switch (st) {
	case Z_OK:
		dst->buf_wpos = (int32_t) uncomp_len; /* < 2^31 ever */
		dst->buf_rpos = 0;
		return(0);
	case Z_MEM_ERROR:
	case Z_BUF_ERROR:
		errno = ENOMEM;
		break;
	case Z_STREAM_ERROR:
		errno = EINVAL;
		break;
	}

	return (-1);
}


/*
 * buf_cipher():
 *	Encrypt/decrypt src buffer and put output on dst buffer;
 *	NOTE 1: Destination buffer must have enough space.
 *	NOTE 2: Don't need to reset destination buffer (it's done
 *		automatically).
 */
void
buf_cipher(BUFFER *dst, BUFFER *src, const EVP_CIPHER *type,
	   unsigned char *key, int enc)
{
	int len;
        EVP_CIPHER_CTX ctx;
        unsigned char iv[EVP_MAX_IV_LENGTH];

	bzero(iv, sizeof(iv));
	EVP_CipherInit(&ctx, type, key, iv, enc);
	dst->buf_wpos = dst->buf_size;
	EVP_CipherUpdate(&ctx, dst->buf_data, &dst->buf_wpos, src->buf_data,
	    src->buf_wpos);
	len = dst->buf_size - dst->buf_wpos;
	EVP_CipherFinal(&ctx, dst->buf_data + dst->buf_wpos, &len);
	dst->buf_wpos += len;
	dst->buf_rpos = 0;
}


/*
 * buf_write():
 *	Write all util buffer data to the specified file descriptor;
 *	on sucess return the no. of written bytes, or -1 on error.
 *	NOTE 1: Calling this function don't means that all data was written.
 *	NOTE 2: The file descriptor is a SOCKET on windows.
 */
#ifndef WIN32
int32_t
buf_write(BUFFER *buf, int fd)
#else
int32_t
buf_write(BUFFER *buf, SOCKET fd)
#endif /* WIN32 */
{
	int32_t written, util_space;

	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}

	util_space = _buf_get_util_space(buf);
	if (util_space) {
#ifndef WIN32
		written = write(fd, buf->buf_data + buf->buf_rpos, util_space);
#else
		written = send(fd, buf->buf_data + buf->buf_rpos, util_space,
		    0);
#endif /* WIN32 */

		if (written > 0)
			buf->buf_rpos += written;
	} else
		written = 0;	/* can't write if buffer haven't util data */

	return (written);
}


/*
 * buf_read();
 *	Read data from the specified file descriptor and put it into
 *	the free space on the buffer;
 *	on success return the no. of readed bytes or -1 on error.
 *	NOTE 1: It tries to read a no. of bytes that fills the whole
 *	        buffer.
 *	NOTE 2: The file descriptor is a SOCKET on windows.
 */
#ifndef WIN32
int32_t
buf_read(BUFFER *buf, int fd, int32_t size)
#else
int32_t
buf_read(BUFFER *buf, SOCKET fd, int32_t size)
#endif /* WIN32 */
{
	int32_t free_space, readed, total;

	if (buf == NULL) {
		errno = EINVAL;
		return (-1);
	}

	total = 0;
	free_space = _buf_get_free_space(buf);
	size = (free_space < size) ? free_space : size;
	while (size) {
#ifndef WIN32
		readed = read(fd, buf->buf_data + buf->buf_wpos, size);
		if (readed < 0)
			return (-1);
#else
		readed = recv(fd, buf->buf_data + buf->buf_wpos, size, 0);
		if (readed == SOCKET_ERROR)
			return (-1);
#endif /* WIN32 */

		buf->buf_wpos += readed;
		size -= readed;
		total += readed;
	}

	return (total);
}


