/*****************************************************************
 * i/o functions
 *
 *   WriteToClient, ReadRequestFromClient
 *
 *****************************************************************/

#include <stdio.h>
#include <string.h>
#include "Xos.h"
#include "Xmd.h"
#include <errno.h>
#include "X.h"
#include "Xproto.h"
#include "opaque.h"
#include "dixstruct.h"
#include "os.h"
#include "osdep.h"
#include "misc.h"
#include "funcs.h"
#include "starnet.h"

static int timesThisConnection = 0;
int OutputBufferSize = BUFSIZ;
static ConnectionPtr FreeInputs = (ConnectionPtr)NULL;
static ConnectionPtr FreeOutputs = (ConnectionPtr)NULL;
static OsCommPtr AvailableInput = (OsCommPtr)NULL;
static Bool CriticalOutputPending;
Bool AnyClientsWriteBlocked;
Bool NewOutputPending;
int minpriority;

char isItTimeToYield = 1;

extern Bool mouse_moved;

extern OsCommPtr MyPrivate[NPORTS];

extern void  mouseBlockHandler(void);

static ConnectionPtr AllocateBuffer(size_t);

int lastRequest;

#define request_length(req, cli) ((cli->swapped ? \
	lswaps((req)->length) : (req)->length) << 2)

#define MAX_TIMES_PER         7

/*****************************************************************
 * ReadRequestFromClient
 *    Returns one request in client->requestBuffer.  Return status is:
 *
 *    > 0  if  successful, specifies length in bytes of the request
 *    = 0  if  entire request is not yet available
 *    < 0  if  client should be terminated
 *
 *    The request returned must be contiguous so that it can be
 *    cast in the dispatcher to the correct request type.  Because requests
 *    are variable length, ReadRequestFromClient() must look at the first 4
 *    bytes of a request to determine the length (the request length is
 *    always the 3rd and 4th bytes of the request).  
 *
 *****************************************************************/

#define YieldControl()				\
        { isItTimeToYield = TRUE;		\
	  mouseBlockHandler();			\
	  timesThisConnection = 0; }
#define YieldControlDeath()			\
        { timesThisConnection = 0; }

/* for debug */
extern int (* InitialVector[3]) (ClientPtr);

/* This is really a violation of the X protocol, but since we accept a
 * buffer size of 4096 words anyway it isn't too much of a problem.  This
 * number is 2048 because it's much more efficient.
 */
int max_request_size = 2048;

