/*
 *	This file contains the functions needed for processing outgoing
 *	TCP segments.
 *
 *	04/15/94, Kay Roemer.
 */

#include "config.h"
#include "atarierr.h"
#include "sockerr.h"
#include "kerbind.h"
#include "net.h"
#include "buf.h"
#include "tcp.h"
#include "ip.h"
#include "if.h"
#include "util.h"
#include "iov.h"
#include "timer.h"

extern void	*memcpy (void *, const void *, unsigned long);

static void	tcbos_idle	(struct tcb *, short);
static void	tcbos_xmit	(struct tcb *, short);
static void	tcbos_retrans	(struct tcb *, short);
static void	tcbos_persist	(struct tcb *, short);

static BUF *	tcp_mkseg	(struct tcb *, long);
static long	tcp_sndseg	(struct tcb *, BUF *);
static void	tcp_retrans	(struct tcb *);
static void	tcp_resend	(struct tcb *, short, short);
static void	tcp_probe	(struct tcb *);
static long	tcp_dropdata	(struct tcb *);
static long	tcp_sndhead	(struct tcb *);
static void	tcp_rtt		(struct tcb *, BUF *);

static short	canretrans	(struct tcb *);
static void	wakeme		(long);

void (*tcb_ostate[]) (struct tcb *, short) = {
	tcbos_idle,
	tcbos_retrans,
	tcbos_persist,
	tcbos_xmit
};

/*
 * Output finite state machine.
 */

static void
tcbos_idle (tcb, event)
	struct tcb *tcb;
	short event;
{
	long tmout;

	switch (event) {
	case TCBOE_SEND:
		if (SEQGE (tcb->snd_nxt, tcb->seq_write))
			break;
		tcp_sndhead (tcb);
		tcb->persist_tmo =
		tcb->retrans_tmo = tmout = tcp_timeout (tcb);
		if (canretrans (tcb)) {
			tcb->ostate = TCBOS_XMIT;
			tmout -= DIFTIME (tcb->data->snd.qfirst->info,
				GETTIME ());
		} else {
			tcb->ostate = TCBOS_PERSIST;
			tcb->flags &= ~TCBF_DORTT;
		}
		event_add (&tcb->timer_evt, tmout, wakeme, (long)tcb);

KAYDEBUG (("tcpout: port %d: IDLE -> %s", tcb->data->src.port,
	tcb->ostate == TCBOS_XMIT ? "XMIT" : "PERSIST"));

		break;
	}
}

static void
tcbos_xmit (tcb, event)
	struct tcb *tcb;
	short event;
{
	long tmout;

	switch (event) {
	case TCBOE_SEND:
		if (SEQLT (tcb->snd_nxt, tcb->seq_write))
			tcp_sndhead (tcb);
		break;

	case TCBOE_WNDOPEN:
		if (SEQLT (tcb->snd_nxt, tcb->seq_write))
			tcp_sndhead (tcb);

	case TCBOE_WNDCLOSE:
		if (!canretrans (tcb)) {
			tcb->persist_tmo = tcb->retrans_tmo;
			tcb->ostate = TCBOS_PERSIST;
			tcb->flags &= ~TCBF_DORTT;

KAYDEBUG (("tcpout: port %d: XMIT -> PERSIST", tcb->data->src.port));

		}
		break;

	case TCBOE_ACKRCVD:
		tcp_dropdata (tcb);
		if (SEQGE (tcb->snd_una, tcb->seq_write)) {
			event_del (&tcb->timer_evt);
			tcb->ostate = TCBOS_IDLE;

KAYDEBUG (("tcpout: port %d: XMIT -> IDLE", tcb->data->src.port));

			break;
		}
		tcb->retrans_tmo = tmout = tcp_timeout (tcb);
		if (canretrans (tcb))
			tmout -= DIFTIME (tcb->data->snd.qfirst->info,
				GETTIME ());
		event_reset (&tcb->timer_evt, tmout);
		break;

	case TCBOE_TIMEOUT:

KAYDEBUG (("tcpout: port %d: XMIT -> RETRANS", tcb->data->src.port));

		tcb->ostate = TCBOS_RETRANS;
		tcb->flags &= ~TCBF_DORTT;
		tcp_retrans (tcb);
		if (tcb->dupacks > TCP_REXMITHRESH)
			tcb->dupacks = TCP_REXMITHRESH;
		break;
	}
}

