#define MASTERDEF 1

#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <stdlib.h>
#include "pcdefs.h"
#include "shared.h"
#include "route.h"
#include "in.h"
#include "config.h"
#include "data.h"
#include "tcp.h"
#include "funcdef.h"

void transq(struct tcpport *);
u_short slip_mode;

struct conf Scon;
u_long nameserver;
u_long nmask;
u_short tcpmss;
char *net_file;

#define	CHECK_SOCKET(x)	{ \
	if ((x) < 0 || (x) >= NPORTS) \
		return -1; }

void
Ssetgate(ipn)
u_long	ipn;
{

	route[0].real = ipn;
}

void
Ssetname(ipn)
u_long	ipn;
{

	nameserver = ipn;
}

u_long
Sgetname()
{

	return(nameserver);
}

u_char *
Sgetdomain()
{

	return(Scon.domain);
}

u_long
Sgetbroadcast()
{

	return(Scon.broadcast);
}

/************************************************************************/
/*  Snetinit
*   Do network initialization for those who want the defaults all
*   set for them.  Recommend that neterrchange be called before
*   initializing network stuff.
*/
int
Snetinit()
{
	u_long	time;
	int i;
	char _far *cp;
	char *env;

	env = getenv("X11");
	if (!env)
		return -13;
	net_file = malloc(strlen(env) + 10);
	if (!net_file)
		return -14;
	strcpy(net_file, env);
	strcat(net_file, "\\PKT.NET");

	i = pglue_open(Scon.address, Scon.ioaddr, Scon.myip);
	if (i)
		return i;

	/* Get our DS */
	cp = (char _far *)&Scon;
	pkshared->prot_ds = FP_SEG(cp);

	slip_mode = pkshared->slip_mode;
	if (slip_mode)
	    tcpmss = SLIPSENDSIZE;
	else
	    tcpmss = TSENDSIZE;
	protinit();

/*
*  Check for the need to RARP and do it
*/
	if (Scon.flags & H_BOOTP) {
		if (bootp_process() < 0) {
			pglue_close();
			return -13;
		}
		/* Update the real-mode program concept of myip for use
		 * in ARP code.
		 */
		pkshared->myip = Scon.myip;
		pkshared->xmitarp.spa = Scon.myip;
	}
	else if (Scon.flags & H_RARP) {
#ifdef notdef
		if (getrarp() < 0) {
			pglue_close();
			return -14;	/* failure return */
		}
#else
		pglue_close();
		return -31;
#endif
	}
	blankip.i.ipsource = Scon.myip;
	ipout->i.ipsource = Scon.myip;
	ipout->i.ipdest = 0;
#ifdef notdef
	arpinit();
#endif
	switch ((u_char)Scon.myip >> 6) {
	    case 0:			/* class A */
	    case 1:
/*
dfputs("class A\r\n");
*/
		nmask = 0x000000ffL;
		break;
	    case 2:			/* class B */
/*
dfputs("class B\r\n");
*/
		nmask = 0x0000ffffL;
		break;
	    case 3:			/* class C */
/*
dfputs("class C\r\n");
*/
		nmask = 0x00ffffffL;
	}

	if (route[0].real) {
		if (!Scon.snetmask) {
			(void) neticmpsend(route[0].real, INMREQ, 0,
			    (u_char *)0, 0);
			time = n_clicks() + 18L;
			while (n_clicks() < time) {
				if (Scon.snetmask)
					break;
			}
		} else {
			dfputs("no icmp netmask required\r\n");
		}
	} else {
		dfputs("no default gateway\r\n");
	}
	if (!Scon.snetmask) {
/*
dfputs("snetmask == 0.. fixing\r\n");
*/
		Scon.snetmask = nmask;
	}
	if (!Scon.broadcast)
		Scon.broadcast = Scon.myip | ~Scon.snetmask;
	blankip.i.ipdest = Scon.broadcast;

	return 0;
}

void
Snetshut()
{
    unsigned	i;
    struct tcpport *p;
    u_long	time;
    int	nports = 0;

    netsleep();
    for (i = 0; i < NPORTS ; i++) {
	p = (struct tcpport *)portlist[i];
	if (!p)
		continue;
	if (p->type != SOCK_STREAM)
		continue;
	(void) SoClose(i);
	p->inbase = 0;
	p->infree = 0;
	p->credit = WINDOWSIZE;
	p->wait_tx = 1;
	nports++;
    }
    netsleep();
    time = n_clicks() + 500L;
    for (;;) {
	netsleep();
	if (time < n_clicks())
	    break;
	if (!nports)
	    break;
	nports = 0;
	for (i = 0; i < NPORTS ; i++) {
	    p = (struct tcpport *)portlist[i];
	    if (!p || p->type != SOCK_STREAM)
		continue;
	    if (p->state >= SEST && p->state < STWAIT) {
		nports++;
		/* discard all input */
		if (p->infree) {
		    p->infree = 0;
		    p->credit = WINDOWSIZE;
		    p->wait_tx = 1;
		}
	    }
	}
    }
    pglue_close();
}

