/*
 * btcp_subr.c
 *
 * Derived from:
 *
 * Copyright (c) 1982, 1986, 1988, 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)tcp_subr.c	8.1 (Berkeley) 6/10/93
 *
 * Modified for x-kernel v3.3
 * Modifications Copyright (c) 1996,1991  Arizona Board of Regents
 *
 * $Revision: 1.3 $
 * $Date: 1996/06/19 16:34:07 $
 */

#include "xkernel.h"
#include "ip.h"
#include "icmp.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"

/* patchable/settable parameters for tcp */
static int 	tcp_mssdflt = TCP_MSS;
static int 	tcp_rttdflt = TCPTV_SRTTDFLT / PR_SLOWHZ;
#ifdef FIX_3
static int	tcp_do_rfc1323 = 0;
#else
static int      tcp_do_rfc1323 = 1;
#endif

#ifdef XMEMTRACK

extern int BtcpTrackId;

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

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

#endif

extern void btcp_delete_tcpstate(struct tcpstate *);

/*
 * Create template to be used to send tcp packets on a connection.
 * Call after host entry created, allocates an mbuf and fills
 * in a skeletal tcp/ip header, minimizing the amount of work
 * necessary when the connection is used.
 */
struct tcpiphdr *
btcp_template(tp)
	struct tcpcb *tp;
{
	register struct inpcb *inp = tp->t_inpcb;
	register struct tcpiphdr *n;
	Sessn	lls, tcpSessn;
	IPpseudoHdr	pHdr;

	if ((n = tp->t_template) == 0) {
	  	n = (struct tcpiphdr *)xMalloc(sizeof *n);
	}
	tcpSessn = tcpcbtoso(tp);
	xAssert(xIsSessn(tcpSessn));
	lls = xGetSessnDown(tcpSessn, 0);
	xAssert(xIsSessn(lls));
        if ( xControlSessn(lls, IP_GETPSEUDOHDR, (char *)&pHdr, sizeof(pHdr)) < 0 ){
	  xTrace0(tcpp, TR_ERRORS, 
		  "btcp_template could not get pseudo-hdr from lls");
	  pHdr.prot = 0;
        }
	
	n->ti_next = n->ti_prev = 0;
	n->ti_x1 = 0;
	n->ti_pr = pHdr.prot;
	n->ti_len = sizeof (struct tcpiphdr) - sizeof (struct ipovly);
	n->ti_src = inp->inp_laddr;
	n->ti_dst = inp->inp_raddr;
	n->ti_sport = inp->inp_lport;
	n->ti_dport = inp->inp_rport;
	n->ti_seq = 0;
	n->ti_ack = 0;
	n->ti_x2 = 0;
	n->ti_off = 5;
	n->ti_flags = 0;
	n->ti_win = 0;
	n->ti_sum = 0;
	n->ti_urp = 0;
	/*
	 * IP pseudo-header
	 */
	n->ti_p.src = *(IPhost *)&n->ti_src;
	n->ti_p.dst = *(IPhost *)&n->ti_dst;
	n->ti_p.zero = 0;
	n->ti_p.prot = n->ti_pr;
	return (n);
}

/*
 * Send a single message to the TCP at address specified by
 * the given TCP/IP header.  If m == 0, then we make a copy
 * of the tcpiphdr at ti and send directly to the addressed host.
 * This is used to force keep alive messages out using the TCP
 * template for a connection tp->t_template.  If flags are given
 * then we send a message back to the TCP which originated the
 * segment ti, and discard the mbuf containing it and any other
 * attached mbufs.
 *
 * In any case the ack and sequence number of the transmitted
 * segment are as specified by the parameters.
 */
