/* NET/ROM level 4 processing */

#include <stdio.h>
#include "global.h"
#include "mbuf.h"
#include "iface.h"
#include "trace.h"
#include "timer.h"
#include "arp.h"
#include "ax25.h"
#include "netrom.h"

#ifdef NETROM4
unsigned nr_trtimeout = 60;
unsigned nr_trtries = 3;
unsigned nr_trackdelay = 3;
unsigned nr_trbusdelay = 180;
unsigned nr_trwindow = 4;
unsigned nr_trbacklog = 4;
unsigned nr_noactive = 900;

struct nr_circ *nr_circ[NR4NUMCIRC];	/* the circuit table */
#endif

extern char badcall[];

static void nr4_connect(),nr4ack(),nr4_state(),nr4_reset(),nrtrtime(),nrtrack();
static int nr4sendit();
static struct nr_circ *nr4_circptr(),*nr4_newcirc();
void nr4_close();

/* send IP datagrams across a NET/ROM network connection */
int
nr_send(bp,interface,gateway,precedence,delay,throughput,reliability)
struct mbuf *bp;
struct interface *interface;		/* the "netrom" pseudo-iface */
int32 gateway;
char precedence;
char delay;
char throughput;
char reliability;
{
	struct arp_tab *arp;
	struct mbuf *hbp;
	struct nr4hdr n4hdr;
	struct ax25_addr dest;

	dump(interface,IF_TRACE_OUT,TRACE_IP,bp); /* trace to "netrom" */

	if ((arp = arp_lookup(interface,ARP_NETROM,gateway)) == NULLARP) {
		free_p(bp);		/* drop the packet if no route */
		return -1;
	}

	/* new NET/ROM encapsulation method: put a  NET/ROM transport */
	/* header with opcode=protocol extension before the IP packet */
	n4hdr.opcode = NR4PID;
	n4hdr.family = PID_IP;
	n4hdr.proto  = PID_IP;
	if ((hbp = htonnr4(&n4hdr)) == NULLBUF) {
		free_p(bp);		/* no space for level4 header */
		return -1;
	}
	hbp->next = bp;

	getaxaddr(&dest,arp->hw_addr);	/* get destination call */
	return nr_send3(hbp,&dest);	/* send it to level 3 */
}

/* Process NET/ROM transport layer packets.
 */
void
nr4recv(bp)
struct mbuf *bp;
{
	struct nr4hdr n4hdr;
	struct mbuf *bph;
	register struct nr_circ *circ;

	if (ntohnr4(&n4hdr,&bp) < 0) {		/* get transport header */
		free_p(bp);			/* bad header, ignore it */
		return;
	}

