/*
 * mbuf.c
 *		Memory buffer operations.
 *
 * Copyright (c) 2005 Marko Kreen
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 * contrib/pgcrypto/mbuf.c
 */

#include "postgres.h"

#include "mbuf.h"
#include "px.h"

#define STEP  (16*1024)

struct MBuf
{
	uint8	   *data;
	uint8	   *data_end;
	uint8	   *read_pos;
	uint8	   *buf_end;
	bool		no_write;
	bool		own_data;
};

int
mbuf_avail(MBuf *mbuf)
{
	return mbuf->data_end - mbuf->read_pos;
}

int
mbuf_size(MBuf *mbuf)
{
	return mbuf->data_end - mbuf->data;
}

int
mbuf_tell(MBuf *mbuf)
{
	return mbuf->read_pos - mbuf->data;
}

int
mbuf_free(MBuf *mbuf)
{
	if (mbuf->own_data)
	{
		px_memset(mbuf->data, 0, mbuf->buf_end - mbuf->data);
		px_free(mbuf->data);
	}
	px_free(mbuf);
	return 0;
}

static void
prepare_room(MBuf *mbuf, int block_len)
{
	uint8	   *newbuf;
	unsigned	newlen;

	if (mbuf->data_end + block_len <= mbuf->buf_end)
		return;

	newlen = (mbuf->buf_end - mbuf->data)
		+ ((block_len + STEP + STEP - 1) & -STEP);

	newbuf = px_realloc(mbuf->data, newlen);

	mbuf->buf_end = newbuf + newlen;
	mbuf->data_end = newbuf + (mbuf->data_end - mbuf->data);
	mbuf->read_pos = newbuf + (mbuf->read_pos - mbuf->data);
	mbuf->data = newbuf;
}

int
mbuf_append(MBuf *dst, const uint8 *buf, int len)
{
	if (dst->no_write)
	{
		px_debug("mbuf_append: no_write");
		return PXE_BUG;
	}

	prepare_room(dst, len);

	memcpy(dst->data_end, buf, len);
	dst->data_end += len;

	return 0;
}

MBuf *
mbuf_create(int len)
{
	MBuf	   *mbuf;

	if (!len)
		len = 8192;

	mbuf = px_alloc(sizeof *mbuf);
	mbuf->data = px_alloc(len);
	mbuf->buf_end = mbuf->data + len;
	mbuf->data_end = mbuf->data;
	mbuf->read_pos = mbuf->data;

	mbuf->no_write = false;
	mbuf->own_data = true;

	return mbuf;
}

MBuf *
mbuf_create_from_data(uint8 *data, int len)
{
	MBuf	   *mbuf;

	mbuf = px_alloc(sizeof *mbuf);
	mbuf->data = (uint8 *) data;
	mbuf->buf_end = mbuf->data + len;
	mbuf->data_end = mbuf->data + len;
	mbuf->read_pos = mbuf->data;

	mbuf->no_write = true;
	mbuf->own_data = false;

	return mbuf;
}


int
mbuf_grab(MBuf *mbuf, int len, uint8 **data_p)
{
	if (len > mbuf_avail(mbuf))
		len = mbuf_avail(mbuf);

	mbuf->no_write = true;

	*data_p = mbuf->read_pos;
	mbuf->read_pos += len;
	return len;
}

int
mbuf_rewind(MBuf *mbuf)
{
	mbuf->read_pos = mbuf->data;
	return 0;
}

int
mbuf_steal_data(MBuf *mbuf, uint8 **data_p)
{
	int			len = mbuf_size(mbuf);

	mbuf->no_write = true;
	mbuf->own_data = false;

	*data_p = mbuf->data;

	mbuf->data = mbuf->data_end = mbuf->read_pos = mbuf->buf_end = NULL;

	return len;
}

/*
 * PullFilter
 */

