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

#include <stdio.h>
#include "Xos.h"
#include "Xmd.h"
#define _CDECL
#define _NEAR
#include <errno.h>
#include "X.h"
#include "Xproto.h"
#include "dixstruct.h"
#include "os.h"
#include "opaque.h"
#include "osdep.h"
#include "misc.h"
#include "funcs.h"
#include "generic.h"

#define OUTBUFSIZE	512
#define MAXOUTBUF	8192
#define BLOCKWRITE	512

struct _connection {
    struct _connection *next;
    u_char *ptr;		/* pointer to start of request */
    int count;                /* count of bytes in buffer, past ptr */
    int size;			/* size of buffer */
    int lastRequestSize;
};
#define	con_buffer(con)	((u_char *)((con)+1))

static int timesThisConnection = 0;
int OutputBufferSize = OUTBUFSIZE;
static ConnectionPtr FreeInputs = (ConnectionPtr)NULL;
static ConnectionPtr FreeOutputs = (ConnectionPtr)NULL;
static OsCommPtr AvailableInput = (OsCommPtr)NULL;
static Bool CriticalOutputPending;
Bool AnyClientsWriteBlocked;
int minpriority;
extern u_long ClosingClients, InputSockets;
extern int xdmcpSessionSocket;

char isItTimeToYield = 1;

extern Bool mouse_moved;

extern OsCommPtr MyPrivate[NPORTS];

extern void mouseBlockHandler(void);

static ConnectionPtr AllocateBuffer(size_t);

#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);

int max_request_size = MAXBUFSIZE >> 2;

int
ReadRequestFromClient(client)
    ClientPtr client;
{
    OsCommPtr oc = (OsCommPtr)client->osPrivate;
    ConnectionPtr oci = oc->input;
    int fd = oc->fd;
    int needed, result;
    static u_long lastRead;
    u_long t;

    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)
	{
	    oci->ptr = con_buffer(oci);
	    oci->count = 0;
	    FreeInputs = oci->next;
	}
	else if ((oci = AllocateBuffer(OUTBUFSIZE)) == 0)
	{
	    YieldControlDeath();
	    return -1;
	}
	oc->input = oci;
	oci->lastRequestSize = 0;
    } else if (oc->flags & OS_REQUEST) {
	/* If this flag is set, then we have already processed the request
	 * in the buffer.  Remove it from the buffer now.
	 */
	oci->count -= oci->lastRequestSize;
	if (oci->count == 0)
	    oci->ptr = con_buffer(oci);
	else
	    oci->ptr += oci->lastRequestSize;
    }
    oc->flags &= ~OS_REQUEST;

    if (oci->size - (oci->ptr - con_buffer(oci)) < sizeof (xReq)) {
	memcpy(con_buffer(oci), oci->ptr, oci->count);
	oci->ptr = con_buffer(oci);
    }

    /* If xdm is waiting for input, it has priority. */
    if (xdmcpSessionSocket >= 0 && fd != xdmcpSessionSocket &&
      timesThisConnection) {
	CLI;
	t = *(u_long *)0x46c;
	STI;
	if (t - lastRead > 4) {
	    u_long iflags;

	    iflags = 1 << xdmcpSessionSocket;
	    (void)net_select(32, &iflags, 0);
	    if (iflags) {
		lastRead = t;
		YieldControl();
		return 0;
	    }
	}
	lastRead = t;
    }
    /* Get an X request header so we know how much more we need to read. */
    if (oci->count < sizeof (xReq)) {
	result = net_read(fd, (char *)oci->ptr + oci->count,
	    (con_buffer(oci) + oci->size) - (oci->ptr + oci->count));
	if (result <= 0) {
	    if (result == 0 || neterrno != NETERR_WOULD_BLOCK) {
		oc->flags |= OS_FCLOSED;
		InputSockets &= ~(1L << fd);
		YieldControlDeath();
		return -1;
	    }
	} else
	    oci->count += result;
	if (oci->count < sizeof (xReq)) {
	    oc->flags &= ~OS_MORE;
	    YieldControl();
	    return 0;
	}
    }
    needed = request_length((xReq *)oci->ptr, client);

    if (needed == 0)
	needed = sizeof (xReq);
    if (needed > MAXBUFSIZE || needed < sizeof (xReq)) {
/*
printf("needed=%u\n", needed);
*/
	if (needed > MAXBUFSIZE) {
	    SendErrorToClient(client, ((xReq *)oci->ptr)->reqType,
			      MinorOpcodeOfRequest(client),
			      client->errorValue, BadLength);
	}
	/* Naughty client */
	YieldControlDeath();
	return -1;
    }

    /* If we need more memory for this request, allocate it. */
    if (oci->ptr + needed > con_buffer(oci) + oci->size) {
	/* Shift the request to the beginning of the buffer. */
	if (oci->ptr != con_buffer(oci)) {
	    memcpy(con_buffer(oci), oci->ptr, oci->count);
	    oci->ptr = con_buffer(oci);
	}
	if (needed > oci->size) {
	    oci = xrealloc(oci, needed + sizeof (ConnectionRec));
	    if (oci == 0) {	/* ran out of memory */
		xfree(oc->input);
		oc->input = 0;
		oc->flags &= ~OS_MORE;
		YieldControlDeath();
		return -1;
	    }
	    oc->input = oci;
	    oci->size = _msize(oci) - sizeof (ConnectionRec);
	    oci->ptr = con_buffer(oci);
	}
    }

    if (oci->count < needed) {
	result = net_read(fd, (char *)oci->ptr + oci->count,
		(con_buffer(oci) + oci->size) - (oci->ptr + oci->count));
	if (result <= 0) {
	    if (result == 0 || neterrno != NETERR_WOULD_BLOCK) {
		oc->flags |= OS_FCLOSED;
		InputSockets &= ~(1L << fd);
		oc->flags &= ~OS_MORE;
		YieldControlDeath();
		return -1;
	    }
	} else
	    oci->count += result;
	if (oci->count < needed) {
	    oc->flags &= ~OS_MORE;
	    YieldControl();
/* printf("not ready\n"); */
	    return 0;
	}
    }

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

    oc->flags |= OS_REQUEST;
