/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:r.c 12.2$ */
/* $ACIS:r.c 12.2$ */
/* $Source: /ibm/acis/usr/sys/afs/RCS/r.c,v $ */

#ifndef lint
static char *rcsid = "$Header:r.c 12.2$";
#endif

/* A quick remote procedure call package using pure source routing.
Mike Kazar
David Nichols
July, 1986 */

#include "../h/types.h"
#include "../h/socket.h"
#include "../h/file.h"
#include "../h/stat.h"
#include "../netinet/in.h"
#include "../h/time.h"
#include "../rpc/types.h"
#include "../rpc/xdr.h"

#define R_INTERNALS	1
#include "../afs/osi.h"
#include "../afs/r.h"
#include "../afs/lock.h"

int r_debug = 0;
extern int afs_running;
char *r_GetRequest_sleep_addr;
int r_SocketListener();

#define PACKETSIZE	1500	/* normal size of a packet */
#define NPACKETS	10	/* number of them to preallocate */
#define SMALLPACKETSIZE	100	/* size of a "small" packet */
#define NRETRIES	10	/* number of retries before giving up */
#define RETRYINTERVAL	2	/* time between retries, in seconds */
#define CHTableSize	128	/* size of the connection hash table */
#define CHTableMask	0x7f	/* mask for quick mod operation */
#define MAXREPLYRETRIES	10	/* times to retry the reply */
#define IDLECONNECTIONTIME 700	/* time before we can toss an idle connection */

/* Globals that the client can change before initializing. */
int r_sendVersion = 3;
int r_nPackets = NPACKETS;	/* number of packets to allocate */
int r_packetSize = PACKETSIZE;	/* the size of them */
int r_nRetries = NRETRIES;	/* number of retries on a call */
int r_retryInterval = RETRYINTERVAL;		/* in seconds */
int r_idleConnectionTime = IDLECONNECTIONTIME;	/* in seconds */
int (*r_FreeConnProc)();	/* proc to call when conn freed */
int (*r_GetKeys)();		/* client's get keys routine / called on SL stack */
int (*r_WhoIsThisReally)();	/* server's decode routine / called on SL stack */

int r_errno = 0;		/* for better error detection */
long r_epoch;			/* for client use in detecting crashes */
static u_short myPortal = 0;	/* identification of this rpc */
static struct r_packet *pbFreeList;	/* the free packet list */
static short pbFreeWaiters = 0;	/* how many waiting for free packets */
static struct r_packet *slExtraSendPacket;	/* these two for emergency use */
static struct r_packet *slExtraRecvPacket;
static struct r_server *allServers = 0;
struct lock afs_rlock;

static struct r_connection  *connHashTable[CHTableSize];
static long nextSid = 0;	/* next sid to give out */

int r_packetFlakiness = 0;		/* # of packet of each 100 to discard on sending */

long r_freeCount;		/* number of free packets */
struct r_stat r_stat;		/* r statistics block */

int r_initialized = FALSE;	/* true after call to r_Init */

#define CHash(a,b,c) (((long)(a)+(long)(b)+(long)(c)) & CHTableMask)

/*********Manually insert queue package here*********/
/* we do this to avoid library name conflicts */
static struct q *qFreeList=0;

static QInit (aq)
    register struct q *aq; {
    aq->prev = aq->next = aq;
    aq->data = aq->data2 = 0;
}

static struct q *QNew () {
    register struct q *tq;
    if (qFreeList) {
	tq = qFreeList;
	qFreeList = tq->next;
    }
    else
	tq = (struct q *) osi_Alloc(sizeof (struct q));
    if (tq != 0)
	QInit(tq);
    return tq;
}

static QFree(aq)
    register struct q *aq; {
    aq->next = qFreeList;
    qFreeList = aq;
}

static QAddBefore (aq, ae)
    register struct q *aq, *ae; {
    ae->next = aq;
    ae->prev = aq->prev;
    aq->prev->next = ae;
    aq->prev = ae;
}

static QRemove (ae)
    register struct q *ae; {
    ae->next->prev = ae->prev;
    ae->prev->next = ae->next;
}

r_cleanup() {
    r_debug = 0;
    r_GetRequest_sleep_addr = 0;
    r_sendVersion = 3;
    r_nPackets = NPACKETS;
    r_packetSize = PACKETSIZE;
    r_nRetries = NRETRIES;
    r_retryInterval = RETRYINTERVAL;
    r_idleConnectionTime = IDLECONNECTIONTIME;
    r_errno = 0;
    r_epoch = 0;
    myPortal = 0;
    pbFreeList = 0;
    pbFreeWaiters = 0;
    slExtraSendPacket = 0;
    slExtraRecvPacket = 0;
    allServers = 0;
    bzero(&afs_rlock, sizeof(afs_rlock));
    bzero(connHashTable, sizeof(connHashTable));
    nextSid = 0;
    r_packetFlakiness = 0;
    r_freeCount = 0;
    bzero(&r_stat, sizeof(r_stat));
    r_initialized = FALSE;
    qFreeList=0;
}

struct r_server *r_NewServer(aport)
    u_short aport; {
    register struct r_server *ts;
    struct osi_socket *newSocket;
    r_Init();	    /* make sure packets have been created */
    ObtainWriteLock(&afs_rlock);
    newSocket = osi_NewSocket(aport);
    if (newSocket == (struct osi_socket *) 0) return (struct r_server *) 0;
    ts = (struct r_server *) kmem_alloc(sizeof(struct r_server));
    bzero(ts, sizeof(struct r_server));
    ts->socket = newSocket;
    ts->next = allServers;
    QInit(&ts->requestWaiters);
    QInit(&ts->lastAckConns);
    allServers = ts;
    ReleaseWriteLock(&afs_rlock);
    return ts;
}

