#include "machdep.h"
#include "timer.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "tcp.h"

int16 tcp_mss = DEF_MSS; /* Maximum segment size to be sent with SYN */

/* Send a segment on the specified connection. One gets sent only
 * if there is data to be sent or if "force" is non zero
 */
void
tcp_output(tcb)
register struct tcb *tcb;
{
	struct pseudo_header ph;
	struct mbuf *hbp;
	int16 hsize;	/* Size of header */
	struct tcp_header *tcph;
	struct mss *mssp;
	int16 ssize;	/* Size of current segment being sent,
			 * including SYN and FIN flags */
	int16 dsize;	/* Size of segment less SYN and FIN */
	int16 usable;	/* Usable window */
	int16 sent;	/* Sequence count (incl SYN/FIN) already in the pipe */

	if(tcb == NULLTCB)
		return;

	switch(tcb->state){
	case LISTEN:
	case CLOSED:
		return;	/* Don't send anything */
	}
	for(;;){
		sent = tcb->snd.ptr - tcb->snd.una;

		/* If this is a retransmission, send only the oldest segment
		 * (first-only retransmission policy)
		 */
		if(tcb->retry != 0 && sent != 0)
			break;

		/* There can only be one outstanding segment in this state
		 * since the other end would reject any segment without SYN
		 */
		if(tcb->state == SYN_SENT && sent != 0)
			break;
		if(tcb->snd.wnd == 0){
			/* Allow only one closed-window probe at a time */
			if(sent != 0)
				break;
			/* Force a closed-window probe */
			usable = 1;
		} else {
			/* usable window = offered window - unacked bytes in transit */
			usable = tcb->snd.wnd - sent;

			/* John Nagle's "single outstanding segment" rule.
			 * Allow only one segment in the pipeline unless there is enough
			 * unsent data to form at least one maximum-sized segment.
			 */
			if(sent != 0 && tcb->sndcnt - sent < tcb->mss){
				usable = 0;
			}
			/* Silly window avoidance. Don't send anything if the usable window
			 * is less than a quarter of the offered window.
			 * This test comes into play only when the offered window is at
			 * least 4 times the MSS; otherwise Nagle's test is sufficient
			 * to prevent SWS.
		 	 */
			else if(usable < tcb->snd.wnd/4){
				usable = 0;
			}
		}
		/* Compute size of segment to send. This is either the usable
		 * window, the mss, or the amount we have on hand, whichever is less.
		 * (I don't like optimistic windows)
		 */
		ssize = min(tcb->sndcnt - sent,usable);
		ssize = min(ssize,tcb->mss);
		dsize = ssize;

		if(ssize == 0 && tcb->force == 0)
			break;		/* No need to send anything */

		/* Determine size of TCP header and allocate mbuf.
		 * If sending SYN, allow space for the MSS option
		 */
		switch(tcb->state){
		case SYN_SENT:
		case SYN_RECEIVED:
			hsize = sizeof(struct tcp_header) + sizeof(struct mss);
			break;
		default:
			hsize = sizeof(struct tcp_header);
			break;
		}
		if((hbp = alloc_mbuf(hsize)) == NULLBUF)
			break;	/* No room to form a packet */

		tcb->force = 0;	/* Only one forced segment! */
		hbp->cnt = hsize;

		tcph = (struct tcp_header *)hbp->data;
		tcph->source = htons(tcb->conn.local.port);
		tcph->dest = htons(tcb->conn.remote.port);
		tcph->offset = hsize/sizeof(int32) << DSHIFT;
		tcph->flags = 0;

		/* Set the SYN and ACK flags according to the state we're in. It is
		 * assumed that if this segment is associated with a state transition,
		 * then the state change will already have been made. This allows
		 * this routine to be called from a retransmission timeout with
		 * force=1.
		 * If SYN is being sent, adjust the dsize counter so we'll
		 * try to get the right amount of data off the send queue.
		 */
		switch(tcb->state){
		case SYN_SENT:
		case SYN_RECEIVED:
			if(tcb->snd.ptr == tcb->iss){
				tcph->flags = SYN;
				dsize--;
			}
			/* Also send MSS */
			mssp = (struct mss *)(tcph + 1);
			mssp->kind = MSS_KIND;
			mssp->length = MSS_LENGTH;
			mssp->mss = htons(tcp_mss);
		}
		switch(tcb->state){
		case SYN_RECEIVED:
		case ESTABLISHED:
		case CLOSE_WAIT:
		case FINWAIT2:
		case TIME_WAIT:
		case CLOSING:
		case FINWAIT1:
		case LAST_ACK:
			tcph->flags |= ACK;
			break;
		}
		tcph->seq = htonl(tcb->snd.ptr);
		tcph->ack = htonl(tcb->rcv.nxt);
		tcph->wnd = htons(tcb->rcv.wnd);
		tcph->checksum = 0;
		tcph->up = 0;

		/* Now try to extract some data from the send queue.
		 * Since SYN and FIN occupy sequence space and are reflected
		 * in sndcnt but don't actually sit in the send queue,
		 * dup_p will return one less than dsize if a FIN needs to be sent.
		 */
		if(dsize != 0){
			if(dup_p(&hbp->next,tcb->sndq,sent,dsize) != dsize){
				/* We ran past the end of the send queue; send a FIN */
				tcph->flags |= FIN;
				dsize--;
			}
		}
		/* If the entire send queue will now be in the pipe, set the
		 * push flag
		 */
		if(dsize != 0 && sent + ssize == tcb->sndcnt)
			tcph->flags |= PSH;

		tcb->snd.ptr += ssize;
		/* If this is the first transmission of a range of sequence
		 * numbers, record it so we'll accept acknowledgments
		 * for it later
		 */
		if(seq_gt(tcb->snd.ptr,tcb->snd.nxt))
			tcb->snd.nxt = tcb->snd.ptr;
		/* Fill in fields of pseudo IP header */
		ph.source = tcb->conn.local.address;
		ph.dest = tcb->conn.remote.address;
		ph.protocol = TCP_PTCL;
		ph.zero = 0;
		ph.length = hsize + dsize;

		/* Compute checksum over pseudo-header, TCP header and data,
		 * and pass it off to IP
		 */
		tcph->checksum = cksum(&ph,hbp,ph.length);

		/* If we're sending some data or flags, start retransmission
		 * timer if it isn't already running.
		 */
		if(ssize != 0){
			if(tcb->timer.state != TIMER_RUN){
				/* Never initialize the timer with zero; it won't run! */
				tcb->timer.start = max(tcb->timer.start,1);
				start_timer(&tcb->timer);
			}
			/* If round trip timer isn't running, start it */
			if(seq_ge(tcb->snd.una,tcb->rttseq))
				tcb->rttseq = tcb->snd.ptr;
		}
		ip_send(tcb->conn.local.address,tcb->conn.remote.address,
		 TCP_PTCL,tcb->tos,0,hbp,ph.length,0,0);
	}
}
