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

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

/*
*	R File Transfer Package
*
*	Other possible optimizations:
*		1.  Use readv to read in data in 4Kbyte chunks if the file is big enough.  Effects
*			kernel disk readahead, as well as cutting down on the number of read
*			system calls.
*
*	Ethernet limits are currently used as the generic system limits.  An ethernet hardware
*		packet can have 1500 data bytes (plus 4 bytes cksum, 12 bytes of addrs, 
*		2 bytes of type).
*		From that 1500, there's a 20 byte IP header and an 8 byte udp header.
*		That leaves 1472 as the maximum udp data size before fragmentation.
*/

#include "../h/types.h"
#include "../h/param.h"
#include "../h/time.h"
#include "../h/kernel.h"
#include "../h/socket.h"
#include "../h/socketvar.h"
#include "../h/protosw.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/file.h"
#include "../h/uio.h"
#include "../h/vfs.h"
#include "../h/vnode.h"
#include "../ufs/inode.h"
#include "../netinet/in.h"
#include "../h/mbuf.h"
#include "../rpc/types.h"
#include "../rpc/xdr.h"

#include "../afs/lock.h"
#include "../afs/osi.h"
#define RFTP_INTERNALS 1
#include "../afs/rftp.h"

#ifndef NULL
#define	NULL	0
#endif

#define NPACKETS 		20	/* number of packets to allocate */
#define SMALLPACKETSIZE		200	/* another packet size */
#define PACKETSIZE		1472	/* packetsize */
#define APACKETSIZE		1472	/* packetsize */
#define READAHEAD		1	/* # of packets to read ahead */
#define RECVTIMEOUT		60	/* timeout on receive */
#define TIMEOUT			2	/* time to wait before rexmit */
#define MAXRETRIES		25	/* number of rexmits before punting */
#define WINDOW			8	/* send-ahead window */
#define DALLY			40	/* how long to wait for OEnd */
#define CHECKPERIOD		30	/* the period to check for dead connections */
#define REXMITDELAY		1	/* must be < the period for retransmissions */

int rftp_debug = 0;
int initd = 0;
int rftp_SocketListener();
extern int afs_running;
struct osi_socket *rftp_ts_socket;

/* here are the globals that users can change before calling init */
long rftp_readAhead = READAHEAD;	/* number of packets to keep ready to go */
long rftp_nPackets = NPACKETS;		/* total packets we can use */
long rftp_packetSize = PACKETSIZE;	/* size of packet we'll send */
long rftp_allocSize = APACKETSIZE;	/* size of packet buffer for max reception */
long rftp_timeout = TIMEOUT;		/* timeout on ack request */
long rftp_window = WINDOW;		/* max outstanding packets */
long rftp_recvTimeout = RECVTIMEOUT;	/* the time the recv side waits for active conns */
long rftp_dally = DALLY;		/* the receiver's dally time */
long rftp_maxRetries = MAXRETRIES;	/* max attempts to get through */

/* these globals are really just for debugging */
struct rftp_stat rftp_stat;		/* the debugging structure */

/* these globals are for status monitoring */
int rftp_freeCount=0;			/* the count of above, for window calcs */

static struct lock afs_rftpLock;
static struct rftp_server *allServers = 0;
static long rftpAckPos;			/* how far in advance to ask for an ack */
static struct rftp_conn	*freeConnList;	/* free connection list */
static struct rftp_packet *rftpFreeList;
static struct rftp_packet *emergencyPacket[3];	/* 0 not used */

rftp_cleanup() {
    initd = 0;
    rftp_ts_socket = 0;
    rftp_readAhead = READAHEAD;
    rftp_nPackets = NPACKETS;
    rftp_packetSize = PACKETSIZE;
    rftp_allocSize = APACKETSIZE;
    rftp_timeout = TIMEOUT;
    rftp_window = WINDOW;
    rftp_recvTimeout = RECVTIMEOUT;
    rftp_dally = DALLY;
    rftp_maxRetries = MAXRETRIES;
    bzero(&rftp_stat, sizeof(rftp_stat));
    rftp_freeCount=0;
    bzero(&afs_rftpLock, sizeof(afs_rftpLock));
    allServers = 0;
    rftpAckPos = 0;
    freeConnList = 0;
    rftpFreeList = 0;
    bzero(emergencyPacket, sizeof(emergencyPacket));
}

