/* net/rom level 4 (transport) protocol implementation
 * Copyright 1989 by Daniel M. Frank, W9NK.  Permission granted for
 * non-commercial distribution only.
 * Ported to NOS by SM0RGV, 890525.
 * Inactivity timeout by WG7J, 920325
 */

#include "global.h"
#ifdef NETROM
#include "mbuf.h"
#include "timer.h"
#include "ax25.h"
#include "netrom.h"
#include "cmdparse.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: nr4.c,v 1.14 1997/01/19 21:13:05 root Exp $";
#endif

#undef NR4DEBUG

/* Globals: */

/* The circuit table */

struct nr4circp *Nr4circuits;


/* Various limits */

unsigned short Nr4window = 4;	/* Max window to negotiate */
unsigned short Nr4retries = 10;	/* Max retries */
unsigned short Nr4qlimit = 2048;/* Max bytes on receive queue */

int NR4MAXCIRC = _NR4MAXCIRC;	/* maximum number of open circuits */


/* Timers */

int32 Nr4irtt = 45000L;		/* Initial round trip time */
int32 Nr4acktime = 3000L;	/* ACK delay timer */
int32 Nr4choketime = 180000L;	/* CHOKEd state timeout */

static void nr4ackours (struct nr4cb *, unsigned, int);
static void nr4choke (struct nr4cb *);
static void nr4gotnak (struct nr4cb *, unsigned);
static void nr4rframe (struct nr4cb *, unsigned, struct mbuf *);
int donr4tdisc (int argc, char **argv, void *p);

#ifdef NR4TDISC

static int32 Nr4tdiscinit = 0UL;	/* Inactivity timeout - WG7J */


int
donr4tdisc (int argc, char **argv, void *p OPTIONAL)
{
	return setlong (&Nr4tdiscinit, "NR4 redundancy timer (sec)", argc, argv);
}


/* Changes are if we reachs this, with 'normal' values of tdisc,
 * the other end has already timed  and retried out.
 * So simply reset the control block - WG7J
 */

#ifdef notdef
/* This doesn't seem to catch ALL cases,
 * if someone can fix this, please let me know - WG7J
 */
void
nr4_redundant (cb)
struct nr4cb *cb;
{
struct nr4hdr hdr;

	switch (cb->state) {
		case NR4STCPEND:
		case NR4STCON:
			/* Format disconnect request packet */
			hdr.opcode = NR4OPDISRQ;
			hdr.yourindex = cb->yournum;
			hdr.yourid = cb->yourid;

			/* Set and start timer */
			cb->cdtries = 1;
			set_timer (&cb->tcd, 2 * cb->srtt);
			cb->tcd.func = nr4cdtimeout;
			cb->tcd.arg = cb;
			start_timer (&cb->tcd);

			/* Send packet */
			nr4sframe (cb->remote.node, &hdr, NULLBUF);

			/* Signal state change.  nr4state will take care of stopping */
			/* the appropriate timers and resetting window pointers. */
			nr4state (cb, NR4STDPEND);
			break;
	}

}

#endif

#endif /* NR4TDISC */



/* This function is called when a net/rom layer four frame */
/* is discovered inside a datagram addressed to us */

