/*
 * Micro-X -- an X server for DOS
 * Copyright (C) 1995 StarNet Communications Corp.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * StarNet Communications Corp.
 * 550 Lakeside Dr. #3
 * Sunnyvale CA 94086 US
 * http://www.starnet.com
 * x-dos@starnet.com
 */

#include "pcdefs.h"
#include "protocol.h"
#include "funcdef.h"
#include "../defs.h"
#include "../funcs.h"
#include "config.h"
#include "tcp.h"

struct port _far *ports[NPORTS];

extern u_char myether[DADDLEN];
extern u_char etherall[DADDLEN];
extern int neterrno;
extern char net_inited;
int tcpmss;
extern char slip_mode;
extern u_short nnipident;
extern struct icmp icmpout;
extern struct tcp tcpout;
u_long nmask;		/* net (not sub-net) mask */

static short nextport = 1024;

int _fastcall
tcp_shutdown(arg, len, buf)
int arg, *len;
void _far *buf;
{
    struct tcpport _far *p;
    int i, wait, nports = 0;
    u_long time;

    dfputs("Please wait while network is shut down...");
    for (i = 0; i < NPORTS ; i++) {
	    p = (struct tcpport _far *)ports[i];
	    if (!p || !(p->flag & O_STREAM))
		    continue;
	    (void) SoClose(i);
	    p->inbase = 0;
	    p->infree = 0;
	    p->credit = WINDOWSIZE;
	    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 *)ports[i];
		    if (!p || !(p->flag & O_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 = 5;
			    }
		    }
	    }
    }
    net_inited = 0;
    pkclose();
    intr_exit();
    dfputs("\r\n");
    return 0;
}

int _fastcall
tcp_socket(arg, len, buf)
int arg, *len;
void _far *buf;
{
    int fd;
    struct port _far *p;
    struct net_addr addr = *(struct net_addr _far *)buf;

    for (fd = 0; fd < NPORTS; fd++)
	if (!ports[fd])
	    break;
    if (fd == NPORTS) {
	neterrno = NETERR_NOMEM;
	return -1;
    }
    switch (arg) {
	case NET_TYPE_TCP:
	    p = alloc_seg(sizeof (struct tcpport));
	    if (!p) {
		neterrno = NETERR_NOMEM;
		return -1;
	    }
	    ((struct tcpport _far *)p)->outpkt.i.protocol = PROTTCP;
	    ((struct tcpport _far *)p)->outfree = 0;
	    ((struct tcpport _far *)p)->outbase = 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] =
		htons(MAXSEG);
	    ((struct tcpport _far *)p)->outpkt.t.hlen =
		(sizeof(struct tcph) + 1 * sizeof(u_long) << 2) & 0xf0;
	    if (addr.socket)
		((struct tcpport _far *)p)->state = SLISTEN;
	    else
		((struct tcpport _far *)p)->state = SCLOSED;
	    ((struct tcpport _far *)p)->credit = WINDOWSIZE;
	    ((struct tcpport _far *)p)->lasttime = WRAPTIME - 1L;
	    ((struct tcpport _far *)p)->outsize = tcpmss;
	    ((struct tcpport _far *)p)->rto = MINRTO;
	    ((struct tcpport _far *)p)->resends = 0;
	    p->flag = O_STREAM;
	    break;
	case NET_TYPE_UDP:
	    p = alloc_seg(sizeof (struct udpport));
	    if (!p) {
		neterrno = NETERR_NOMEM;
		return -1;
	    }
	    ((struct udpport _far *)p)->outpkt.u.source = htons(addr.socket);
	    ((struct udpport _far *)p)->outpkt.i.protocol = PROTUDP;
	    p->flag = 0;
	    break;
	default:
	    neterrno = NETERR_BADARGS;
	    dfputs("unknown socket type\r\n");
	    return -1;
    }
    ports[fd] = p;
    p->fd = (u_char)fd;
    p->infree = 0;
    p->inbase = 0;
    p->flag |= O_OPEN;
    /* bind to port */
    if (addr.socket) {
	p->inport = htons(addr.socket);
    } else {
	p->inport = htons(nextport);
	if (nextport++ < 0)
		nextport = 1024;
    }
    if (p->flag & O_STREAM)
	((struct tcpport _far *)p)->outpkt.t.source = p->inport;
    else
	((struct udpport _far *)p)->outpkt.u.source = p->inport;
    p->outport = 0;
    /*
    p->fromip = addr.host;
    */
    memcpy(p->outpkt.d.me, myether, DADDLEN);
    p->outpkt.d.type = EIP;
    p->outpkt.i.versionandhdrlen = 0x40 | sizeof(struct iph) >> 2;
    p->outpkt.i.service = 0;
    p->outpkt.i.ttl = 100;
    p->outpkt.i.frags = 0;
    p->outpkt.i.ipsource = Scon.myip;
    p->outpkt.i.ipdest = addr.host;

    *(struct net_addr _far *)buf = addr;
    *len = sizeof (struct net_addr);
    return fd;
}