void
btcp_respond(myprotl, tp, th, pHdr, ack, seq, flags)
  	Protl myprotl;
	struct tcpcb *tp;
	register struct tcphdr *th;
  	register IPpseudoHdr *pHdr;
	tcp_seq ack, seq;
	int flags;
{
	register int tlen;
	int win = 0;
	Msg m;
	Sessn s = NULL;
	struct tcphdr tHdr;
	hdrStore_t store;
	void       *buf;

	if (tp) {
		s = tp->t_inpcb->inp_session;
		win = sototcpst(s)->rcv_space;
		s = xGetSessnDown(s, 0);
	}
	/* LSB-w: different in prev vers */
#ifdef TCP_COMPAT_42
		tlen = 1;
#else
		tlen = 0;
#endif
	tHdr = *th;
	if (flags == 0) {
		flags = TH_ACK;
	} else {
	    	u_short tmp;

		tmp = tHdr.th_sport;
		tHdr.th_sport = tHdr.th_dport;
		tHdr.th_dport = tmp;
	}
	tHdr.th_seq = seq;
	tHdr.th_ack = ack;
	tHdr.th_x2 = 0;
	tHdr.th_off = sizeof (struct tcphdr) >> 2;
	tHdr.th_flags = flags;
	tHdr.th_win = win;
	tHdr.th_urp = 0;
	msgConstructEmpty(&m);
	
	store.m = &m;
	store.h = pHdr;
        buf = msgPush(&m, sizeof(struct tcphdr));
        xAssert(buf);
        tcpHdrStore(&tHdr, buf, sizeof(struct tcphdr), &store);

	if (s) {
	    xPush(s, &m);
	} else {
	    Part p[2];
/* 	    extern Protl TCP; */
	    
	    partInit(p, 2);
	    partPush(p[0], &pHdr->dst,sizeof(IPhost));
	    partPush(p[1], &pHdr->src,sizeof(IPhost));
	    s = xOpen(myprotl, myprotl, xGetProtlDown(myprotl, 0), p);
	    if (xIsSessn(s)) {
		xPush(s, &m);
		xClose(s);
	    } else {
		xError("tcp_respond could not open lower session!");
	    }
	}
	msgDestroy(&m);
}

/*
 * Create a new TCP control block, making an
 * empty reassembly queue and hooking it to the argument
 * protocol control block.
 */
struct tcpcb *
btcp_newtcpcb(ps, inp)
  	PSTATE *ps;
	struct inpcb *inp;
{
	register struct tcpcb *tp;

	tp = (struct tcpcb *)xMalloc(sizeof *tp);
	bzero((char *) tp, sizeof(struct tcpcb));
	tp->seg_next = tp->seg_prev = (struct reass *)tp;
	tp->t_maxseg = tcp_mssdflt;

	/* LSB: add SACKS here */
	tp->t_flags = tcp_do_rfc1323 ? (TF_REQ_SCALE|TF_REQ_TSTMP) : TF_REQ_SCALE;
/*         tp->t_flags = tcp_do_rfc1323 ? (TF_REQ_SCALE|TF_REQ_TSTMP) : 0; */
	tp->t_inpcb = inp;
	/*
	 * Init srtt to TCPTV_SRTTBASE (0), so we can tell that we have no
	 * rtt estimate.  Set rttvar so that srtt + 2 * rttvar gives
	 * reasonable initial retransmit time.
	 */
	tp->t_srtt = TCPTV_SRTTBASE;
	tp->t_rttvar = tcp_rttdflt * PR_SLOWHZ << 2;
	tp->t_rttmin = TCPTV_MIN;
	TCPT_RANGESET(tp->t_rxtcur, 
	    ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1,
	    TCPTV_MIN, TCPTV_REXMTMAX);
	DO_TRACE(ps, TCP_EVENT_OTHER9, tp->t_rxtcur, 4, 0);

 	tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT; 
/* 	tp->snd_cwnd = sbspace(sototcpst(inp->inp_session)->snd); */
	tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT;
	inp->inp_ppcb = (caddr_t)tp;

	/* Added to test delayed acks vs. immediate */
	tp->delack = ps->delack;

	/* LSB: this are in prev */
	tp->t_rexmtthresh = 3; 
	tp->t_slowstart = ps->slowstart;
/*         tp->t_rtt = TCPTV_SRTTBASE; */

	return (tp);
}

/*
 * Drop a TCP connection, reporting
 * the specified error.  If connection is synchronized,
 * then send a RST to peer.
 */