int
ReadRequestFromClient(client)
    ClientPtr client;
{

    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oci = oc->input;
    int fd = oc->fd;
    int needed, gotnow, result;
    xReq *request;

    if (AvailableInput)
    {
	if (AvailableInput != oc)
	{
	    AvailableInput->input->next = FreeInputs;
	    FreeInputs = AvailableInput->input;
	    AvailableInput->input = (ConnectionPtr)NULL;
	}
	AvailableInput = (OsCommPtr)NULL;
    }
    if (!oci)
    {
	if ((oci = FreeInputs) != 0)
	{
	    FreeInputs = oci->next;
	}
	else if ((oci = AllocateBuffer(OUTBUFSIZE)) == 0)
	{
	    YieldControlDeath();
	    return -1;
	}
	oc->input = oci;
	oci->count = 0;
	oci->next = 0;
    }
    /* reuse the last request */
    if (oc->flags & OS_REUSE) {
	oc->flags &= ~OS_REUSE;
	request = (xReq *)oci->buffer;
	needed = request_length(request, client);
	goto gotreq;
    }
    if (oc->flags & OS_REQUEST) {
	/* This code puts only one request into the buffer at a time.
	 * If we have a full request in buffer, then we can toss it now.
	 */
	oc->flags &= ~OS_REQUEST;
	oci->count = 0;
    }

/*
if (client->requestVector == InitialVector)
printf(".");
*/
    gotnow = oci->count;

    needed = sizeof (xReq) - gotnow;
    if (needed > 0) {
	result = SoRead(fd, (u_char *)oci->buffer + gotnow, needed);
	if (result != needed) {
		if (result < 0) {
		    YieldControlDeath();
		    return -1;
		}
		oci->count += result;
		YieldControl();
		return 0;
	}
	oci->count = sizeof (xReq);
	gotnow = sizeof (xReq);
	request = (xReq *)oci->buffer;
	needed = request_length(request, client);
	if (needed == 0)
	    needed = sizeof (xReq);
	else if (needed > MAXBUFSIZE || needed < sizeof (xReq)) {
/*
printf("needed=%u\n", needed);
*/
	    /* Naughty client */
	    YieldControlDeath();
	    return -1;
	}

	/* see if we have enough memory */
	if (needed > oci->size)
	{
		xReq xBuf;

		xBuf = *request;
		xfree(oci->buffer);
		oci->buffer = (char *)xalloc(needed);
		if (oci->buffer == 0) {	/* ran out of memory */
		    /* Try freeing some other memory */
		    FlushAllOutput();
		    ResetOsBuffers();
		    if ((oci->buffer = (char *)xalloc(needed)) == 0) {
			YieldControlDeath();
			return -1;
		    }
		}
		oci->size = needed;
		request = (xReq *)oci->buffer;
		*request = xBuf;
	}
    } else {
	/* We know we have enough memory because we called the above
	 * code last time. */
	request = (xReq *)oci->buffer;
	needed = request_length(request, client);
    }

/*
printf("request=%d, client=%d len=%d, seq=%d\n", request->reqType, client->index, needed, client->sequence);
*/

    if (needed > gotnow) {
	result = SoRead(fd, (u_char *)oci->buffer + gotnow, needed - gotnow);
	if (result < 0) {
	    YieldControlDeath();
	    return -1;
	}
	oci->count += result;
	if (oci->count < needed) {
	    YieldControl();
/* printf("not ready\n"); */
	    return 0;
	}
    }

gotreq:
    netsleep();
/*
if (client->requestVector == InitialVector)
printf("=");
*/
    oc->flags |= OS_REQUEST;
/* printf("complete\n"); */

    AvailableInput = oc;

    if (++timesThisConnection >= MAX_TIMES_PER ||
	    oc->priority - minpriority > 3)
	YieldControl()
    else if (mouse_moved)
	mouseBlockHandler();
    if (oc->priority < MAXPRIORITY)
	oc->priority++;

    client->requestBuffer = (pointer)oci->buffer;
    return needed;
}

/*****************************************************************
 * InsertFakeRequest
 *    Splice a consed up (possibly partial) request in as the next request.
 * This routine will only be called once per session and it will be called
 * before ReadRequestFromClient is called for this client.  Therefore,
 * we know the buffer will be empty.
 *
 * I am assuming this code will only be called from NextAvalibleClient.
 * If it's called from other places, certain modifications must be made.
 *
 **********************/

Bool
InsertFakeRequest(client, data, count)
    ClientPtr client;
    char *data;
    int count;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oci = oc->input;

    if (AvailableInput)
    {
	if (AvailableInput != oc)
	{
	    AvailableInput->input->next = FreeInputs;
	    FreeInputs = AvailableInput->input;
	    AvailableInput->input = (ConnectionPtr)NULL;
	}
	AvailableInput = (OsCommPtr)NULL;
    }
    if (!oci)
    {
	if ((oci = FreeInputs) != 0)
	    FreeInputs = oci->next;
	else if ((oci = AllocateBuffer(OUTBUFSIZE)) == 0)
	    return FALSE;
	oc->input = oci;
	oci->next = 0;
    }
    /* This is cheating, but it doesn't matter. */
    oci->count = count;
    memcpy(oci->buffer, data, count);
    /* The request inserted is always partial.  Therefore nothing needs
     * to be checked until more data is avaliable.  This bit shouldn't
     * be set anyway, but just in case... */
    oc->flags &= ~OS_REQUEST;
    return(TRUE);
}