static void
tcbos_retrans (tcb, event)
	struct tcb *tcb;
	short event;
{
	long tmout;

	switch (event) {
	case TCBOE_TIMEOUT:
		tcp_retrans (tcb);
		break;

	case TCBOE_WNDCLOSE:
		if (!canretrans (tcb)) {
			tcb->persist_tmo = tcb->retrans_tmo;
			tcb->ostate = TCBOS_PERSIST;

KAYDEBUG (("tcpout: port %d: RETRANS -> PERSIST", tcb->data->src.port));

		}
		break;

	case TCBOE_DUPACK:
		if (tcb->dupacks == TCP_REXMITHRESH) {
			++tcb->dupacks;
			tcp_resend (tcb, 0, 1);
		} else if (tcb->dupacks > TCP_REXMITHRESH)
			tcp_resend (tcb, tcb->dupacks-TCP_REXMITHRESH-1, 1);
		break;

	case TCBOE_ACKRCVD:
		if (!tcp_dropdata (tcb))
			break;
		if (SEQGE (tcb->snd_una, tcb->seq_write)) {
			event_del (&tcb->timer_evt);
			tcb->ostate = TCBOS_IDLE;

KAYDEBUG (("tcpout: port %d: RETRANS -> IDLE", tcb->data->src.port));

			break;
		}
		if (SEQLT (tcb->snd_nxt, tcb->seq_write))
			tcp_sndhead (tcb);

KAYDEBUG (("tcpout: port %d: RETRANS -> XMIT", tcb->data->src.port));

		tcb->ostate = TCBOS_XMIT;
		tcb->retrans_tmo = tmout = tcp_timeout (tcb);
		if (canretrans (tcb))
			tmout -= DIFTIME (tcb->data->snd.qfirst->info,
				GETTIME ());
		event_reset (&tcb->timer_evt, tmout);
		break;
	}
}

static void
tcbos_persist (tcb, event)
	struct tcb *tcb;
	short event;
{
	switch (event) {
	case TCBOE_TIMEOUT:
		tcp_probe (tcb);
		break;

	case TCBOE_ACKRCVD:
		tcp_dropdata (tcb);
		if (SEQGE (tcb->snd_una, tcb->seq_write)) {
			event_del (&tcb->timer_evt);
			tcb->ostate = TCBOS_IDLE;

KAYDEBUG (("tcpout: port %d: PERSIST -> IDLE", tcb->data->src.port));

		}
		break;

	case TCBOE_WNDOPEN:
		if (SEQLT (tcb->snd_nxt, tcb->seq_write))
			tcp_sndhead (tcb);

		if (canretrans (tcb)) {

KAYDEBUG (("tcpout: port %d: PERSIST -> XMIT", tcb->data->src.port));

			tcb->ostate = TCBOS_XMIT;
			tcb->retrans_tmo = tcp_timeout (tcb);
			event_reset (&tcb->timer_evt, tcb->retrans_tmo);
		} else
			tcb->flags &= ~TCBF_DORTT;
		break;
	}
}

/*
 * Helper routines for the output state machine.
 */

#define TH(b)		((struct tcp_dgram *)(b)->dstart)
#define SEQ1ST(b)	(TH(b)->seq)
#define DATLEN(b)	(TH(b)->urgptr)

/*
 * Make a TCP segment of size 'size' an fill in some header fields.
 */
