/*
 * tcp_x.c
 *
 * Derived from:
 *
 * Copyright (c) 1982, 1986 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of California at Berkeley. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 *
 * Modified for x-kernel v3.3
 * Modifications Copyright (c) 1996,1991  Arizona Board of Regents
 *
 * $Revision: 1.7 $
 * $Date: 1996/06/19 16:33:23 $
 */

/*
 * DOGN: Importan changes in relation to the version in the simulator area:
 *       - Added the option to set big windows on/off by a command line option
 *         (-rfc1323) and to disable checksum computation (-nochksum)
 *       - Replaced calls to tcpVAll by tcpSemVAll
 *       - Removed code for tcpSemDestroy, unused
 */

#include "xkernel.h"
#include "ip.h"
#include "tcp_internal.h"
#include "tcp_fsm.h"
#include "tcp_seq.h"
#include "tcp_timer.h"
#include "tcp_var.h"
#include "tcpip.h"
#include "btcp.h"

#ifdef XNETSIM
#include "xsim.h"
#include "simul.h"
#endif


/*char *prurequests[] = {
	"ATTACH",	"DETACH",	"BIND",		"LISTEN",
	"CONNECT",	"ACCEPT",	"DISCONNECT",	"SHUTDOWN",
	"RCVD",		"SEND",		"ABORT",	"CONTROL",
	"SENSE",	"RCVOOB",	"SENDOOB",	"SOCKADDR",
	"PEERADDR",	"CONNECT2",	"FASTTIMO",	"SLOWTIMO",
	"PROTORCV",	"PROTOSEND",
};*/

/* Protl            TCP; */
#define         IP (xGetProtlDown(self,0))

int tracebtcpp;
int Btcp_rcv_size = TCPRCVWIN;
int PrintBadChksum = 1;
extern int tcp_do_rfc1323;
extern int tcpcksum;

#ifdef __STDC__

void		btcp_init(Protl);
static int	extractPart( Part *, long *, IPhost **, char * );
static void	tcpSessnInit( Sessn );
static Sessn	tcp_establishopen( Protl, Protl, Protl, IPhost *, IPhost *,
				   int, int );
static void	tcp_dumpstats( struct tcpstat * );
static int	tcpControlProtl( Protl, int, char *, int );
static Sessn	tcpOpen( Protl, Protl, Protl, Part * );
static XkReturn	tcpOpenEnable( Protl, Protl, Protl, Part * );
static XkReturn	tcpOpenDisable( Protl, Protl, Protl, Part * );

void tcpSemVAll( TcpSem	*ts );

#else

void		btcp_init();
static int	extractPart();
static void	tcpSessnInit();
static Sessn	tcp_establishopen();
static void	tcp_dumpstats();
static int	tcpControlProtl();
static Sessn	tcpOpen();
static XkReturn	tcpOpenEnable();
static XkReturn	tcpOpenDisable();

#endif __STDC__

int		BtcpSessionCnt=0;

#ifdef XMEMTRACK

int BtcpTrackId = 0;

extern char *xMallocTrack(unsigned, int);
extern char *xMallocZeroTrack(unsigned, int);
extern void xFreeTrack(char *, unsigned, int);
extern int  TrackGetId(char *);

#define xMalloc(s)     xMallocTrack(s, BtcpTrackId)
#define xMallocZero(s) xMallocZeroTrack(s, BtcpTrackId)

#endif

/* 
 * Check for a valid participant list
 */
#define partCheck(p, name, max, retval)					\
	{								\
	  if ( ! (p) || partLength(p) < 1 || partLength(p) > (max)) { 	\
		xTrace1(tcpp, TR_ERRORS,				\
			"%s -- bad participants",			\
			(name));  					\
		return (retval);					\
	  }								\
	}


/*************************************************************************/

#ifdef XNETSIM
extern int	xsimArgc;
extern char	**xsimArgv;
extern int	TcpCnt, TcpCntMax;
       /* int      TcpPacket=0; */
#endif