/**************************************************************************/
/*  Socket
*
*   This is the intialization for TCP based communication.  When a port
*   needs to be created, this routine is called to do as much pre-initialization
*   as possible to save overhead during operation.
*
*   This structure is created upon open of a port, either listening or 
*   wanting to send.
*
*   A TCP port, in this implementation, includes all of the state data for the
*   port connection, a complete packet for the TCP transmission, and two
*   queues, one each for sending and receiving.  The data associated with
*   the queues is in struct window.
*/
int
Socket(type, addr, options, p)
u_char	type;
struct sockaddr *addr;		/* don't know what this is */
unsigned	options;
struct port *p;
{
	int i;

	/* Unused arguments */
	addr = addr;
	options = options;

/*
*  Check for a free portlist slot
*/
	for (i = 0; i < NPORTS; i++) {
		if (!portlist[i])
			break;
	}
/*  
* None available
*/
	if (i == NPORTS)
		return -1;

	portlist[i] = p;

	/* static initialization */
	p->type = type;
	p->flag = 0;
	p->inport = 0;
	p->outport = 0;
	memcpy(&p->outpkt, &blankip, sizeof(struct ether) + sizeof(struct iph));

	switch (type) {
	    case SOCK_DGRAM:
		((struct udpport *)p)->outpkt.i.protocol = PROTUDP;
		((struct udpport *)p)->inbase = 0;
		((struct udpport *)p)->infree = 0;
		break;

	    case SOCK_STREAM:
		((struct tcpport *)p)->outpkt.i.protocol = PROTTCP;
		((struct tcpport *)p)->inbase = 0;
		((struct tcpport *)p)->infree = 0;
		((struct tcpport *)p)->outfree = 0;
		((struct tcpport *)p)->outpkt.t.urgent = 0;
		((struct tcpport *)p)->outpkt.t.flags = TPUSH;
/*
*  install maximum segment size which will be sent out in the first
*  ACK-SYN packet
*/
		((struct tcpport *)p)->outpkt.x.options[0] = 2;
		((struct tcpport *)p)->outpkt.x.options[1] = 4;
		*(u_short *)
		    &((struct tcpport *)p)->outpkt.x.options[2] =
		    htons(MAXSEG);
		((struct tcpport *)p)->outpkt.t.hlen =
		    (sizeof(struct tcph) + 1 * sizeof(u_long) << 2) & 0xf0;
		((struct tcpport *)p)->state = SCLOSED;
		((struct tcpport *)p)->credit = WINDOWSIZE;
		((struct tcpport *)p)->rto = MINRTO;
		((struct tcpport *)p)->lasttime = WRAPTIME - 1L;
		((struct tcpport *)p)->outsize = tcpmss;
		break;
	}
	return(i);
}

static int nextport = 1024;
/*
 *	SoBind
 */
int
SoBind(s, me)
int	s;
struct sockaddr *me;
{
	struct port *p;

	CHECK_SOCKET(s);
	p = portlist[s];
	if (!p)
		return -1;

	if (me && me->port) {
		p->inport = me->port;
	} else {
		p->inport = htons(nextport);
		if (nextport++ < 0)
			nextport = 1024;
	}
	if (p->type == SOCK_STREAM)
		((struct tcpport *)p)->outpkt.t.source = p->inport;
	return 0;
}

/*
 *	SoListen
 */
int
SoListen(s)
int	s;
{
	struct tcpport *p;

	CHECK_SOCKET(s);
	p = (struct tcpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_STREAM)
		return -1;

	if (p->state != SCLOSED && p->state != STWAIT)
		return -1;

	p->state = SLISTEN;
	return 0;
}

/*
 *	SoAccept
 */
int
SoAccept(s, from)
int	s;
struct sockaddr *from;
{
	struct tcpport *p;

	CHECK_SOCKET(s);
	p = (struct tcpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_STREAM)
		return -1;

	if (p->state != SEST)
		return 0;

	from->port = p->outport;
	from->addr = p->outpkt.i.ipdest;
	return 1;
}