	switch (n4hdr.opcode & NR4OMASK) {	/* look at opcode bits */
	case NR4PID:				/* network protocol extension */
		if (n4hdr.family == PID_IP &&	/* is it IP family? */
		    n4hdr.proto == PID_IP)	/* IP protocol? */
		    ip_route(bp,0);		/* send to the IP router */
		else
		    free_p(bp);			/* something else, ignore */
		return;

	case NR4CONRQ:				/* connect request */
		nr4_connect(&n4hdr);

		if ((bph = htonnr4(&n4hdr)) != NULLBUF)
		    nr_send3(bph,&n4hdr.snode);
		break;

#ifdef NETROM4
	case NR4CONACK:				/* connect acknowledge */
		if ((circ = nr4_circptr(&n4hdr)) != NULLNRCIRC &&
		    circ->state == NR4STCPEND){
		    if (n4hdr.opcode & NR4CREFUSE) {
			circ->d_reason = NR4REBUSY;
			nr4_reset(circ);
		    } else {
			ASSIGN(circ->your,n4hdr.my);
			circ->window = min(n4hdr.window,nr_trwindow);
			stop_timer(&circ->trtimer);
			nr4_state(circ,NR4STCON);
		    }
		}
		break;

	case NR4DISRQ:				/* disconnect request */
		if ((circ = nr4_circptr(&n4hdr)) != NULLNRCIRC){
		    ASSIGN(n4hdr.your,circ->your);
		    n4hdr.my.index = n4hdr.my.id = 0;
		    n4hdr.opcode = NR4DISACK;

		    if ((bph = htonnr4(&n4hdr)) != NULLBUF)
			nr_send3(bph,&circ->dnode);

		    nr4_reset(circ);
		}
		break;

	case NR4DISACK:				/* disconnect acknowledge */
		if ((circ = nr4_circptr(&n4hdr)) != NULLNRCIRC &&
		    circ->state == NR4STDPEND){
		    nr4_reset(circ);
		}
		break;

	case NR4INFO:				/* information */
		if ((circ = nr4_circptr(&n4hdr)) != NULLNRCIRC &&
		    circ->state == NR4STCON){
		    start_timer(&circ->acktimer);/* send ACK/NAK after a while */
						/* unless someone below does it */

		    if (circ->rxseq == n4hdr.txseq){ /* check sequence# */
			circ->rxseq = n4hdr.txseq + 1; /* ok, update */
			circ->mystate &= ~NR4NAK;
			nr4ack(circ,&n4hdr);	/* process ACK information */

			append(&circ->rxasm,bp);/* assemble packets */
			bp = NULLBUF;

			if (!(n4hdr.opcode & NR4MORE)) { /* not MORE coming? */
			    enqueue(&circ->rxq,circ->rxasm);
			    circ->rxasm = NULLBUF;

			    if (circ->r_upcall != NULLVFP)
				(*circ->r_upcall)(circ,len_q(circ->rxq));

			    if (len_q(circ->rxq) >= nr_trbacklog)
				circ->mystate |= NR4CHOKE;
			}

			start_timer(&circ->noacttim); /* kick no-activity */
		    } else {			/* bad sequence # */
			if (uchar(n4hdr.txseq - circ->rxseq) <= circ->window)
			    circ->mystate |= NR4NAK; /* in window, NAK it */
						/* (others are ignored) */

			nr4ack(circ,&n4hdr);	/* process ACK information */
		    }
		}
		break;

	case NR4INFACK:				/* information acknowledge */
		if ((circ = nr4_circptr(&n4hdr)) != NULLNRCIRC &&
		    circ->state == NR4STCON)
		    nr4ack(circ,&n4hdr);	/* process it */
		break;
#endif
	}

	free_p(bp);				/* free anything left over */
}

/* attach the NET/ROM interface.  optionally set NET/ROM call too. */
int
nr_attach(argc,argv)
int argc;
char *argv[];
{
	register struct ax25_call *axc;
	struct ax25_addr hwaddr;

	if (nr_interface != NULLIF) {
		printf("netrom interface already attached\n");
		return -1;
	}

	if (argc > 1) {
		if (setcall(&hwaddr,argv[1]) < 0) {
			printf(badcall,argv[1]);
			return -1;
		}

		if((axc = cr_axcall(ax25_call,&hwaddr)) == NULLAXCALL)
			return -1;

		axc->flags |= MULTI_IF;
	}

	nr_arp();

	if((nr_interface = (struct interface *)calloc(1,sizeof(struct interface))) == NULLIF)
		return -1;
	nr_interface->name = "netrom";
	nr_interface->mtu = NR4ILEN;
	nr_interface->send = nr_send;
	if (argc > 1){			/* callsign specified? */
		if((nr_interface->hwaddr = malloc(sizeof(struct ax25_addr))) == NULLCHAR)
			return -1;
		memcpy(nr_interface->hwaddr,(char *)&hwaddr,sizeof(struct ax25_addr));
	}

	nr_interface->next = ifaces;
	ifaces = nr_interface;
	return 0;
}

/* process incoming level 4 connect requests */