static BUF *
tcp_mkseg (tcb, size)
	struct tcb *tcb;
	long size;
{
	struct tcp_dgram *tcph;
	BUF *b;

	b = buf_alloc (TCP_RESERVE + size, TCP_RESERVE/2, BUF_NORMAL);
	if (b == 0) {
		DEBUG (("tcp_mkseg: no space for segment"));
		return 0;
	}
	tcph = (struct tcp_dgram *)b->dstart;
	tcph->srcport = tcb->data->src.port;
	tcph->dstport = tcb->data->dst.port;
	tcph->flags = 0;
	tcph->seq = tcb->snd_nxt;
	tcph->ack = tcb->rcv_nxt;
	tcph->hdrlen = TCP_MINLEN/4;
	tcph->window = 0;
	tcph->urgptr = 0;
	tcph->chksum = 0;
	b->dend += size;
	return b;
}

/*
 * Send a copy of the segment in 'b' to the net.
 */
static long
tcp_sndseg (tcb, b)
	struct tcb *tcb;
	BUF *b;
{
	struct tcp_dgram *tcph;
	long len;
	BUF *nb;

	len = b->dend - b->dstart;
	nb = buf_alloc (TCP_RESERVE + len, TCP_RESERVE/2, BUF_NORMAL);
	if (nb == 0) {
		DEBUG (("tcp_sndseg: no mem to send"));
		return ENSMEM;
	}
	memcpy (nb->dstart, b->dstart, len);
	nb->dend += len;
	
	tcph = (struct tcp_dgram *)nb->dstart;
	tcph->ack = tcb->rcv_nxt;
	tcph->window = tcp_rcvwnd (tcb, 1);
	tcph->chksum = 0;
	tcph->urgptr = 0;
	if (SEQGT (tcb->snd_urg, tcph->seq)) {
		BUF *buf = b;
		while (buf && !(TH(buf)->flags & TCPF_URG))
			buf = buf->next;
		if (buf) {
			tcph->flags |= TCPF_URG;
			tcph->urgptr = (unsigned short)
				(SEQ1ST(buf) + DATLEN(buf) - tcph->seq);
		}
	}
	tcph->chksum = tcp_checksum (tcph, nb->dend - nb->dstart,
		tcb->data->src.addr,
		tcb->data->dst.addr);

	return ip_send (tcb->data->src.addr, tcb->data->dst.addr,
			nb, IPPROTO_TCP, 0, &tcb->data->opts);
}

/*
 * Update the round trip time mean and deviation. Note that tcb->rtt is scaled
 * by 8 and tcb->rttdev by 4.
 */
static inline void
tcp_rtt (tcb, buf)
	struct tcb *tcb;
	BUF *buf;
{
	long err, oerr;
	long seq1st = SEQ1ST (buf);

	if (tcb->flags & TCBF_DORTT && SEQLE (tcb->rttseq, seq1st)) {
		oerr = err = DIFTIME (buf->info, GETTIME ());
		err -= tcb->rtt >> 3;
		tcb->rtt += err;
		if (err < 0)
			err = -err;
		tcb->rttdev += err - (tcb->rttdev >> 2);
		tcb->backoff = 0;
		tcb->rttseq = seq1st;

KAYDEBUG (("tcp_rtt: port %d: rtt = %ld ms, rto = %ld ms, rttdev = %ld ms",
	tcb->data->src.port,
	oerr*EVTGRAN, (tcb->rtt*EVTGRAN) >> 3, (tcb->rttdev*EVTGRAN) >> 2));

	}
}

/*
 * Drop acked data from the segment retransmission queue.
 */
static long
tcp_dropdata (tcb)
	struct tcb *tcb;
{
	struct in_dataq *q = &tcb->data->snd;
	BUF *b, *nxtb;
	long una, oldlen;
	short n = 0;