void
nr4input (struct nr4hdr *hdr, struct mbuf *bp)
{
struct nr4hdr rhdr;
struct nr4cb *cb, *cb2 = NULLNR4CB;
int op;
unsigned window;
int acceptc;		/* indicates that connection should be accepted */
int newconn;		/* indicates that this is a new incoming connection.  You'll see. */
int gotchoke;		/* The choke flag was set in this packet */
int i;
long t;

	op = hdr->opcode & NR4OPCODE;	/* Mask off flags */

	if (op == NR4OPCONRQ) {	/* process connect request first */
		acceptc = 1;
		newconn = 0;

		/* These fields are sent regardless of success */
		rhdr.yourindex = hdr->u.conreq.myindex;
		rhdr.yourid = hdr->u.conreq.myid;

		/* Check to see if we have already received a connect */
		/* request for this circuit. */
		if ((cb = match_n4circ (hdr->u.conreq.myindex,
		hdr->u.conreq.myid, hdr->u.conreq.user, hdr->u.conreq.node))
		    == NULLNR4CB) {	/* No existing circuit if NULL */

			/* If low on memory, don't accept - WG7J */
			/* Try to get a new circuit */
			if ((cb = new_n4circ ()) == NULLNR4CB)
				acceptc = 0;
			else {
				/* See if we have any listening sockets */
				for (i = 0; i < NR4MAXCIRC; i++) {
					if ((cb2 = Nr4circuits[i].ccb) == NULLNR4CB)
						continue;	/* not an open circuit */
					if (cb2->state == NR4STLISTEN)
						/* A listener was found */
						break;
				}
				if (i == NR4MAXCIRC) {	/* We are refusing connects */
					acceptc = 0;
					free_n4circ (cb);
				}
				if (cb2 && acceptc) {
					/* Load the listeners settings */
					cb->clone = cb2->clone;
					cb->user = cb2->user;
					cb->t_upcall = cb2->t_upcall;
					cb->s_upcall = cb2->s_upcall;
					cb->r_upcall = cb2->r_upcall;
					ASSIGN (cb->local, cb2->local);

					/* Window is set to min of the offered
					 * and local windows */
					window = hdr->u.conreq.window > Nr4window ?
						Nr4window : hdr->u.conreq.window;

					if (init_nr4window (cb, window) == -1) {
						free_n4circ (cb);
						acceptc = 0;
					} else {
						/* Set up control block */
						cb->yournum = hdr->u.conreq.myindex;
						cb->yourid = hdr->u.conreq.myid;
						memcpy (cb->remote.user, hdr->u.conreq.user, AXALEN);
						memcpy (cb->remote.node, hdr->u.conreq.node, AXALEN);
						/* Default round trip time */
						cb->srtt = Nr4irtt;
						/* set up timers, window pointers */
						nr4defaults (cb);
						cb->state = NR4STDISC;
						newconn = 1;
					}	/* End if window successfully allocated */
				}	/* End if new circuit available */
			}	/* End of memory-low else */
		}		/* End if no existing circuit matching parameters */
		/* Now set up response */
		if (!acceptc) {
			rhdr.opcode = NR4OPCONAK | NR4CHOKE;	/* choke means reject */
			rhdr.u.conack.myindex = 0;
			rhdr.u.conack.myid = 0;
			rhdr.u.conack.window = 0;
		} else if (cb) {
			rhdr.opcode = NR4OPCONAK;
			rhdr.u.conack.myindex = uchar(cb->mynum);
			rhdr.u.conack.myid = uchar(cb->myid);
			rhdr.u.conack.window = uchar(cb->window);
		}
		nr4sframe (hdr->u.conreq.node, &rhdr, NULLBUF);

		/* Why, you ask, do we wait until now for the state change
		 * upcall?  Well, it's like this:  if the state change triggers
		 * something like the mailbox to send its banner, the banner
		 * would have gone out *before* the conn ack if we'd done this
		 * in the code above.  This is what happens when you don't plan
		 * too well.  Learn from my mistakes :-)
		 */
		if (newconn)
			nr4state (cb, NR4STCON);	/* connected (no 3-way handshake) */

		free_p (bp);
		return;
	}			/* end connect request code */
	/* validate circuit number */
	if ((cb = get_n4circ (hdr->yourindex, hdr->yourid)) == NULLNR4CB) {
		free_p (bp);
		return;
	}
	/* Check for choke flag */
	if (hdr->opcode & NR4CHOKE)
		gotchoke = 1;
	else
		gotchoke = 0;

	/* Here's where the interesting stuff gets done */
	switch (cb->state) {
		case NR4STCPEND:
			switch (op) {
				case NR4OPCONAK:
					/* Save the round trip time for later use */
					t = dur_timer (&cb->tcd) - read_timer (&cb->tcd);
					stop_timer (&cb->tcd);
					if (gotchoke) {	/* connect rejected */
						cb->dreason = NR4RREFUSED;
						nr4state (cb, NR4STDISC);
						break;
					}
					cb->yournum = hdr->u.conack.myindex;
					cb->yourid = hdr->u.conack.myid;
					window = hdr->u.conack.window > Nr4window ?
						Nr4window : hdr->u.conack.window;

					if (init_nr4window (cb, window) == -1) {
						cb->dreason = NR4RRESET;
						nr4state (cb, NR4STDISC);
					} else {
						nr4defaults (cb);	/* set up timers, window pointers */

						if (cb->cdtries == 1)	/* No retries */
							/* Use measured rtt */
							cb->srtt = t;
						else
							/* else use default */
							cb->srtt = Nr4irtt;

						nr4state (cb, NR4STCON);
						(void) nr4output (cb);	/* start sending anything on the txq */
					}
					break;
				default:
					/* We can't respond to anything else without
					 * Their ID and index */
					free_p (bp);
					return;
			}
			break;
		case NR4STCON:
			switch (op) {
				case NR4OPDISRQ:
					/* format reply packet */
					rhdr.opcode = NR4OPDISAK;
					rhdr.yourindex = uchar(cb->yournum);
					rhdr.yourid = uchar(cb->yourid);
					nr4sframe (cb->remote.node, &rhdr, NULLBUF);
					cb->dreason = NR4RREMOTE;
					nr4state (cb, NR4STDISC);
					break;
				case NR4OPINFO:
					/* Do receive frame processing */
					nr4rframe (cb, (unsigned) hdr->u.info.txseq, bp);

					/* Reset the choke flag if no longer choked.  Processing
					 * the ACK will kick things off again. */
					if (cb->choked && !gotchoke) {
						stop_timer (&cb->tchoke);
						cb->choked = 0;
					}
					/* We delay processing the receive sequence number until
					 * now, because the ACK might pull more off the txq and send
					 * it, and we want the implied ACK in those frames to be right
					 *
					 * Only process NAKs if the choke flag is off.  It appears
					 * that NAKs should never be sent with choke on, by the way,
					 * but you never know, considering that there is no official
					 * standard for this protocol
					 */
					if (hdr->opcode & NR4NAK && !gotchoke)
						nr4gotnak (cb, (unsigned) hdr->u.info.rxseq);

					/* We always do ACK processing, too, since the NAK of one
					 * packet may be the implied ACK of another.  The gotchoke
					 * flag is used to prevent sending any new frames, since
					 * we are just going to purge them next anyway if this is
					 * the first time we've seen the choke flag.  If we are
					 * already choked, this call will return immediately.
					 */
					nr4ackours (cb, (unsigned) hdr->u.info.rxseq, gotchoke);

					/* If we haven't seen the choke flag before, purge the
					 * send window and set the timer and the flag.
					 */
					if (!cb->choked && gotchoke)
						nr4choke (cb);
					break;
				case NR4OPACK:
					if (cb->choked && !gotchoke) {
						/* clear choke if appropriate */
						stop_timer (&cb->tchoke);
						cb->choked = 0;
					}
					if (hdr->opcode & NR4NAK && !gotchoke)
						nr4gotnak (cb, (unsigned) hdr->u.ack.rxseq);	/* process NAKs */

					nr4ackours (cb, (unsigned) hdr->u.ack.rxseq, gotchoke);	/* and ACKs */

					if (!cb->choked && gotchoke)	/* First choke seen */
						nr4choke (cb);	/* Set choke status */

					break;
				default:
					break;
			}
			break;
		case NR4STDPEND:
			switch (op) {
				case NR4OPDISAK:
					cb->dreason = NR4RNORMAL;
					nr4state (cb, NR4STDISC);
					break;
				case NR4OPINFO:
					/* We can still do receive frame processing until
					 * the disconnect acknowledge arrives, but we won't
					 * bother to process ACKs, since we've flushed our
					 * transmit buffers and queue already.
					 */
					nr4rframe (cb, (unsigned) hdr->u.info.txseq, bp);
					break;
				default:
					break;
			}
			break;
		default:
			break;
	}			/* End switch(state) */
}