/* Find a connection given the client host, portal and sid. */
struct r_connection *r_FindConn (ahost, aportal, asid)
    register u_long ahost;
    register u_short aportal;
    register long asid;
{
    register int i;
    register struct r_connection *tconn;
    i = CHash(ahost, aportal, asid);
    for(tconn=connHashTable[i]; tconn; tconn=tconn->hnext) {
	if (tconn->isClient && ahost == 0 && aportal == myPortal && asid == tconn->sid)
	    return tconn;
	if (!tconn->isClient && tconn->host == ahost && tconn->portal == aportal & tconn->sid == asid)
	    return tconn;
    }
    /* otherwise return 0 */
    return 0;
}

/* get a connection out of the lastAckConns queue */
static int RemQueue(as, aconn)
    struct r_server *as;
    register struct r_connection *aconn; {
    register struct q *tq;
    aconn->flags &= ~CFINQUEUE;
    for (tq = as->lastAckConns.next; tq != &as->lastAckConns; tq=tq->next) {
	if (tq->data == (long) aconn) {
	    QRemove(tq);
	    QFree(tq);
	    return 0;
	}
    }
    return -1;
}

r_SetAuth(aconn, awho)
    register struct r_connection *aconn;
    register long awho; {
    char *stp, *ctp;
    char ctpSpace[8];	/* the space for the key */
    char stpSpace[100];	/* space for the secret token */
    long len;

    ObtainWriteLock(&afs_rlock);
    aconn->auth.who = awho;
    if (r_GetKeys) {
	/* now, if there is a getkeys procedure, we should make sure we have the encryption key
	    so we can send the first packet encrypted. */
	ctp = ctpSpace;	/* in case r_GetKeys proc doesn't provide own space */
	stp = stpSpace;
	if (r_GetKeys(awho, &stp, &len, &ctp, aconn) == 0) {
	    bcopy((char *) ctp, (char *) &aconn->auth.key, sizeof(struct r_encryptionKey));
	}
    }
    ReleaseWriteLock(&afs_rlock);
}

/* Initialize the package.  Supply a port address in network order, or give a zero and R will pick one for you. */
r_Init ()
{
    register int i;
    register struct r_packet *tp;
    register int ps;
    register char *space;

    if (r_initialized)
	return 0;
    r_initialized = TRUE;
    Lock_Init(&afs_rlock);
    /* Clean out the connection hash table. */
    for (i=0; i < CHTableSize; i++) connHashTable[i] = 0;
    /* Allocate some packets. */
    if (r_packetSize < SMALLPACKETSIZE)
	r_packetSize = SMALLPACKETSIZE;
    ps = sizeof(struct r_packet);
    ps = ((ps - 1) | 3) + 1;		/* round up to next multiple of 4 */
    space = osi_Alloc((2+r_nPackets) * ps);	/* alloc in one blob to avoid fragmentation */
    for(i=0; i < r_nPackets; i++) {
	tp = (struct r_packet *) (space + (2+i)*ps);	/* two emergency packets */
	tp->next = pbFreeList;
	pbFreeList = tp;
    }
    r_freeCount = r_nPackets;
    /* slExtraSendPacket always has associated data buffer.  Emergency Receive
	buffer, on the other hand, has data buffer repeated allocated and deallocated in
	socket listener, so as to enable use of driver-provided mbufs. */
    tp = (struct r_packet *) (space);
    tp->data = osi_AllocSendSpace();
    xdrmem_create(&tp->xdrs, tp->data, r_packetSize, XDR_ENCODE);
    slExtraSendPacket = tp;
    tp = (struct r_packet *) (space+ps);
    tp->data = 0;
    tp->mbuf = 0;
    slExtraRecvPacket = tp;
    r_epoch = osi_Time();
    return 0;
}

/* Make a new client connection. */
struct r_connection *r_NewConn(aserver, ahost, aportal)
    struct r_server *aserver;
    u_long ahost;
    u_short aportal;
{
    register struct r_connection *tconn;
    register int i;
    long localSid;

    ObtainWriteLock(&afs_rlock);
    localSid = (r_epoch << 16) | (++nextSid & 0xFFFF);
    i = CHash(0, 0, localSid);
    tconn = (struct r_connection *) osi_Alloc(sizeof (struct r_connection));
    bzero((char *) tconn, sizeof(struct r_connection));
    tconn->hnext = connHashTable[i];
    connHashTable[i] = tconn;

    tconn->isClient = TRUE;
    tconn->host = ahost;
    tconn->portal = aportal;
    tconn->auth.who = R_NOAUTH;
    tconn->sid = localSid;
    tconn->seqno = osi_Time();	/* Never will be zero */
    tconn->server = aserver;
    tconn->state = CIDLE;
    tconn->retryCount = r_nRetries;
    ReleaseWriteLock(&afs_rlock);
    return tconn;
}

/* Make a new server connection.  Called only by socket listener */
static struct r_connection *r_NewSConn(aserver, cHost, cPortal, cSid)
    u_long cHost;
    u_short cPortal;
    struct r_server *aserver;
    long cSid;
{
    register struct r_connection *tconn;
    register int i;

    i = CHash(cHost, cPortal, cSid);
    tconn = (struct r_connection *) osi_Alloc(sizeof (struct r_connection));
    bzero((char *) tconn, sizeof(struct r_connection));
    tconn->hnext = connHashTable[i];
    connHashTable[i] = tconn;

    tconn->isClient = FALSE;
    tconn->server = aserver;
    tconn->host = cHost;
    tconn->auth.who = R_NOAUTH;
    tconn->portal = cPortal;
    tconn->sid = cSid;
    tconn->state = SIDLE;
    return tconn;
}