void
btcp_init(self)
    Protl            self;
{
  int		k, n=0, d1=-1, d2=0;
  Part		p;
  PSTATE	*pstate;
#ifdef XNETSIM
  int		j, s;
  char		*cp;
  ushort	randState[3];
#endif

/*   TCP = self; */
  xTrace0(tcpp, TR_GROSS_EVENTS, "TCP init");
  xTrace1(tcpp, TR_GROSS_EVENTS, "ENDIAN = %d", ENDIAN);

  xAssert(xIsProtl(self));
#ifdef TCP_TRACE  
  tcpInitTime();
#endif

#ifdef XMEMTRACK
  if (BtcpTrackId == 0) {
    BtcpTrackId = TrackGetId("btcp");
    BtcpReassTrackId = TrackGetId("btcp reass");
    BtcpStateTrackId = TrackGetId("btcp state");
    BtcpEnableTrackId = TrackGetId("btcp enable");
  }
#endif
  pstate = (PSTATE *)xMalloc(sizeof(PSTATE));
  bzero((char *)pstate,sizeof(PSTATE));  
  self->state = (char *)pstate;
  pstate->traceObj = NULL;
  /* pstate->connectionCnt = 1; */
#ifdef FIX_5
  pstate->delack = 0;
#else
  pstate->delack = 2;
#endif
  pstate->reqScale = 1;

#ifdef FIX_7
  pstate->slowstart = 2;
#else
  pstate->slowstart = 1;
#endif

#ifdef XNETSIM
  for (k=0; k<xsimArgc; k++) {
    if (!strncmp(xsimArgv[k], "-tcpTrace=", 10)) {
      sscanf(xsimArgv[k]+10, "%d", &n);
      if (n <= 0)
	continue;
      cp = xsimArgv[k] + 10;
      while (*cp != '\0'  &&  *cp != ':')
        cp++;
      if (*cp == '\0')
        j = 1;
      else {
	cp++;
        sscanf(cp, "%d", &j);
      }
      pstate->traceObj = dtCreateTraceObj(self->name, self->instName,
                                          sizeof(TcpTrace)*n, j);
      /* break; */
    }
    else if (!strncmp(xsimArgv[k], "-tcpTimer=", 10)) {
      sscanf(xsimArgv[k]+10, "%d", &d1);
      d1 = d1 % TCP_FAST_INTERVAL;
      d2 = (d1 * TCP_SLOW_INTERVAL)/TCP_FAST_INTERVAL;
    }
    else if (!strcmp(xsimArgv[k], "-tcpDelack"))
      pstate->delack = 2;
    else if (!strcmp(xsimArgv[k], "-tcpAcknow"))
      pstate->delack = 0;
    else if (!strncmp(xsimArgv[k], "-tcpSlowstart=", 14))
      sscanf(xsimArgv[k]+14, "%d", &pstate->slowstart);
    else if (!strcmp(xsimArgv[k], "-tcpTimerAuto")) {
      d1 = ++TcpCnt*TCP_FAST_INTERVAL/(TcpCntMax+1);
      d2 = TcpCnt*TCP_SLOW_INTERVAL/(TcpCntMax+1);
    }
/*
    else if (!strncmp(xsimArgv[k], "-tcpPacket=", 11))
      sscanf(xsimArgv[k]+11, "%d", &TcpPacket);
*/
  }
  if (d1 < 0) {
    if (xControlProtl(xGetProtlDown(self, 0), SIM_GETSEED, (char *)&s, sizeof(int)) !=
        sizeof(int)) {
      printf("[%s-%s] SIM_GETSEED failed!\n", self->name, self->instName);
      s = (int) time(NULL);
    }
    for (j=k=0; *(self->instName+k) != '\0'; k++)
      j ^= *(self->instName+k) << 6*k;
    s ^= j;
    randState[0] = 0x531a;
    randState[1] = s & 0xffff;
    randState[2] = (s >> 16) & 0xffff;
    nrand48(randState), nrand48(randState), nrand48(randState);
    d1 = nrand48(randState) % TCP_FAST_INTERVAL;
    d2 = nrand48(randState) % TCP_SLOW_INTERVAL;
  }
  printf("[%s-%s] Using TCP timer delay of: %d, %d\n", self->name, 
         self->instName, d1, d2);
#else
  d1 = 0;
#endif

  for (k=1; k<globalArgc; k++) {
    if (!strcmp(globalArgv[k], "-tcpTrace")) {
      sscanf(globalArgv[++k], "%d", &n);
      if (n <= 0)
	continue;
      pstate->traceObj = dtCreateTraceObj(self->name, self->instName,
                                          sizeof(TcpTrace)*n, 1);
      /* break; */
    }
    else if (!strcmp(globalArgv[k], "-tcpDelack"))
      pstate->delack = 2;
    else if (!strcmp(globalArgv[k], "-tcpAcknow"))
      pstate->delack = 0;
    else if (!strcmp(globalArgv[k], "-rfc1323"))
      tcp_do_rfc1323 = 1;
    else if (!strcmp(globalArgv[k], "-nochksum"))
      tcpcksum = 0;
  }
  printf("TCP %s do big windows (rfc1323)\n",(tcp_do_rfc1323)?"WILL":"WON'T");
  printf("TCP %s compute checksums)\n",(tcpcksum)?"WILL":"WON'T");


  self->controlprotl = tcpControlProtl;
  self->open = tcpOpen;
  self->openenable = tcpOpenEnable;
  self->opendisable = tcpOpenDisable;
  self->demux = btcp_input;

  pstate->tcp_input = btcp_input;
  pstate->tcp_output = btcp_output;
  pstate->tcp_timers = btcp_timers;
  pstate->passiveMap = mapCreate(37, sizeof(PassiveId));
  pstate->activeMap = mapCreate(101, sizeof(ActiveId));

  pstate->tcb.inp_next = pstate->tcb.inp_prev = &pstate->tcb;
  pstate->tcp_iss = 1;

  /* LSB: tell trace program to shift window 4 to the left */
  DO_TRACE(pstate, TCP_EVENT_OTHER9, 4, 6, 0);
  /* Turn on IP pseudoheader length-fixup kludge in lower protocols. */
  xControlProtl(IP, IP_PSEUDOHDR, (char *)0, 0); /* LSB 9/8/94 */
  partInit(&p, 1);
  partPush(p, ANY_HOST, 0);
  xOpenEnable(self, self, IP, &p);
  /*xControlProtl(IP, GETMYHOST, (char *)&pstate->myHost, sizeof(pstate->myHost));*/
  evDetach(evSchedule(btcp_fasttimo, (void *)pstate, TCP_FAST_INTERVAL-d1));
  evDetach(evSchedule(btcp_slowtimo, (void *)pstate, TCP_SLOW_INTERVAL-d2));
  tcpPortMapInit(&pstate->portstate);
}

/*************************************************************************/
static struct tcpstate *new_tcpstate()
{
  struct tcpstate *tcpstate;
 
  BtcpSessionCnt++;
  tcpstate = (struct tcpstate *)xMalloc(sizeof(struct tcpstate));
  bzero((char *)tcpstate, sizeof(struct tcpstate));
  tcpSemInit(&tcpstate->waiting, 0);
/*   tcpSemInit(&tcpstate->lock, 1); */
  tcpstate->closed = 0;
  tcpstate->snd = (struct sb *)xMalloc(sizeof(struct sb));
  /* initialize buffer size to TCPRCVWIN to get things started: */
  tcpstate->rcv_space = Btcp_rcv_size;
  tcpstate->rcv_hiwat = Btcp_rcv_size;
  Btcp_rcv_size = TCPRCVWIN;
  return tcpstate;
}

