/* TCP header conversion routines
 * Copyright 1991 Phil Karn, KA9Q
 */
#include "global.h"
#include "mbuf.h"
#include "tcp.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: tcphdr.c,v 1.9 1996/09/01 17:09:50 root Exp $";
#endif


/* Convert TCP header in host format into mbuf ready for transmission,
 * link in data (if any). If ph != NULL, compute checksum, otherwise
 * take checksum from tcph->checksum
 */
struct mbuf *
htontcp (tcph, data, ph)
register struct tcp *tcph;
struct mbuf *data;
struct pseudo_header *ph;
{
int16 hdrlen;
struct mbuf *bp;
register unsigned char *cp;

	hdrlen = TCPLEN;
	if (tcph->optlen > 0 && tcph->optlen <= TCP_MAXOPT)
		hdrlen = (int16) (hdrlen + tcph->optlen);
	else if (tcph->mss != 0)
		hdrlen += MSS_LENGTH;

	if ((bp = pushdown (data, hdrlen)) == NULLBUF)	{
		free_p (data);
		return NULLBUF;
	}
	cp = bp->data;
	cp = put16 (cp, tcph->source);
	cp = put16 (cp, tcph->dest);
	cp = put32 (cp, (uint32) tcph->seq);
	cp = put32 (cp, (uint32) tcph->ack);
	*cp++ = (char)(hdrlen << 2);	/* Offset field */
	*cp = 0;
	if (tcph->flags.congest)
		*cp |= 64;
	if (tcph->flags.urg)
		*cp |= 32;
	if (tcph->flags.ack)
		*cp |= 16;
	if (tcph->flags.psh)
		*cp |= 8;
	if (tcph->flags.rst)
		*cp |= 4;
	if (tcph->flags.syn)
		*cp |= 2;
	if (tcph->flags.fin)
		*cp |= 1;
	cp++;
	cp = put16 (cp, tcph->wnd);
	if (ph == NULLHEADER)		/* Use user-supplied checksum */
		cp = put16 (cp, tcph->checksum);
	else {
		/* Zero out checksum field for later recalculation */
		*cp++ = 0;
		*cp++ = 0;
	}
	cp = put16 (cp, tcph->up);

	/* Write options, if any */
	if (hdrlen > TCPLEN)	{
		if (tcph->mss != 0)	{
			*cp++ = MSS_KIND;
			*cp++ = MSS_LENGTH;
			cp = put16 (cp, tcph->mss);
		} else
			memcpy (cp, tcph->options, (size_t) (int) tcph->optlen);
	}
	/* Recompute checksum, if requested */
	if (ph != NULLHEADER)
		(void) put16 (&bp->data[16], cksum (ph, bp, ph->length));

	return bp;
}



/* Pull TCP header off mbuf */
int
ntohtcp (tcph, bpp)
register struct tcp *tcph;
struct mbuf **bpp;
{
int hdrlen, i, optlen, kind;
register int flags;
char hdrbuf[TCPLEN], *cp;

	i = pullup (bpp, (unsigned char *)hdrbuf, TCPLEN);
	/* Note that the results will be garbage if the header is too short.
	 * We don't check for this because returned ICMP messages will be
	 * truncated, and we at least want to get the port numbers.
	 */
	tcph->source = get16 (&hdrbuf[0]);
	tcph->dest = get16 (&hdrbuf[2]);
	tcph->seq = (int32) get32 (&hdrbuf[4]);
	tcph->ack = (int32) get32 (&hdrbuf[8]);
	hdrlen = (hdrbuf[12] & 0xf0) >> 2;
	flags = hdrbuf[13];
	tcph->flags.congest = flags & 64;
	tcph->flags.urg = flags & 32;
	tcph->flags.ack = flags & 16;
	tcph->flags.psh = flags & 8;
	tcph->flags.rst = flags & 4;
	tcph->flags.syn = flags & 2;
	tcph->flags.fin = flags & 1;
	tcph->wnd = get16 (&hdrbuf[14]);
	tcph->checksum = get16 (&hdrbuf[16]);
	tcph->up = get16 (&hdrbuf[18]);
	tcph->mss = 0;
	tcph->optlen = (char) (hdrlen - TCPLEN);

	/* Check for option field. Only space for one is allowed, but
	 * since there's only one TCP option (MSS) this isn't a problem
	 */
	if (i < TCPLEN || hdrlen < TCPLEN)
		return -1;	/* Header smaller than legal minimum */
	if (tcph->optlen == 0)
		return (int)hdrlen;	/* No options, all done */

	if ((int16) (int) tcph->optlen > len_p(*bpp))
		/* Remainder too short for options length specified */
		return -1;

	(void) pullup (bpp, (unsigned char *)tcph->options, (int16) (int) tcph->optlen);	/* "Can't fail */
	/* Process options */
	for (cp = tcph->options, i = tcph->optlen; i > 0; )	{
		kind = *cp++;
		/* Process single-byte options */
		switch (kind)	{
			case EOL_KIND:	i--;
					cp++;
					return (int)hdrlen;	/* End of options list */
			case NOOP_KIND:	i--;
					cp++;
					continue;	/* Go look for next option */
			default:	break;
		}
		/* All other options have a length field */
		optlen = uchar(*cp++);

		/* Process valid multi-byte options */
		switch (kind)	{
			case MSS_KIND:	if(optlen == MSS_LENGTH)
						tcph->mss = get16(cp);
					break;
			default:	break;
		}
		optlen = max(2, optlen);	/* Enforce legal minimum */
		i -= optlen;
		cp += optlen - 2;
	}
	return (int)hdrlen;
}
