/****************************************************************
 *								*
 * Copyright (c) 2001-2015 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"
#include "gtm_socket.h"
#include "gtm_string.h"
#include "cmidef.h"
#include <errno.h>
#include <sys/uio.h>
#include "gtm_inet.h"

#define NUM_IOVECS	2
#define NUM_IOVECS_BOTH	2
#define NUM_IOVECS_DATA	1

error_def(CMI_DCNINPROG);
error_def(CMI_LNKNOTIDLE);
error_def(CMI_OVERRUN);

cmi_status_t cmj_read_start(struct CLB *lnk)
{
	cmi_status_t status = SS_NORMAL;
	int save_errno;
        ssize_t rval;

	if (-1 == lnk->mun)
		return ENOTCONN;
	if (CM_CLB_IDLE != lnk->sta)
		return (CM_CLB_DISCONNECT == lnk->sta) ? CMI_DCNINPROG : CMI_LNKNOTIDLE;
	lnk->sta = CM_CLB_READ;
	CMI_CLB_IOSTATUS(lnk) = SS_NORMAL;
	lnk->ios.xfer_count = 0;
	lnk->ios.len_len = 0;

	while ((-1 == (rval = recv(lnk->mun, (void *)lnk->ios.u.lenbuf, CMI_TCP_PREFIX_LEN, 0))) && EINTR == errno)
		;
	/*
	 * rval == 0 --> eof
	 * rval == 2 --> entire length read (2 == CMI_TCP_PREFIX_LEN)
	 * rval == 1 || (rval < 0 && (errno == EWOULDBLOCK || errno == EAGAIN) --> read stream empty
	 */
	/* weed out the bad things first */
	if (0 > rval && !CMI_IO_WOULDBLOCK(errno))
	{
		save_errno = errno;
		cmj_err(lnk, CMI_REASON_STATUS, save_errno);
		cmj_postevent(lnk);
		return save_errno;
	}
	if (0 == rval)
	{
		cmj_err(lnk, CMI_REASON_STATUS, ECONNRESET);
		cmj_postevent(lnk);
		return ECONNRESET;
	}
	if (CMI_TCP_PREFIX_LEN == rval)
	{
		/* got the entire length */
		lnk->ios.len_len = CMI_TCP_PREFIX_LEN;
		lnk->ios.u.len = ntohs(lnk->ios.u.len);
		if (lnk->ios.u.len > lnk->mbl)
			return CMI_OVERRUN;

		while ((-1 == (rval = recv(lnk->mun, (void *)lnk->mbf, (int)lnk->ios.u.len, 0))) && EINTR == errno)
			;
		if (0 > rval && !CMI_IO_WOULDBLOCK(errno))
		{
			save_errno = errno;
			cmj_err(lnk, CMI_REASON_STATUS, save_errno);
			cmj_postevent(lnk);
			return save_errno;
		}
		if (0 == rval)
		{
			cmj_err(lnk, CMI_REASON_STATUS, ECONNRESET);
			cmj_postevent(lnk);
			return ECONNRESET;
		}
		if (rval == lnk->ios.u.len)
		{
			lnk->ios.xfer_count = rval;
			cmj_fini(lnk);
		}
		else
		{
			/* partial read or no read of packet */
			if (0 < rval)
				lnk->ios.xfer_count = rval;
			status = cmj_clb_set_async(lnk);
		}
	}
	else
	{
		/* partial or no read of length */
		if (0 < rval)
		{
			assert(CMI_TCP_PREFIX_LEN > rval);
			lnk->ios.len_len = (int)rval;
		}
		status = cmj_clb_set_async(lnk);
	}
	return status;
}

void cmj_read_interrupt(struct CLB *lnk, int signo)
{
	ssize_t rval;
	cmi_status_t status = SS_NORMAL;
	char peekchar;
	struct iovec vec[NUM_IOVECS];
	struct msghdr msg;

	if (-1 == lnk->mun)
		return;
	if (CM_CLB_IDLE == lnk->sta)
	{
		/* potential eof, error */
		/* just peek to see if a 0 or error comes back */
		while ((-1 == (rval = recv(lnk->mun, (void *)&peekchar, 1, MSG_PEEK))) && EINTR == errno)
			;
		if (0 == rval)
			cmj_err(lnk, CMI_REASON_STATUS, (cmi_status_t)ECONNRESET);
		else
		{
			if (-1 ==  rval && !CMI_IO_WOULDBLOCK(errno))
				cmj_err(lnk, CMI_REASON_STATUS, (cmi_status_t)errno);
		}
		/* no I/O outstand - return leaving data in socket */
		return;
	}
	/* setup I/O vec based on past history */
	memset(&msg, 0, SIZEOF(msg));
	msg.msg_iov = vec;
	if (CMI_TCP_PREFIX_LEN > lnk->ios.len_len)
	{
		/* length not entirely read */
		msg.msg_iovlen = NUM_IOVECS_BOTH;
		vec[0].iov_len = CMI_TCP_PREFIX_LEN - lnk->ios.len_len;
		vec[0].iov_base = (lnk->ios.u.lenbuf + lnk->ios.len_len);
		vec[1].iov_len = lnk->mbl;
		vec[1].iov_base = (caddr_t)lnk->mbf;
	}
	else
	{
		/* length had been read, need to complete data */
		msg.msg_iovlen = NUM_IOVECS_DATA;
		vec[0].iov_len = lnk->ios.u.len - lnk->ios.xfer_count;
		vec[0].iov_base = (caddr_t)(lnk->mbf + lnk->ios.xfer_count);
	}

	while ((-1 == (rval = recvmsg(lnk->mun, &msg, 0))) && EINTR == errno)
		;
	/* weed out the bad things first */
	if (0 == rval)
	{
		cmj_err(lnk, CMI_REASON_STATUS, (cmi_status_t)ECONNRESET);
		return;
	}
	if (-1 == rval && !CMI_IO_WOULDBLOCK(errno))
	{
		cmj_err(lnk, CMI_REASON_STATUS, (cmi_status_t)errno);
		return;
	}

	/* now we either have a positive rval or a no data error */
	if (0 < rval)
	{
		if (NUM_IOVECS_BOTH == msg.msg_iovlen)
		{
			if ((CMI_TCP_PREFIX_LEN - lnk->ios.len_len) <= rval)
			{
				/* we now have the whole length */
				rval -= (CMI_TCP_PREFIX_LEN - lnk->ios.len_len);
				lnk->ios.len_len = CMI_TCP_PREFIX_LEN;
				lnk->ios.u.len = ntohs(lnk->ios.u.len);
			}
			else
			{
				/* we just read part of the length */
				assert(CMI_TCP_PREFIX_LEN > rval);
				lnk->ios.len_len +=  (int)rval;
				status = cmj_clb_set_async(lnk);
				if (CMI_ERROR(status))
					cmj_err(lnk, CMI_REASON_STATUS, status);
				return;
			}
		}
		if (rval == (lnk->ios.u.len - lnk->ios.xfer_count))
		{
			lnk->ios.xfer_count += rval;
			cmj_fini(lnk);
			return;
		}
		lnk->ios.xfer_count += rval;
	}
	status = cmj_clb_set_async(lnk);
	if (CMI_ERROR(status))
		cmj_err(lnk, CMI_REASON_STATUS, status);
}