/*****************************************************************
 * ResetRequestFromClient
 *    Reset to reexecute the current request, and yield.
 *
 * This code is pretty easy since the current request begins at the
 * start of the buffer.
 *
 **********************/

void
ResetCurrentRequest(client)
    ClientPtr client;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    xReq *request = (xReq *)oc->input->buffer;

    if (AvailableInput == oc)
	AvailableInput = (OsCommPtr)NULL;
    /* Check if we need more (probably authorization data).  From the
     * place where this code is called, oc->input is never NULL. */
    if (request_length(request, client) > oc->input->count)
	oc->flags &= ~OS_REQUEST;
    else
	oc->flags |= OS_REUSE;		/* just re-use this request */
/*
printf("flags = %x\n", oc->flags);
*/
    YieldControl();
}

 /********************
 * FlushClient()
 *    If the client isn't keeping up with us, then we try to continue
 *    buffering the data and set the apropriate bit in ClientsWritable
 *    (which is used by WaitFor in the select).  If the connection yields
 *    a permanent error, or we can't allocate any more space, we then
 *    close the connection.
 *
 **********************/

int
FlushClient(oc, extraBuf, extraCount)
    OsCommPtr oc;
    char *extraBuf;
    int extraCount;
{
    int connection = oc->fd, n, i, notWritten;
    ConnectionPtr oco = oc->output;

    if (!oco)
	return 0;
    i = extraCount;
    extraCount += -extraCount & 3;
/*
printf("flush client %d: %d %d.\n", connection, oc->count, extraCount);
*/
retry:
    if (oco->count)
    {
	n = SoWrite(connection, oco->buffer, oco->count);
	if (n < 0)
		goto bad;
	if (n && (oco->count -= n))
		memcpy(oco->buffer, oco->buffer + n, oco->count);
        /* Notice that padding isn't needed for oc->outbuf since
           it is alreay padded by WriteToClient */
    }
    notWritten = oco->count;
    if (extraCount) {
	if (notWritten)
	    notWritten += extraCount;
	else {
	    n = SoWrite(connection, (u_char *)extraBuf, extraCount);
	    if (n < 0)
		goto bad;
	    extraBuf += n;
	    extraCount -= n;
	    notWritten = extraCount;
	}
    }

    if (notWritten == 0) {
	/* everything was flushed out */
	oc->flags &= ~OS_BLOCKED;
	oco->next = FreeOutputs;
	FreeOutputs = oco;
	oc->output = 0;
	return i; /* return only the amount explicitly requested */
    }

    /* If we've arrived here, then the client is stuffed to the gills
       and not ready to accept more.  Make a note of it and buffer
       the rest. */
    AnyClientsWriteBlocked = TRUE;
    oc->flags |= OS_BLOCKED;

    if (notWritten > oco->size)
    {
	if (notWritten > MAXOUTBUF) {
		netsleep();
		goto retry;
	}
	/* allocate at least enough to contain it plus one
	   OutputBufferSize */
	oco->size = notWritten + OutputBufferSize;
	oco->buffer = (unsigned char *)xrealloc(oco->buffer, oco->size);
	if (oco->buffer == NULL)
	{
/*
printf("Out of memory! notWritten=%d.\n", notWritten);
*/
	    xfree(oco);
	    oc->output = 0;
	    MarkClientException(clients[oc->client]);
	    return(-1);
	}
    }

    if (extraCount)
	memcpy((char *)oco->buffer + oco->count, extraBuf, extraCount);

    oco->count = notWritten; /* this will include the pad */

    return i; /* return only the amount explicitly requested */

bad:
    MarkClientException(clients[oc->client]);
    return(-1);
}

 /********************
 * FlushAllOutput()
 *    Flush all clients with output.  However, if some client still
 *    has input in the queue (more requests), then don't flush.  This
 *    will prevent the output queue from being flushed every time around
 *    the round robin queue.  Now, some say that it SHOULD be flushed
 *    every time around, but...
 *
 * This routine is called from dix and WaitForSomething().
 *
 **********************/