/*************************************************************************/
static Sessn
tcp_establishopen(self, hlp, hlpType, raddr, laddr, rport, lport)
    Protl self, hlp, hlpType;
    IPhost *raddr, *laddr;
    int rport, lport;
{
  Part          p[2];
  Sessn		new;
  struct tcpstate *tcpst;
  struct inpcb *inp;
  ActiveId      ex_id;
  Sessn		lls;
  PSTATE	*pstate;

  xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp_establish: on %X", hlp);
  xAssert(xIsProtl(hlp));

  pstate = (PSTATE *)self->state;
  bzero((char *)&ex_id, sizeof(ex_id));
  ex_id.localport = lport;
  ex_id.remoteport = rport;
  ex_id.remoteaddr = *raddr;
  if (mapResolve(pstate->activeMap, &ex_id, (void **)&new) == XK_SUCCESS) {
    xTrace3(tcpp, TR_GROSS_EVENTS, "tcp_establish: (%d->%s.%d) already open",
	    ex_id.localport, ipHostStr(raddr), ex_id.remoteport);
    return 0;
  }

  partInit(p, laddr ? 2 : 1);
  partPush(p[0], raddr, sizeof(IPhost));
  if ( laddr ) {
      partPush(p[1], laddr, sizeof(IPhost));
  }
  xTrace0(tcpp, TR_EVENTS, "tcpOpen: opening IP");
  if ( (lls = xOpen(self, self, IP, p)) == ERR_SESSN ) {
    xTrace0(tcpp, TR_ERRORS, "tcpOpen: cannot open IP session");
    return 0;
  }

  new = xCreateSessn(tcpSessnInit, hlp, hlpType, self, 1, &lls);
  xTrace3(tcpp, TR_GROSS_EVENTS, "Mapping %d->%s.%d", lport, ipHostStr(raddr),
	  rport);
  new->binding = mapBind(pstate->activeMap, (char *) &ex_id, new);
  if ((int)new->binding == -1) {
    xTrace3(tcpp, TR_GROSS_EVENTS, "tcp_establish: Bind of %d->%s.%d failed", 
      lport, ipHostStr(raddr), rport);
    return 0;
  }

  tcpst = new_tcpstate();
  tcpst->hlpType = hlpType;
#ifdef TCP_TRACE
  tcpst->tid = GET_TID(rport,lport);
#endif
  new->state = (char *)tcpst;
  xTrace0(tcpp, TR_EVENTS, "tcpOpen: attaching...");
  btcp_attach(pstate, new);

  inp = sotoinpcb(new);
  tcpst->inpcb = inp;
  tcpst->tcpcb = (struct tcpcb *)inp->inp_ppcb;
  /*
   * ACB: Changed method of getting local IP address to use xControl
   * Bug fix indicated by LSB 9/26/95
   */
  xControlSessn(lls, GETMYHOST, (char *)&inp->inp_laddr, sizeof(IPhost));
  inp->inp_lport = ex_id.localport;
  *(IPhost *)&inp->inp_raddr = ex_id.remoteaddr;
  inp->inp_rport = ex_id.remoteport;

  return new;
}


static int
extractPart( p, port, host, s )
    Part	*p;
    long	*port;
    IPhost	**host;
    char	*s;
{
    long	*portPtr;
    IPhost	*hostPtr;

    xAssert(port);
    if (!p || partStackTopByteLen(*p) < sizeof(long) ||  
	(portPtr = (long *)partPop(*p)) == 0 ) {
	xTrace1(tcpp, TR_SOFT_ERRORS, "Bad participant in %s -- no port", s);
	return -1;
    }
    *port = *portPtr;
    if ( *port > MAX_PORT ) {
	xTrace2(tcpp, TR_SOFT_ERRORS,
		"Bad participant in %s -- port %d out of range", s, *port);
	return -1;
    }
    if ( host ) {
	if ( partStackTopByteLen(*p) < sizeof(IPhost) || 
	    (hostPtr = (IPhost *)partPop(*p)) == 0 ) {
	    xTrace1(tcpp, TR_SOFT_ERRORS,
		    "Bad participant in %s -- no host", s);
	    return -1;
	}
	*host = hostPtr;
    }
    return 0;
}


/*************************************************************************/
static Sessn
tcpOpen(self, hlp, hlpType, p)
    Protl            self, hlp, hlpType;
    Part           *p;
{
    Sessn	s;
    long	remotePort, localPort = ANY_PORT;
    IPhost	*remoteHost, *localHost = 0;
    PSTATE	*ps=(PSTATE *)self->state;
    
    xTrace0(tcpp, TR_GROSS_EVENTS, "TCP open");
    partCheck(p, "tcpOpen", 2, ERR_SESSN);
    if ( extractPart(p, &remotePort, &remoteHost, "tcpOpen (remote)") ) {
	return ERR_SESSN;
    }
    if ( partLength(p) > 1 ) {
	if ( extractPart(p+1, &localPort, &localHost, "tcpOpen (local)") ) {
	    return ERR_SESSN;
	}
	if ( localHost == (IPhost *)ANY_HOST ) {
	    localHost = 0;
	}
    }
    /* DO_TRACE(ps, TCP_EVENT_OPEN, tcpGetTime(), remotePort, 0); */
    if ( localPort == ANY_PORT ) {
	/* 
	 * We need to find a free local port
	 */
	long	freePort;
	if ( tcpGetFreePort(((PSTATE *)(self->state))->portstate, 
			     &freePort) ) {
	    xError("tcpOpen -- no free ports!");
	    return ERR_SESSN;
	}
	localPort = freePort;
    } else {
	/* 
	 * A specific local port was requested
	 */
	if ( tcpDuplicatePort(((PSTATE *)(self->state))->portstate, 
			       localPort) ) {
	    xTrace1(tcpp, TR_MAJOR_EVENTS,
		    "tcpOpen: requested port %d is already in use",
		    localPort);
	    return ERR_SESSN;
	}
    }
    s = tcp_establishopen(self, hlp, hlpType, remoteHost, localHost,
			  remotePort, localPort);
    DO_TRACE(ps, TCP_EVENT_OPEN, tcpGetTime(), remotePort,
	     GET_TID(remotePort, localPort));
    if (!s) {
	tcpReleasePort(((PSTATE *)(self->state))->portstate, localPort);
	s = ERR_SESSN;
    } else {
	xTrace0(tcpp, TR_EVENTS, "tcpOpen: connecting...");
	btcp_usrreq(s, PRU_CONNECT, 0, 0);
	
	xTrace0(tcpp, TR_EVENTS, "tcpOpen: waiting...");
	tcpSemWait(&(sototcpst(s)->waiting));
	if (sototcpcb(s)->t_state != TCPS_ESTABLISHED) {
	    xTrace3(tcpp, TR_EVENTS,"tcpOpen: open (%d->%s.%d) connect failed",
		    localPort,  ipHostStr(remoteHost), remotePort);
	    DO_TRACE(ps, TCP_EVENT_OPEN0, 0, localPort,
		     GET_TID(remotePort, localPort));
	    btcp_destroy(sototcpcb(s));
	    tcpReleasePort(((PSTATE *)(self->state))->portstate, localPort);
	    s = ERR_SESSN;
	}
	else {
	  DO_TRACE(ps, TCP_EVENT_OPEN1, 0, localPort, 
		   sototcpst(s)->tid);
	}
    }
    xTrace1(tcpp, TR_GROSS_EVENTS, "Return from tcpOpen, session = %lx", 
	    (u_long)s);
    return s;
}