static struct rftp_packet *QPop(aq)
    register struct rftp_packet **aq; {
    register struct rftp_packet *tq;
    tq = (*aq);
    (*aq) = tq->next;
    return tq;
}

static QAppend(aq, ap)
    register struct rftp_packet **aq;
    register struct rftp_packet *ap; {
    register struct rftp_packet **lq;
    register struct rftp_packet *tq;
    lq = aq;
    for(tq = *aq; tq; tq = tq->next) {
	lq = &tq->next;
    }
    *lq = ap;
    ap->next = 0;
    return;
}

#define HEADER_WORDS	5
/* Pack or unpack the header of a packet. */
static bool_t xdr_rftpHeader(xdrs, header)
    XDR *xdrs;
    register struct rftp_header *header;
{
    register long *buf;
    long temp;

    if (xdrs->x_op == XDR_FREE)
	return TRUE;
    if ((buf = XDR_INLINE(xdrs, HEADER_WORDS*BYTES_PER_XDR_UNIT)) == NULL)
	return FALSE;

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

    if (xdrs->x_op == XDR_ENCODE) {
	IXDR_PUT_LONG(buf, header->protocolVersion);
	IXDR_PUT_LONG(buf, header->sid);
	IXDR_PUT_LONG(buf, header->seqno);
	IXDR_PUT_LONG(buf, header->who);
	IXDR_PUT_LONG(buf, header->opcode << 16 | header->flags);
    }
    else {
	header->protocolVersion = IXDR_GET_LONG(buf);
	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;
    }
    return TRUE;
}
/* Send a single packet. */
/* Note that unlike in r, we do not assume that the packet length is derived from the xdr pointer, but rather that it has been left in packet.length.  This saves us a lot of grief when we are changing the header during retransmissions */