int _fastcall
tcp_release(fd, len, buf)
int fd, *len;
void _far *buf;
{
    struct tcpport _far *p;

    if (fd >= NPORTS) {
	neterrno = NETERR_BADFD;
	return -1;
    }
    p = (struct tcpport _far *)ports[fd];
    if (!p) {
	neterrno = NETERR_BADFD;
	return -1;
    }
    if (p->flag & O_STREAM && p->state >= SEST) {
	p->flag |= O_RELEASED;
    } else {
	dealloc_seg((u_short)((u_long)p >> 16));
	ports[fd] = 0;
    }
    return 0;
}

int _fastcall
tcp_select(nfds, len, buf)
int nfds, *len;
void _far *buf;
{
    int i, n;
    u_long iflags = ((u_long _far *)buf)[0];
    u_long oflags = ((u_long _far *)buf)[1];
    u_long ifds, ofds, mask;
    struct tcpport _far *p;

    ifds = 0;
    ofds = 0;
    n = 0;
    for (i = 0, mask = 1; i < NPORTS; i++, mask <<= 1) {
	if (!((iflags | oflags) & mask))
	    continue;
	p = (struct tcpport _far *)ports[i];
	if (!p || !(p->flag & O_OPEN)) {
	    neterrno = NETERR_BADFD;
	    continue;
	}
	if (iflags & mask) {
	    if (p->inbase != p->infree || p->flag & O_GOTALL) {
		ifds |= mask;
		n++;
	    }
	}
	if (oflags & mask) {
	    if (!(p->flag & O_STREAM) ||
		p->state >= SEST && p->outfree < WINDOWSIZE) {
		    ofds |= mask;
		    n++;
		}
	}
    }

    ((u_long _far *)buf)[0] = ifds;
    ((u_long _far *)buf)[1] = ofds;
    return n;
}

int _fastcall
tcp_sendto(fd, len, buf)
int fd, *len;
void _far *buf;
{
    struct sendto_head head = *(struct sendto_head _far *)buf;
    int n;
    struct udpport _far *p = (struct udpport _far *)ports[fd];
    u_char *pc;
    struct pseudotcp otcps;

    if (fd >= NPORTS || (p->flag & (O_STREAM|O_OPEN)) != O_OPEN) {
	neterrno = NETERR_BADFD;
	return -1;
    }
    buf = (char _far *)buf + sizeof (struct sendto_head);
    *len -= sizeof (struct sendto_head);
    if (!head.addr.host)
	head.addr.host = Scon.broadcast;

    if (*len > UMAXLEN)
	    *len = UMAXLEN;

    if (head.addr.host != p->outpkt.i.ipdest) {
	p->outpkt.i.ipdest = head.addr.host;
	if (!slip_mode) {
		if (head.addr.host == Scon.broadcast)
			pc = etherall;
		else {
			head.addr.host = find_route(head.addr.host);
			if (!head.addr.host) {
			    neterrno = NETERR_NETUNREACH;
			    return -1;
			}
			pc = netdlayer(head.addr.host);
			if (!pc) {
			    neterrno = NETERR_TIMEOUT;
			    return -1;
			}
		}
		memcpy(p->outpkt.d.dest, pc, DADDLEN);
	}
    }

    memcpy(&otcps, &p->outpkt.i.ipsource, 2 * sizeof(u_long));
    otcps.proto = PROTUDP;
    otcps.z = 0;
    p->outpkt.u.dest = htons(head.addr.socket);
    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 _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);