struct PullFilter
{
	PullFilter *src;
	const PullFilterOps *op;
	int			buflen;
	uint8	   *buf;
	int			pos;
	void	   *priv;
};

int
pullf_create(PullFilter **pf_p, const PullFilterOps *op, void *init_arg, PullFilter *src)
{
	PullFilter *pf;
	void	   *priv;
	int			res;

	if (op->init != NULL)
	{
		res = op->init(&priv, init_arg, src);
		if (res < 0)
			return res;
	}
	else
	{
		priv = init_arg;
		res = 0;
	}

	pf = px_alloc(sizeof(*pf));
	memset(pf, 0, sizeof(*pf));
	pf->buflen = res;
	pf->op = op;
	pf->priv = priv;
	pf->src = src;
	if (pf->buflen > 0)
	{
		pf->buf = px_alloc(pf->buflen);
		pf->pos = 0;
	}
	else
	{
		pf->buf = NULL;
		pf->pos = 0;
	}
	*pf_p = pf;
	return 0;
}

void
pullf_free(PullFilter *pf)
{
	if (pf->op->free)
		pf->op->free(pf->priv);

	if (pf->buf)
	{
		px_memset(pf->buf, 0, pf->buflen);
		px_free(pf->buf);
	}

	px_memset(pf, 0, sizeof(*pf));
	px_free(pf);
}

/* may return less data than asked, 0 means eof */
int
pullf_read(PullFilter *pf, int len, uint8 **data_p)
{
	int			res;

	if (pf->op->pull)
	{
		if (pf->buflen && len > pf->buflen)
			len = pf->buflen;
		res = pf->op->pull(pf->priv, pf->src, len, data_p,
						   pf->buf, pf->buflen);
	}
	else
		res = pullf_read(pf->src, len, data_p);
	return res;
}

int
pullf_read_max(PullFilter *pf, int len, uint8 **data_p, uint8 *tmpbuf)
{
	int			res,
				total;
	uint8	   *tmp;

	res = pullf_read(pf, len, data_p);
	if (res <= 0 || res == len)
		return res;

	/* read was shorter, use tmpbuf */
	memcpy(tmpbuf, *data_p, res);
	*data_p = tmpbuf;
	len -= res;
	total = res;

	while (len > 0)
	{
		res = pullf_read(pf, len, &tmp);
		if (res < 0)
		{
			/* so the caller must clear only on success */
			px_memset(tmpbuf, 0, total);
			return res;
		}
		if (res == 0)
			break;
		memcpy(tmpbuf + total, tmp, res);
		total += res;
		len -= res;
	}
	return total;
}

/*
 * caller wants exactly len bytes and don't bother with references
 */
int
pullf_read_fixed(PullFilter *src, int len, uint8 *dst)
{
	int			res;
	uint8	   *p;

	res = pullf_read_max(src, len, &p, dst);
	if (res < 0)
		return res;
	if (res != len)
	{
		px_debug("pullf_read_fixed: need=%d got=%d", len, res);
		return PXE_PGP_CORRUPT_DATA;
	}
	if (p != dst)
		memcpy(dst, p, len);
	return 0;
}

/*
 * read from MBuf
 */
static int
pull_from_mbuf(void *arg, PullFilter *src, int len,
			   uint8 **data_p, uint8 *buf, int buflen)
{
	MBuf	   *mbuf = arg;

	return mbuf_grab(mbuf, len, data_p);
}

static const struct PullFilterOps mbuf_reader = {
	NULL, pull_from_mbuf, NULL
};

int
pullf_create_mbuf_reader(PullFilter **mp_p, MBuf *src)
{
	return pullf_create(mp_p, &mbuf_reader, src, NULL);
}


/*
 * PushFilter
 */

struct PushFilter
{
	PushFilter *next;
	const PushFilterOps *op;
	int			block_size;
	uint8	   *buf;
	int			pos;
	void	   *priv;
};