/* free a client connection, don't obtain lock */
static FreeConnection (aconn)
    register struct r_connection * aconn; {
    register int i;
    register struct r_connection *tc, **lc;
#ifdef DEBUG
    if (r_debug) printf("F: freeing connection sid %x\n", aconn->sid);
#endif
    /* we only call this on a server conn if it is safe to nuke it, thus only check client conns */
    if (aconn->isClient) {
	if (aconn->state != CIDLE) {
	    aconn->flags |= CFDODELETE;
	    return;
	}
	i = CHash(0, 0, aconn->sid);
    }
    else {
	if (aconn->flags & CFINQUEUE) {
	    RemQueue(aconn->server, aconn);
	}
	i = CHash(aconn->host, aconn->portal, aconn->sid);
    }
    lc = &connHashTable[i];
    for(tc = *lc; tc; tc=tc->hnext) {
	if (tc == aconn) {
	    *lc = aconn->hnext;
	    if (aconn->callp) FreePacket(aconn->callp);
	    if (aconn->reply) FreePacket(aconn->reply);
	    if (r_FreeConnProc && !aconn->isClient) (*r_FreeConnProc)(aconn);
	    osi_Free((char *) aconn, sizeof(struct r_connection));
	    return;
	}
	lc = &tc->hnext;
    }
}

/* free connection under monitor lock */
r_FreeConnection(aconn)
    register struct r_connection *aconn; {
    ObtainWriteLock(&afs_rlock);
    FreeConnection(aconn);
    ReleaseWriteLock(&afs_rlock);
}

/* Allocate a packet with an XDR gismo set up for it. */
static struct r_packet *r_AllocPacket (asize, xdrOp, wait)
    register int asize;		/* packet size */
    register enum xdr_op xdrOp;	/* xdr direction for xdr gismo */
    int wait;			/* if false, don't wait, just return */
{
    register struct r_packet *tp;
    if (asize > r_packetSize)
	return 0;		/* Do something worse? */
    while (1) {
	if (pbFreeList) {
	    r_freeCount--;
	    tp = pbFreeList;
	    pbFreeList = tp->next;
	    tp->header.flags = 0;
	    tp->mbuf = (struct mbuf *) 0;
	    tp->data = (char *) 0;
#ifdef DEBUG
	    if (r_debug) printf("Alloc: %x\n", tp);
#endif
#ifdef DEBUG
	    tp->next = 0;	/* means packet is allocated; checked in r_FreePacket */
#endif
	    return tp;
	}
	if (!wait)
	    return NULL;
	pbFreeWaiters++;
	osi_Sleep((char *) &pbFreeList);
	pbFreeWaiters--;
    }
}

/* free packet without locks */
static FreePacket(apacket)
register struct r_packet *apacket; {
    /* XDR_DESTROY(&apacket->xdrs);		save a few cycles on noop destroy */
    r_freeCount++;

    /* free associated packet data */
    if (apacket->mbuf) {
	osi_FreeRecvBuffer(apacket->mbuf);
	apacket->mbuf = (struct mbuf *) 0;
    }
    else if (apacket->data) {
	osi_FreeSendSpace(apacket->data);
    }
    apacket->data = (char *) 0;

    apacket->next = pbFreeList;

    pbFreeList = apacket;
    if (pbFreeWaiters) osi_Wakeup((char *) &pbFreeList);
}

r_FreePacket(apacket)
register struct r_packet *apacket; {
    /* XDR_DESTROY(&apacket->xdrs);		save a few cycles on noop destroy */
    ObtainWriteLock(&afs_rlock);
    r_freeCount++;

    /* free associated packet data */
    if (apacket->mbuf) {
	osi_FreeRecvBuffer(apacket->mbuf);
	apacket->mbuf = (struct mbuf *) 0;
    }
    else if (apacket->data) {
	osi_FreeSendSpace(apacket->data);
    }
    apacket->data = (char *) 0;

    apacket->next = pbFreeList;
    pbFreeList = apacket;
    if (pbFreeWaiters) osi_Wakeup((char *) &pbFreeList);
    ReleaseWriteLock(&afs_rlock);
}

/* Allocate a packet for receiving. */
static struct r_packet *r_AllocRecvPacket(size)
    int size;
{
    /* Right now, assume this is the socket listener calling: don't wait. */
    return r_AllocPacket(size, XDR_DECODE, FALSE);
}