static void
nr4_connect (hdr)
register struct nr4hdr *hdr;
{
	register struct nr_circ *circ;
	extern void nr7_rxnr();

	/* setup default response to reject the connect request */
	ASSIGN(hdr->your,hdr->my);
	hdr->my.index = hdr->my.id = 0;
	hdr->opcode = NR4CONACK | NR4CREFUSE;

#ifdef NETROM4
	if (nr_trtimeout < 60 ||		/* NET/ROM 4 inoperable? */
	    nr_trtries == 0 ||
	    nr_trackdelay == 0 ||
	    nr_trbusdelay < 60 ||
	    nr_trwindow == 0 ||
	    nr_noactive < 60)
	    return;				/* reject connection */

	if (hdr->window == 0 ||			/* bad window? */
	    valid_addr(&hdr->suser) != 0 ||	/* invalid source call? */
	    valid_addr(&hdr->snode) != 0)	/* invalid source node? */
	    return;				/* reject it */

	if ((circ = nr4_newcirc(&hdr->your,&hdr->snode)) == NULLNRCIRC)
	    return;				/* no space, refuse it */

	/* got a circuit, initialize it and build CONACK */

	ASSIGN(hdr->my,circ->my);
	hdr->window = circ->window = min(hdr->window,nr_trwindow);
	ASSIGN(circ->suser,hdr->suser);

	nr4_state(circ,NR4STCON);

	circ->trtimer.start = SEC2TICK(nr_trtimeout);
	circ->trtimer.func = nrtrtime;

	circ->acktimer.start = SEC2TICK(nr_trackdelay);
	circ->acktimer.func = nrtrack;

	circ->noacttim.start = SEC2TICK(nr_noactive);
	circ->noacttim.func = nr4_close;

	circ->trtimer.arg = circ->acktimer.arg = circ->noacttim.arg = (char *) circ;

	start_timer(&circ->noacttim);		/* start no-activity timer */

	circ->r_upcall = nr7_rxnr;		/* receive = NET/ROM cmds */

	hdr->opcode &= ~NR4CREFUSE;		/* accept connection */
#endif
}

#ifdef NETROM4
/* connect a NET/ROM l4 circuit */

struct nr_circ *
nr4_conn (dnode,snode,suser,r_upcall,t_upcall,s_upcall,user)
struct ax25_addr *dnode;	/* destination node's call */
struct ax25_addr *snode;	/* originating node's call */
struct ax25_addr *suser;	/* originating user's call */
void (*r_upcall)();		/* Receiver upcall handler */
void (*t_upcall)();		/* Transmitter upcall handler */
void (*s_upcall)();		/* State-change upcall handler */
char *user;			/* user ctrlblock pointer */

{
	register struct nr_circ *circ;
	int idx;

	if (nr_trtimeout < 60 ||		/* NET/ROM 4 inoperable? */
	    nr_trtries == 0 ||
	    nr_trackdelay == 0 ||
	    nr_trbusdelay < 60 ||
	    nr_trwindow == 0 ||
	    nr_noactive < 60)
	    return NULLNRCIRC;			/* reject connection */

	/* find a free slot in the circuit table */

	for (idx = 0; idx < NR4NUMCIRC; idx++)
	    if (nr_circ[idx] == NULLNRCIRC)	/* free circuit? */
		break;

	if (idx == NR4NUMCIRC)			/* no free circuits found? */
	    return NULLNRCIRC;			/* then refuse the connect */

	if ((circ = (struct nr_circ *) calloc(1,sizeof(struct nr_circ))) == NULLNRCIRC)
	    return NULLNRCIRC;			/* no space, refuse it */

	circ->my.index = idx;			/* init the circuit */
	circ->my.id = uchar(rand());
	nr_circ[idx] = circ;

	ASSIGN(circ->suser,*suser);
	ASSIGN(circ->snode,*snode);
	ASSIGN(circ->dnode,*dnode);

	circ->trtimer.start = SEC2TICK(nr_trtimeout);
	circ->trtimer.func = nrtrtime;

	circ->acktimer.start = SEC2TICK(nr_trackdelay);
	circ->acktimer.func = nrtrack;

	circ->noacttim.start = SEC2TICK(nr_noactive);
	circ->noacttim.func = nr4_close;

	circ->trtimer.arg = circ->acktimer.arg = circ->noacttim.arg = (char *) circ;

	start_timer(&circ->noacttim);		/* start no-activity timer */

	circ->r_upcall = r_upcall;
	circ->t_upcall = t_upcall;
	circ->s_upcall = s_upcall;

	circ->user = user;

	nr4_state(circ,NR4STCPEND);

	nrtrtime(circ);				/* kick-out CONRQ */

	return circ;
}

