#define MASTERDEF 1

#include "pcdefs.h"
#include "mbuf.h"
#include "route.h"
#include "protocol.h"
#include "in.h"
#include "config.h"
#include "data.h"
#include "tcp.h"
#include "funcdef.h"

void _near _fastcall transq(struct tcpport _far *);

struct ebuf _far * _far _loadds
Sgeteb()
{

	return((struct ebuf _far *)&eb[0]);
}

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

struct conf _far * _far _loadds
SgetScon()
{

	return((struct conf _far *)&Scon);
}

void _far _loadds
Ssetgate(ipn)
u_long	ipn;
{

	route[0].real = ipn;
}

void _far _loadds
Ssetname(ipn)
u_long	ipn;
{

	nameserver = ipn;
}

u_long _far _loadds
Sgetname()
{

	return(nameserver);
}

u_char _far * _far _loadds
Sgetdomain()
{

	return(Scon.domain);
}

u_long _far _loadds
Sgetbroadcast()
{

	return(ipall);
}

void _far _loadds
Sgetether(cp)
u_char _far *cp;
{

	(void) memcpy(cp, (u_char _far *)myether, DADDLEN);
}

static int nextport = 0;

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

/*
*   Initializes all buffers and hardware for data link layer.
*   Machine/board dependent.
*/
	(void) mbinit();
	if ((i = pkopen(myether, Scon.address, Scon.ioaddr, Scon.flags)) < 0)
		return i - 4;

	if (slip_mode)
	    tcpmss = SLIPSENDSIZE;
	else
	    tcpmss = TSENDSIZE;

	_disable();
	nextport = 1024 + N_CLICKS() % 30000;
	_enable();

#ifdef notdef
	(void) neteventinit();
#endif

/*
*  Check for the need to RARP and do it
*/
	if (Scon.flags & H_BOOTP) {
		(void) protinit();
		if (bootp() < 0) {
			(void) pkclose();
#ifdef notdef
			(void) neteventshut();
#endif
			return -3;
		}
		Scon.flags &= ~H_BOOTP;
	}
	if (Scon.flags & H_RARP) {
		(void) protinit();
		if (slip_mode)
			return -4;
		if (getrarp() < 0) {
			(void) pkclose();
#ifdef notdef
			(void) neteventshut();
#endif
			return -3;	/* failure return */
		}
		Scon.flags &= ~H_RARP;
	}
	(void) protinit();
/*
*  Give the lower layers a chance to check to see if anyone else
*  is using the same ip number.  Usually generates an ARP packet.
*/
        if (!slip_mode) {
		sendarp(etherall, Scon.myip, htons(ARPREP));
	}
	if (route[0].real) {
/*
dfputs("calling setgate\r\n");
*/
		(void) setgate(0L, route[0].real);
		time = n_clicks() + 4L;
		while (n_clicks() < time)
			;
		if (!Scon.snetmask) {
/*
dfputs("calling icmp netmask\r\n");
*/
			(void) neticmpsend(route[0].real, INMREQ, 0,
			    (u_char near *)0, 4);
			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;
	}
	ipall = Scon.myip | ~Scon.snetmask;
	blankip.i.ipdest = ipall;

	return 0;
}

void _far _loadds
Snetshut()
{
	u_int	i;
	struct tcpport _far *p;
	int	wait;
	u_long	time;
	int	nports = 0;

/*
dfputs("Snetshut: closing down network\r\n");
*/
	for (i = 0; i < NPORTS ; i++) {
		p = (struct tcpport _far *)portlist[i];
		if (!p)
			continue;
		if (p->type != SOCK_STREAM)
			continue;
		(void) SoClose(i);
		nports++;
	}
	wait = 180;
	while (wait--) {

		time = n_clicks() + 4L;
		for (;;) {
			netsleep();
			if (time < n_clicks())
				break;
		}
		if (!nports)
			break;
		nports = 0;
		for (i = 0; i < NPORTS ; i++) {
			p = (struct tcpport _far *)portlist[i];
			if (!p || p->type != SOCK_STREAM)
				continue;
			if (p->state >= SEST && p->state < STWAIT) {
				nports++;
				/* discard all input */
				if (p->inbase != p->infree) {
					p->inbase = 0;
					p->infree = 0;
					p->wait_tx = 1;
				}
			}
		}
	}
/*
dfputs("shutting down\r\n");
*/
	(void) pkclose();
#ifdef notdef
	(void) neteventshut();
#endif
}

/**************************************************************************/
/*  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 _far _loadds
Socket(type, addr, options, p)
u_char	type;
struct sockaddr _far *addr;		/* don't know what this is */
u_int	options;
struct port _far *p;
{
	int i;

/*
*  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;
	(void) memcpy((u_char _far *)&p->outpkt, (u_char _far *)&blankip,
	     sizeof(struct ether) + sizeof(struct iph));

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

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

/*
 *	SoBind
 */
int _far _loadds
SoBind(s, me)
int	s;
struct sockaddr _far *me;
{
	struct port _far *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 _far *)p)->outpkt.t.source = p->inport;
	return 0;
}

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

	CHECK_SOCKET(s);
	p = (struct tcpport _far *)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 _far _loadds