/* don't change these two without changing r.h's declaration of the structure */
#define HEADER_WORDS	8	/* 4 byte words in xdr header */
#define CLEAR_WORDS	5	/* 4 byte words sent in the clear */
/* Pack or unpack the header of a packet.
    this routine is generally very messy, because in here we put all the smartness for handling
    incompatible versions
*/
bool_t xdr_rheader(xdrs, header)
    XDR *xdrs;
    register struct r_header *header;
{
    register long *buf;
    long temp;
    register long tsize;

    if (xdrs->x_op == XDR_FREE)
	return TRUE;

    /* DANGER!  Make sure the two parts of this if statement match! */

    if (xdrs->x_op == XDR_ENCODE) {
	/* This is the snap-compatible transmitter. */
	/* and don't forget to change the protocol version */
	/* clear words is 5, and header words is 8 */
	if ((buf = XDR_INLINE(xdrs, HEADER_WORDS*BYTES_PER_XDR_UNIT)) == NULL)
	    return FALSE;
	IXDR_PUT_LONG(buf, (RPROTOVERSION << 16)
	    | (HEADER_WORDS << 8) | header->flags);
	IXDR_PUT_LONG(buf, header->who);
	IXDR_PUT_LONG(buf, header->sid);
	IXDR_PUT_LONG(buf, 0x10001);	/* fragment 1 of 1 */
	IXDR_PUT_LONG(buf,  (header->opcode << 24) |
	    (CLEAR_WORDS << 16) |
	    header->length);
	/* encryption starts here */
	IXDR_PUT_LONG(buf, header->epoch);
	IXDR_PUT_LONG(buf, header->seqno);
	IXDR_PUT_LONG(buf, -1L);	/* checksum not computed */
    }
    else {
	xdr_long(xdrs, &temp);
	if ((temp>>16) == 2) {
	    /* old stuff for compatability.  eventuall get rid of this code! */
	    tsize = temp & 0xFFFF;
	    if ((buf = XDR_INLINE(xdrs, (tsize-1)*BYTES_PER_XDR_UNIT)) == NULL)
		return FALSE;
	    header->sid = IXDR_GET_LONG(buf);
	    header->seqno = IXDR_GET_LONG(buf);
	    header->who = IXDR_GET_LONG(buf);
	    temp = IXDR_GET_LONG(buf);
	    header->opcode = temp >> 16;
	    header->flags = temp & 0xFFFF;
	    header->epoch = IXDR_GET_LONG(buf);
	    header->headerSize = HEADER_WORDS;
	    header->clearSize = CLEAR_WORDS;
	}
	else {
	    /* new snap-compatible version */
	    tsize = (temp&0xff00)>>8;
	    header->headerSize = tsize;
	    if ((buf = XDR_INLINE(xdrs, (tsize-1)*BYTES_PER_XDR_UNIT)) == NULL)
		return FALSE;
	    header->flags = temp & 0xff;
	    header->who = IXDR_GET_LONG(buf);
	    header->sid = IXDR_GET_LONG(buf);
	    IXDR_GET_LONG(buf);	/* skip fragmentation junk */
	    tsize = IXDR_GET_LONG(buf);
	    header->opcode = (tsize>>24) & 0xff;
	    header->clearSize = (tsize >> 16) & 0xff;
	    /* throw away message length */
	    header->epoch = IXDR_GET_LONG(buf);
	    header->seqno = IXDR_GET_LONG(buf);
	    /* throw away checksum for now */
	    IXDR_GET_LONG(buf);
	}
    }
    return TRUE;
}

/* Allocate a packet for sending. */
struct r_packet *r_AllocSendPacket(size)
    int size;
{
    register struct r_packet *tp;

    ObtainWriteLock(&afs_rlock);
    tp = r_AllocPacket(size, XDR_ENCODE, TRUE);
    ReleaseWriteLock(&afs_rlock);
    if (tp == NULL)
	return tp;
    /* The header is uninitialized, but reserve the space anyway */
    tp->data = osi_AllocSendSpace();
    xdrmem_create(&tp->xdrs, tp->data, r_packetSize, XDR_ENCODE);
    XDR_SETPOS(&tp->xdrs, HEADER_WORDS*BYTES_PER_XDR_UNIT);
    return tp;
}

/* Send a packet on a connection and wait for a reply. */
struct r_packet *r_SendPacket(aconn, apacket)
register struct r_connection *aconn;
register struct r_packet *apacket; {
    register int code;
    register struct r_packet *tp;
    int retries;
    int retryInterval = r_retryInterval;

    r_stat.outCalls++;
    retries = 0;
    r_errno = 0;
    /* see if we're doing an abbreviated call */
    while (aconn->state != CIDLE) {
	++aconn->nWaiters;
	osi_Sleep((char *) aconn);
	--aconn->nWaiters;
    }
    ObtainWriteLock(&afs_rlock);
    apacket->header.sid = aconn->sid;
    apacket->header.flags = aconn->encryptionLevel << HELShift;
    apacket->header.seqno = aconn->seqno;
    apacket->header.who = aconn->auth.who;
    apacket->header.opcode = OCall;
    apacket->header.epoch = r_epoch;
    apacket->header.length = (int) XDR_GETPOS(&apacket->xdrs);
    XDR_SETPOS(&apacket->xdrs, 0);
    if (!xdr_rheader(&apacket->xdrs, &apacket->header)) {
	FreePacket(apacket);
	r_errno = R_ERROR;
	ReleaseWriteLock(&afs_rlock);
	return NULL;
    }
    /* time to actually send the data */
    r_sendto(apacket, aconn, 1);	/* encrypt it the first time */
    aconn->state = CSENT;
    while (1) {
	/* what can you do but retry anyway if fails? */
	ReleaseWriteLock(&afs_rlock);
	code = osi_NetWait(0, retryInterval*1000, &aconn->handler);
	ObtainWriteLock(&afs_rlock);
	/* now see if we're done */
	if (code == 2) {		/* signalled */
	    if (aconn->flags & CFGOTBUSY) {
		/* Great, just slow down now. */
		if (aconn->flags & CFNOLWP)
		    retryInterval = r_retryInterval << 2;
		else
		    retryInterval = r_retryInterval << 1;
		aconn->flags &= ~(CFGOTBUSY|CFNOLWP);
		retries = 0;
		continue;		/* skip first retransmit */
	    }
	    if (tp=aconn->reply) {
		if (tp->header.seqno == aconn->seqno /* and other tests */) {
		    aconn->reply = 0;
		    aconn->state = CIDLE;
		    aconn->seqno++;
		    FreePacket(apacket);
		    if (aconn->nWaiters != 0)
			osi_Wakeup((char *) aconn);  /* wakeup others waiting for conn */
		    else if (aconn->flags & CFDODELETE) FreeConnection(aconn);
		    if ((tp->header.flags & HError) == 0) {
			ReleaseWriteLock(&afs_rlock);
			return tp;
		    }
		    else {
			r_errno = R_ERROR;
			r_stat.inErrors++;
			FreePacket(tp);
			ReleaseWriteLock(&afs_rlock);
			return 0;
		    }
		}
		/* if we get here, we got a bogus response packet */
		FreePacket(tp);
		aconn->reply = NULL;
	    }
	}
	else {		/* a timeout */
	    if (++retries >= aconn->retryCount) {
		aconn->state = CIDLE;
		aconn->seqno++;
		FreePacket(apacket);
		/* check for race between delivery and IOMGR timeout */
		if (tp = aconn->reply) {
		    FreePacket(tp);
		    aconn->reply = 0;
		}
		if (aconn->nWaiters != 0)
		    osi_Wakeup((char *) aconn);	/* wakeup others waiting for conn */
		else if (aconn->flags & CFDODELETE) FreeConnection(aconn);
		r_stat.callDeads++;
		r_errno = R_TIMEOUT;
		ReleaseWriteLock(&afs_rlock);
		return 0;
	    }
	}
	r_sendto(apacket, aconn, 0);
	r_stat.outCallsX++;
    }
}