/*************************************************************************/
static XkReturn
tcpOpenEnable(self, hlp, hlpType, p)
    Protl	self, hlp, hlpType;
    Part	*p;
{
    PassiveId	key;
    long	protNum;
    PSTATE	*pstate;
    Enable	*e;
    
    partCheck(p, "tcpOpenEnable", 1, XK_FAILURE);
    if ( extractPart(p, &protNum, 0, "tcpOpenEnable") ) {
	return XK_FAILURE;
    }
    key = protNum;
    pstate = (PSTATE *)self->state;
    xTrace2(tcpp, TR_MAJOR_EVENTS, "tcpOpenEnable mapping %d->%lx", key,
	    (u_long)hlp);
    if (mapResolve(pstate->passiveMap, &key, (void **)&e) == XK_SUCCESS) {
	if ( e->hlp == hlp && e->hlpType == hlpType ) {
	    e->rcnt++;
	    return XK_SUCCESS;
	}
	return XK_FAILURE;
    }
    tcpDuplicatePort(pstate->portstate, key);
    e = (Enable *)xMalloc(sizeof(Enable));
    e->hlp = hlp;
    e->hlpType = hlpType;
    e->info = (void *)Btcp_rcv_size;
    Btcp_rcv_size = TCPRCVWIN;
    e->rcnt = 1;
    e->binding = mapBind(pstate->passiveMap, (char *) &key, e);
    if ( e->binding == ERR_BIND ) {
#ifdef XMEMTRACK
	xFreeTrack((char *)e, sizeof(Enable), BtcpEnableTrackId);
#else
	xFree((char *)e);
#endif
	return XK_FAILURE;
    }
    return XK_SUCCESS;
}

/*************************************************************************/
static XkReturn
tcpOpenDisable(self, hlp, hlpType, p)
    Protl	self, hlp, hlpType;
    Part        *p;
{
    PassiveId	key;
    long	protNum;
    Enable	*e;
    PSTATE	*pstate;

    pstate = (PSTATE *)self->state;
    partCheck(p, "tcpOpenDisable", 1, XK_FAILURE);
    if ( extractPart(p, &protNum, 0, "tcpOpenEnable") ) {
	return XK_FAILURE;
    }
    key = protNum;
    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp_disable removing %d", key);
    if (mapResolve(pstate->passiveMap, &key, (void **)&e) == XK_FAILURE ||
	e->hlp != hlp || e->hlpType != hlpType) {
	return XK_FAILURE;
    }
    if (--(e->rcnt) == 0) {
	mapRemoveBinding(pstate->passiveMap, e->binding);
#ifdef XMEMTRACK
	xFreeTrack((char *)e, sizeof(Enable), BtcpEnableTrackId);
#else
	xFree((char *)e);
#endif
	tcpReleasePort(pstate->portstate, key);
    }
    return XK_SUCCESS;
}

/*************************************************************************/
/*
 * User issued close, and wish to trail through shutdown states:
 * if never received SYN, just forget it.  If got a SYN from peer,
 * but haven't sent FIN, then go to FIN_WAIT_1 state to send peer a FIN.
 * If already got a FIN from peer, then almost done; go to LAST_ACK
 * state.  In all other cases, have already sent FIN to peer (e.g.
 * after PRU_SHUTDOWN), and just have to play tedious game waiting
 * for peer to send FIN or not respond to keep-alives, etc.
 * We can let the user exit from the close as soon as the FIN is acked.
 */
static XkReturn
tcpClose(so)
    Sessn	so;
{
    register struct tcpcb *tp;
    register struct tcpstate *tcpst;
    
    xTrace1(tcpp, TR_FUNCTIONAL_TRACE, "tcpClose(so=%lx)", (u_long)so);

    tcpst = sototcpst(so);
    tcpst->closed |= 1;
    
    tp = sototcpcb(so);
    tp = btcp_usrclosed(tp);
    if (tp) btcp_output(tp);

    xTrace2(tcpp, TR_MAJOR_EVENTS, "tcpClose: tp=%lx, tcpst->closed=%x",
	    (u_long)tp, tcpst->closed);
    
    if (tcpst->closed == 1) {
	/*
	 * This was a user close before the network close
	 */
	xTrace0(tcpp, TR_EVENTS, "tcpClose: waiting...");
	tcpSemWait(&tcpst->waiting);
	xTrace0(tcpp, TR_EVENTS, "tcpClose: done");
    }
    return XK_SUCCESS;
}


/*************************************************************************/
static XkHandle
tcpPush(so, msg)
     Sessn so;
     Msg *msg;
{
    int error;
    int space;
    Msg pushMsg;
    PSTATE *ps=(PSTATE *) so->myprotl->state;
    register struct tcpstate *tcpst = sototcpst(so);
    register struct tcpcb *tp = sototcpcb(so);

    xTrace2(tcpp, TR_MAJOR_EVENTS, "tcpPush: session %lx, msg %d bytes",
	    (u_long)so, msgLength(msg));
    DO_TRACE(ps, TCP_EVENT_PUSH, tcpGetTime(), 
	     msgLength(msg), tcpst->tid);
    
    if (so->rcnt < 1) {
	xTrace1(tcpp, TR_GROSS_EVENTS, "tcpPush: session ref count = %d",
		so->rcnt);
	return XMSG_ERR_HANDLE;
    }
    if (tcpst->closed) {
	xTrace1(tcpp, TR_SOFT_ERRORS, "tcpPush: session already closed %d",
		tcpst->closed);
	return XMSG_ERR_HANDLE;
    }

#undef CHUNKSIZE (sbhiwat(tcpst->snd) / 2)
#define CHUNKSIZE (tp->t_maxseg)

    if ((tcpst->flags & TCPST_NBIO) &&
	(sbspace(tcpst->snd) < MIN(msgLength(msg), CHUNKSIZE)))
    {
	return XMSG_ERR_WOULDBLOCK;
    } /* if */

    msgConstructEmpty(&pushMsg);
    while ( msgLength(msg) != 0 ) {
	xTrace1(tcpp, TR_FUNCTIONAL_TRACE,
		"tcpPush: msgLength = <%d>", msgLength(msg));
	msgBreak(msg, &pushMsg, CHUNKSIZE);
	xTrace2(tcpp, TR_FUNCTIONAL_TRACE,
		"tcpPush: after break pushMsg = <%d> msg = <%d>",
		msgLength(&pushMsg), msgLength(msg));
	while ((space = sbspace(tcpst->snd)) < msgLength(&pushMsg)) {
	    xTrace3(tcpp, TR_GROSS_EVENTS,
		    "tcpPush: waiting for space (%d available) (hiwat=%d,len=%d)",space, sbhiwat(tcpst->snd),sblength(tcpst->snd));
	    xTrace1(tcpp, TR_FUNCTIONAL_TRACE,
		    "tcpPush: msgLength == %d before blocking",
		    msgLength(msg));
	    DO_TRACE(ps, TCP_EVENT_PUSHW, msgLength(msg), 
		     space, tcpst->tid);
	    tcpSemWait(&tcpst->waiting);
	    DO_TRACE(ps, TCP_EVENT_PUSHA, tcpGetTime(), 0, 
		     tcpst->tid);

	    if (tcpst->closed) {
		xTrace1(tcpp, TR_FUNCTIONAL_TRACE,
			"tcpPush: session already closed %d", tcpst->closed);
		DO_TRACE(ps, TCP_EVENT_PUSHEC, 0, 0, 
			 tcpst->tid);
		msgDestroy(&pushMsg);
		return XMSG_ERR_HANDLE;
	    }
	}
	xTrace1(tcpp, TR_FUNCTIONAL_TRACE,
		"tcppush about to append to sb.  orig msgLength == %d",
		msgLength(msg));
	sbappend(tcpst->snd, &pushMsg);

	/* LSB 8/9/94 replaced vtcp_output call with if.else */
	if (tcpst->flags & TCPST_PUSH_ALWAYS) {
	  tp->t_force = 1;
	  error = btcp_output(sototcpcb(so));
	  tp->t_force = 0;
	} else {
	  error = btcp_output(sototcpcb(so));
	}
	if (error) {
	    xTrace1(tcpp, TR_ERRORS, "tcpPush failed with code %d", error);
	    DO_TRACE(ps, TCP_EVENT_PUSHEO, error, 0, 
		     tcpst->tid);
	    msgDestroy(&pushMsg);
	    return XMSG_ERR_HANDLE;
	}
    }
    msgDestroy(&pushMsg);
    return XMSG_NULL_HANDLE;
}