	oldlen = q->curdatalen;
	una = tcb->snd_una;
	for (b = q->qfirst; b; ++n, b = nxtb) {
		if (SEQLE (SEQ1ST (b) + tcp_seglen (b, TH (b)), una)) {

KAYDEBUG (("tcp_dropdata: port %d: <%ld,%ld>", TH(b)->srcport,
	SEQ1ST (b), tcp_seglen (b, TH (b))));

			if ((nxtb = b->next)) {
				nxtb->prev = 0;
				q->qfirst = nxtb;
				q->curdatalen -= DATLEN (b);
			} else {
				q->qfirst = 0;
				q->qlast = 0;
				q->curdatalen = 0;
			}
			tcp_rtt (tcb, b);
			buf_deref (b, BUF_NORMAL);
		} else	break;
	}
	if (tcb->data->sock && q->curdatalen < MIN (q->maxdatalen, oldlen)) {
		so_wakewsel (tcb->data->sock);
		wake (IO_Q, (long)tcb->data->sock);
	}
	/*
	 * If packets were acked then reset retransmission counter and
	 * update congestion window.
	 */
	if (n > 0) {
		tcb->nretrans = 0;
		tcb->snd_cwnd += n * ((tcb->snd_cwnd < tcb->snd_thresh)
			? tcb->snd_mss
			: ((tcb->snd_mss * tcb->snd_mss) / tcb->snd_cwnd));
	}
	/*
	 * Drag the urgent pointer along with the left window edge
	 */
	if (SEQLT (tcb->snd_urg, tcb->snd_una))
		tcb->snd_urg = tcb->snd_una;
	return n;
}

/*
 * Send all unsent urgent segments and all non urgent segments that fall
 * into the receivers window.
 */
static long
tcp_sndhead (tcb)
	struct tcb *tcb;
{
	long wndnxt, seqnxt, rttseq = 0, stamp;
	struct in_dataq *q = &tcb->data->snd;
	short sent = 0;
	long r;
	BUF *b = q->qlast;

	if (SEQGE (tcb->snd_nxt, tcb->seq_write))
		return 0;
	r = tcb->snd_cwnd;
	/*
	 * Kludge cwnd so that the last segment is not sent if
	 * last segment is not full sized and
	 * 1) Something unacked is outstanding, or
	 * 2) Nothing unacked is outstanding but there is more than
	 *    one segment in the queue.
	 * Otherwise send what we've got (or what cwnd allows).
	 */
	if (!(tcb->flags & TCBF_NDELAY) && b->info > DATLEN (b) &&
	    (SEQLT (tcb->snd_una, tcb->snd_nxt) || q->qfirst != b))
		r = MIN (SEQ1ST (b) - tcb->snd_nxt, r);

	wndnxt = tcb->snd_wndack + MIN (tcb->snd_wnd, r);

/* Skip already sent segments */
	for (b = q->qfirst; b; b = b->next) {
		seqnxt = SEQ1ST (b) + tcp_seglen (b, TH (b));
		if (SEQLT (tcb->snd_nxt, seqnxt))
			break;
	}

/* Send the segments that fall into the window */
	for ( ; b; b = b->next) {
		seqnxt = SEQ1ST (b) + tcp_seglen (b, TH (b));
		stamp = GETTIME ();
		/*
		 * See if the segment fits into the window.
		 */
		if (SEQGT (seqnxt, wndnxt))
			break;
		/*
		 * If no outstanding data and nothing can be sent, pretend
		 * we sent the first segment and make it appear as if it
		 * was dropped.
		 */
		if ((r = tcp_sndseg (tcb, b)) < 0) {
			if (SEQEQ (tcb->snd_una, tcb->snd_nxt)) {
				tcb->snd_nxt = seqnxt;
				b->info = stamp;
				if (r==ENSMEM && tcb->dupacks<TCP_REXMITHRESH)
					tcb->dupacks = TCP_REXMITHRESH;
				return 1;
			}
			break;
		}
		if (++sent == 1)
			rttseq = tcb->snd_nxt;

		tcb->snd_nxt = seqnxt;
		b->info = stamp;
	}

/* Schedule an RTT measurement */
	if (sent && !(tcb->flags & TCBF_DORTT)) {
		tcb->rttseq = rttseq;
		tcb->flags |= TCBF_DORTT;
	}
	return sent;
}

/*
 * Probe the other TCP's window
 */
static void
tcp_probe (tcb)
	struct tcb *tcb;
{
	struct tcp_dgram *tcph;
	BUF *b;

KAYDEBUG (("tcp_probe: port %d: sending probe", tcb->data->src.port));

