/* slip.c		SLIP interface unit
 *
 * $Id: slip.c,v 1.11 1995/03/19 17:21:06 bdale Exp $
 *
 * Copyright 1991, Michael Westerhof, Sun Microsystems, Inc.
 * This software may be freely used, distributed, or modified, providing
 * this header is not removed.
 */

#include "ipip.h"

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <fcntl.h>
#include <memory.h>
#include <stdio.h>
#include <string.h>
#ifdef __bsdi__
# include <stdlib.h>
#else
# include <malloc.h>
#endif
#include <errno.h>
#include <syslog.h>

#ifdef __bsdi__
#define USE_TERMIOS
#endif

#ifndef USE_TERMIOS
#ifndef USE_TERMIO
#define USE_SGTTY
#endif
#endif

#ifdef USE_TERMIOS
# include <termios.h>
# include <unistd.h>
#endif

#ifdef USE_TERMIO
#include <sys/termio.h>
#endif

#ifdef USE_SGTTY
#include <sgtty.h>
#endif

#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif

extern int errno;

#define IF_NAME "slip"		/* for use with the error checking macros */

/*
 * Define the special characters used by SLIP
 */

#define FEND  0xc0
#define FESC  0xdb
#define TFEND 0xdc
#define TFESC 0xdd

/*
 * This is the routine that assembles a slip packet, and the private
 * data structure we need to keep track of where we are.
 */

static int assemble_slip();
struct slippy {
	unsigned char buffer[MAX_SIZE];		/* buffer from the serial line */
	int bcount;				/* number of total chars in buffer */
	int bnext;				/* next character to process */
	unsigned char ipacket[MAX_SIZE];	/* the packet we are assembling */
	int ifcount;				/* total size of assembled packet */
	int iescaped;				/* flag set if we are escaped */
};

/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * open and initialize the IO interface.  Return -1 for error.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
int slip_open(ifp)
struct interface *ifp;
{
	int baudrate;
#ifdef USE_TERMIOS
	struct termios nterm;
#endif
#ifdef USE_TERMIO
	struct termio  nterm;
#endif
#ifdef USE_SGTTY
	struct sgttyb  nterm;
#endif

	CK_IFNULL(ifp);
	CK_IFTYPE(ifp,IF_TYPE_SLIP);

	if((ifp->status & IF_STAT_OPEN)) return 1;

#ifdef __bsdi__
	/* BSDI cares about the modem control lines, so this won't ever 
	   come back unless we do the open non-blocking, then set CLOCAL. */
	ifp->fd = open(ifp->devname, O_RDWR | O_NONBLOCK);
#else
	ifp->fd = open(ifp->devname, O_RDWR);
#endif
	if (ifp->fd<0) {
		PERR(ifp->devname);
		return -1;
	}