/* Send a net/rom layer 4 frame.  bp should be NULLBUF unless the frame
 * type is info.
 */
void
nr4sframe (char *dest, struct nr4hdr *hdr, struct mbuf *bp)
{
struct mbuf *n4b;

	if ((n4b = htonnr4 (hdr)) == NULLBUF) {
		free_p (bp);
		return;
	} else {
		append (&n4b, bp);
		nr3output (dest, n4b);
	}
}


/* Receive frame processing */
static void
nr4rframe (struct nr4cb *cb, unsigned rxseq, struct mbuf *bp)
{
struct nr4hdr rhdr;
unsigned window = cb->window;
unsigned rxbuf = rxseq % window;
unsigned newdata = 0;	/* whether to upcall */

#ifdef NR4DEBUG
	printf ("Processing received info\n");
#endif

#ifdef NR4TDISC
	/* We received data, reset the inactivity timer - WG7J */
	start_timer (&cb->tdisc);
#endif

	/* If we're choked, just reset the ACK timer to blast out
	 * another CHOKE indication after the ackdelay
	 */
	if (cb->qfull) {
		start_timer (&cb->tack);
		return;
	}
	/* If frame is out of sequence, it is either due to a lost frame
	 * or a retransmission of one seen earlier.  We do not want to NAK
	 * the latter, as the far end would see this as a requirement to
	 * retransmit the expected frame, which is probably already in the
	 * pipeline.  This in turn would cause another out-of-sequence
	 * condition, another NAK, and the process would repeat indefinitely.
	 * Therefore, if the frame is out-of-sequence, but within the last
	 * 'n' frames by sequence number ('n' being the window size), just
	 * accept it and discard it.  Else, NAK it if we haven't already.
	 *      (Modified by Rob Stampfli, kd8wk, 9 Jan 1990)
	 */
	if (rxseq != cb->rxpected && !cb->naksent) {
#ifdef NR4DEBUG
		printf ("Frame out of sequence -- expected %u, got %u.\n",
			cb->rxpected, rxseq);
#endif
		if (nr4between ((unsigned) cb->rxpected,
		   (rxseq + window) & NR4SEQMASK, (unsigned) cb->rxpastwin))
			/* just a repeat of old frame -- queue ack for
			 * expected frame
			 */
			start_timer (&cb->tack);
		else {		/* really bogus -- a NAKable frame */
			rhdr.opcode = NR4OPACK | NR4NAK;
			rhdr.yourindex = uchar(cb->yournum);
			rhdr.yourid = uchar(cb->yourid);
			rhdr.u.ack.rxseq = cb->rxpected;
			nr4sframe (cb->remote.node, &rhdr, NULLBUF);

			/* Now make sure we don't send any more of these until
			 * we see some good data.  Otherwise full window
			 * retransmissions would result in a flurry of NAKs
			 */

			cb->naksent = 1;
		}
	}
	/* If this is a new frame, within the window, buffer it,
	 * then see what we can deliver
	 */
	if (nr4between ((unsigned) cb->rxpected, rxseq, (unsigned) cb->rxpastwin)
	    && !cb->rxbufs[rxbuf].occupied) {
#ifdef NR4DEBUG
		printf ("Frame within window\n");
#endif
		cb->rxbufs[rxbuf].occupied = 1;
		cb->rxbufs[rxbuf].data = bp;

		for (rxbuf = cb->rxpected % window; cb->rxbufs[rxbuf].occupied;
		     rxbuf = cb->rxpected % window) {
#ifdef NR4DEBUG
			printf ("Removing frame from buffer %d\n", rxbuf);
#endif
			newdata = 1;
			cb->rxbufs[rxbuf].occupied = 0;
			append (&cb->rxq, cb->rxbufs[rxbuf].data);
			cb->rxbufs[rxbuf].data = NULLBUF;
			cb->rxpected = (cb->rxpected + 1) & NR4SEQMASK;
			cb->rxpastwin = (cb->rxpastwin + 1) & NR4SEQMASK;
		}
		if (newdata) {
			cb->naksent = 0;	/* OK to send NAKs again */
			if (cb->r_upcall != NULLVFP ((struct nr4cb *, int16)))
				(*cb->r_upcall) (cb, len_p (cb->rxq));

			/* Now that our upcall has had a shot at the queue, */
			/* see if it's past the queue length limit.  If so, */
			/* go into choked mode (i.e. flow controlled). */

			if (len_p (cb->rxq) > Nr4qlimit) {
				cb->qfull = 1;
				nr4ackit ((void *) cb);	/* Tell `em right away */
			} else
				start_timer (&cb->tack);
		}
	} else			/* It's out of the window or we've seen it already */
		free_p (bp);
}