	/*
	 * Make the probe contain one bogus data byte outside the window.
	 * Otherwise a 4.2 BSD (and derivate) host won't respond to the
	 * probe.
	 */
	b = tcp_mkseg (tcb, TCP_MINLEN + 1);
	if (b != 0) {
		tcph = (struct tcp_dgram *)b->dstart;
		tcph->flags = TCPF_ACK;
		tcph->seq = tcb->snd_una - 1;
		tcph->window = tcp_rcvwnd (tcb, 0);
		if (SEQGT (tcb->snd_urg, tcb->snd_una)) {
			BUF *buf = tcb->data->snd.qfirst;
			while (buf && !(TH(buf)->flags & TCPF_URG))
				buf = buf->next;
			if (buf) {
				tcph->flags |= TCPF_URG;
				tcph->urgptr = (unsigned short)
					(SEQ1ST(buf) + DATLEN(buf) - tcph->seq);
			}
		}
		tcph->chksum = 0;
		tcph->chksum = tcp_checksum (tcph, TCP_MINLEN,
			tcb->data->src.addr,
			tcb->data->dst.addr);

		ip_send (tcb->data->src.addr, tcb->data->dst.addr, b,
			IPPROTO_TCP, 0, &tcb->data->opts);
	} else
		DEBUG (("do_probe: no memory to probe"));

	tcb->persist_tmo = MIN (TCP_MAXPROBE, tcb->persist_tmo << 1);
	event_add (&tcb->timer_evt, tcb->persist_tmo, wakeme, (long)tcb);
}

/*
 * Retransmit segments after timeout. 
 * If the first segment on the queue has been dropped (ie when tcb->dupacks
 * greater then threshold) then cause no backoff and use a smaller timeout
 * for the first 5 retransmitts of this segment.
 */
static void
tcp_retrans (tcb)
	struct tcb *tcb;
{
	short opri = tcb->data->opts.pri;
	BUF *b;

KAYDEBUG (("tcp_retrans: port %d: sending %dnd retransmit",
	tcb->data->src.port, tcb->nretrans+1));

	if (++tcb->nretrans > TCP_MAXRETRY) {
		DEBUG (("do_retrans: connection timed out"));
		tcb_reset (tcb, ETIMEDOUT);
		tcb->state = TCBS_CLOSED;
		return;
	}
	if ((b = tcb->data->snd.qfirst) == 0) {
		ALERT (("do_retrans: queue is empty ?!"));
		tcb->ostate = TCBOS_IDLE;
		return;
	}

	/*
	 * Shrink threshold to half of its former size on every retransmit
	 * but don't make it smaller than two full sized packets so reopening
	 * the cong. window is fast enough.
	 * Drop cong. window to one full sized packet.
	 */
	if (tcb->snd_cwnd > tcb->snd_thresh)
		tcb->snd_thresh = tcb->snd_cwnd;
	tcb->snd_thresh = MIN (tcb->snd_thresh, tcb->snd_wnd) >> 1;
	if (tcb->snd_thresh < 2*tcb->snd_mss)
		tcb->snd_thresh = 2*tcb->snd_mss;
	tcb->snd_cwnd = tcb->snd_mss;

	/*
	 * Send retransmits at high priority. If memory is low then
	 * prentend the packet was dropped.
	 */
	tcb->data->opts.pri = IF_PRIORITIES-1;
	if (tcp_sndseg (tcb, b) == ENSMEM && tcb->dupacks < TCP_REXMITHRESH)
		tcb->dupacks = TCP_REXMITHRESH;
	tcb->data->opts.pri = opri;

	/*
	 * Use small timeout and clear backoff in case of dropped
	 * segments, otherwise double timeout.
	 */
	if (tcb->dupacks >= TCP_REXMITHRESH && tcb->nretrans <= 3) {
		tcb->backoff = 0;
		tcb->retrans_tmo = MAX (tcb->rttdev >> 1, TCP_MINRETRANS);
	} else {
		++tcb->backoff;
		tcb->retrans_tmo = tcp_timeout (tcb);
	}
	event_add (&tcb->timer_evt, tcb->retrans_tmo, wakeme, (long)tcb);
}