/* printf("complete\n"); */

    oci->lastRequestSize = needed;
    if (oci->count == needed) {
	AvailableInput = oc;
	if (con_buffer(oci) + oci->size != oci->ptr + needed) {
	    oc->flags &= ~OS_MORE;
	    YieldControl();
	}
    } else
	oc->flags |= OS_MORE;

    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->ptr;
    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;
    int size;

    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;
	    oci->ptr = con_buffer(oci);
	    oci->count = 0;
	} else if ((oci = AllocateBuffer(OUTBUFSIZE)) == 0)
	    return FALSE;
	oc->input = oci;
    } else if (oc->flags & OS_REQUEST) {
	/* If this flag is set, then we have already processed the request
	 * in the buffer.  Remove it from the buffer now.
	 */
	size = request_length((xReq *)oci->ptr, client);
	oci->count -= size;
	if (oci->count == 0)
	    oci->ptr = con_buffer(oci);
	else
	    oci->ptr += size;
    }
    oc->flags &= ~OS_REQUEST;
    memcpy(oci->ptr, data, count);
    oci->count += count;
    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->ptr;

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

 /********************
 * 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.
 *
 **********************/

void
FlushClient(oc)
    OsCommPtr oc;
{
    int n;
    ConnectionPtr oco = oc->output;

    if (!oco || !oco->count)
	return;
/*
printf("flush client %d: %d %d.\n", fd, oc->count, extraCount);
*/

    /* If we're blocked, then we've probably already tried to write
     * this data.  Write just a little here to see if the connection
     * is no longer blocked.
     */
    if ((oc->flags & OS_BLOCKED) && oco->count > BLOCKWRITE) {
	n = net_write(oc->fd, (char *)oco->ptr, BLOCKWRITE);
	if (n < 0) {
	    if (neterrno != NETERR_WOULD_BLOCK) {
		oc->flags &= ~OS_BLOCKED;
		if (oc->client > 0) {
net_perror("net_write");
		    MarkClientException(clients[oc->client]);
		}
		return;
	    }
	} else {
	    oco->count -= n;
	    oco->ptr += n;
	}
	if (n == BLOCKWRITE)
	    n = net_write(oc->fd, (char *)oco->ptr, oco->count);
	else
	    n = 0;
    } else {
	n = net_write(oc->fd, (char *)oco->ptr, oco->count);
    }
    if (n < 0) {
	if (neterrno != NETERR_WOULD_BLOCK) {
	    oc->flags &= ~OS_BLOCKED;
	    if (oc->client > 0) {
net_perror("net_write");
		MarkClientException(clients[oc->client]);
	    }
	    return;
	}
    } else {
	oco->ptr += n;
	oco->count -= n;
    }
    if (oco->count) {
	AnyClientsWriteBlocked = TRUE;
	oc->flags |= OS_BLOCKED;
	return;
    }
    /* everything was flushed out */
    oc->flags &= ~OS_BLOCKED;
    oc->output = 0;
    if (oco->size > 3 * OUTBUFSIZE) {
	xfree(oco);
    } else {
	oco->next = FreeOutputs;
	FreeOutputs = oco;
    }
}

 /********************
 * 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;

    for (i = 0; i < NPORTS; i++) {
	oc = MyPrivate[i];
	if (oc)
	    FlushClient(oc);
    }
}

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 padCount;

    if (client == serverClient) {
	return client_write(count, buf);
    }
    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;

    padCount = (count + 3) & ~3;

    if (!oco)
    {
	if ((oco = FreeOutputs) != 0)
	{
	    FreeOutputs = oco->next;
	    oco->ptr = con_buffer(oco);
	    oco->count = 0;
	}
	else if ((oco = AllocateBuffer(max(padCount,OutputBufferSize))) == 0)
	{
	    MarkClientException(client);
	    return -1;
	}
	oc->output = oco;
    } else {
	if (oco->ptr + oco->count + padCount > con_buffer(oco) + oco->size) {
	    if (oco->ptr != con_buffer(oco)) {
		memcpy(con_buffer(oco), oco->ptr, oco->count);
		oco->ptr = con_buffer(oco);
	    }
	}
    }

    if (oco->count + padCount > oco->size)
    {
	oco = (ConnectionPtr)xrealloc(oco,
	    oco->count + padCount + sizeof (ConnectionRec));
	if (oco == NULL)
	{
	    xfree(oc->output);
	    oc->output = 0;
	    MarkClientException(client);
	    return(-1);
	}
	oco->size = _msize(oco) - sizeof (ConnectionRec);
	oco->ptr = con_buffer(oco);
	oc->output = oco;
    }
    memcpy (oco->ptr + oco->count, buf, count);
    oco->count += padCount;
    if (oco->count > OutputBufferSize) {
	FlushClient(oc);
	if (oco->count > OutputBufferSize)
	    FlushClient(oc);
    } else {
	oc->flags |= OS_OUTPUT;
	AnyClientsWriteBlocked = TRUE;
    }

    return count;
}

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

    occ = (ConnectionPtr)xalloc(sizeof(ConnectionRec) + size);
    if (!occ)
	return (ConnectionPtr)NULL;
    occ->ptr = con_buffer(occ);
    occ->size = _msize(occ) - sizeof (ConnectionRec);
    occ->count = 0;
    return occ;
}

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

    if (oc->fd >= 0) {
	if (net_eof(oc->fd) == 0)
	    oc->flags |= OS_CLOSED;
	ClosingClients |= 1L << oc->fd;
    }
    if (AvailableInput == oc)
	AvailableInput = (OsCommPtr)NULL;
    if ((occ = oc->input) != 0)
    {
	oc->input = 0;
	if (FreeInputs)
	{
	    xfree(occ);
	}
	else
	{
	    FreeInputs = occ;
	    occ->next = (ConnectionPtr)NULL;
	    occ->count = 0;
	}
    }
    if ((occ = oc->output) != 0)
    {
	oc->output = 0;
	if (FreeOutputs)
	{
	    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);
    }
    while ((occ = FreeOutputs) != 0)
    {
	FreeOutputs = occ->next;
	xfree(occ);
    }
}

/* stubs to make linking work with NCSA modules */

void
tcp_int()
{
}

void
tcp_noint()
{
}