int
pushf_create(PushFilter **mp_p, const PushFilterOps *op, void *init_arg, PushFilter *next)
{
	PushFilter *mp;
	void	   *priv;
	int			res;

	if (op->init != NULL)
	{
		res = op->init(next, init_arg, &priv);
		if (res < 0)
			return res;
	}
	else
	{
		priv = init_arg;
		res = 0;
	}

	mp = px_alloc(sizeof(*mp));
	memset(mp, 0, sizeof(*mp));
	mp->block_size = res;
	mp->op = op;
	mp->priv = priv;
	mp->next = next;
	if (mp->block_size > 0)
	{
		mp->buf = px_alloc(mp->block_size);
		mp->pos = 0;
	}
	else
	{
		mp->buf = NULL;
		mp->pos = 0;
	}
	*mp_p = mp;
	return 0;
}

void
pushf_free(PushFilter *mp)
{
	if (mp->op->free)
		mp->op->free(mp->priv);

	if (mp->buf)
	{
		px_memset(mp->buf, 0, mp->block_size);
		px_free(mp->buf);
	}

	px_memset(mp, 0, sizeof(*mp));
	px_free(mp);
}

void
pushf_free_all(PushFilter *mp)
{
	PushFilter *tmp;

	while (mp)
	{
		tmp = mp->next;
		pushf_free(mp);
		mp = tmp;
	}
}

static int
wrap_process(PushFilter *mp, const uint8 *data, int len)
{
	int			res;

	if (mp->op->push != NULL)
		res = mp->op->push(mp->next, mp->priv, data, len);
	else
		res = pushf_write(mp->next, data, len);
	if (res > 0)
		return PXE_BUG;
	return res;
}

/* consumes all data, returns len on success */
int
pushf_write(PushFilter *mp, const uint8 *data, int len)
{
	int			need,
				res;

	/*
	 * no buffering
	 */
	if (mp->block_size <= 0)
		return wrap_process(mp, data, len);

	/*
	 * try to empty buffer
	 */
	need = mp->block_size - mp->pos;
	if (need > 0)
	{
		if (len < need)
		{
			memcpy(mp->buf + mp->pos, data, len);
			mp->pos += len;
			return 0;
		}
		memcpy(mp->buf + mp->pos, data, need);
		len -= need;
		data += need;
	}

	/*
	 * buffer full, process
	 */
	res = wrap_process(mp, mp->buf, mp->block_size);
	if (res < 0)
		return res;
	mp->pos = 0;

	/*
	 * now process directly from data
	 */
	while (len > 0)
	{
		if (len > mp->block_size)
		{
			res = wrap_process(mp, data, mp->block_size);
			if (res < 0)
				return res;
			data += mp->block_size;
			len -= mp->block_size;
		}
		else
		{
			memcpy(mp->buf, data, len);
			mp->pos += len;
			break;
		}
	}
	return 0;
}

int
pushf_flush(PushFilter *mp)
{
	int			res;

	while (mp)
	{
		if (mp->block_size > 0)
		{
			res = wrap_process(mp, mp->buf, mp->pos);
			if (res < 0)
				return res;
		}

		if (mp->op->flush)
		{
			res = mp->op->flush(mp->next, mp->priv);
			if (res < 0)
				return res;
		}

		mp = mp->next;
	}
	return 0;
}


/*
 * write to MBuf
 */
static int
push_into_mbuf(PushFilter *next, void *arg, const uint8 *data, int len)
{
	int			res = 0;
	MBuf	   *mbuf = arg;

	if (len > 0)
		res = mbuf_append(mbuf, data, len);
	return res < 0 ? res : 0;
}

static const struct PushFilterOps mbuf_filter = {
	NULL, push_into_mbuf, NULL, NULL
};

int
pushf_create_mbuf_writer(PushFilter **res, MBuf *dst)
{
	return pushf_create(res, &mbuf_filter, dst, NULL);
}