/* disconnect a NET/ROM l4 circuit */

void
nr4_close (circ)
register struct nr_circ *circ;

{
	if (circ->state == NR4STCON) {		/* connected? */
	    nr4_state(circ,NR4STDPEND);
	    circ->retries = 0;
	    nrtrtime(circ);
	} else {
	    nr4_reset(circ);
	}
}

/* reset a NET/ROM l4 circuit */

static void
nr4_reset (circ)
register struct nr_circ *circ;

{
	nr4_state(circ,NR4STDISC);

	nr_circ[circ->my.index] = NULLNRCIRC;
	free_q(circ->txq);
	free_q(circ->rxq);
	free_p(circ->rxasm);

	stop_timer(&circ->trtimer);
	stop_timer(&circ->acktimer);
	stop_timer(&circ->noacttim);

	free(circ);
}

/* get information from NET/ROM l4 receive queue */
struct mbuf *
nr4_recv (circ,cnt)
register struct nr_circ *circ;
int cnt;

{
	struct mbuf *bp = NULLBUF;
	struct mbuf *bp2;

	while (cnt-- && (bp2 = dequeue(&circ->rxq)) != NULLBUF)
	    append(&bp,bp2);

	/* see if we got un-choked and got a pending "choked" state */

	if (len_q(circ->rxq) < nr_trbacklog) {
	    circ->mystate &= ~NR4CHOKE;

	    if (circ->s_choked) {		/* already sent a CHOKE? */
		circ->s_choked = 0;

		if (!run_timer(&circ->acktimer)) /* not already deferring ACK? */
		    start_timer(&circ->acktimer); /* then do so */
	    }
	}

	return bp;
}

/* send NET/ROM l4 information */
int
nr4_send (circ,bp)
register struct nr_circ *circ;
struct mbuf *bp;

{
	struct mbuf *bp1;
	unsigned char outs;

	if (!run_timer(&circ->trtimer))		/* transport timer not yet started? */
	    start_timer(&circ->trtimer);	/* then do so */

	/* when the packet is longer than the maximum, split it up and */
	/* put the first block(s) in mbufs that are 1 byte too large */
	/* this is a signal to nr4_sendit to set the MORE flag */

	while (len_mbuf(bp) > NR4ILEN) {
	    if ((bp1 = alloc_mbuf(NR4ILEN + 1)) == NULLBUF) {
		free_p(bp);
		return -1;
	    }

	    pullup(&bp,bp1->data,NR4ILEN);
	    bp1->cnt = NR4ILEN + 1;
	    enqueue(&circ->txq,bp1);		/* queue for transmission */

	    outs = circ->txseq - circ->txack;

	    if (outs < circ->window)
		if (nr4sendit(circ,outs) < 0) { /* send this frame now */
		    free_p(bp);
		    return -1;
		}
	}

	if (bp != NULLBUF)
	    enqueue(&circ->txq,bp);		/* queue for transmission */

	outs = circ->txseq - circ->txack;

	if (outs < circ->window)
	    return nr4sendit(circ,outs);	/* send this frame now */

	return 0;
}

/* send the nth frame from the queue */
static int
nr4sendit (circ,n)
register struct nr_circ *circ;
int n;