/*************************************************************************/
static int
tcpControlSessn(s, opcode, buf, len)
    Sessn           s;
    char           *buf;
    int             opcode;
    int             len;
{
  struct tcpstate *ts;
  struct tcpcb   *tp;
  int size;
  PSTATE  *ps;

  ts = (struct tcpstate *) s->state;
  ps = (PSTATE *)s->myprotl->state;
  tp = ts->tcpcb;
  switch (opcode) {

   case GETMYPROTO:
     checkLen(len, sizeof(long));
     *(long *)buf = tp->t_template->ti_sport;
     return sizeof(long);

   case GETPEERPROTO:
     checkLen(len, sizeof(long));
     *(long *)buf = tp->t_template->ti_dport;
     return sizeof(long);

   case TCP_PUSH:
     DO_TRACE(ps, TCP_EVENT_CTL_PUSH, tcpGetTime(), 0, 
	      ts->tid);     
     tp->t_force = 1;
     btcp_output(tp);
     tp->t_force = 0;
     return 0;

   case TCP_GETSTATEINFO:
     *(int *) buf = tp->t_state;
     return (sizeof(int));

   case GETOPTPACKET:
   case GETMAXPACKET:
     if (tp->t_flags & TF_RCVD_TSTMP)
       *(int *)buf = tp->t_maxseg - 12; 
     else 
       *(int *)buf = tp->t_maxseg; 
     return sizeof(int); 
/*      if ( xControlSessn(xGetSessnDown(s, 0), opcode, buf, len) < sizeof(int) ) { */
/*          return -1; */
/*      }       */
/*      *(int *)buf -= sizeof(struct tcphdr); */
/*      return sizeof(int); */
     
   case SETNONBLOCKINGIO:
     xControlSessn(xGetSessnDown(s, 0), opcode, buf, len);
     if (*(int*)buf) {
	 ts->flags |= TCPST_NBIO;
     } else {
	 ts->flags &= ~TCPST_NBIO;
     } /* if */
     return 0;

/* LSB 9/8/94 added SETKEEPALIVE, TCP_SETPUSHALWAYS, TCP_SETRCVACKALWAYS */

   case TCP_SETKEEPALIVE:
     xControlSessn(xGetSessnDown(s, 0), opcode, buf, len);
     if (*(int*)buf) {
	 ts->flags |= TCPST_KEEP_ALIVE;
     } else {
	 ts->flags &= ~TCPST_KEEP_ALIVE;
     } /* if */
     return 0;

   case TCP_SETPUSHALWAYS:
     checkLen(len, sizeof(int));
     if (*(int*)buf) {
	 ts->flags |= TCPST_PUSH_ALWAYS;
     } else {
	 ts->flags &= ~TCPST_PUSH_ALWAYS;
     } /* if */
     return 0;

   case TCP_SETRCVACKALWAYS:
     checkLen(len, sizeof(int));
     if (*(int*)buf) {
	 ts->flags |= TCPST_RCV_ACK_ALWAYS;
     } else {
	 ts->flags &= ~TCPST_RCV_ACK_ALWAYS;
     } /* if */
     return 0;

   case TCP_SETTHRESH:
     checkLen(len, sizeof(int));
     tp->snd_ssthresh = *(int *)buf;
     return 0;
     
   case TCP_SETRCVBUFSPACE:
     checkLen(len, sizeof(int));
     size = *(int *)buf;
     ts->rcv_space = size;
     /* after receiving message possibly send window update: */
     btcp_output(tp);
     return 0;

   case TCP_GETSNDBUFSPACE:
     checkLen(len, sizeof(int));
     *(int *)buf = sbspace(ts->snd);
     return sizeof(int);

   case TCP_SETRCVBUFSIZE:
     checkLen(len, sizeof(int));
     size = *(int *)buf;
     ts->rcv_hiwat = size;
     return 0;

   case TCP_SETSNDBUFSIZE:
     checkLen(len, sizeof(int));
     sbhiwat(ts->snd) = *(int *)buf;
     xTrace1(tcpp,TR_MAJOR_EVENTS,"**** TCP control: bufsize set to %d (hiwat)",sbhiwat(ts->snd));
     return 0;

   case TCP_SETOOBINLINE:
     checkLen(len, sizeof(int));
     if (*(int*)buf) {
	 ts->flags |= TCPST_OOBINLINE;
     } else {
	 ts->flags &= ~TCPST_OOBINLINE;
     } /* if */
     return 0;

   case TCP_GETOOBDATA:
     {     
	 char peek;

	 checkLen(len, sizeof(char));

	 peek = *(char*)buf;
	 if ((tp->t_oobflags & TCPOOB_HADDATA) ||
	     !(tp->t_oobflags & TCPOOB_HAVEDATA))
	 {
	     return 0;
	 } /* if */
	 *(char*)buf = tp->t_iobc;
	 if (!peek) {
	     tp->t_oobflags ^= TCPOOB_HAVEDATA | TCPOOB_HADDATA;
	 } /* if */
	 return 1;
     }

   case TCP_OOBPUSH:
     {
	 Msg *m;

	 checkLen(len, sizeof(Msg *));

	 m = (Msg *)buf;
	 sbappend(ts->snd, m);
	 tp->snd_up = tp->snd_una + sblength(ts->snd);
	 tp->t_force = 1;
	 btcp_output(tp);
	 tp->t_force = 0;
	 return 0;
     }

   case TCP_DUMPTRACE:
     tcpSaveTrace(ps->traceObj, (char *)buf, len, &ps->tcpstat);
     ps->traceObj = NULL;
     return 0;
     
   case TCP_GETINFO:
     {
       int *ip=(int *)buf;

       if (len >= sizeof(int)*5) {
	 *ip++ = (4 << 8) | ( VERS );	/* fix- protl (btcp) and version */
	 *ip++ = ps->tcpstat.tcps_rexmttimeo;
	 *ip++ = ps->tcpstat.tcps_sndbyte;
	 *ip++ = ps->tcpstat.tcps_sndrexmitbyte;
	 *ip   = ps->tcpstat.tcps_rcvdupbyte;
	 if (len < sizeof(int)*8)
	   return sizeof(int)*5;
	 else if (len >= sizeof(int)*8) {
	   *(++ip) = ps->tcpstat.tcps_sndpack;
	   *(++ip) = ps->tcpstat.tcps_sndrexmitpack;
	   *(++ip) = ps->tcpstat.tcps_rcvduppack;
           if (len < sizeof(int)*9)
             return sizeof(int)*8;
           else if (len < sizeof(int) * 10) {
             *(++ip) = ps->tcpstat.tcps_rcvackbyte;
             return sizeof(int)*9;
           }
	   else {
	     *(++ip) = ps->tcpstat.tcps_rcvackbyte;
	     *(++ip) = tp->v_other14;
	     return sizeof(int)*10;
	   }
         }
       }
     }
     return 0;
     
   default:
     return xControlSessn(xGetSessnDown(s, 0), opcode, buf, len);
  }
}