/*
 *	SoConnect
 */
int
SoConnect(s, to)
int s;				/* socket number */
struct sockaddr *to;	/* destination */
{
	struct tcpport *p;
	u_char *pc;
	u_long t_end;

	CHECK_SOCKET(s);
	if (!to->addr)
		return -2;

	if (to->addr == Scon.broadcast)
		return -3;

	p = (struct tcpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_STREAM)
		return -1;
/*
*  get the hardware address for that host, or use the one for the gateway
*  all handled by 'netdlayer' by ARPs.
*/
	if (!slip_mode) {
		pc = netdlayer(to->addr);	/* we have ether? */
		if (!pc)
			return -2;
		memcpy(p->outpkt.d.dest, pc, DADDLEN);
	}
	p->outpkt.i.ipdest = to->addr;
	/* If the destination host is not on the same network, then
	 * send a smaller MSS option.
	 */
	if ((to->addr & Scon.snetmask) != (Scon.myip & Scon.snetmask)) {
		*(u_short *)&p->outpkt.x.options[2] = htons(MSS_SMALL);
	}
/*
*  make the connection, if you can, we will get an event notification later
*  if it connects.  Timeouts must be done at a higher layer.
*/
	p->outport = to->port;
	p->outpkt.t.dest = to->port;	/* for example, telnet=23 */
	p->outpkt.t.flags = TSYN;	/* want to start up sequence */
	p->outpkt.t.ack = 0;		/* beginning has no ACK */
	t_end = p->outseq = n_clicks();
	t_end += 182L;
	(void) tcpsend(p, 4);		/* send opening volley, including MSS */
	p->state = SSYNS;		/* syn sent */

	while (n_clicks() < t_end) {
		netsleep();
		if (p->state == SEST)
			return 0;
		if (p->state == SCLOSED)
			return -10;
	}
	return -11;
}

/*
 *	SoReceive
 */
int
SoReceive(s, from, buf, len)
int	s;
struct sockaddr *from;
u_char *buf;
unsigned	len;
{
	unsigned	n;
	struct udpport *p;
	u_char *cp;

	CHECK_SOCKET(s);
	p = (struct udpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_DGRAM)
		return -1;

	if (!p->infree)
		return 0;		/* EWOULDBLOCK */

	cp = p->datain;
	n = *(u_short *)cp;
	cp += sizeof (u_short);
	from->port = *(u_short *)cp;
	cp += sizeof (u_short);
	from->addr = *(u_long *)cp;
	cp += sizeof (u_long);
	if (len > n)
		len = n;
	memcpy(buf, cp, len);
	CLI;
	p->infree -= (n + sizeof(u_long) + 2 * sizeof(u_short));
	if (p->infree)
		memcpy(p->datain, cp + n, p->infree);
	STI;
	return(len);
}

/*
 *	SoSend
 */
int
SoSend(s, to, buf, len)
int	s;
struct sockaddr *to;
u_char *buf;
unsigned	len;
{
	unsigned	n;
	struct udpport *p;
	u_char *pc;

	CHECK_SOCKET(s);
	p = (struct udpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_DGRAM)
		return -1;

	if (!to->addr)
		to->addr = Scon.broadcast;

	p->outport = to->port;

	if (len > UMAXLEN)
		len = UMAXLEN;
/*
*  make sure that we have the right dlayer address
*/
	if (to->addr != p->outpkt.i.ipdest) {
		if (!slip_mode) {
			if (to->addr == Scon.broadcast)
				pc = etherall;
			else {
				pc = netdlayer(to->addr);
				if (!pc)
					return -1;
			}
			memcpy(p->outpkt.d.dest, pc, DADDLEN);
		}
		p->outpkt.i.ipdest = to->addr;
	}
	memcpy(&otcps, &p->outpkt.i.ipsource, 2 * sizeof(u_long));
	otcps.proto = PROTUDP;
	p->outpkt.u.dest = p->outport;
	p->outpkt.u.source = p->inport;
	memcpy(p->outpkt.data, buf, len);
	n = len + sizeof(struct udph);
	otcps.tcplen = p->outpkt.u.length = htons(n);

/*
*  put in checksum
*/
	p->outpkt.u.check = 0;
	p->outpkt.u.check = tcpcheck((struct pseudotcp *)&otcps,
	    (struct tcph *)&p->outpkt.u, n);

/*
*   iplayer for send
*/
	n += sizeof(struct iph);
	p->outpkt.i.tlen = htons(n);
	p->outpkt.i.ident = htons(nnipident);
	nnipident++;
	p->outpkt.i.check = 0;
	p->outpkt.i.check = iphcheck(&p->outpkt.i);
/*
*  send it
*/
	n += sizeof (struct ether);
	_fmemcpy(pkshared->xmitbuf, (u_char _far *)&p->outpkt, n);
	if (!slip_mode && n < 60)
		n = 60;
	n = send_pkt(n);
	if (n)
		return -1;
	return len;
}