/* Send the transmit buffer whose sequence number is seq */
void
nr4sbuf (struct nr4cb *cb, unsigned seq)
{
struct nr4hdr hdr;
struct mbuf *bufbp, *bp;
unsigned bufnum = seq % cb->window;
struct timer *t;

	/* sanity check */
	if (bufnum >= cb->window) {
#ifdef NRDEBUG
		printf ("sbuf: buffer number %u beyond window\n", bufnum);
#endif
		return;
	}
	/* Stop the ACK timer, since our sending of the frame is
	 * an implied ACK.
	 */
	stop_timer (&cb->tack);

	/* Duplicate the mbuf, since we have to keep it around
	 * until it is acknowledged
	 */
	bufbp = cb->txbufs[bufnum].data;

	/* Notice that we use copy_p instead of dup_p.  This is because
	 * a frame can still be sitting on the AX.25 send queue when it
	 * get acknowledged, and we don't want to deallocate its data
	 * before it gets sent!
	 */
	if ((bp = copy_p (bufbp, len_p (bufbp))) == NULLBUF) {
		(void) free_mbuf (bp);
		return;
	}
	/* Prepare the header */
	if (cb->qfull)		/* are we choked? */
		hdr.opcode = NR4OPINFO | NR4CHOKE;
	else
		hdr.opcode = NR4OPINFO;
	hdr.yourindex = uchar(cb->yournum);
	hdr.yourid = uchar(cb->yourid);
	hdr.u.info.txseq = (unsigned char) (seq & NR4SEQMASK);
	hdr.u.info.rxseq = cb->rxpected;

	/* Send the frame, then set and start the timer */
	nr4sframe (cb->remote.node, &hdr, bp);

	t = &cb->txbufs[bufnum].tretry;
	set_timer (t, (1 << cb->blevel) * (4 * cb->mdev + cb->srtt));
	start_timer (t);
}