/* Servers should call this instead of r_SendResponse it they don't intend to respond to a request. */
r_NoResponse(aconn)
register struct r_connection *aconn; {
    register struct r_packet *tp;
    tp = r_AllocSendPacket(100);
    tp->header.flags |= HError;
    return r_SendResponse(aconn, tp);
}

/* Send a reply packet. */
r_SendResponse (aconn, apacket)
register struct r_connection *aconn;
register struct r_packet *apacket; {
    register struct q *tq;

    ObtainWriteLock(&afs_rlock);
    r_stat.outResps++;
    aconn->reply = apacket;
    aconn->replyRetries = 0;
    aconn->state = SIDLE;
    apacket->header.sid = aconn->sid;
    apacket->header.seqno = aconn->seqno;
    apacket->header.who = R_NOAUTH;	/* later will be set from connection */
    apacket->header.opcode = OResponse;
    apacket->header.length = (int) XDR_GETPOS(&apacket->xdrs);
    XDR_SETPOS(&apacket->xdrs, 0);
    if (!xdr_rheader(&apacket->xdrs, &apacket->header)) {
	FreePacket(apacket);
	aconn->reply = NULL;
	ReleaseWriteLock(&afs_rlock);
	return -1;
    }
    r_sendto(apacket, aconn, 1);
    aconn->timeResponseSent = osi_Time();
#ifdef DEBUG
    if (aconn->flags & CFINQUEUE) panic("r inqueue");
#endif
    tq = QNew();
    tq->data = (long) aconn;	/* queue of connections needing retransmission */
    QAddBefore(&aconn->server->lastAckConns, tq);
    aconn->flags |= CFINQUEUE;
    ReleaseWriteLock(&afs_rlock);
    return 0;
}

/* call after a getrequest, if you decide you can not handle the request right now */
r_SendBusy (aconn)
register struct r_connection *aconn; {
    register struct r_packet *tpacket;

    tpacket = r_AllocSendPacket(100);
    ObtainWriteLock(&afs_rlock);
    aconn->state = SIDLE;
    tpacket->header.sid = aconn->sid;
    tpacket->header.seqno = --aconn->seqno;		/* back out sequence increment */
    tpacket->header.who = R_NOAUTH;	/* later will be set from connection */
    tpacket->header.opcode = OAckCall;
    tpacket->header.length = 4*HEADER_WORDS;
    XDR_SETPOS(&tpacket->xdrs, 0);
    if (!xdr_rheader(&tpacket->xdrs, &tpacket->header)) {
	FreePacket(tpacket);
	ReleaseWriteLock(&afs_rlock);
	return -1;
    }
    r_sendto(tpacket, aconn, 1);
    aconn->timeResponseSent = osi_Time();
    FreePacket(tpacket);
    ReleaseWriteLock(&afs_rlock);
    return 0;
}

/* Get the next incoming request packet. */
r_GetRequest(aserver, aconn, apacket)
    struct r_server *aserver;
    struct r_connection **aconn;
    struct r_packet **apacket; {
    register struct q *tq;

    ObtainWriteLock(&afs_rlock);
    tq = QNew();
    QAddBefore(&aserver->requestWaiters, tq);
    ReleaseWriteLock(&afs_rlock);
    r_GetRequest_sleep_addr = (char *) tq;
    osi_Sleep((char *) tq);
    *aconn = (struct r_connection *) tq->data;
    *apacket = (struct r_packet *) tq->data2;
    ObtainWriteLock(&afs_rlock);
    QFree(tq);
    ReleaseWriteLock(&afs_rlock);
    return 0;
}

/* Decrypt or encrypt (based on encryptp)  a packet (ap), from connection aconn, whose
    length, counting headers, is alen.
    
    Note that trashing the rgen opcode field in the data part of the packet will
    be sufficient to ensure that no fake calls get through.
    
    Eventually we should ensure there's a magic number in the encrypted header to check.
*/
SecretPacket(ap, aconn, encryptp)
    register struct r_packet *ap;
    int encryptp;
    register struct r_connection *aconn; {
    register long len;
    register char *pos;

    /* see if we should not encrypt this packet */
    if (aconn->auth.who == R_NOAUTH || !(ap->header.opcode == OCall ||
      ap->header.opcode == OResponse))
	return;

    len = ap->header.length;

    /* full enchilada, headers only is same, only stop after HEADER_WORDS */
    if (aconn->encryptionLevel == RSECURE) {
	if (encryptp) {
	    pos = ap->data + 4*CLEAR_WORDS;
	    Encrypt (pos, pos, len - 4*CLEAR_WORDS, &aconn->auth.key);
	}
	else {
	    pos = ap->data + 4*ap->header.clearSize;
	    Decrypt (pos, pos, len - 4*ap->header.clearSize, &aconn->auth.key);
	    /* if we're decrypting, we should give the user all the right stuff in the
		header structure.  Note that we might re-xdr only the encrypted
		stuff, but that would require duplicating in another part of the code
		the incredibly hairy (during times of incompatible changes) xdr_rheader
		and it's just probably not worth it, 'least right now */
	    XDR_SETPOS(&ap->xdrs, 0);
	    xdr_rheader(&ap->xdrs, &ap->header);
	}
    }

}