static int rftp_sendto(ac, pb)
    register struct rftp_conn *ac;
    register struct rftp_packet *pb; {
    struct sockaddr_in addr;
#ifdef DEBUG
    struct timeval delay;
#endif

    rftp_stat.outPackets++;
    addr.sin_family = AF_INET;
    addr.sin_port = ac->portal;
    addr.sin_addr.s_addr = ac->host;

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

static struct rftp_conn *NewConn (aserver, remoteHost, remotePort, remoteSid)
    register struct rftp_server *aserver;
    long remoteHost, remotePort, remoteSid; {
    register struct rftp_conn *tc;
    if (tc=freeConnList) {
	freeConnList = tc->next;
    }
    else tc = (struct rftp_conn *) osi_Alloc(sizeof (struct rftp_conn));
    /* now fill in some reasonable defaults */
    tc->next = aserver->connList;
    aserver->connList = tc;
    tc->host = remoteHost;
    tc->portal = remotePort;
    tc->sid = remoteSid;
    tc->key = 0;
    tc->server = aserver;
    tc->flags = 0;
    tc->fd = (struct osi_file *) 0;
    tc->code = -1000;
    if (aserver->bigSleep) osi_CancelNetWait(&aserver->slHandle);    /* startup sl again */
    return tc;
}

static struct rftp_conn *FindConn (aserver, remoteHost, remotePortal, remoteSid)
    register struct rftp_server *aserver;
    register long remoteHost;
    register long remotePortal;
    register long remoteSid; {
    register struct rftp_conn *tc;
    for(tc = aserver->connList; tc; tc=tc->next) {
	if (tc->sid == remoteSid && remoteHost == tc->host && remotePortal == tc->portal) return tc;
    }
    return 0;
}

/* free when lock already held */
static FreeConnection (ac)
    register struct rftp_conn *ac; {
    register struct rftp_conn *tc, **lc;
    register struct rftp_packet *tq;
    struct rftp_packet *nq;

    lc = &ac->server->connList;
    for(tc = *lc; tc; tc=tc->next) {
	if (tc == ac) {
	    /* found the element to delete */
	    /* free any packets for sender */
	    if (tc->flags & CFSender) {
		for(tq = tc->s.send.ra; tq; tq=nq) {
		    nq = tq->next;
		    rftp_FreePacket(tq);
		}
		for(tq = tc->s.send.rxmit; tq; tq=nq) {
		    nq = tq->next;
		    rftp_FreePacket(tq);
		}
	    }
	    *lc = tc->next;
	    tc->next = freeConnList;
	    freeConnList = tc;
	    ReleaseWriteLock(&afs_rftpLock);
	    return;
	}
	lc = &tc->next;
    }
    /* not found */
    return;
}

/* free under lock */
rftp_FreeConnection (ac)
    register struct rftp_conn *ac; {
    ObtainWriteLock(&afs_rftpLock);
    FreeConnection(ac);
    ReleaseWriteLock(&afs_rftpLock);
}

struct rftp_server *rftp_NewServer(aportal)
    u_short aportal; {
    register struct rftp_server *ts;

    if (!initd) {
	rftp_Init();
	initd = 1;
    }
    bzero(rftp_stat, sizeof(rftp_stat));
    ObtainWriteLock(&afs_rftpLock);
    ts = (struct rftp_server *) osi_Alloc(sizeof(struct rftp_server));
    bzero(ts, sizeof(struct rftp_server));
    osi_InitWaitHandle(&ts->slHandle);
    ts->slCheckTime = 0;
    ts->slRetryTime = 0;
    ts->socket = osi_NewSocket(aportal);
    rftp_ts_socket = ts->socket;
    if (ts->socket == (struct osi_socket *) 0) return (struct rftp_server *) 0;
    ts->connList = (struct rftp_conn *) 0;
    ts->states = 0;
    ts->bigSleep = 0;
    ts->port = aportal;	    /* in network byte order */
    ts->next = allServers;
    allServers = ts;
    ReleaseWriteLock(&afs_rftpLock);
    return ts;
}

rftp_KillServer()
    {
    osi_FreeSocket( rftp_ts_socket );
    }

static rftp_Init () {
    register int i;
    register struct rftp_packet *tp;
    register char *space;
    register int ps;

    rftp_freeCount = 0;
    Lock_Init(&afs_rftpLock);
    rftpAckPos = (rftp_window-1)>>1;
    if (rftpAckPos < 0) rftpAckPos = 0;
    /* Allocate some packets. */
    if (rftp_allocSize < SMALLPACKETSIZE)
	rftp_allocSize = SMALLPACKETSIZE;
    ps = sizeof(struct rftp_packet);
    /* force rouding to 4 byte boundary */
    ps = ((ps - 1) | 3) + 1;
    /* use one allocation to avoid fragmentation problems */
    space = osi_Alloc((2 + rftp_nPackets) * ps);    /* two extra for emergencyPackets */
    tp = emergencyPacket[1] = (struct rftp_packet *) (space);
    tp->mbuf = 0;
    tp->data = 0;
    tp = emergencyPacket[2] = (struct rftp_packet *) (space+ps);
    tp->mbuf = 0;
    tp->data = 0;
    for(i=0; i < rftp_nPackets; i++) {
	tp = (struct rftp_packet *) (space + (2+i)*ps);	/* skip two emergency packets */
	tp->mbuf = 0;
	tp->data = 0;
	tp->next = rftpFreeList;
	rftpFreeList = tp;
	rftp_freeCount++;
    }
    return 0;
}

/* note that there are two emergency packets, one for the socket listener to receive with, and the other for it to send acknowledgements with.  Data is never associated with free packets, only allocated ones, even emergency packets.  */

/* get a packet, priority tells how badly we need the packet */
static struct rftp_packet *rftp_AllocPacket(xop, priority)
    int priority;
    register enum xdr_op xop; { 		/* xdr direction for xdr gismo */
    register struct rftp_packet *tp;
    tp = 0;
    if (rftpFreeList) {
	tp = rftpFreeList;
	rftpFreeList = tp->next;
	rftp_freeCount--;
    }
    if (tp == 0 && priority > 0) tp = emergencyPacket[priority];
    if (tp == 0) return 0;
    if (xop == XDR_ENCODE) {
	tp->data = osi_AllocSendSpace();
	xdrmem_create(&tp->xdr, tp->data, rftp_allocSize, xop);
    }
    tp->header.flags = 0;
    return tp;
}

static rftp_FreePacket (ap)
    register struct rftp_packet *ap; {

    /* put back the data */
    if (ap->mbuf) {
	osi_FreeRecvBuffer(ap->mbuf);
	ap->mbuf = (struct mbuf *) 0;
    }
    else if (ap->data) {
	osi_FreeSendSpace(ap->data);
    }
    ap->data = (char *) 0;

    if (emergencyPacket[1] == ap || emergencyPacket[2] == ap) return;
    ap->next = rftpFreeList;
    rftpFreeList = ap;
    rftp_freeCount++;
}

/* If await > 0, then we wait.   If it is == 0 we return early, if it is < 0 we return early and won't
    start the transfer until SendStart is called.
*/
long rftp_SendFile (as, fd, remoteHost, remotePortal, remoteSid, await)
    struct rftp_server *as;
    struct osi_file *fd;
    int await;
    long remoteHost, remotePortal, remoteSid; {
    register struct rftp_conn *tc;
    register long code;
    struct osi_stat tstat;
    struct timeval;

    ObtainWriteLock(&afs_rftpLock);
    rftp_stat.sendCalls++;
    tc = NewConn(as, remoteHost, remotePortal, remoteSid);
    tc->flags |= CFSender;
    tc->fd = fd;
    tc->s.send.ra = tc->s.send.rxmit = 0;
    tc->s.send.filePosn = 0;
    tc->s.send.raQSize = 0;
    tc->s.send.nextSend = 0;
    tc->s.send.retries = 0;
    tc->s.send.lastAck = -1;
    if (osi_Stat(fd, &tstat) != 0) {
	ReleaseWriteLock(&afs_rftpLock);
	rftp_FreeConnection(tc);
	rftp_stat.deads++;
	rftp_stat.lastCode = RFTP_NOTOPEN;
	return RFTP_NOTOPEN;
    }
    tc->s.send.bytesLeft = tstat.size;
    tc->s.send.blksize = tstat.blksize;	    /* do optimal-sized reads ... */
    if (tstat.size == 0) tc->flags |= CFEmptyFile;    /* force at least one output packet */
    tc->s.send.laTime  = osi_Time();
    tc->code = -1000;
    if (await == 0) {
	/* comment next line for compatability */
	tc->flags |= CFWait;
	ReleaseWriteLock(&afs_rftpLock);
	return (long) tc;
    }
    tc->flags |= CFMayTimeOut;
    CheckSender(tc);
    ReleaseWriteLock(&afs_rftpLock);
    if (tc->code == -1000) osi_Sleep(tc);
    /* don't forget to free the connection */
    code = tc->code;
    rftp_FreeConnection(tc);
    return code;
}		/* procedure */

long rftp_SendWait(aconn)
    register struct rftp_conn *aconn; {
    register long code;

    ObtainWriteLock(&afs_rftpLock);
    aconn->flags |= CFMayTimeOut;
    CheckSender(aconn);
    ReleaseWriteLock(&afs_rftpLock);
    if (aconn->code == -1000) osi_Sleep(aconn);
    code = aconn->code;
    rftp_FreeConnection(aconn);
    return code;
}

static CheckSender (tc)
    register struct rftp_conn *tc; {
    register struct rftp_packet *tq;
    register long code, i;
    long now, window, ackPos, len, goal;

    /* called on the data sender side only */
    if (tc->flags & CFWait) return;
    now = osi_Time();
    {
	/* if we have a zero-length file, must send an empty OData packet anyway */
	if (tc->s.send.bytesLeft == 0 && (tc->flags & CFEmptyFile)) {
	    tq=rftp_AllocPacket(XDR_ENCODE, 0);
	    if (tq) {/* allocate an empty data packet */
		tq->header.seqno = tc->s.send.nextSend++;
		tq->header.sid = tc->sid;
		tq->header.opcode = OData;		
		tq->header.flags |= (HFAckMe | HFEnd);
		tq->length = HEADER_WORDS*BYTES_PER_XDR_UNIT;
		tc->s.send.laTime = now;
		tc->flags &= ~CFEmptyFile;
		xdr_rftpHeader(&tq->xdr, &tq->header);
		rftp_sendto(tc, tq);
		/* now put packet at end of retransmission queue */
		QAppend(&tc->s.send.rxmit, tq);
	    }
	    else return;
	}
	/* check to see if we're all done, except for the OEnd packet */
	else if (tc->s.send.ra == 0 && tc->s.send.rxmit == 0 &&
		 tc->s.send.bytesLeft == 0) {
	    tc->code = 0;
	    osi_Wakeup(tc);
	    return;
	}

	/* check to see if the other side has not responded */
	if (tc->s.send.retries > rftp_maxRetries && (tc->flags & CFMayTimeOut)) {
	    tc->code = RFTP_TIMEOUT;
	    rftp_stat.lastCode = RFTP_TIMEOUT;
	    rftp_stat.deads++;
	    osi_Wakeup(tc);
	    return;
	}

	/* check if there's unacknowledged data */
	/* and either the data is older than rftp_timeout, or that an error occurred */
	if (tc->s.send.rxmit && ((tc->flags & CFNAck) || (tc->s.send.laTime+rftp_timeout < now))) {
	    /* re-transmit from last acknowledged packet */
	    rftp_stat.outRexmits++;
	    tc->flags &= ~CFNAck;
	    tc->s.send.retries++;
	    for (tq=tc->s.send.rxmit; tq; tq=tq->next) {
		/* ask for ack on the last packet of a retransmitted set */
		if (tq->next == 0) tq->header.flags |= HFAckMe;
		else tq->header.flags &= ~HFAckMe;
		XDR_SETPOS(&tq->xdr, 0);
		xdr_rftpHeader(&tq->xdr, &tq->header);
		rftp_sendto(tc, tq);
	    }
	    tc->s.send.laTime = now;
	}
	/* otherwise if no acks outstanding, send more data if we can */
	else {
	    /* first compute the window */
	    window = tc->s.send.lastAck + rftp_window;	/* window will be last to send */
	    if (window > rftp_freeCount + tc->s.send.nextSend)
		window = rftp_freeCount + tc->s.send.nextSend;
	    /* also compute acknowledgement request position */
	    ackPos = window - rftpAckPos;
	    /* now if rftp_window much bigger than number of free packets, rftpAckPos could
	       be bigger than window, so force an ack request somewhere within range */
	    if (ackPos < tc->s.send.nextSend) ackPos = tc->s.send.nextSend;

	    /* first send from read-ahead queue */
	    while ((tq=tc->s.send.ra) && tq->header.seqno <= window) {
		QPop(&tc->s.send.ra);		/* pull off of queue */
		tc->s.send.raQSize--;
		if (tq->header.seqno == ackPos || (tq->header.flags & HFEnd)) {
		    tq->header.flags |= HFAckMe;
		    tc->s.send.laTime = now;
		}
		else tq->header.flags &= ~HFAckMe;
		/* patch the header again */
		XDR_SETPOS(&tq->xdr, 0);
		xdr_rftpHeader(&tq->xdr, &tq->header);
		rftp_sendto(tc, tq);				/* send the packet */
		QAppend(&tc->s.send.rxmit, tq);	/* save for retransmission */
	    }

	    /* next try to get any remaining data from file that we're allowed to send */
	    while(tc->s.send.bytesLeft > 0 && tc->s.send.nextSend <= window) {
		/* first try to get a packet */
		if (!(tq=rftp_AllocPacket(XDR_ENCODE, 0))) break;	/* no packets, nothing to do */
		tq->header.seqno = tc->s.send.nextSend++;
		tq->header.sid = tc->sid;
		tq->header.opcode = OData;
		if (tq->header.seqno == ackPos) {
		    tq->header.flags |= HFAckMe;
		    tc->s.send.laTime = now;
		}
		else tq->header.flags &= ~HFAckMe;
		xdr_rftpHeader(&tq->xdr, &tq->header);
		/* get the data into the packet from the file */
		len = HEADER_WORDS*BYTES_PER_XDR_UNIT;
		goal = ((tc->s.send.filePosn-1) | tc->s.send.blksize) + 1 - tc->s.send.filePosn;
		if (goal >= rftp_packetSize - len  || goal < (rftp_packetSize>>2))
		    goal = rftp_packetSize - len;
		code = osi_Read(tc->fd, tq->data+len, goal);
		if (code < 0) {
		    tc->code = RFTP_IOERR;
		    rftp_stat.deads++;
		    rftp_stat.lastCode = RFTP_IOERR;
		    osi_Wakeup(tc);
		    return;
		}
		tq->length = len+code;
		tc->s.send.filePosn += code;
		tc->s.send.bytesLeft -= code;
		if (tc->s.send.bytesLeft == 0) {
		    /* turn on ack and eof flags if we hit EOF */
		    XDR_SETPOS(&tq->xdr, 0);
		    tq->header.flags |= (HFEnd | HFAckMe);
		    xdr_rftpHeader(&tq->xdr, &tq->header);
		    tc->s.send.laTime = now;
		}

		/* finally send the packet */
		rftp_sendto(tc, tq);
		/* now put packet at end of retransmission queue */
		QAppend(&tc->s.send.rxmit, tq);
	    }		/* file reading while loop */
	}		/* no acks outstanding else clause */
	/* try to do some read ahead */
	for(i=tc->s.send.raQSize; i<rftp_readAhead; i++) {
	    if (tc->s.send.bytesLeft <= 0)  break;
	    if (!(tq=rftp_AllocPacket(XDR_ENCODE, 0))) break;	/* no packets, nothing to do */
	    tq->header.seqno = tc->s.send.nextSend++;
	    tq->header.sid = tc->sid;
	    tq->header.opcode = OData;
	    xdr_rftpHeader(&tq->xdr, &tq->header);
	    /* get the data into the packet from the file */
	    len = HEADER_WORDS*BYTES_PER_XDR_UNIT;
	    goal = ((tc->s.send.filePosn-1) | tc->s.send.blksize) + 1 - tc->s.send.filePosn;
	    if (goal >= rftp_packetSize - len  || goal < (rftp_packetSize>>2))
		goal = rftp_packetSize - len;
	    code = osi_Read(tc->fd, tq->data+len, goal);
	    if (code < 0) {
		tc->code = RFTP_IOERR;
		rftp_stat.deads++;
		rftp_stat.lastCode = RFTP_IOERR;
		osi_Wakeup(tc);
		return;
	    }
	    tq->length = len+code;
	    tc->s.send.bytesLeft -= code;
	    tc->s.send.filePosn += code;
	    if (tc->s.send.bytesLeft == 0) {
		/* turn on eof flag if we hit EOF, ack is handled at actual transmission time */
		XDR_SETPOS(&tq->xdr, 0);
		tq->header.flags |= HFEnd;
		xdr_rftpHeader(&tq->xdr, &tq->header);
	    }
	    /* now put packet at end of the read-ahead queue */
	    QAppend(&tc->s.send.ra, tq);
	    tc->s.send.raQSize++;
	}
    }		/* main body (it used to be a loop) */
}		/* procedure */

static SendAck(ac, aopcode, aflags, aseq)
    struct rftp_conn *ac;
    long aopcode, aflags, aseq; {
    register struct rftp_packet *tp;
    tp = rftp_AllocPacket(XDR_ENCODE, 2);
    tp->header.opcode = aopcode;
    tp->header.seqno = aseq;
    tp->header.sid = ac->sid;
    tp->header.flags = aflags;
    xdr_rftpHeader(&tp->xdr, &tp->header);
    tp->length = HEADER_WORDS*BYTES_PER_XDR_UNIT;
    rftp_sendto(ac, tp);
    rftp_FreePacket(tp);
}

long rftp_GetFile (as, fd, remoteHost, remotePortal, remoteSid, await)
    struct rftp_server *as;
    struct osi_file *fd;
    int await;
    long remoteHost, remotePortal, remoteSid; {
    register struct rftp_conn *tc;
    register long code;

    ObtainWriteLock(&afs_rftpLock);
    rftp_stat.recvCalls++;
    tc = NewConn(as, remoteHost, remotePortal, remoteSid);
    tc->fd = fd;
    tc->s.recv.seqno = 0;
    /* these numbers must be small enough so that when rftp_timeout is added, they're stll > 0 */
    code = osi_Time();
    tc->s.recv.lgTime = code;
    tc->s.recv.endTime = code;
    tc->s.recv.lastActive = code;
    if (!await) {
	ReleaseWriteLock(&afs_rftpLock);
	return (long) tc;
    }
    else {
	tc->flags |= CFSendGo;
	SendAck(tc, OGo, 0, 0);
    }
    tc->flags |= CFMayTimeOut;
    ReleaseWriteLock(&afs_rftpLock);
    osi_Sleep(tc);
    code = tc->code;
    /* connection is freed after DALLY seconds by socket listener. */
    /* This gives sender time to retransmit for lost acknowledgements. */
    return code;
}

long rftp_GetWait(aconn)
    register struct rftp_conn *aconn; {
    register long code;

    ObtainWriteLock(&afs_rftpLock);
    aconn->flags |= CFMayTimeOut;
    ReleaseWriteLock(&afs_rftpLock);
    if (aconn->code == -1000) osi_Sleep(aconn);
    code = aconn->code;
    /* connection freed in socket listener after DALLY seconds */
    return code;
}

rftp_SocketListener (as)
    register struct rftp_server *as; {
    register long code, i;
    register struct rftp_conn *tc;
    struct rftp_conn *nc;
    struct timeval delay;
    struct sockaddr_in tfrom;
    register struct rftp_packet *tp, *tq;
    long packetLength, now, recheckConn;

    ObtainWriteLock(&afs_rftpLock);
    recheckConn = 0;
    tc = 0;
    while(afs_running) {
	/* check if last conn should be nudged */

	/* tc is still valid here from the lookup after the FindConn call, or it is null */
	/* depends upon lack of lwp context swaps that would allow conn to be freed */
	/* it could be a receiver connection, so check before calling CheckSender */
	if (recheckConn && tc && (tc->flags & CFSender)) {
	    /* before calling checksender, make sure we've read all we've been sent */
	    recheckConn = 0;
	    CheckSender(tc);
	    tc = (struct rftp_conn *) 0;
	}

	/* read a packet */
	if (as->connList) delay.tv_sec = 1;
	else {
	    delay.tv_sec = 1000;
	    as->bigSleep = 1;
	}
	delay.tv_usec = 0;
	ReleaseWriteLock(&afs_rftpLock);
	code = osi_NetWait(as->socket, delay.tv_sec * 1000, &as->slHandle);
	ObtainWriteLock(&afs_rftpLock);
	as->bigSleep = 0;
	if (code == 2) continue;	/* signalled */
	now = osi_Time();
	
	/* check for required retranmissions */
	if (as->slRetryTime + REXMITDELAY <= now) {
	    as->slRetryTime = now;
	    for (tc = as->connList; tc; tc=tc->next) {
		if (tc->flags & CFSender) {
		    /* a sender connection */
		    CheckSender(tc);
		}
		else {
		    /* a receiver connection */
		    if ((tc->flags & CFSendGo) && tc->s.recv.lgTime + 2 < now) {
			tc->s.recv.lgTime = now;
			SendAck(tc, OGo, 0, 0);	/* this packet acked by any OData packet */
		    }
		}
	    }
	}

	/* check for timeout */
	if (as->slCheckTime + CHECKPERIOD <= now) {
	    as->slCheckTime = now;
	    for(tc = as->connList; tc; tc=nc) {
		nc = tc->next;		/* avoid lossage when freeing tc */
		if ((tc->flags & CFSender) == 0) {
		    if ((tc->flags & CFEnd) && tc->s.recv.endTime + rftp_dally < now) {
			FreeConnection(tc);
		    }
		    else if (tc->s.recv.lastActive + rftp_recvTimeout < now
		      && (tc->flags & CFMayTimeOut)) {
			tc->code = RFTP_TIMEOUT;
			rftp_stat.deads++;
			rftp_stat.lastCode = RFTP_TIMEOUT;
			osi_Wakeup(tc);
		    }
		}
	    }
	}

	if (code == 0) continue;		/* no packet, just a timeout */

	/* ok, try to read the packet */
	tp = rftp_AllocPacket(XDR_DECODE, 1);
	if (tp->data) panic("rftp decode");
	packetLength = rftp_allocSize;	/* currently ignored */
	code = osi_NetReceive(as->socket, &tfrom, &tp->mbuf, &tp->data, &packetLength);
	if (code != 0) panic("rftp receive");
	xdrmem_create(&tp->xdr, tp->data, rftp_allocSize, XDR_DECODE);
	rftp_stat.inPackets++;
	xdr_rftpHeader(&tp->xdr, &tp->header);
#ifdef DEBUG
	if (rftp_debug) {
	    osi_GetTime(&delay);
	    printf("R: op = %d, seqno = %d, port=%d, sid=%x flags %x (%d.%d).\n",
		tp->header.opcode, tp->header.seqno & 0xff, tfrom.sin_port, tp->header.sid, tp->header.flags, delay.tv_sec, delay.tv_usec);
	}
#endif
	tc = FindConn(as, tfrom.sin_addr.s_addr, tfrom.sin_port, tp->header.sid);		/* find the connection */
	if (!tc) {
	    rftp_FreePacket(tp);
	    continue;
	}
	if (tp->header.opcode == OData) {
	    /* we have some data we're suppose to process */
	    if(tc->flags & CFSendGo) tc->flags &= ~CFSendGo;
	    if (tp->header.seqno == tc->s.recv.seqno) {
		if (tp->header.flags & HFAckMe) SendAck(tc, OAck, 0, tc->s.recv.seqno);
		tc->s.recv.lastActive = now;	/* we made some progress */
		tc->s.recv.seqno++;		/* we've got it now */
		tc->flags &= ~CFNAck;
		i = (long) XDR_GETPOS(&tp->xdr);
		code = osi_Write(tc->fd, tp->data+i, packetLength-i);
		if (code != packetLength-i) {
		    tc->code = RFTP_IOERR;
		    rftp_stat.deads++;
		    rftp_stat.lastCode = RFTP_IOERR;
		    recheckConn = 1;
		    rftp_FreePacket(tp);
		    osi_Wakeup(tc);
		    continue;
		}
		if (tp->header.flags & HFEnd) {
		    tc->flags |= CFEnd;
		    tc->s.recv.endTime = now;
		    tc->code = 0;
		    osi_Wakeup(tc);	    /* wake up the guy in rftp_GetFile*/
		}
	    }
	    else if (tp->header.seqno < tc->s.recv.seqno) {
		/* earlier packet, just resend ack (not NAck, can cause loops) */
		if (tp->header.flags & HFAckMe) SendAck(tc, OAck, 0, tc->s.recv.seqno-1);
	    }
	    else if ((tc->flags & CFNAck) == 0) /*  && (tp->header.seqno > tc->s.recv.seqno) */ {
		/* here we send an ONAck packet; we've missed some data */
		rftp_stat.outNacks++;
		rftp_FreePacket(tp);
		SendAck(tc, ONAck, 0, tc->s.recv.seqno-1);
		tc->flags |= CFNAck;		/* no more until made some progress */
		continue;
	    }
	}
	else if (tp->header.opcode == OAck || tp->header.opcode == ONAck) {
	    /* got an ack, so find sending connection and handle it */
	    /* note we can get a NAck r.e. same packet just acked */
	    if (tp->header.seqno >= tc->s.send.lastAck) {
		/* this packet is a new acknowledgement */

		if (tp->header.seqno > tc->s.send.lastAck)
		    tc->s.send.retries = 0;		/* made progress again */

		/* peel off and free the acked packets */
		while (tq=tc->s.send.rxmit) {
		    if (tq->header.seqno <= tp->header.seqno) {
			tc->s.send.rxmit = tq->next;
			rftp_FreePacket(tq);
		    }
		    else break;
		}
		/* now check if something unexpected happened */
		if (tp->header.opcode == ONAck) tc->flags |= CFNAck;
		/* update the acked packet state */
		tc->s.send.lastAck = tp->header.seqno;
		/* finally wakeup the dude who is waiting */
		recheckConn = 1;
	    }
	}
	else if (tp->header.opcode == OGo) {
	    tc->flags &= ~CFWait;
	    recheckConn = 1;
	}
	/* we used to handle OEnd packets here; they're ignored these days */
	
	/* free the packet */
	rftp_FreePacket(tp);
	
    }	/* the outer while loop */
}	/* the whole program */