/* Check to see if any of our frames have been ACKed */

static void
nr4ackours (cb, seq, gotchoke)
struct nr4cb *cb;
unsigned seq;
int gotchoke;			/* The choke flag is set in the received frame */
{
unsigned txbuf;
struct timer *t;

	/* If we are choked, there is nothing in the send window
	 * by definition, so we can just return.
	 */
	if (cb->choked)
		return;

	/* Adjust seq to point to the frame being ACK'd, not the one
	 * beyond it, which is how it arrives.
	 */
	seq = (seq - 1) & NR4SEQMASK;

	/* Free up all the ack'd frames, and adjust the round trip
	 * timing stuff
	 */
	while (nr4between ((unsigned) cb->ackxpected, seq, (unsigned) cb->nextosend)) {
#ifdef NR4DEBUG
		printf ("Sequence # %u acknowledged\n", seq);
#endif
		cb->nbuffered--;
		txbuf = cb->ackxpected % cb->window;
		(void) free_mbuf (cb->txbufs[txbuf].data);
		cb->txbufs[txbuf].data = NULLBUF;
		cb->ackxpected = (cb->ackxpected + 1) & NR4SEQMASK;

		/* Round trip time estimation, cribbed from TCP */
		if (cb->txbufs[txbuf].retries == 0) {
			/* We only sent this one once */
			int32 rtt;
			int32 abserr;

			t = &cb->txbufs[txbuf].tretry;
			/* get our rtt in msec */
			rtt = dur_timer (t) - read_timer (t);
			abserr = (rtt > cb->srtt) ? rtt - cb->srtt : cb->srtt - rtt;
			cb->srtt = (cb->srtt * 7 + rtt) >> 3;		/*lint !e704 */
			cb->mdev = (cb->mdev * 3 + abserr) >> 2;	/*lint !e704 */

			/* Reset the backoff level */
			cb->blevel = 0;
		}
		stop_timer (&cb->txbufs[txbuf].tretry);
	}
	/* Now we recalculate tmax, the maximum number of retries for
	 * any frame in the window.  tmax is used as a baseline to
	 * determine when the window has reached a new high in retries.
	 * We don't want to increment blevel for every frame that times
	 * out, since that would lead to us backing off too fast when
	 * all the frame timers expired at around the same time.
	 */
	cb->txmax = 0;

	for (seq = cb->ackxpected;
	     nr4between ((unsigned) cb->ackxpected, seq, (unsigned) cb->nextosend);
	     seq = (seq + 1) & NR4SEQMASK)
		if (cb->txbufs[seq % cb->window].retries > cb->txmax)
			cb->txmax = cb->txbufs[seq % cb->window].retries;

	/* This is kind of a hack.  This function is called under
	 * three different conditions:  either we are choked, in
	 * which case we return immediately, or we are not choked,
	 * in which case we proceed normally to keep the send
	 * window full, or we have seen the choke flag for the first
	 * time.  In the last case, gotchoke is true while cb->choked
	 * is false.  We want to process any acknowledgments of existing
	 * frames in the send window before we purge it, while at the
	 * same time we don't want to take anything else off the txq
	 * or send it out.  So, in the third case we listed, we return
	 * now since we've processed the ACK.
	 */

	if (gotchoke)
		return;

	(void) nr4output (cb);		/* yank stuff off txq and send it */

	/* At this point, either the send window is full, or
	 * nr4output() didn't find enough on the txq to fill it.
	 * If the window is not full, then the txq must be empty,
	 * and we'll make a tx upcall
	 */
	if (cb->nbuffered < cb->window && cb->t_upcall != NULLVFP ((struct nr4cb *, int16)))
		(*cb->t_upcall) (cb, (int16) ((cb->window - cb->nbuffered) * NR4MAXINFO));

}