/*
 * Resend segments from the retransmission queue starting with segment
 * no. 'beg' (where 0 means the 2nd packet in the queue) and ending with
 * segment no. 'beg'+'end'-1.
 * Causes no backoff (unlike tcp_retrans).
 * Also update timestamp of the resent segments.
 */
static void
tcp_resend (tcb, beg, end)
	struct tcb *tcb;
	short beg, end;
{
	BUF *b = tcb->data->snd.qfirst;
	long stamp;
	short i;

	end = MIN (TCP_REXMITHRESH+1, beg+end);
KAYDEBUG (("tcp_resend: resending segments %d to %d", beg, end));
	for (i = -1; b && i < end; ++i, b = b->next) {
		if (SEQLT (tcb->snd_nxt, SEQ1ST (b) + tcp_seglen (b, TH (b))))
			break;
		if (i < beg)
			continue;
		stamp = GETTIME ();
		if (tcp_sndseg (tcb, b) == 0)
			b->info = stamp;
	}
}

static void
wakeme (arg)
	long arg;
{
	TCB_OSTATE ((struct tcb *)arg, TCBOE_TIMEOUT);
}

static short
canretrans (tcb)
	struct tcb *tcb;
{
	long seqnxt;
	BUF *b;

	if (SEQGE (tcb->snd_una, tcb->snd_nxt))
		return 0;

	b = tcb->data->snd.qfirst;
	if (b == 0) return 0;

	seqnxt = SEQ1ST (b) + tcp_seglen (b, TH (b));
	return (SEQLE (seqnxt, tcb->snd_wndack + tcb->snd_wnd) &&
		SEQLE (seqnxt, tcb->snd_nxt));
}

/*
 * Exported functions for other modules.
 */

static inline long
cancombine (tcb, buf, flags)
	struct tcb *tcb;
	BUF *buf;
	short flags;
{
	if (buf && SEQLE (tcb->snd_nxt, SEQ1ST (buf)) &&
            !((flags ^ TH(buf)->flags) & TCPF_URG) &&
	    !(flags & TCPF_SYN))
		return buf->info - DATLEN (buf);
	return 0;
}

/*
 * TCP maximum segment size option we send with every SYN segment.
 */
static struct {
	char	code;
	char	len;
	short	mss;
} mss_opt = { TCPOPT_MSS, 4, 0 };

/*
 * Generate and send TCP segments from the data in `iov' and/or with
 * the flags in `flags'.
 */