#define LASLEEP		4	/* how often to retransmit last acks */
#define IDLESLEEP	60	/* how often to check for idle connections */
r_SocketListener (as)
    struct r_server *as; {
    struct timeval tv;
    int emergency;		/* TRUE when we're out of packet bufs */
    register long code;
    register struct r_connection *tconn, *nconn;
    register struct q *tq;
    struct q *nq;
    struct r_packet *ap;
    register struct r_packet *tp;
    struct r_packet *tp2;
    char *stp;
    struct r_encryptionKey *ctp;
    char secretTokenSpace[100];
    struct r_encryptionKey keySpace;
    long lastTime, laLastTime, now;
    struct sockaddr_in tfrom;
    long len;
    register int i;
#ifdef DEBUG
    struct timeval delay;
#endif

    lastTime = 0;
    laLastTime = 0;
    ObtainWriteLock(&afs_rlock);
    while (afs_running) {
	/* give up memory associated with slExtraRecvPacket, now that we're done with it */
	tp = slExtraRecvPacket;
	if (tp->mbuf) {
	    osi_FreeRecvBuffer(tp->mbuf);
	    tp->mbuf = (struct mbuf *) 0;
	}
	else if (tp->data) {
	    osi_FreeSendSpace(tp->data);
	}
	tp->data = (char *) 0;

	/* if no last acks to re-transmit, just watch for server conns to knock off */
	tv.tv_sec = (as->lastAckConns.next == &as->lastAckConns? IDLESLEEP : 2);
	ReleaseWriteLock(&afs_rlock);
	code = osi_NetWait(as->socket, tv.tv_sec * 1000, 0);
	ObtainWriteLock(&afs_rlock);

	now = osi_Time();
	/* Check to see what must be retransmitted. */
	if (laLastTime + LASLEEP < now) {
	    laLastTime = now;
	    for (tq = as->lastAckConns.next; tq != &as->lastAckConns; tq=nq) {
		nq = tq->next;
		tconn = (struct r_connection *) tq->data;
		if (tconn->reply != NULL
			&& tconn->timeResponseSent + LASLEEP < now
			&& tconn->replyRetries < MAXREPLYRETRIES) {
		    /* Time to retransmit. */
		    r_sendto(tconn->reply, tconn, 0);
		    tconn->replyRetries++;
		    tconn->timeResponseSent = now;
		    r_stat.outRespsX++;
		}
		else if (tconn->reply == NULL
			|| (tconn->timeResponseSent+LASLEEP < now
			  && tconn->replyRetries >= MAXREPLYRETRIES)) {
		    if (tconn->reply != NULL)
			FreePacket(tconn->reply);
		    tconn->reply = NULL;
		    tconn->flags &= ~CFINQUEUE;
		    r_stat.replyDeads++;
		    QRemove(tq);
		    QFree(tq);
		 }
	    }
	}

	/* Check for idle connections. */
	if (lastTime + IDLESLEEP < now) {
	    lastTime = now;
	    for (i = 0; i < CHTableSize; ++i) {
		tconn = connHashTable[i];
		while (tconn != NULL) {
		    nconn = tconn->hnext;
		    /* connection has been idle for a while */
		    if (tconn->state == SIDLE
			    && tconn->timeResponseSent + r_idleConnectionTime < now) {
			/* Toss connection */
			FreeConnection(tconn);
		    }
		    /* connection has been waiting for token response for a while */
		    else if (tconn->state == SAUTHWAIT
			    && tconn->timeResponseSent + (r_idleConnectionTime>>1) < now) {
			FreeConnection(tconn);
		    }
		    tconn=nconn;
		}
	    }
	}

	if (code != 1)		/* Cancel or timeout */
	    continue;
	/* we have a packet */
	emergency = FALSE;
	tp = r_AllocRecvPacket(r_packetSize);
	if (tp == NULL) {
	    tp = slExtraRecvPacket;
	    if (tp->data) panic("slrecv	data");	/* watch for memory leaks */
	    emergency = TRUE;
#ifdef DEBUG
	    if (r_debug) printf("emergency packet\n");
#endif
	}
	code = osi_NetReceive(as->socket, &tfrom, &tp->mbuf, &tp->data, &len);
	if (code != 0) {
	    if (!emergency)
		FreePacket(tp);
	    continue;		/* done with processing */
	}
	xdrmem_create(&tp->xdrs, tp->data, r_packetSize, XDR_DECODE);
	XDR_SETPOS(&tp->xdrs, 0);
	tp->header.length = len;
	if (!xdr_rheader(&tp->xdrs, &tp->header)) {
	    if (!emergency)
		FreePacket(tp);
	    continue;
	}
#ifdef DEBUG
	if (r_debug) {
	    osi_GetTime(&delay);
	    printf("R: op = %d, seqno = %d, port=%d, sid=%x (%d.%d)\n",
		tp->header.opcode, tp->header.seqno & 0xff, tfrom.sin_port, tp->header.sid, delay.tv_sec, delay.tv_usec);
	}
#endif

	/* Find the connection this packet belongs to if there is one. */
	if (tp->header.opcode == OCall
		|| tp->header.opcode == OAckResponse
		|| tp->header.opcode == OKeys)
	    /* We're the server */
	    tconn = r_FindConn((long) tfrom.sin_addr.s_addr, (long) tfrom.sin_port, tp->header.sid);
	else
	    tconn = r_FindConn(0, myPortal, tp->header.sid);

	/* if we have a connection */
	if (tconn) {
	    /* decrypt packet (except for those for which we must create a new conn */
	    if (tconn->encryptionLevel != RAUTHONLY) SecretPacket(tp, tconn, 0);

	    /* Process acknowledgement of reply */
	    if (tconn->reply != NULL &&
		(tp->header.opcode == OCall || tp->header.opcode == OAckResponse)
		&& tp->header.seqno > tconn->seqno) {
		FreePacket(tconn->reply);
		/* Now see if we can use the packet we just freed. */
		if (emergency && (tp2 = r_AllocRecvPacket(r_packetSize)) != NULL) {
		    slExtraRecvPacket = tp2;
		    emergency = FALSE;
		}
		tconn->reply = NULL;
		if (tconn->flags & CFINQUEUE) RemQueue(as, tconn);
	    }
    

	}
	else {	/* no connection */
	    if (tp->header.opcode != OCall) {
		/* No connection?  Only call packets can do anything useful (create a new conn)
		    so throw away the packet unless we've got a OCall opcode.
		*/
		if (!emergency) FreePacket(tp);
		continue;
	    }
	}

	/* Now do switch statement on opcode. */
	/* The OAckResponse case has already been handled. */
	/* Note that OKeys and OGetKeys packets use the calls' sequence #, and the */
	/* connection state sequence number is not changed during the authentication */
	/* information exchange */

	if (tp->header.opcode == OKeys) {
	    /* we have just received the keys for the connection */
	    if (tconn->state == SAUTHWAIT) {
		if (r_WhoIsThisReally) {
		    i = XDR_GETPOS(&tp->xdrs);
		    code = (*r_WhoIsThisReally) (tconn, tp->data+i, tp->header.length - i);
		    if (code != 0) tconn->auth.who = R_NOAUTH;
		    tconn->flags |= CFKEYVALID;
		}
		/* free auth packet and continue processing old held packet */
		if (!emergency)
		    FreePacket(tp);
		tp = tconn->callp;
		tconn->callp = 0;
		emergency = FALSE;
		tconn->state = SIDLE;
		/* decrypt the held packet; can't do before have keys! */
		if (tconn->encryptionLevel != RAUTHONLY) SecretPacket(tp, tconn, 0);
		/* we now continue processing with the old call packet!! */
	    }
	    else {
		if (!emergency)
		    FreePacket(tp);
		continue;
	    }
	    /* FALL THROUGH TO HANDLE NEWLY-DECRYPTED OCall PACKET!!!! */
	}

	if (tp->header.opcode == OGetKeys) {
	    /* Need to ask user for keys. */
	    stp	= (char	*) secretTokenSpace;	/* GetKey may also supply own ptr */
	    ctp = &keySpace;
	    if (r_GetKeys && ((*r_GetKeys)(tp->header.who, &stp, &len, &ctp, tconn)) == 0) {
		/* we have good tokens here */
		bcopy((char *) ctp, (char *) &tconn->auth.key, sizeof(struct r_encryptionKey));
		/* and ship the encrypted secret key to the other side */
		ap = slExtraSendPacket;
		ap->header.sid = tp->header.sid;
		ap->header.seqno = tp->header.seqno;
		ap->header.who = tp->header.who;
		ap->header.opcode = OKeys;
		ap->header.flags = 0;
		ap->header.length = len + 4*HEADER_WORDS;
		XDR_SETPOS(&ap->xdrs, 0);
		xdr_rheader(&ap->xdrs, &ap->header);
		/* avoid using xdr_opaque to avoid sending length, since opaque adds chars */
		bcopy((char *) stp, (char *) (ap->data + 4*HEADER_WORDS), (int) len);
		r_sendto(ap, tconn, 1);
	    }
	    /* finally free the packet, or drop emergency receive packet */
	    if (!emergency)
		FreePacket(tp);
	}

	else if (tp->header.opcode == OCall) {
	    if (!tconn) {
		/* should check bounds here if desired */
		tconn = r_NewSConn(as, (long) tfrom.sin_addr.s_addr, (long) tfrom.sin_port, tp->header.sid);
		tconn->seqno = 0;
		tconn->epoch = 0;
		tconn->encryptionLevel = (tp->header.flags >> HELShift) & HEncryptionLevel;
	    }
	    if (tp->header.who != R_NOAUTH) {
		/* packet contains authentication information */
		if (!(tconn->flags&CFKEYVALID)) {
		    if (emergency) continue;	/* can't hold emergency packet in callp */
		    SLSendAck(OGetKeys, tp->header.seqno, tconn, tp->header.who);
		    tconn->state = SAUTHWAIT;
		    /* used to keep the oldest packet, but always should keep newest */
		    if (tconn->callp) {
			/* already checked emergency above */
			FreePacket(tconn->callp);
		    }
		    tconn->callp = tp;
		    /* start clock on leaving SAUTHWAIT */
		    tconn->timeResponseSent = osi_Time();
		    continue;
		}
	    }
	    if (tp->header.epoch != tconn->epoch) {
		/* other side crashed and re-started */
		/* tconn->epoch will be set later, when we send the call to the lwp */
		tconn->seqno = tp->header.seqno-1;	/* force re-sync */
	    }

	    /* switch on state */
	    /* packet may be still encrypted, so don't look at sequence number yet */
	    /* if we are in SAUTHWAIT mode, the CFKEYVALID flag is off, and we handled */
	    /* this case above. */
	    if (tconn->state == SBUSY) {
		if (tconn->seqno == tp->header.seqno) {
		    SLSendAck(OAckCall, tp->header.seqno, tconn, tp->header.who);
		    r_stat.outBusies++;
		}
		if (!emergency)
		    FreePacket(tp);
		continue;
	    }

	    /* Here we have an idle connection and a new call. */

	    if (tconn->seqno == tp->header.seqno) {
		/* retransmission, just resend tconn->reply */
		if (tconn->reply) code = r_sendto(tconn->reply, tconn, 0);
		if (!emergency) FreePacket(tp);
		continue;
	    }
	    else if (tp->header.seqno < tconn->seqno) {
		/* Out of sequence, toss it. */
		r_stat.inOldPackets++;
		if (!emergency) FreePacket(tp);
		continue;
	    }

	    if (emergency) {
		/* no free packets, we can still keep other side alive */
		SLSendBusy(OAckCall, tp->header.seqno, tconn, tp->header.who);
	    	continue;		/* Can't sit on emergency packets */
	    }

	    /* make sure that we're still not retransmitting last reply.
		Obscure paths in OCall (e.g. new epochs) can handle a call
		even when early ack code didn't think it could.  So we are
		conservative here and toss the reply packet if it is present. */
	    if (tconn->flags & CFINQUEUE) {
		RemQueue(tconn->server, tconn);
	    }
	    if (tconn->reply) {
		r_FreePacket(tconn->reply);
		tconn->reply = (struct r_packet *) 0;
	    }

	    /* now find a process that can handle a new request */
	    if (as->requestWaiters.next != &as->requestWaiters) {
		r_stat.inCalls++;
		tconn->seqno = tp->header.seqno;	/* only bump when call is accepted */
		tconn->epoch = tp->header.epoch;
		tconn->state = SBUSY;
		QRemove(tq = as->requestWaiters.next);
		tq->data = (long) tconn;
		tq->data2 = (long) tp;
		osi_Wakeup((char *) tq);
		/* both tq and packet are free'd by the guy in r_GetRequest */
	    }
	    else {
		/* send a busy packet to avoid timeouts on busy servers */
		SLSendBusy(OAckCall, tp->header.seqno, tconn, tp->header.who);
		r_stat.outBusies++;
		FreePacket(tp);	/* no one waiting for it */
	    }
	}

	else if (tp->header.opcode == OAckCall) {
	    if (tconn->state == CSENT) {
		/* Find the lwp waiting for this. */
		r_stat.inBusies++;
		if (!osi_NullHandle(&tconn->handler)) {
		    tconn->flags |= CFGOTBUSY;
		    /* test for other busy cause */
		    if (tp->header.flags & HError) tconn->flags |= CFNOLWP;
		    osi_CancelNetWait(&tconn->handler);
		}
	    }
	    if (!emergency)
		FreePacket(tp);
	}

	else if (tp->header.opcode == OResponse) {
	    /* See if this is a retransmit of the reply */
	    if (!(tconn->state == CSENT && tp->header.seqno == tconn->seqno)) {
		SLSendAck(OAckResponse, tp->header.seqno+1, tconn, tp->header.who);
		if (!emergency)
		    FreePacket(tp);
		continue;
	    }
	    /* Else we must have a reply. */
	    if (emergency)
		continue;		/* Can't give it away */
	    /* find the lwp waiting for the response */
	    if (!osi_NullHandle(&tconn->handler)) {
		tconn->reply = tp;
		osi_CancelNetWait(&tconn->handler);
		/* cancel net wait resets the handle, too */
	    }
	    else FreePacket(tp);
	}
	
	else {
	    /* unrecognized or ack response packet */
	    if (!emergency)
		FreePacket(tp);
	}

    }
}