/* If the send window is open and there are frames on the txq,
 * move as many as possible to the transmit buffers and send them.
 * Return the number of frames sent.
 */
int
nr4output (struct nr4cb *cb)
{
int numq, i;
struct mbuf *bp;
struct nr4txbuf *tp;

	/* Are we in the proper state? */
	if (cb->state != NR4STCON || cb->choked)
		return 0;	/* No sending if not connected */
	/* or if choked */

	/* See if the window is open */
	if (cb->nbuffered >= cb->window)
		return 0;

	numq = len_q (cb->txq);

#ifdef NR4DEBUG
	printf ("nr4output: %d packets on txq\n", numq);
#endif

	for (i = 0; i < numq; i++) {
		bp = dequeue (&cb->txq);
#ifdef NR4DEBUG
		if (len_p (bp) > NR4MAXINFO) {	/* should be checked higher up */
			printf ("Upper layers queued too big a buffer\n");
			continue;
		}
#endif
		/* Set up and send buffer */
		tp = &cb->txbufs[cb->nextosend % cb->window];
		tp->retries = 0;
		tp->data = bp;
		nr4sbuf (cb, (unsigned) cb->nextosend);

		/* Update window and buffered count */
		cb->nextosend = (cb->nextosend + 1) & NR4SEQMASK;
		if (++cb->nbuffered >= cb->window)
			break;
	}
	return i;
}


