/* This module implements the serial line framing method used by
 * net/rom nodes.  This allows the net/rom software to talk to
 * an actual net/rom over its serial interface, which is useful
 * if we want to do packet switching for multi-line wormholes.
 * Dan Frank, W9NK
 */
#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "ax25.h"
#include "nrs.h"
#include "asy.h"
#include "combios.h"
#include "trace.h"

/* control structures, sort of overlayed on async control blocks */
struct nrs nrs[ASY_MAX];

/* Send a raw net/rom serial frame */
nrs_raw(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	dump(interface,IF_TRACE_OUT,TRACE_AX25,bp);

	/* Queue a frame on the output queue and start transmitter */
	nrsq(interface->dev,bp);
}

/* Encode a raw packet in net/rom framing, put on link output queue, and kick
 * transmitter
 */
static
nrsq(dev,bp)
int16 dev;		/* Serial line number */
struct mbuf *bp;	/* Buffer to be sent */
{
	register struct nrs *sp;
	struct mbuf *nrs_encode();

	if((bp = nrs_encode(bp)) == NULLBUF)
		return;

	sp = &nrs[dev];
	enqueue(&sp->sndq,bp);
	sp->sndcnt++;
	if(sp->tbp == NULLBUF)
		nrasy_start(dev);
}

/* Start output, if possible, on asynch device dev */
static
nrasy_start(dev)
int16 dev;
{
	register struct nrs *sp;
	int asy_output();
#ifdef	COMBIOS
	struct com *cp;
	unsigned char c;
	unsigned stat;
	int numsend;
#endif

#ifdef	COMBIOS
	com[dev].stat |= (stat = combios(0x0300,dev));
	if(!(stat & 0x2000))
#else
	if(!stxrdy(dev))
#endif
		return;		/* Transmitter not ready */

	sp = &nrs[dev];

	if(sp->tbp != NULLBUF){
#ifdef	COMBIOS
		goto comsend;	/* still something to send */
#else
		/* transmission just completed */
		free_p(sp->tbp);
		sp->tbp = NULLBUF;
#endif
	}

	if(sp->sndq == NULLBUF)
		return; /* No work */

	sp->tbp = dequeue(&sp->sndq);
	sp->sndcnt--;

#ifdef	COMBIOS
comsend:
	cp = &com[dev];				/* quick ptr to our com */

	if ((numsend = cp->txbuf) == 0)
		numsend = cp->speed;		/* enough for 10 seconds */

	while(--numsend && pullup(&sp->tbp,&c,1) == 1) {
		cp->stat |= (stat = combios(0x0100 | c,dev)) & 0x7f00;
		if (cp->txbuf == 0 &&		/* no explicit tx buffer max */
		    (stat & 0xa000) != 0x2000)	/* timeout or full */
			break;
	}
#else
	asy_output(dev,sp->tbp->data,sp->tbp->cnt);
#endif
}

/* Encode a packet in net/rom serial format */
static
struct mbuf *
nrs_encode(bp)
struct mbuf *bp;
{
	struct mbuf *lbp;	/* Mbuf containing line-ready packet */
	register char *cp;
	char c;
	unsigned char csum = 0;

	/* Allocate output mbuf that's twice as long as the packet.
	 * This is a worst-case guess (consider a packet full of STX's!)
	 * Add five bytes for STX, ETX, checksum, and two nulls.
	 */
	lbp = alloc_mbuf(2*len_mbuf(bp) + 5);
	if(lbp == NULLBUF){
		/* No space; drop */
		free_p(bp);
		return NULLBUF;
	}
	cp = lbp->data;

	*cp++ = STX;

	/* Copy input to output, escaping special characters */
	while(pullup(&bp,&c,1) == 1){
		switch(uchar(c)){
		case STX:
		case ETX:
		case DLE:
			*cp++ = DLE;
			/* notice drop through to default */
		default:
			*cp++ = c;
		}
		csum += uchar(c);
	}
	*cp++ = ETX;
	*cp++ = csum;
	*cp++ = NUL;
	*cp++ = NUL;

	lbp->cnt = cp - lbp->data;
	return lbp;
}
/* Process incoming bytes in net/rom serial format
 * When a buffer is complete, return it; otherwise NULLBUF
 */