static SLSendBusy(opcode, seqno, aconn, awho)
    int opcode;
    long awho;
    long seqno;
    register struct r_connection *aconn;
{
    register struct r_packet *p;

    p = slExtraSendPacket;
    p->header.sid = aconn->sid;
    p->header.seqno = seqno;
    p->header.who = awho;
    p->header.opcode = opcode;
    p->header.flags = HError;		/* ONLY DIFFERENCE with SLSendAck */
    XDR_SETPOS(&p->xdrs, 0);
    p->header.length = 4*HEADER_WORDS;
    xdr_rheader(&p->xdrs, &p->header);
    r_sendto(p, aconn, 1);
}

static SLSendAck(opcode, seqno, aconn, awho)
    int opcode;
    long awho;
    long seqno;
    register struct r_connection *aconn;
{
    register struct r_packet *p;

    p = slExtraSendPacket;
    p->header.sid = aconn->sid;
    p->header.seqno = seqno;
    p->header.who = awho;
    p->header.opcode = opcode;
    p->header.flags = 0;
    XDR_SETPOS(&p->xdrs, 0);
    p->header.length = 4*HEADER_WORDS;
    xdr_rheader(&p->xdrs, &p->header);
    r_sendto(p, aconn, 1);
}

/* Send a single packet. */
int r_sendto(pb, aconn, aencrypt)
    register struct r_packet *pb;	/* packet to send */
    struct r_connection *aconn;		/* connection to send it on */
    int aencrypt;			/* should we (re)enrypt? */
{
    struct sockaddr_in addr;
#ifdef DEBUG
    struct timeval delay;
#endif

    if (pb == 0) panic("r sendto");

    addr.sin_family = AF_INET;
    addr.sin_port = aconn->portal;
    addr.sin_addr.s_addr = aconn->host;

    if (aconn->encryptionLevel != RAUTHONLY && aencrypt) {
	SecretPacket(pb, aconn, 1);
    }

#ifdef DEBUG
    if (r_debug) {
	osi_GetTime(&delay);
	printf("S: op = %d, seqno = %d, port=%x, sid=%x (%d.%d)...\n",
	    pb->header.opcode, pb->header.seqno & 0xff, addr.sin_port,
	    pb->header.sid, delay.tv_sec, delay.tv_usec);
    }
#endif
    return osi_NetSend(aconn->server->socket, &addr, pb->data, pb->header.length); 
}