void
nr4state (struct nr4cb *cb, int newstate)
{
int i;
int oldstate = cb->state;

	cb->state = newstate;

	switch (cb->state) {
#ifdef NR4TDISC
		case NR4STCON:
			/* We're connected now, start inactivity timer - WG7J */
			set_timer (&cb->tdisc, Nr4tdiscinit * 1000L);
			/* cb->tdisc.func = nr4_redundant ;
			 * Second try; the call to nr4_redundant() (see above)
			 * doesn't seem to catch ALL situations.
			 * Changes are if we reachs this, with 'normal' values of tdisc,
			 * the other end has already timed out and retried out.
			 * So simply reset the control block - WG7J
			 */
			cb->tdisc.func = (void (*)(void *)) reset_nr4;
			cb->tdisc.arg = cb;
			start_timer (&cb->tdisc);
			break;
#endif
		case NR4STDPEND:
			stop_timer (&cb->tchoke);
#ifdef NR4TDISC
			start_timer (&cb->tdisc);
#endif

			/* When we request a disconnect, we lose the contents of
			 * our transmit queue and buffers, but we retain our ability
			 * to receive any packets in transit until a disconnect
			 * acknowledge arrives
			 */
			free_q (&cb->txq);

			for (i = 0; (unsigned) i < cb->window; i++) {
				(void) free_mbuf (cb->txbufs[i].data);
				cb->txbufs[i].data = NULLBUF;
				stop_timer (&cb->txbufs[i].tretry);
			}

			/* Tidy up stats: roll the top window pointer back
			 * and reset nbuffered to reflect this.  Not really
			 * necessary, but leads to a bit more truth telling
			 * in the status displays.
			 */
			cb->nextosend = cb->ackxpected;
			cb->nbuffered = 0;
			break;
		case NR4STDISC:
			stop_timer (&cb->tchoke);
			stop_timer (&cb->tack);
			stop_timer (&cb->tcd);
#ifdef NR4TDISC
			stop_timer (&cb->tdisc);
#endif

			/* We don't clear the rxq, since the state change upcall
			 * may pull something off of it at the last minute.
			 */
			free_q (&cb->txq);

			/* The following loop will only be executed if the
			 * window was set, since when the control block is
			 * calloc'd the window field gets a 0 in it.  This
			 * protects us from dereferencing an unallocated
			 * window buffer pointer
			 */
			for (i = 0; (unsigned) i < cb->window; i++) {
				(void) free_mbuf (cb->rxbufs[i].data);
				cb->rxbufs[i].data = NULLBUF;
				(void) free_mbuf (cb->txbufs[i].data);
				cb->txbufs[i].data = NULLBUF;
				stop_timer (&cb->txbufs[i].tretry);
			}
			break;
		default:
			break;
	}

	if (oldstate != newstate && cb->s_upcall != NULLVFP ((struct nr4cb *, int, int)))
		(*cb->s_upcall) (cb, oldstate, newstate);

	/* We take responsibility for deleting the circuit
	 * descriptor.  Don't do this anywhere else!
	 */
	if (newstate == NR4STDISC)
		free_n4circ (cb);
}


/* Process NAKs.  seq indicates the next frame expected by the
 * NAK'ing station.
 */

static void
nr4gotnak (struct nr4cb *cb, unsigned seq)
{
	if (nr4between ((unsigned) cb->ackxpected, seq, (unsigned) cb->nextosend))
		nr4sbuf (cb, seq);
}


/* This is called when we first get a CHOKE indication from the
 * remote.  It purges the send window and sets the choke timer.
 */

static void
nr4choke (struct nr4cb *cb)
{
unsigned seq;
struct mbuf *q, *bp;
struct nr4txbuf *t;

	q = cb->txq;

	/* We purge the send window, returning the buffers to the
	 * txq in the proper order.
	 */
	for (seq = (cb->nextosend - 1) & NR4SEQMASK;
	     nr4between ((unsigned) cb->ackxpected, seq, (unsigned) cb->nextosend);
	     seq = (seq - 1) & NR4SEQMASK) {

		t = &cb->txbufs[seq % cb->window];
		stop_timer (&t->tretry);
		bp = t->data;
		t->data = NULLBUF;
		enqueue (&bp, q);	/* prepend this packet to the queue */
		q = bp;
	}

	cb->nextosend = cb->ackxpected;	/* close the window */
	cb->nbuffered = 0;	/* nothing in the window */
	cb->txq = q;		/* Replace the txq with the one that has */
	/* the purged packets prepended */
	cb->choked = 1;		/* Set the choked flag */

	start_timer (&cb->tchoke);
}

#endif /* NETROM */