{
	struct nr4hdr n4hdr;
	struct mbuf *bp,*bph,*bpd;
	int i;

	if (circ->r_choked)		/* choked, cannot accept it */
	    return 0;

	bp = circ->txq;
	for (i = 0; i < n; i++) {	/* find frame in send queue */
	    if (bp == NULLBUF)
		break;
	    bp = bp->anext;
	}
	if (bp == NULLBUF)
	    return 0;			/* nothing to send */

	ASSIGN(n4hdr.your,circ->your);	/* prepare the level 4 header */
	n4hdr.txseq = circ->txack + n;
	n4hdr.rxseq = circ->rxseq;
	n4hdr.opcode = NR4INFO | circ->mystate;

	if ((i = len_mbuf(bp)) == (NR4ILEN + 1)) {
	    n4hdr.opcode |= NR4MORE;	/* special case: MORE bit on */
	    i--;			/* and correct the length */
	}

	if (dup_p(&bpd,bp,0,(unsigned) i) == 0 || bpd == NULLBUF)
	    return -1;

	if ((bph = htonnr4(&n4hdr)) == NULLBUF) {
	    free_p(bpd);
	    return -1;
	}

	append(&bph,bpd);
	nr_send3(bph,&circ->dnode);

	stop_timer(&circ->acktimer);	/* implicit acknowledgement sent */
	circ->mystate &= ~NR4NAK;	/* NAK only once */

	if (uchar(circ->txseq - circ->txack) <= n)
	    circ->txseq = circ->txack + n + 1;

	return 1;
}

/* process NET/ROM acknowledgements (in INFACK and INFO) */
static void
nr4ack (circ,hdr)
register struct nr_circ *circ;
register struct nr4hdr *hdr;

{
	register unsigned char n;

	if (circ->txseq != circ->txack &&
	    hdr->rxseq != circ->txack &&
	    uchar(hdr->rxseq - circ->txack) <= circ->window)
	{
	    stop_timer(&circ->trtimer);
	    start_timer(&circ->noacttim);

	    for (n = circ->txack; n != hdr->rxseq; n++) {
		free_p(dequeue(&circ->txq));
		circ->retries = 0;
		circ->txack++;
	    }

	    if (circ->t_upcall != NULLVFP)
		(*circ->t_upcall)(circ,circ->window - uchar(circ->txseq - circ->txack));
	}

	if (hdr->opcode & NR4CHOKE){		/* CHOKE indication? */
	    circ->r_choked = 1;
	    stop_timer(&circ->trtimer);
	    circ->trtimer.start = SEC2TICK(nr_trbusdelay);
	} else {				/* not CHOKED */
	    circ->r_choked = 0;

	    if (hdr->opcode & NR4NAK &&		/* NAK indication? */
		circ->txack != circ->txseq) {	/* still frames to send? */
		nr4sendit(circ,0);		/* repeat first frame */
	    } else {				/* ACK, fill the window */
		for (n = uchar(circ->txseq - circ->txack); n < circ->window; n++)
		    if (nr4sendit(circ,n) <= 0)
			break;			/* quit when no more or error */
	    }

	    circ->trtimer.start = SEC2TICK(nr_trtimeout);
	}

	if (circ->txack != circ->txseq)
	    start_timer(&circ->trtimer);
}

/* transport timeout */
static void
nrtrtime (circ)
register struct nr_circ *circ;