long
tcp_output (tcb, iov, niov, len, offset, flags)
	struct tcb *tcb;
	struct iovec *iov;
	long len, offset;
	short niov, flags;
{
	struct tcp_dgram *tcph = 0; /* for gcc */
	struct in_dataq *q = &tcb->data->snd;
	short first = 1;
	long ret = 0, r = 0;
	BUF *b;

/* Check if ACK only */
	if (len == 0 && (flags & (TCPF_SYN|TCPF_FIN)) == 0) {
		b = tcp_mkseg (tcb, TCP_MINLEN);
		if (b == 0) {
			DEBUG (("tcp_out: cannot send, memory low"));
			return ENSMEM;
		}
		tcph = TH (b);
		tcph->flags = TCPF_ACK | (flags & TCPF_PSH);
		tcph->window = tcp_rcvwnd (tcb, 1);

		tcph->chksum = tcp_checksum (tcph, TCP_MINLEN,
			tcb->data->src.addr,
			tcb->data->dst.addr);

		return ip_send (tcb->data->src.addr, tcb->data->dst.addr,
			b, IPPROTO_TCP, 0, &tcb->data->opts);
	}
	do {
		b = q->qlast;
		if ((r = cancombine (tcb, b, flags)) > 0) {
			tcph = TH (b);
			if (r > len)
				r = len;
			q->curdatalen += r;
		} else {
			r = MIN (tcb->snd_mss, len);
			b = tcp_mkseg (tcb, tcb->snd_mss);
			if (b == 0) {
				DEBUG (("tcp_out: cannot send, memory low"));
				if (first)
					return ENSMEM;
				b = q->qlast;
				ret = ENSMEM;
				break;
			}
			tcph = TH (b);
			tcph->flags = flags & ~(TCPF_SYN|TCPF_FIN);
			tcph->seq = tcb->seq_write;
			tcph->urgptr = 0;
			b->info = tcb->snd_mss; /* space left in segment */
			b->dend = tcph->data;

			if (first && flags & TCPF_SYN) {
				tcph->flags |= TCPF_SYN;
				++tcb->seq_write;
				/*
				 * Add maximum segment size TCP option
				 */
				tcph->hdrlen += sizeof (mss_opt) / 4;
				mss_opt.mss = tcb->rcv_mss;
				memcpy (b->dend, &mss_opt, sizeof (mss_opt));
				b->dend += sizeof (mss_opt);
			} else
				tcph->flags |= TCPF_ACK;
			/*
			 * Enqueue the segment.
			 */
			if (q->qlast == 0) {
				b->next = b->prev = 0;
				q->qlast = q->qfirst = b;
				q->curdatalen = r;
			} else {
				b->next = 0;
				b->prev = q->qlast;
				q->qlast->next = b;
				q->qlast = b;
				q->curdatalen += r;
			}
		}
		if (r > 0) {
			r = iov2buf_cpy (b->dend, r, iov, niov, offset);
			offset += r;
			b->dend += r;
			len -= r;
			/*
			 * Store segment length in urgptr field.
			 */
			tcph->urgptr += r;
			tcb->seq_write += r;
			tcph->flags |= TCPF_PSH;
		}
		if (len == 0 && flags & TCPF_FIN) {
			tcph->flags |= TCPF_FIN;
			++tcb->seq_write;
			/*
			 * Make this segment appear full sized in order
			 * to send it immediatly.
			 */
			b->info = tcph->urgptr;
		}
		first = 0;

KAYDEBUG (("tcp_output: port %d: <%ld,%ld%s%s%s>",
	tcph->srcport, tcph->seq, r,
	tcph->flags & TCPF_URG ? ",URG" : "",
	tcph->flags & TCPF_SYN ? ",SYN" : "",
	tcph->flags & TCPF_FIN ? ",FIN" : ""));

	} while (len > 0);

	if (flags & TCPF_URG) {
		tcb->snd_urg = tcb->seq_write;
		/*
		 * In PERSIST state we force a probe to be sent
		 * immediatly which communicates our new urgent
		 * pointer.
		 */
		if (tcb->ostate == TCBOS_PERSIST) {
			event_del (&tcb->timer_evt);
			tcb->persist_tmo = tcp_timeout (tcb);
			tcp_probe (tcb);
		}
	}
	TCB_OSTATE (tcb, TCBOE_SEND);
	return ret;
}

/*
 * Return the initial timeout to use for retransmission and persisting.
 */
long
tcp_timeout (tcb)
	struct tcb *tcb;
{
	long tmout;

	tmout = ((tcb->rtt >> 2) + tcb->rttdev) >> 1;
	if (tmout > (0x7ffffffful >> TCP_MAXRETRY))
		tmout = TCP_MAXRETRANS;
	else {
		tmout <<= tcb->backoff;
		if (tmout < TCP_MINRETRANS)
			tmout = TCP_MINRETRANS;
		else if (tmout > TCP_MAXRETRANS)
			tmout = TCP_MAXRETRANS;
	}
	return tmout;
}

/*
 * Return the size of our window we should advertise to the remote TCP.
 * For now only return the current free buffer space, later we have
 * to take into account congestion control.
 */
long
tcp_rcvwnd (tcb, wnd_update)
	struct tcb *tcb;
	short wnd_update;
{
	long space, minwnd;

	space = tcb->data->rcv.maxdatalen - tcb->data->rcv.curdatalen;
	if (space < tcb->rcv_mss && space*4 < tcb->data->rcv.maxdatalen)
		space = 0;

	if (tcb->state >= TCBS_SYNRCVD) {
		minwnd = tcb->rcv_wnd - tcb->rcv_nxt;
		if (space < minwnd)
			return minwnd;

		if (wnd_update)
			tcb->rcv_wnd = tcb->rcv_nxt + space;
	}
	return space;
}