void
FlushAllOutput()
{
    int i;
    OsCommPtr oc;

    CriticalOutputPending = FALSE;
    NewOutputPending = FALSE;

    for (i = 0; i < NPORTS; i++) {
	oc = MyPrivate[i];
	if (!oc || !oc->output || !oc->output->count)
		continue;
	FlushClient(oc, (char *)NULL, 0);
    }
}

void
FlushIfCriticalOutputPending()
{
    if (CriticalOutputPending)
	FlushAllOutput();
}

void
SetCriticalOutputPending()
{
    CriticalOutputPending = TRUE;
}

/*****************
 * WriteToClient
 *    Copies buf into ClientPtr.buf if it fits (with padding), else
 *    flushes ClientPtr.buf and buf to client.  As of this writing,
 *    every use of WriteToClient is cast to void, and the result
 *    is ignored.  Potentially, this could be used by requests
 *    that are sending several chunks of data and want to break
 *    out of a loop on error.  Thus, we will leave the type of
 *    this routine as int.
 *****************/

int
WriteToClient (client, count, buf)
    ClientPtr client;
    int count;
    char *buf;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oco = oc->output;
    int padBytes;

    if (oc->fd < 0) 
    {
	ErrorF( "OH NO, %d translates to %d\n", client->index, oc->fd);
	return(-1);
    }

    if (oc->priority > 5)
	oc->priority -= 5;
    if (oc->priority < minpriority)
	minpriority = oc->priority;

    if (!oco)
    {
	if ((oco = FreeOutputs) != 0)
	{
	    FreeOutputs = oco->next;
	}
	else if ((oco = AllocateBuffer(OUTBUFSIZE)) == 0)
	{
	    MarkClientException(client);
	    return -1;
	}
	oco->count = 0;
	oco->next = 0;
	oc->output = oco;
    }

    padBytes = -count & 3;
    if (oco->count + count + padBytes > oco->size) {
	return FlushClient(oc, buf, count);
    }

    NewOutputPending = TRUE;
    memcpy((char *)oco->buffer + oco->count, buf, count);
    oco->count += count + padBytes;

    return count;
}

static ConnectionPtr
AllocateBuffer(size)
size_t size;
{
    ConnectionPtr oci;

    oci = (ConnectionPtr)xalloc(sizeof(Connection));
    if (!oci)
{
printf("AllocateBuffer: xalloc failed\n");
	return (ConnectionPtr)NULL;
}
    oci->buffer = xalloc(size);
    if (!oci->buffer)
    {
printf("AllocateBuffer: xalloc failed\n");
	xfree(oci);
	return (ConnectionPtr)NULL;
    }
    oci->size = size;
    oci->count = 0;
    return oci;
}

void
CloseDownFileDescriptor(oc)
    OsCommPtr oc;
{
    ConnectionPtr occ;

    SoClose(oc->fd);
    oc->flags |= OS_CLOSING;
    oc->conn_time = GetTimeInMillis();
    if (AvailableInput == oc)
	AvailableInput = (OsCommPtr)NULL;
    if ((occ = oc->input) != 0)
    {
	oc->input = 0;
	if (occ->buffer == 0 || FreeInputs)
	{
	    xfree(occ->buffer);
	    xfree(occ);
	}
	else
	{
	    FreeInputs = occ;
	    occ->next = (ConnectionPtr)NULL;
	}
    }
    if ((occ = oc->output) != 0)
    {
	oc->output = 0;
	if (occ->buffer == 0 || FreeOutputs)
	{
	    xfree(occ->buffer);
	    xfree(occ);
	}
	else
	{
	    FreeOutputs = occ;
	    occ->next = (ConnectionPtr)NULL;
	    occ->count = 0;
	}
    }
}

void
ResetOsBuffers()
{
    ConnectionPtr occ;

    while ((occ = FreeInputs) != 0)
    {
	FreeInputs = occ->next;
	xfree(occ->buffer);
	xfree(occ);
    }
    while ((occ = FreeOutputs) != 0)
    {
	FreeOutputs = occ->next;
	xfree(occ->buffer);
	xfree(occ);
    }
}