int
SoRead(s, buf, len)
int	s;
u_char *buf;
unsigned	len;
{
	struct tcpport *p;
	int count;

	CHECK_SOCKET(s);
	p = (struct tcpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_STREAM)
		return -1;

	if (p->state < SEST)
		return -1;

	CLI;
	count = p->infree - p->inbase;
	if (!count) {
		STI;
		if (p->flag & O_GOTALL)
			return -1;
		return 0;
	}

	if (count <= len) {
		memcpy(buf, &p->datain[p->inbase], count);
		p->inbase = 0;
		p->infree = 0;
		/* If the window was < 1/2, then send fast to wake other
		 * side up.
		 */
		if (p->credit < 2048)
			p->wait_tx = 4;
		else {
			if (!p->wait_tx || p->wait_tx > 50)
				p->wait_tx = 50;
			else if (p->wait_tx > 4)
				p->wait_tx -= 4;
		}
		p->credit = WINDOWSIZE;
		STI;
		return count;
	} /* else */
	memcpy(buf, &p->datain[p->inbase], len);
	count -= len;
	p->inbase += len;
	/* If there is more to read, we will probably read it in the
	 * near future, so don't send a window update yet unless
	 * the window size has increased from zero.
	 */
	if (p->inbase > WINDOWSIZE / 4) {
		memcpy(p->datain, &p->datain[p->inbase], count);
		p->infree = count;
		if (p->credit < 1024 && count < WINDOWSIZE - 1024)
			p->wait_tx = 4;
		else {
			/* count will be < 3072.  The lower count, the
			 * bigger the window size.  If the window size
			 * is big, send faster.  count >> 6 will be between
			 * 0 and 48. */
			if (!p->wait_tx || p->wait_tx > 25)
				p->wait_tx = (count >> 6) + 4;
			else if (p->wait_tx > 3)
				p->wait_tx -= 3;
		}
		p->credit = WINDOWSIZE - count;
		p->inbase = 0;
	}
	STI;
	return len;
}

/*
 *	SoWrite
 */
int
SoWrite(s, buf, len)
int	s;
u_char *buf;
unsigned	len;
{
	struct tcpport *p;

	CHECK_SOCKET(s);
	p = (struct tcpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_STREAM)
		return -1;

	if (p->state < SEST)
		return -1;

	if (p->flag & (O_CLOSED || O_CLOSING))
		return -1;

	CLI;
	len = min(len, WINDOWSIZE - p->outfree);
	if (len) {
		memcpy(&p->dataout[p->outfree], buf, len);
		p->outfree += len;
	}
	STI;
	if (p->outfree == len || p->rto < n_clicks() - p->lasttime)
		transq(p);
	return(len);
}

/*
 *	SoClose
 */
int
SoClose(s)
int	s;
{
	struct tcpport *p;

	CHECK_SOCKET(s);
	p = (struct tcpport *)portlist[s];
	if (!p)
		return -1;		/* EBADF */

	if (p->type != SOCK_STREAM)
		return -1;

	switch (p->state) {
	    case SCLOSED:
		break;
	    case SLISTEN:		/* we don't care anymore */
	    case SSYNS:
		p->state = SCLOSED;
		break;
	    case SEST:		/* must initiate close */
	    case SCWAIT:		/* other side already closed */
					/* send FIN */
		if (!p->outfree) {
dfputs("SoClose: closing\r\n");
			p->flag = O_CLOSED;
			p->outpkt.t.flags = TFIN | TACK;
			(void) tcpsend(p, 0);
			if (p->state == SEST)
				p->state = SFW1; /* wait for ACK of FIN */
			else
				p->state = SLAST;
		} else {
dfputs("SoClose: setting O_CLOSING\r\n");
			p->flag |= O_CLOSING;
		}
		break;
	    default:
dfputs("SoClose: bad state\r\n");
		break;
	}
	return 0;
}

/*
 *	SoFree
 */
void
SoFree(s)
int	s;
{
	struct tcpport *p;

	if (s < 0 || s >= NPORTS)
		return;

	CLI;
	p = (struct tcpport *)portlist[s];
	if (p)
		portlist[s] = 0;
	STI;
}