static
struct mbuf *
nrs_decode(dev,c)
int16 dev;	/* net/rom unit number */
char c;		/* Incoming character */
{
	struct mbuf *bp;
	register struct nrs *sp;

	sp = &nrs[dev];
	switch(sp->state) {
		case NRS_INTER:
			if (uchar(c) == STX) {	/* look for start of frame */
				sp->state = NRS_INPACK; /* we're in a packet */
				sp->csum = 0;		/* reset checksum */
#ifdef	COMBIOS
				com[dev].stat = 0;	/* reset error bits */
#endif
			}
			return NULLBUF;

		case NRS_CSUM:
			bp = sp->rbp;
			sp->rbp = NULLBUF;
			sp->rcnt = 0;
			sp->state = NRS_INTER;	/* go back to inter-packet state */
			if (sp->csum == uchar(c)	/* checksum ok */
#ifdef	COMBIOS
			    && !(com[dev].stat & 0x1a00) /* no overruns etc */
#endif
			   ) {
				sp->packets++;
				return bp;
			}
			free_p(bp);		/* drop packet with bad checksum */
			sp->errors++;		/* increment error count */
			return NULLBUF;

		case NRS_ESCAPE:
			sp->state = NRS_INPACK; /* end of escape */
			break;			/* this will drop through to char processing */

		case NRS_INPACK:
			switch (uchar(c)) {
				/* If we see an STX in a packet, assume that previous */
				/* packet was trashed, and start a new packet */
				case STX:
					free_p(sp->rbp);
					sp->rbp = NULLBUF;
					sp->rcnt = 0;
					sp->csum = 0;
#ifdef	COMBIOS
					com[dev].stat = 0;
#endif
					sp->errors++;
					return NULLBUF;

				case ETX:
					sp->state = NRS_CSUM;  /* look for checksum */
					return NULLBUF;

				case DLE:
					sp->state = NRS_ESCAPE;
					return NULLBUF;
			}
	}
#ifdef	COMBIOS
	if (com[dev].stat & 0x1a00)		/* recv error detected? */
		return NULLBUF;			/* don't waste buffers */
#endif

	/* If we get to here, it's with a character that's part of the packet.
	 * Make sure there's space for it.
	 */
	if(sp->rbp == NULLBUF){
		/* Allocate first mbuf for new packet */
		if((sp->rbp1 = sp->rbp = alloc_mbuf(NRS_ALLOC)) == NULLBUF) {
			sp->state = NRS_INTER;
			return NULLBUF; /* No memory, drop */
		}
		sp->rcp = sp->rbp->data;
	} else if(sp->rbp1->cnt == NRS_ALLOC){
		/* Current mbuf is full; link in another */
		if((sp->rbp1->next = alloc_mbuf(NRS_ALLOC)) == NULLBUF){
			/* No memory, drop whole thing */
			free_p(sp->rbp);
			sp->rbp = NULLBUF;
			sp->rcnt = 0;
			sp->state = NRS_INTER;
			return NULLBUF;
		}
		sp->rbp1 = sp->rbp1->next;
		sp->rcp = sp->rbp1->data;
	}
	/* Store the character, increment fragment and total
	 * byte counts
	 */
	*sp->rcp++ = c;
	sp->rbp1->cnt++;
	sp->rcnt++;
	sp->csum += uchar(c);  /* add to checksum */
	return NULLBUF;
}

/* Process net/rom serial line I/O */
void
nrs_recv(interface)
struct interface *interface;
{
#ifdef	COMBIOS
	register unsigned stat;
#else
	char c;
#endif
	struct mbuf *bp;
	int16 dev;
	int16 asy_recv(),com_recv();
	int ax_recv();

	dev = interface->dev;

#ifdef	COMBIOS
	/* Process any pending input */
fossil:
	com[dev].stat |= (stat = combios(0x0300,dev));

	while(stat & 0x0100) {
		com[dev].stat |= (stat = combios(0x0200,dev));

		if((bp = nrs_decode(dev,stat & 0xff)) != NULLBUF) {
			dump(interface,IF_TRACE_IN,TRACE_AX25,bp);
			ax_recv(interface,bp);
		}

		if(com[dev].drtype == 3)
			goto fossil;		/* FOSSIL needs new status */
	}

	/* Kick the transmitter if it's idle */
	if(stat & 0x2000)
		nrasy_start(dev);
#else
	/* Process any pending input */
	while(asy_recv(dev,&c,1) != 0)
		if((bp = nrs_decode(dev,c)) != NULLBUF) {
			dump(interface,IF_TRACE_IN,TRACE_AX25,bp);
			ax_recv(interface,bp);
		}

	/* Kick the transmitter if it's idle */
	nrasy_start(dev);
#endif
}

/* donrstat:  display status of active net/rom serial interfaces */
donrstat(argc,argv)
int argc;
char *argv[];
{
	register struct nrs *np;
	register int i;

	printf("Interface  SndQ  RcvB  NumReceived  CSumErrors\n");

	for (i = 0, np = nrs; i < ASY_MAX; i++, np++)
		if (np->iface != NULLIF)
			printf(" %8s   %3d  %4d   %10lu  %10lu\n",
					np->iface->name, np->sndcnt, np->rcnt,
					np->packets, np->errors);

	return 0;
}