/*
*  send it
*/
    n += sizeof (struct ether);
    n = pkxmit(&p->outpkt, n);
    if (n) {
	neterrno = NETERR_SYSTEM;
	return -1;
    }
    return *len;
}

void
protinit()
{
    if (Scon.myip)
	set_netmask();

    memcpy(icmpout.d.me, myether, DADDLEN);
    memcpy(icmpout.d.dest, etherall, DADDLEN);	/* for BOOTP */
    icmpout.d.type = EIP;
    icmpout.i.versionandhdrlen = 0x40 | sizeof(struct iph) >> 2;
    icmpout.i.service = 0;
    icmpout.i.frags = 0;
    icmpout.i.ttl = 100;
    icmpout.i.protocol = PROTICMP;
    icmpout.i.ipsource = Scon.myip;
    icmpout.i.ipdest = Scon.broadcast;		/* for BOOTP */
    tcpout.d = icmpout.d;
    tcpout.i = icmpout.i;
    tcpout.i.protocol = PROTTCP;
    tcpout.t.urgent = 0;
    if (slip_mode)
	tcpmss = SLIPSENDSIZE;
    else
	tcpmss = TSENDSIZE;
}

int _fastcall
tcp_recv(fd, len, buf)
int fd, *len;
void _far *buf;
{
    struct udpport _far *p = (struct udpport _far *)ports[fd];
    u_short n;
    u_char _far *cp;

    if (fd >= NPORTS || (p->flag & (O_STREAM|O_OPEN)) != O_OPEN) {
	neterrno = NETERR_BADFD;
	return -1;
    }
    if (p->infree == p->inbase) {
	neterrno = NETERR_WOULD_BLOCK;
	return -1;
    }
    cp = &p->datain[p->inbase];
    n = *(short _far *)cp;
    cp += sizeof (u_short);
    ((struct net_addr _far *)buf)->socket = ntohs(*(u_short _far *)cp);
    cp += sizeof (u_short);
    ((struct net_addr _far *)buf)->host = *(u_long _far *)cp;
    cp += sizeof (u_long);
    buf = (char _far *)buf + sizeof (struct net_addr);
    if (n > *len - sizeof (struct net_addr))
	n = *len - sizeof (struct net_addr);
    memcpy(buf, cp, n);
    *len = n + sizeof (struct net_addr);
    p->inbase += (n + sizeof(u_long) + 2 * sizeof(u_short));
    if (p->inbase == p->infree) {
	p->inbase = 0;
	p->infree = 0;
    }
    return n;
}

int _fastcall
tcp_accept(fd, len, buf)
int fd, *len;
void _far *buf;
{
    struct tcpport _far *p = (struct tcpport _far *)ports[fd];

    if (fd >= TCPPORTS || !p || !(p->flag & O_OPEN)) {
	neterrno = NETERR_BADFD;
	return -1;
    }
    ((struct net_addr _far *)buf)->socket = ntohs(p->outpkt.t.dest);
    ((struct net_addr _far *)buf)->host = p->outpkt.i.ipdest;
    *len = sizeof (struct net_addr);
    return fd;
}

void
free_port(p)
struct port _far *p;
{
    int fd = p->fd;

/*
    p->flags = 0;
*/
    dealloc_seg((u_short)((u_long)p >> 16));
    ports[fd] = 0;
}