struct tcpcb *
btcp_drop(tp, error_no)
	register struct tcpcb *tp;
	int error_no;
{
  	Sessn so = tp->t_inpcb->inp_session;
	PSTATE *ps=(PSTATE *)so->myprotl->state;

	xTrace2(tcpp, 2, "btcp_drop: tcpcb %X s %X", tp, so);
	if (TCPS_HAVERCVDSYN(tp->t_state)) {
		tp->t_state = TCPS_CLOSED;
		(void) (ps->tcp_output)(tp);
		ps->tcpstat.tcps_drops++;
	} else
		ps->tcpstat.tcps_conndrops++;
	socantrcvmore(so);
#if 0
	return (btcp_destroy(tp));
#else
	return 0;
#endif
}

/*
 * Close a TCP control block:
 *	discard all space held by the tcp
 *	discard internet protocol block
 *	wake up any sleepers
 */
struct tcpcb *
btcp_destroy(tp)
	register struct tcpcb *tp;
{
	register struct reass *this, *next;
	struct inpcb *inp;
	Sessn so = tp->t_inpcb->inp_session;
	PSTATE *pstate=(PSTATE *)so->myprotl->state;

	xTrace1(tcpp, 1, "btcp_destroy: tcpcb %X", tp);
	/* LSB: big chunk out dealing with route rtt */

	/* free the reassembly queue, if any */

	inp = tp->t_inpcb;
	xTrace1(tcpp, 3, "tcp_destroy: inpcb %X", inp);
	so = inp->inp_session;
	this = tp->seg_next;
	while (this != (struct reass *)tp) {
		next = this->next;
		msgDestroy(&this->m);
		remque(this);
#ifdef XMEMTRACK
		xFreeTrack((char *)this, sizeof(struct reass),BtcpTrackId);
#else
		xFree((char *)this);
#endif
		this = next;
	}
	if (tp->t_template) {
	    tcpReleasePort(pstate->portstate, tp->t_template->ti_sport);
#ifdef XMEMTRACK
	    xFreeTrack((char *)tp->t_template, sizeof(struct tcpiphdr), 
			BtcpTrackId);
#else
	    xFree((char *)tp->t_template);
#endif
	}
#ifdef XMEMTRACK
	xFreeTrack((char *)tp, sizeof(struct tcpcb), BtcpTrackId);
#else
	xFree((char *)tp);
#endif
	inp->inp_ppcb = 0;
	/*
	 * This used to be a soisdisconnected, but really needs to delete
	 * all of the state.  
	 */
	btcp_delete_tcpstate(sototcpst(so));
	so->state = 0;
	pstate = (PSTATE *)so->myprotl->state;
	mapRemoveBinding(pstate->activeMap, so->binding);
	{
		int i;

		for (i=0; i < so->numdown; i++) {
			xClose(xGetSessnDown(so, i));
		}
	}
	xDestroySessn(so);

	in_pcbdetach(inp);
	pstate->tcpstat.tcps_closed++;
	return ((struct tcpcb *)0);
}

/*
 * Notify a tcp user of an asynchronous error;
 * store error as soft error, but wake up user
 * (for now, won't do anything until can select for soft error).
 */
void
btcp_notify(inp, error)
	struct inpcb *inp;
	int error;
{
	register struct tcpcb *tp = (struct tcpcb *)inp->inp_ppcb;

	/*
	 * Ignore some errors if we are hooked up.
	 * If connection hasn't completed, has retransmitted several times,
	 * and receives a second error, give up now.  This is better
	 * than waiting a long time to establish a connection that
	 * can never complete.
	 */
	if (tp->t_state == TCPS_ESTABLISHED &&
	     (error == EHOSTUNREACH || error == ENETUNREACH ||
	      error == EHOSTDOWN)) {
		return;
	} else 
		tp->t_softerror = error;
	sorwakeup(inp->inp_session);
	sowwakeup(inp->inp_session);
}

/* void */
/* tcp_ctlinput(cmd, sa, ip) */

/*
 * When a source quench is received, close congestion window
 * to one segment.  We will gradually open it again as we proceed.
 */
void
btcp_quench(inp)
	struct inpcb *inp;
{
	struct tcpcb *tp = intotcpcb(inp);

	if (tp)
		tp->snd_cwnd = tp->t_maxseg;
/* LSB:		tp->snd_cwnd = tp->t_slowstart * tp->t_maxseg; */
}
