/*
 *	New hardware independet SLIP interface implementation using
 *	the pseudo line discipline stuff in serial.c. This way SLIP
 *	can use the Mint device drivers. A device driver is linked
 *	to a SLIP interface using the SIOCSIFLINK ioctl, the device
 *	driver linked to an interface can be queried with SIOCGIFNAME.
 *	Iflink(8) implements this behavior.
 *
 *	06/12/94, Kay Roemer.
 */

#include <string.h>
#include "config.h"
#include "netinfo.h"
#include "kerbind.h"
#include "atarierr.h"
#include "sockerr.h"
#include "sockios.h"
#include "serial.h"
#include "in.h"
#include "if.h"
#include "buf.h"
#include "util.h"
#include "slcompress.h"

#define SLIP_MTU	2000		/* def. maximum transmission unit */
#define SLIP_CHANNELS	4		/* number of SLIP units */

#define ESC		219		/* escape char */
#define END		192		/* end of frame char */
#define ESC_ESC		221		/* escaped esc */
#define ESC_END		220		/* escaped end */

struct slstats {
	unsigned long	i_packets;
	unsigned long	i_toshort;
	unsigned long	i_tolong;
	unsigned long	i_badcode;
	unsigned long	i_badvj;
	unsigned long	i_nomem;
	unsigned long	i_qfull;
	unsigned long	o_packets;
	unsigned long	o_qfull;
};

struct slip {
	struct netif	*nif;		/* backlink to interface */
	short		flags;		/* misc flags */
#define	SLF_ESC		0x01		/* next char is escaped */
#define SLF_DROP	0x02		/* drop this packet */
#define SLF_LINKED	0x04		/* interface is linked to device */
#define SLF_COMPRESS	0x08		/* turn on VJ compression */
#define SLF_AUTOCOMP	0x10		/* turn on VJ comp. when receiving
					   TCP_UNCOMPRESSED frame */
#define SLF_USRMASK	(SLF_COMPRESS|SLF_AUTOCOMP)

	BUF		*snd_buf;	/* BUF currently beeing sent */
	char		*snd_head;	/* head pointer */
	char		*snd_tail;	/* tail pointer */

	BUF		*rcv_buf;	/* BUF currently beeing received */
	char		*rcv_head;	/* head pointer */
	char		*rcv_tail;	/* tail pointer */

	struct slbuf	*slbuf;		/* serial buffer */
	struct slcompress *comp;	/* states for VJ compression */
	struct slstats	stat;
};

static struct netif	slip_chan[SLIP_CHANNELS];
static struct slip	slip_priv[SLIP_CHANNELS];

static long	slip_link	(struct iflink *);
static long	slip_open	(struct netif *);
static long	slip_close	(struct netif *);
static long	slip_output	(struct netif *, BUF *, char *, short, short);
static long	slip_ioctl	(struct netif *, short, long);
static short	slip_send	(struct slbuf *);
static short	slip_recv	(struct slbuf *);

long
driver_init (void)
{
	extern long ppp_init (void);
	extern long slip_init (void);

	serial_init ();
	return (slip_init () || ppp_init ());
}

static long
slip_link (ifl)
	struct iflink *ifl;
{
	short i;

	for (i = 0; i < SLIP_CHANNELS; ++i) {
		if (!(slip_priv[i].flags & SLF_LINKED))
			break;
	}
	if (i > SLIP_CHANNELS) {
		DEBUG (("slip_link: no free slip channels"));
		return ENSMEM;
	}

	if (slip_priv[i].snd_buf)
		buf_deref (slip_priv[i].snd_buf, BUF_NORMAL);

	if (slip_priv[i].rcv_buf)
		buf_deref (slip_priv[i].rcv_buf, BUF_NORMAL);

	slip_priv[i].snd_buf  = 0;
	slip_priv[i].snd_head = 0;
	slip_priv[i].snd_tail = 0;
	slip_priv[i].rcv_buf  = 0;
	slip_priv[i].rcv_head = 0;
	slip_priv[i].rcv_tail = 0;
	slip_priv[i].comp = slc_init ();
	if (slip_priv[i].comp == 0) {
		DEBUG (("slip_link: no space for VJ compression states"));
		return ENSMEM;
	}
	slip_priv[i].slbuf = serial_open (&slip_chan[i], ifl->device,
		slip_send, slip_recv);

	if (slip_priv[i].slbuf == 0) {
		DEBUG (("slip_link: no free serial channels"));
		slc_free (slip_priv[i].comp);
		slip_priv[i].comp = 0;
		return ENSMEM;
	}
	slip_priv[i].flags |= SLF_LINKED;
	sprintf (ifl->ifname, "%s%d", slip_chan[i].name, slip_chan[i].unit);
	return 0;
}

static long
slip_open (nif)
	struct netif *nif;
{
	struct slip *slp = nif->data;