static Part *
tcpGetParticipants(s)
Sessn s;
{
    Part            *p;
    long	    *lPtr;
    struct tcpstate *ts = (struct tcpstate *)s->state;

    p = xGetParticipants(xGetSessnDown(s, 0));
    if (!p)
	return NULL;
    lPtr = (long *)xMalloc(sizeof(long));
    *lPtr = ts->tcpcb->t_template->ti_dport;
    partPush(p[0], (VOID *)lPtr, sizeof(long));
    if (partLength(p) >= 2) {
	lPtr = (long *)xMalloc(sizeof(long));
	*lPtr = ts->tcpcb->t_template->ti_sport;
	partPush(p[1], (VOID *)lPtr, sizeof(long));
    }
    return p;
}

/*************************************************************************/
static int
tcpControlProtl(self, opcode, buf, len)
    Protl	    self;
    int             opcode, len;
    char           *buf;
{
    long   port;
    PSTATE *ps;

    ps = (PSTATE *)self->state;
    switch (opcode) {

      case TCP_DUMPSTATEINFO:
	tcp_dumpstats(&ps->tcpstat);
	return 0;

      /* ACB: Changed PROT -> PORT */
      case TCP_GETFREEPORTNUM:
	checkLen(len, sizeof(long));
	port = *(long *)buf;
	if (tcpGetFreePort(((PSTATE *)(self->state))->portstate, &port)) {
	    return -1;
	} /* if */
	*(long *)buf = port;
	return 0;

      /* ACB: Changed PROT -> PORT */
      case TCP_RELEASEPORTNUM:
	checkLen(len, sizeof(long));
	port = *(long *)buf;
	tcpReleasePort(((PSTATE *)(self->state))->portstate, port);
	return 0;

      case TCP_SETRCVBUFSIZE:
        checkLen(len, sizeof(int));
        Btcp_rcv_size = *(int *)buf;
        return 0;

      case TCP_DUMPTRACE:
        tcpSaveTrace(ps->traceObj, (char *)buf, len, &ps->tcpstat);
        ps->traceObj = NULL;
	return 0;

      case TCP_GETINFO:
	{
	  int *ip=(int *)buf;
	  
	  if (len >= sizeof(int)*5) {
	    *ip++ = (4 << 8) | ( VERS );  /* fix- protl (btcp) and version */
	    *ip++ = ps->tcpstat.tcps_rexmttimeo;
	    *ip++ = ps->tcpstat.tcps_sndbyte;
	    *ip++ = ps->tcpstat.tcps_sndrexmitbyte;
	    *ip   = ps->tcpstat.tcps_rcvdupbyte;
	    if (len < sizeof(int)*8)
	      return sizeof(int)*5;
	    else if (len >= sizeof(int)*8) {
	      *(++ip) = ps->tcpstat.tcps_sndpack;
	      *(++ip) = ps->tcpstat.tcps_sndrexmitpack;
	      *(++ip) = ps->tcpstat.tcps_rcvduppack;
              if (len < sizeof(int)*9)
                return sizeof(int)*8;
              else {
                *(++ip) = ps->tcpstat.tcps_rcvackbyte;
                return sizeof(int)*9;
              }
	    }
	  }
	}
	return 0;

      default:
	return xControlProtl(xGetProtlDown(self,0), opcode, buf, len);
    }
}

/*************************************************************************/
static void
tcpSessnInit(s)
    Sessn s;
{
    xAssert(xIsSessn(s));
    s->push = tcpPush;
    s->close = tcpClose;
    s->controlsessn = tcpControlSessn;
    s->getparticipants = tcpGetParticipants;
    s->demux = btcp_input;
    s->pop = btcpPop;
}

/*************************************************************************/
/* 
 * sonewconn is called by the input routine to establish a passive open
 */
Sessn
btcp_sonewconn(self, so, hlpType, src, dst, sport, dport)
    Protl self, so, hlpType;
    IPhost *src, *dst;
    int sport, dport;
{
  Sessn new;

  xAssert(xIsProtl(so));
  xTrace1(tcpp, TR_MAJOR_EVENTS, "sonewconn on %X", so);
  new = tcp_establishopen(self, so, hlpType, src, dst, sport, dport);
  if ( new ) {
      tcpDuplicatePort(((PSTATE *)(self->state))->portstate, dport);
  }
  return new;
}