{
	struct nr4hdr n4hdr;
	struct mbuf *bph;

	switch (circ->state)
	{
	case NR4STCPEND:			/* connect pending */
	    if (circ->retries++ < nr_trtries) {
		ASSIGN(n4hdr.my,circ->my);
		n4hdr.window = nr_trwindow;
		ASSIGN(n4hdr.suser,circ->suser);
		ASSIGN(n4hdr.snode,circ->snode);
		n4hdr.opcode = NR4CONRQ;

		if ((bph = htonnr4(&n4hdr)) != NULLBUF)
		    nr_send3(bph,&circ->dnode);

		start_timer(&circ->trtimer);
	    } else {
		circ->d_reason = NR4REFAIL;
		nr4_reset(circ);
	    }
	    break;

	case NR4STCON:				/* connected */
	    if (circ->retries++ < nr_trtries) {
		circ->r_choked = 0;		/* override "choked" */
		if (nr4sendit(circ,0) != 0)	/* re-transmit oldest */
		    start_timer(&circ->trtimer); /* restart timer when txq not empty */
	    } else {
		circ->d_reason = NR4REFAIL;
		nr4_close(circ);		/* retried-out, close it */
	    }
	    break;

	case NR4STDPEND:			/* disconnect pending */
	    if (circ->retries++ < nr_trtries) {
		ASSIGN(n4hdr.your,circ->your);
		n4hdr.opcode = NR4DISRQ;

		if ((bph = htonnr4(&n4hdr)) != NULLBUF)
		    nr_send3(bph,&circ->dnode);

		start_timer(&circ->trtimer);
	    } else {
		circ->d_reason = NR4REFAIL;
		nr4_reset(circ);
	    }
	    break;
	}
}

/* acknowledgement delay time elapsed */
static void
nrtrack (circ)
register struct nr_circ *circ;

{
	struct nr4hdr n4hdr;
	struct mbuf *bph;

	ASSIGN(n4hdr.your,circ->your);
	n4hdr.rxseq = circ->rxseq;
	n4hdr.opcode = NR4INFACK | circ->mystate;

	if ((bph = htonnr4(&n4hdr)) != NULLBUF) {
	    nr_send3(bph,&circ->dnode);
	    circ->mystate &= ~NR4NAK;		/* NAK only once */
	    if (circ->mystate & NR4CHOKE)	/* sent a CHOKE? */
		circ->s_choked = 1;		/* remember that */
	}
}

/* state change on NET/ROM level 4 circuit */
static void
nr4_state(circ,s)
register struct nr_circ *circ;
int s;
{
	int oldstate;

	oldstate = circ->state;
	circ->state = s;
	if (oldstate != circ->state && circ->s_upcall != NULLVFP)
		(*circ->s_upcall)(circ,oldstate,circ->state);
}

/* get circuit info pointer (or NULL if no circuit) */

static struct nr_circ *
nr4_circptr (hdr)
register struct nr4hdr *hdr;

{
	register struct nr_circ *circ;

	if (hdr->your.index >= NR4NUMCIRC)	/* check for illegal index */
		return NULLNRCIRC;

	if ((circ = nr_circ[hdr->your.index]) != NULLNRCIRC)
		if (hdr->your.id != circ->my.id)
			return NULLNRCIRC;

	return circ;
}

/* create new circuit and return it's pointer (or NULL) */

static struct nr_circ *
nr4_newcirc (your,snode)
struct ckt *your;				/* other node's circuit */
struct ax25_addr *snode;			/* other node's call */

{
	int idx;
	register struct nr_circ *circ;
	extern int rand();

	/* scan the circuit table to see if we already got this circuit */

	for (idx = 0; idx < NR4NUMCIRC; idx++){
	    if ((circ = nr_circ[idx]) != NULLNRCIRC &&
		circ->your.index == your->index &&
		circ->your.id == your->id &&
		addreq(snode,&circ->dnode))
		return circ;
	}

	/* create a new circuit table entry if we don't have it */

	for (idx = 0; idx < NR4NUMCIRC; idx++){
	    if (nr_circ[idx] == NULLNRCIRC){	/* free circuit? */
		if ((circ = (struct nr_circ *) calloc(1,sizeof(struct nr_circ))) == NULLNRCIRC)
		    return NULLNRCIRC;		/* no space, refuse it */

		circ->my.index = idx;
		circ->my.id = uchar(rand());
		ASSIGN(circ->your,*your);
		ASSIGN(circ->dnode,*snode);

		nr_circ[idx] = circ;
		return circ;
	    }
	}

	return NULLNRCIRC;			/* no free circuits, refuse */
}
#endif