	if (!(slp->flags & SLF_LINKED)) {
		DEBUG (("slip_open: chan %d not linked to device", nif->unit));
		return EUNDEV;
	}
	return 0;
}

static long
slip_close (nif)
	struct netif *nif;
{
	struct slip *slp = nif->data;
	long r = 0;

	if (slp->flags & SLF_LINKED) {
		r = serial_close (slp->slbuf);
		if (r < 0) {
			DEBUG (("slip_close: cannot close serial channel"));
			return r;
		}
		slc_free (slp->comp);
		slp->comp = 0;
		slp->flags &= ~SLF_LINKED;
	}
	return 0;
}

static long
slip_output (nif, buf, hwaddr, hwlen, pktype)
	struct netif *nif;
	BUF *buf;
	char *hwaddr;
	short hwlen, pktype;
{
	struct slip *slp = nif->data;
	long r;

	if ((r = if_enqueue (&nif->snd, buf, buf->info))) {
		++slp->stat.o_qfull;
		++nif->out_errors;
		DEBUG (("slip_output: chan %d: cannot enqueue", nif->unit));
		return r;
	}
	return serial_send (slp->slbuf);
}

static long
slip_ioctl (nif, cmd, arg)
	struct netif *nif;
	short cmd;
	long arg;
{
	struct slip *slp = nif->data;
	struct iflink *ifl;
	struct ifreq *ifr;
	long *l;

	switch (cmd) {
	case SIOCSIFLINK:
		return slip_link ((struct iflink *)arg);

	case SIOCGIFNAME:
		ifl = (struct iflink *)arg;
		if (!(slp->flags & SLF_LINKED)) {
			DEBUG (("slip_ioctl: IFLINK: chan %d not linked",
				nif->mtu));
			return EINVAL;
		}
		strncpy (ifl->device, slp->slbuf->dev, sizeof (ifl->device));
		return 0;

	case SIOCSIFFLAGS:
	case SIOCSIFADDR:
	case SIOCSIFNETMASK:
		return 0;

	case SIOCGLNKFLAGS:
		ifr = (struct ifreq *)arg;
		ifr->ifru.flags = slp->flags & SLF_USRMASK;
		return 0;

	case SIOCSLNKFLAGS:
		ifr = (struct ifreq *)arg;
		slp->flags &= ~SLF_USRMASK;
		slp->flags |= ifr->ifru.flags & SLF_USRMASK;
		return 0;

	case SIOCGLNKSTATS:
		ifr = (struct ifreq *)arg;
		l = ifr->ifru.data;
		memset (l, 0, 40*sizeof (long));
		memcpy (l, &slp->stat, sizeof (slp->stat));
		l += sizeof (slp->stat) / sizeof (*l);
		if (slp->comp)
			slc_getstats (slp->comp, l);
		return 0;
	}
	return EINVFN;
}

long
slip_init (void)
{
	short i;

	c_conws ("SLIP v0.5, four channels\n\r");
	for (i = 0; i < SLIP_CHANNELS; ++i) {
		strcpy (slip_chan[i].name, "sl");
		slip_chan[i].unit = i;
		slip_chan[i].metric = 0;
		slip_chan[i].flags = IFF_POINTOPOINT;
		slip_chan[i].mtu = SLIP_MTU;
		slip_chan[i].timer = 0;
		slip_chan[i].hwtype = HWTYPE_NONE;

		slip_chan[i].rcv.maxqlen = IF_MAXQ;
		slip_chan[i].snd.maxqlen = IF_MAXQ;
		slip_chan[i].open = slip_open;
		slip_chan[i].close = slip_close;
		slip_chan[i].output = slip_output;
		slip_chan[i].ioctl = slip_ioctl;
		slip_chan[i].timeout = 0;

		slip_chan[i].data = &slip_priv[i];
		slip_priv[i].nif = &slip_chan[i];

		if_register (&slip_chan[i]);
	}
	return 0;
}

static inline short
uncompress (b, slp)
	BUF *b;
	struct slip *slp;
{
	unsigned char type;

	type = slc_type (b);
	switch (type) {
	case TYPE_IP:
		return 0;

	case TYPE_COMPRESSED_TCP:
		if (slp->flags & SLF_COMPRESS) {
			return !slc_uncompress (b, type, slp->comp);
		}
		break;

	case TYPE_UNCOMPRESSED_TCP:
		if (slp->flags & SLF_COMPRESS) {
			return !slc_uncompress (b, type, slp->comp);
		}
		if (slp->flags & SLF_AUTOCOMP &&
		    slc_uncompress (b, type, slp->comp)) {
			slp->flags |= SLF_COMPRESS;
			return 0;
		}
		break;
	}
	return 1;
}

static short
slip_recv (slb)
	struct slbuf *slb;
{
#define SL_GETC() ({ char _c = buf[tail++]; tail &= mask; _c; })
	struct slip *slp = slb->nif->data;
	short tail, mask = slb->size - 1;
	long space, nbytes;
	unsigned char c;
	char *cp, *buf;
	BUF *b;