/*************************************************************************/
static void
tcp_dumpstats(statp)
  struct tcpstat *statp;
{
  printf("tcps_badsum %d\n", statp->tcps_badsum);
  printf("tcps_badoff %d\n", statp->tcps_badoff);
  printf("tcps_hdrops %d\n", statp->tcps_hdrops);
  printf("tcps_badsegs %d\n", statp->tcps_badsegs);
  printf("tcps_unack %d\n", statp->tcps_unack);
  printf("connections initiated %d\n", statp->tcps_connattempt);
  printf("connections accepted %d\n", statp->tcps_accepts);
  printf("connections established %d\n", statp->tcps_connects);
  printf("connections dropped %d\n", statp->tcps_drops);
  printf("embryonic connections dropped %d\n", statp->tcps_conndrops);
  printf("conn. closed (includes drops) %d\n", statp->tcps_closed);
  printf("segs where we tried to get rtt %d\n", statp->tcps_segstimed);
  printf("times we succeeded %d\n", statp->tcps_rttupdated);
  printf("delayed acks sent %d\n", statp->tcps_delack);
  printf("conn. dropped in rxmt timeout %d\n", statp->tcps_timeoutdrop);
  printf("retransmit timeouts %d\n", statp->tcps_rexmttimeo);
  printf("persist timeouts %d\n", statp->tcps_persisttimeo);
  printf("keepalive timeouts %d\n", statp->tcps_keeptimeo);
  printf("keepalive probes sent %d\n", statp->tcps_keepprobe);
  printf("connections dropped in keepalive %d\n", statp->tcps_keepdrops);
  printf("total packets sent %d\n", statp->tcps_sndtotal);
  printf("data packets sent %d\n", statp->tcps_sndpack);
  printf("data bytes sent %d\n", statp->tcps_sndbyte);
  printf("data packets retransmitted %d\n", statp->tcps_sndrexmitpack);
  printf("data bytes retransmitted %d\n", statp->tcps_sndrexmitbyte);
  printf("ack-only packets sent %d\n", statp->tcps_sndacks);
  printf("window probes sent %d\n", statp->tcps_sndprobe);
  printf("packets sent with URG only %d\n", statp->tcps_sndurg);
  printf("window update-only packets sent %d\n", statp->tcps_sndwinup);
  printf("control (SYN|FIN|RST) packets sent %d\n", statp->tcps_sndctrl);
  printf("total packets received %d\n", statp->tcps_rcvtotal);
  printf("packets received in sequence %d\n", statp->tcps_rcvpack);
  printf("bytes received in sequence %d\n", statp->tcps_rcvbyte);
  printf("packets received with ccksum errs %d\n", statp->tcps_rcvbadsum);
  printf("packets received with bad offset %d\n", statp->tcps_rcvbadoff);
  printf("packets received too short %d\n", statp->tcps_rcvshort);
  printf("duplicate-only packets received %d\n", statp->tcps_rcvduppack);
  printf("duplicate-only bytes received %d\n", statp->tcps_rcvdupbyte);
  printf("packets with some duplicate data %d\n", statp->tcps_rcvpartduppack);
  printf("dup. bytes in part-dup. packets %d\n", statp->tcps_rcvpartdupbyte);
  printf("out-of-order packets received %d\n", statp->tcps_rcvoopack);
  printf("out-of-order bytes received %d\n", statp->tcps_rcvoobyte);
  printf("packets with data after window %d\n", statp->tcps_rcvpackafterwin);
  printf("bytes rcvd after window %d\n", statp->tcps_rcvbyteafterwin);
  printf("packets rcvd after \"close\" %d\n", statp->tcps_rcvafterclose);
  printf("rcvd window probe packets %d\n", statp->tcps_rcvwinprobe);
  printf("rcvd duplicate acks %d\n", statp->tcps_rcvdupack);
  printf("rcvd acks for unsent data %d\n", statp->tcps_rcvacktoomuch);
  printf("rcvd ack packets %d\n", statp->tcps_rcvackpack);
  printf("bytes acked by rcvd acks %d\n", statp->tcps_rcvackbyte);
  printf("rcvd window update packets %d\n", statp->tcps_rcvwinupd);


  statp->tcps_badsum = 0;
  statp->tcps_badoff = 0;
  statp->tcps_hdrops = 0;
  statp->tcps_badsegs = 0;
  statp->tcps_unack = 0;
  statp->tcps_connattempt = 0;
  statp->tcps_accepts = 0;
  statp->tcps_connects = 0;
  statp->tcps_drops = 0;
  statp->tcps_conndrops = 0;
  statp->tcps_closed = 0;
  statp->tcps_segstimed = 0;
  statp->tcps_rttupdated = 0;
  statp->tcps_delack = 0;
  statp->tcps_timeoutdrop = 0;
  statp->tcps_rexmttimeo = 0;
  statp->tcps_persisttimeo = 0;
  statp->tcps_keeptimeo = 0;
  statp->tcps_keepprobe = 0;
  statp->tcps_keepdrops = 0;
  statp->tcps_sndtotal = 0;
  statp->tcps_sndpack = 0;
  statp->tcps_sndbyte = 0;
  statp->tcps_sndrexmitpack = 0;
  statp->tcps_sndrexmitbyte = 0;
  statp->tcps_sndacks = 0;
  statp->tcps_sndprobe = 0;
  statp->tcps_sndurg = 0;
  statp->tcps_sndwinup = 0;
  statp->tcps_sndctrl = 0;
  statp->tcps_rcvtotal = 0;
  statp->tcps_rcvpack = 0;
  statp->tcps_rcvbyte = 0;
  statp->tcps_rcvbadsum = 0;
  statp->tcps_rcvbadoff = 0;
  statp->tcps_rcvshort = 0;
  statp->tcps_rcvduppack = 0;
  statp->tcps_rcvdupbyte = 0;
  statp->tcps_rcvpartduppack = 0;
  statp->tcps_rcvpartdupbyte = 0;
  statp->tcps_rcvoopack = 0;
  statp->tcps_rcvoobyte = 0;
  statp->tcps_rcvpackafterwin = 0;
  statp->tcps_rcvbyteafterwin = 0;
  statp->tcps_rcvafterclose = 0;
  statp->tcps_rcvwinprobe = 0;
  statp->tcps_rcvdupack = 0;
  statp->tcps_rcvacktoomuch = 0;
  statp->tcps_rcvackpack = 0;
  statp->tcps_rcvackbyte = 0;
  statp->tcps_rcvwinupd = 0;
  statp->tcps_pawsdrop = 0;
  statp->tcps_predack = 0;
  statp->tcps_preddat = 0;
  statp->tcps_pcbcachemiss = 0;
  statp->tcps_dupftimeo = 0;
  statp->tcps_worryftimeo = 0;
}