	if (fcntl(ifp->fd, F_SETFL, FNDELAY) < 0) {
		PERR("setting non-blocking I/O on tty device");
		return -1;
	}

#ifdef USE_TERMIOS
#if defined(__bsdi__) || defined(linux)
	if(tcgetattr(ifp->fd, &nterm)<0){
#else
	if(ioctl(ifp->fd, TCGETS, &nterm)<0){
#endif /* __bsdi__ */
#endif
#ifdef USE_TERMIO
	if(ioctl(ifp->fd, TCGETA, &nterm)<0){
#endif
#ifdef USE_SGTTY
	if(ioctl(ifp->fd, TIOCGETP, &nterm)<0){
#endif
		PERR("fetching tty device parameters");
		return -1;
	}

	if(ifp->unit==50)baudrate=B50;
	else if(ifp->unit==50)baudrate=B50;
	else if(ifp->unit==75)baudrate=B75;
	else if(ifp->unit==110)baudrate=B110;
	else if(ifp->unit==134)baudrate=B134;
	else if(ifp->unit==150)baudrate=B150;
	else if(ifp->unit==200)baudrate=B200;
	else if(ifp->unit==300)baudrate=B300;
	else if(ifp->unit==600)baudrate=B600;
	else if(ifp->unit==1200)baudrate=B1200;
	else if(ifp->unit==1800)baudrate=B1800;
	else if(ifp->unit==2400)baudrate=B2400;
	else if(ifp->unit==4800)baudrate=B4800;
	else if(ifp->unit==9600)baudrate=B9600;
#ifdef B19200
	else if(ifp->unit==19200)baudrate=B19200;
#else
#ifdef EXTA
	else if(ifp->unit==19200)baudrate=EXTA;
#endif
#endif
#ifdef B38400
	else if(ifp->unit==38400)baudrate=B38400;
#else
#ifdef EXTB
	else if(ifp->unit==38400)baudrate=EXTB;
#endif
#endif
	else baudrate = B9600;

#ifdef USE_SGTTY
	nterm.sg_flags = (RAW | ANYP);
	nterm.sg_ispeed = baudrate;
	nterm.sg_ospeed = baudrate;
#else
        nterm.c_iflag = 0;
        nterm.c_oflag = 0;
	nterm.c_cflag = baudrate | CS8 | CREAD | CLOCAL;
	nterm.c_lflag = 0;
        nterm.c_cc[VMIN] = 0;
        nterm.c_cc[VTIME] = 0;
#endif

#ifdef USE_TERMIOS
#if defined(__bsdi__) || defined(linux)
	if(tcsetattr(ifp->fd, TCSANOW, &nterm)<0){
#else
	if(ioctl(ifp->fd, TCSETS, &nterm)<0){
#endif /* __bsdi__ */
#endif
#ifdef USE_TERMIO
	if(ioctl(ifp->fd, TCSETA, &nterm)<0){
#endif
#ifdef USE_SGTTY
	if(ioctl(ifp->fd, TIOCSETP, &nterm)<0){
#endif
		PERR("setting tty device parameters");
		return -1;
	}

	if(ifp->private==NULL)ifp->private = malloc(sizeof(struct slippy));
	if(ifp->private==NULL){
		syslog(LOG_ERR,"cannot allocate private data structure (slip)");
		return -1;
	}

	(void)memset(ifp->private, 0, sizeof(struct slippy));

	ifp->status = IF_STAT_OPEN;
	return 1;
}

/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Read data from the specified interface. Return a complete IP datagram.
 * If the datagram is not complete, then don't return anything.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
int slip_read(ifp, m)
struct interface *ifp;
struct message *m;
{
	int n;
	struct slippy *s;

	CK_IFNULL(ifp);
	CK_IFTYPE(ifp,IF_TYPE_SLIP);
	CK_IFOPEN(ifp);
	CK_MNULL(m);

	s = (struct slippy *)ifp->private;
	ifp->status &= ~IF_STAT_CALL_AGAIN;
	m->length = 0;

	for(;;){
		if(s->bnext >= s->bcount){	/* we need more data! */
			s->bnext = 0;
			s->bcount = 0;
			n = read(ifp->fd, (char *)s->buffer, MAX_SIZE);
			if(n==0)return 0;	/* got nothing */
			if(n<0){
				if(errno==EINTR)return 0;	/* SIGHUP! */
				if(errno==EWOULDBLOCK)return 0; /* got nothing */
				PERR("read from tty device");
				return -1;
			}
			s->bcount = n;
		}

		n = assemble_slip(s, s->buffer[s->bnext]);
		s->bnext++;

		if(n > 0){
			(void)memcpy((char *)m->msg, (char *)s->ipacket, n);
			m->length = n;
			if(s->bnext < s->bcount)
				ifp->status |= IF_STAT_CALL_AGAIN;
			return n;
		}
	}
}

/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * write data from to the specified interface. Return as soon as possible.
 * The buffer provided will be a complete IP datagram.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */

#define SLIPEMIT(x) if(ofcount<MAX_SIZE*2){*ofptr=(x);ofptr++;ofcount++;}

int slip_send(ifp, m)
struct interface *ifp;
struct message *m;
{
	int n, i, ofcount;
	unsigned char opacket[MAX_SIZE*2], *ofptr, *mptr;

	CK_IFNULL(ifp);
	CK_IFTYPE(ifp,IF_TYPE_SLIP);
	CK_IFOPEN(ifp);
	CK_MNULL(m);

	if(m->length<=0)return 0;

	ofptr = opacket;
	ofcount = 0;
	mptr = m->msg;

	SLIPEMIT(FEND);

	for(i=0;i<m->length;i++,mptr++){
		if(*mptr==FEND){
			SLIPEMIT(FESC);
			SLIPEMIT(TFEND);
		}else if (*mptr==FESC){
			SLIPEMIT(FESC);
			SLIPEMIT(TFESC);
		}else {
			SLIPEMIT(*mptr);
		}
	}

	SLIPEMIT(FEND);

/*
 * WARNING!  It is possible for this write to actually write less data
 * than expected if the system has, for example, no buffer space left.
 * We ignore the error (just increment the write overrun counter),
 * drop the current packet, and expect that the higher level protocols
 * will recover.
 *
 * If we got an "interrupted system call" error we print the diagnostic
 * and continue.  (the packet gets dropped as above).
 */
	n = write(ifp->fd, (char *)opacket, ofcount);
	if(n<0){
		syslog(LOG_ERR,"slip_send(): %s",strerror(errno));
		if((errno==EINTR) || (errno==EAGAIN)) return 0;
		return -1;
	}
	if(n < ofcount)ifp->out_overruns++;
	return n;
}

/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * take a character and assemble it into the buffer.  Return the length
 * of the completed packet, 0 if not yet completed.  Packet can be found
 * in the private buffer...
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
static int
assemble_slip(s, c)
struct slippy *s;
unsigned char c;
{
	int n;

	if(c==FEND){
		n = s->ifcount;
		s->ifcount = 0;
		s->iescaped = 0;
		return n;
	}

	if(c==FESC){
		s->iescaped=1;
		return 0;
	}

	if(s->iescaped){
		if(c==TFEND)c = FEND;
		if(c==TFESC)c = FESC;
		s->iescaped = 0;
	}
	if(s->ifcount < MAX_SIZE){
		s->ipacket[s->ifcount] = c;
		s->ifcount++;
	}
	return 0;
}