	nbytes = SL_IUSED (slb);
	while (nbytes > 0) {
		if (slp->rcv_buf == 0) {
			b = buf_alloc (slb->nif->mtu+MAX_HDR+1, MAX_HDR, BUF_ATOMIC);
			if (!b) {
				++slp->stat.i_nomem;
				return 0;
			}
			slp->rcv_buf = b;
			slp->rcv_head = b->dstart + slb->nif->mtu + 1;
			slp->rcv_tail = b->dstart;
		}
		cp = slp->rcv_tail;
		space = slp->rcv_head - cp;
		buf = slb->ibuf;
		tail = slb->itail;
		while (space > 0 && nbytes > 0) {
			if (nbytes > space)
				nbytes = space;

			while (--nbytes >= 0) switch ((c = SL_GETC ())) {
			case ESC:
				slp->flags |= SLF_ESC;
				break;

			case END:
				b = slp->rcv_buf;
				if (slp->flags & SLF_DROP || cp-b->dstart < 3) {
					if (cp-b->dstart < 3)
						++slp->stat.i_toshort;
					slp->flags &= ~(SLF_ESC|SLF_DROP);
					slp->rcv_tail = cp = b->dstart;
					break;
				}
				b->dend = cp;
				if (uncompress (b, slp)) {
					++slp->stat.i_badvj;
					++slb->nif->in_errors;
					slp->flags &= ~(SLF_ESC|SLF_DROP);
					slp->rcv_tail = cp = b->dstart;
					break;
				}
				if (if_input (slb->nif, b, 0, PKTYPE_IP)) {
					++slp->stat.i_qfull;
					++slb->nif->in_errors;
				} else {
					++slp->stat.i_packets;
					++slb->nif->in_packets;
				}

				slp->flags &= ~(SLF_ESC|SLF_DROP);
				slp->rcv_buf = 0;
				slb->itail = tail;
				nbytes = SL_IUSED (slb);
				goto newpacket;

			case ESC_ESC:
				*cp++ = (slp->flags & SLF_ESC) ? ESC : c;
				slp->flags &= ~SLF_ESC;
				break;

			case ESC_END:
				*cp++ = (slp->flags & SLF_ESC) ? END : c;
				slp->flags &= ~SLF_ESC;
				break;

			default:
				*cp++ = c;
				slp->flags &= ~SLF_ESC;
				break;
			}
			slb->itail = tail;
			nbytes = SL_IUSED (slb);
			space = slp->rcv_head - cp;
		}
		if (nbytes > 0) {
			++slp->stat.i_tolong;
			++slb->nif->in_errors;
			slp->flags |= SLF_DROP;
			slp->rcv_tail = cp = slp->rcv_buf->dstart;
		} else	slp->rcv_tail = cp;
newpacket:
	}
	return 0;
}

static short
slip_send (slb)
	struct slbuf *slb;
{
#define SL_PUTC(_c) { buf[head++] = _c; head &= mask; }
	struct slip *slp = slb->nif->data;
	short head, mask = slb->size - 1;
	long space, nbytes;
	unsigned char c;
	char *cp, *buf;
	BUF *b;

	space = SL_OFREE (slb) / 2;
	while (space > 0) {
		buf = slb->obuf;
		head = slb->ohead;
		if (slp->snd_buf == 0) {
			slp->snd_buf = b = if_dequeue (&slp->nif->snd);
			if (b == 0)
				return 0;

			if (slp->flags & SLF_COMPRESS &&
			    IP_PROTO (b) == IPPROTO_TCP)
				*b->dstart |= slc_compress (b, slp->comp, 1);

			slp->snd_head = b->dend;
			slp->snd_tail = b->dstart;
			SL_PUTC (END);
			--space;
		}
		cp = slp->snd_tail;
		nbytes = slp->snd_head - cp;
		while (nbytes > 0 && space > 0) {
			if (space > nbytes)
				space = nbytes;

			while (--space >= 0) switch ((c = *cp++)) {
			case ESC:
				SL_PUTC (ESC);
				SL_PUTC (ESC_ESC);
				break;

			case END:
				SL_PUTC (ESC);
				SL_PUTC (ESC_END);
				break;

			default:
				SL_PUTC (c);
				break;
			}
			slb->ohead = head;
			space = SL_OFREE (slb) / 2;
			nbytes = slp->snd_head - cp;
		}
		if (space > 0) {
			++slp->stat.o_packets;
			++slb->nif->out_packets;
			buf_deref (slp->snd_buf, BUF_ATOMIC);
			slp->snd_buf = 0;
			SL_PUTC (END);
			--space;
		}
		slb->ohead = head;
		slp->snd_tail = cp;
	}
	return 0;
}