void btcp_delete_tcpstate(tcpstate)
struct tcpstate *tcpstate;
{
  /* LSB: check later! */
  tcpSemDestroy(&tcpstate->waiting);
/*   tcpSemVAll(&tcpstate->waiting); */
/*   xFree((char *)tcpstate); */

  /* From RICE 07/03/90 */
/*   tcpSemVAll(&tcpstate->lock); */
#ifdef XMEMTRACK
  xFreeTrack((char *)tcpstate->snd, (unsigned)sizeof(struct sb),
             BtcpStateTrackId);
  xFreeTrack((char *)tcpstate, (unsigned)sizeof(struct tcpstate),
             BtcpStateTrackId);
#else
  xFree((char *)tcpstate->snd);
  xFree((char *)tcpstate);
#endif
  BtcpSessionCnt--;
}


#ifndef XNETSIM

char *prurequests[] = {
	"ATTACH",	"DETACH",	"BIND",		"LISTEN",
	"CONNECT",	"ACCEPT",	"DISCONNECT",	"SHUTDOWN",
	"RCVD",		"SEND",		"ABORT",	"CONTROL",
	"SENSE",	"RCVOOB",	"SENDOOB",	"SOCKADDR",
	"PEERADDR",	"CONNECT2",	"FASTTIMO",	"SLOWTIMO",
	"PROTORCV",	"PROTOSEND",
};


/*************************************************************************/
void
sorwakeup(so)
Sessn so;
{
  xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp: sorwakeup on %lx", (u_long)so);
}
    
/*************************************************************************/
void
sowwakeup(so)
Sessn so;
{
  xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp: sowwakeup on %lx", (u_long)so);
  tcpSemSignal(&sototcpst(so)->waiting);
}

/*************************************************************************/
/*ARGSUSED*/
int
soreserve(so, send, recv)
Sessn so;
int send, recv;
{
  struct tcpstate *tcpst = sototcpst(so);
  sbinit(tcpst->snd);
  return 0;
}

/*************************************************************************/
void
socantsendmore(so)
    Sessn so;
{
    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp:  socantsendmore on %lx", (u_long)so);
}

/*************************************************************************/
void
soisdisconnected(so)
    Sessn so;
{
    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp:  soisdisconnected on %lx", 
	    (u_long)so);
    if (sototcpst(so)->closed) {
	/* It was already closed, either by the network or the user */
    } else {
	/* deliver close done after we're done handling current message: */
	xCloseDone(so);
    } /* if */
    tcpSemVAll(&sototcpst(so)->waiting);
} /* soisdisconnected */

/*************************************************************************/
void
soabort(so)
Sessn so;
{
}

/*************************************************************************/
void
socantrcvmore(so)
    Sessn so;
{
    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp:  socantrcvmore on %lx", (u_long)so);
    
    sototcpst(so)->closed |= 2;
    if (sototcpst(so)->closed & 1) {
	/* do nothing, the user already knows that it is closed */
    } else {
	xCloseDone(so);
    }
}

/*************************************************************************/
void
soisconnected(so)
    Sessn so;
{
    struct tcpstate	*state = sototcpst(so);
    TcpSem 		*s = &state->waiting;

    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp:  soisconnected on %lx", (u_long)so);
    if (s->waitCount) {
	xTrace1(tcpp, TR_EVENTS, "tcp:  waking up opener on %lx", (u_long)so);
	tcpSemSignal(s);
    } else {
	/*
	 * I think that this is the time for a open done
	 */
	xOpenDone(xGetUp(so), xMyProtl(so), so);
    }
}

/*************************************************************************/
void
soisconnecting(so)
    Sessn so;
{
    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp:  soisconnecting on %lx", (u_long)so);
}

/*************************************************************************/
void
soisdisconnecting(so)
Sessn so;
{
    xTrace1(tcpp, TR_MAJOR_EVENTS, "tcp:  soisdisconnecting on %lx", 
	    (u_long)so);
}

/*************************************************************************/
/* 
 * sohasoutofband is called by the input routine to signal the presence
 * of urgent (out-of-band) data
 */
void
sohasoutofband(so, oobmark)
     Sessn so;
     u_int oobmark;
{
    void *buf[2];

    buf[0] = so;
    buf[1] = (void*) oobmark;
    xControlProtl(xGetUp(so), TCP_OOBMODE, (char*) buf, sizeof(buf));
} /* sohasoutofband */

/*************************************************************************/

void
tcpSemWait( ts )
    TcpSem	*ts;
{
    ts->waitCount++;
    semWait(&ts->s);
    ts->waitCount--;
}


void
tcpSemSignal( ts )
    TcpSem	*ts;
{
    semSignal(&ts->s);
}


void
tcpSemVAll( ts )
    TcpSem	*ts;
{
    int i, n;

    n=ts->waitCount;
    for ( i=0; i < n; i++ ) {
	semSignal(&ts->s);
    }
}


void
tcpSemInit( ts, n )
    TcpSem	*ts;
    int		n;
{
    semInit(&ts->s, n);
    ts->waitCount = 0;
}

/* LSB 8/9/94 */
/*
 * Destroy semaphore "ts."  After waking up all threads, hang around
 * till they're all gone.  It is possible to have multiple threads
 * trying to close the same session.  In this case, new threads might
 * come in on "ts" (via tcpClose()) as we're waiting for all threads
 * go away from the semaphore.  It is therefore necessary to repeat
 * the tcpSemVAll() in the loop.  Assuming nobody tries to close the
 * same TCP session an infinite number of times, the loop is guaranteed
 * to terminate.  Delay(0) is used to yield the processor to other
 * threads---the x-kernel manual does not guarantee this behavior but
 * doesn't provide an alternative either.
 */
void
tcpSemDestroy(ts)
     TcpSem *ts;
{
    while (ts->waitCount) {
        xTrace1(tcpp, TR_GROSS_EVENTS,
                "tcpSemDestroy: waiting for %d more sleepers to wakeup",
                ts->waitCount);
        tcpSemVAll(ts);
        Delay(0);
    } /* while */
#ifdef XNETSIM
    FreeSema(ts->s);
#endif
} /* tcpSemDestroy */


#endif