SoAccept(s, from)
int	s;
struct sockaddr _far *from;
{
	struct tcpport _far *p;

	CHECK_SOCKET(s);
	p = (struct tcpport _far *)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 _far _loadds
SoConnect(s, to)
int s;				/* socket number */
struct sockaddr _far *to;	/* destination */
{
	struct tcpport _far *p;
	u_char near *pc;
	u_long t_end;

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

	if (to->addr == ipall)
		return -3;

	p = (struct tcpport _far *)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((u_char _far *)p->outpkt.d.dest, (u_char _far *)pc, DADDLEN);
	}
	p->outpkt.i.ipdest = to->addr;
/*
*  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 */
	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 _far _loadds
SoReceive(s, from, buf, len)
int	s;
struct sockaddr _far *from;
u_char _far *buf;
u_int	len;
{
	u_int	n;
	struct udpport _far *p;
	u_char _far *cp;

	CHECK_SOCKET(s);
	p = (struct udpport _far *)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_int _far *)cp)++;
	from->port = *((u_int _far *)cp)++;
	from->addr = *((u_long _far *)cp)++;
	if (len > n)
		len = n;
	(void) memcpy((u_char _far *)buf, (u_char _far *)cp, len);
	_disable();
	p->infree -= (n + sizeof(u_long) + 2 * sizeof(u_int));
	if (p->infree)
		(void) memcpy((u_char _far *)p->datain,
		    (u_char _far *)cp + n, p->infree);
	_enable();
	return(len);
}

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

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

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

	if (!to->addr)
		to->addr = ipall;

	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 == ipall)
			    pc = etherall;
		    else {
			    pc = netdlayer(to->addr);
			    if (!pc)
				    return -1;
		    }
		    (void) memcpy((u_char _far *)p->outpkt.d.dest,
			(u_char _far *)pc, DADDLEN);
		}
		p->outpkt.i.ipdest = to->addr;
	}
	(void) memcpy((u_char _far *)&otcps,
	    (u_char _far *)&p->outpkt.i.ipsource, 2 * sizeof(u_long));
	otcps.proto = PROTUDP;
	p->outpkt.u.dest = p->outport;
	p->outpkt.u.source = p->inport;
	(void) memcpy((u_char _far *)p->outpkt.data, (u_char _far *)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 _far *)&otcps,
	    (struct tcph _far *)&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((u_long)&p->outpkt.i); /* MSC crock */
/*
*  send it
*/
	(void) pkxmit((u_char _far *)&p->outpkt, n + sizeof(struct ether));
	return(len);
}

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

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

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

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

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

	if (count <= len) {
		(void) memcpy(buf, &p->datain[p->inbase], count);
		p->inbase = 0;
		p->infree = 0;
		if (p->credit < 1024)
			p->wait_tx = 1;
		else {
			if (p->wait_tx > 20)
				p->wait_tx = 20;
			else if (p->wait_tx > 4)
				p->wait_tx -= 4;
		}
		p->credit = WINDOWSIZE;
		_enable();
		return count;
	} /* else */

	(void) 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) {
		(void) memcpy(p->datain, &p->datain[p->inbase], count);
		p->infree = count;
		if (p->credit < 1024 && count < WINDOWSIZE - 1024)
			p->wait_tx = 1;
		else {
			if (p->wait_tx > 40)
				p->wait_tx = 40;
			else if (p->wait_tx > 3)
				p->wait_tx -= 3;
		}
		p->credit = WINDOWSIZE - count;
		p->inbase = 0;
	}
	_enable();
	return len;
}

/*
 *	SoWrite
 */
int _far _loadds
SoWrite(s, buf, len)
int	s;
u_char _far *buf;
u_int	len;
{
	struct tcpport _far *p;
	u_int count;

	CHECK_SOCKET(s);
	p = (struct tcpport _far *)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;

	_disable();
	count = WINDOWSIZE - p->outfree;
	if (count < len)
		len = count;
	if (len) {
		(void) memcpy((u_char _far *)&p->dataout[p->outfree],
		    (u_char _far *)buf, len);
		p->outfree += len;
	}
	_enable();
	if (p->outfree == len || p->rto < (u_int)(n_clicks() - p->lasttime))
		transq(p);
	return(len);
}

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

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

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

	switch (p->state) {
	    case SCLOSED:
	    case STWAIT:
	    case SLAST:
		break;
	    case SLISTEN:		/* we don't care anymore */
	    case SSYNS:
	    case SFW2:
	    case SCLOSING:
		p->state = SCLOSED;
		break;
	    case SSYNR:
	    case SSYNRS:
	    case SFW1:
		p->outpkt.t.flags = TRESET;
		(void) tcpsend(p, 0);
		p->flag = O_CLOSED;
		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 _far _loadds
SoFree(s)
int	s;
{
	struct tcpport _far *p;

	if (s < 0 || s >= NPORTS)
		return;
	p = (struct tcpport _far *)portlist[s];
	portlist[s] = FNULL;
	if (!p)
		return;
	/* discard all input */
	if (p->inbase != p->infree) {
		p->inbase = 0;
		p->infree = 0;
		p->lasttime = 0L;
	}
}
