/*
 * Copyright (c) University of British Columbia, 1984
 * Copyright (c) 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Laboratory for Computation Vision and the Computer Science Department
 * of the University of British Columbia.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)hd_input.c	8.1 (Berkeley) 6/10/93
 * $FreeBSD: stable/2.1/sys/netccitt/hd_input.c 50950 1999-09-05 08:39:22Z peter $
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/domain.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/kernel.h>

#include <net/if.h>

#include <netccitt/hdlc.h>
#include <netccitt/hd_var.h>
#include <netccitt/x25.h>

static void frame_reject();
static void rej_routine();
static void free_iframes();
void	process_sframe ();

/*
 *      HDLC INPUT INTERFACE
 *
 *      This routine is called when the HDLC physical device has
 *      completed reading a frame.
 */

void
hdintr ()
{
	register struct mbuf *m;
	register struct hdcb *hdp;
	register struct ifnet *ifp;
	register int s;
	static struct ifnet *lastifp;
	static struct hdcb *lasthdp;

	for (;;) {
		s = splimp ();
		IF_DEQUEUE (&hdintrq, m);
		splx (s);
		if (m == 0)
			break;
		if (m->m_len < HDHEADERLN) {
			printf ("hdintr: packet too short (len=%d)\n",
				m->m_len);
			m_freem (m);
			continue;
		}
		if ((m->m_flags & M_PKTHDR) == 0)
			panic("hdintr");
		ifp = m->m_pkthdr.rcvif;

		/*
		 * look up the appropriate hdlc control block
		 */

		if (ifp == lastifp)
			hdp = lasthdp;
		else {
			for (hdp = hdcbhead; hdp; hdp = hdp->hd_next)
				if (hdp->hd_ifp == ifp)
					break;
			if (hdp == 0) {
				printf ("hdintr: unknown interface %p\n", ifp);
				m_freem (m);
				continue;
			}
			lastifp = ifp;
			lasthdp = hdp;
		}

		/* Process_rxframe returns FALSE if the frame was NOT queued
		   for the next higher layers. */
		if (process_rxframe (hdp, m) == FALSE)
			m_freem (m);
	}
}

int
process_rxframe (hdp, fbuf)
register struct hdcb *hdp;
register struct mbuf *fbuf;
{
	register int queued = FALSE, frametype, pf;
	register struct Hdlc_frame *frame;

	frame = mtod (fbuf, struct Hdlc_frame *);
	pf = ((struct Hdlc_iframe *) frame) -> pf;

	hd_trace (hdp, RX, frame);
	if (frame -> address != ADDRESS_A && frame -> address != ADDRESS_B)
		return (queued);

	switch ((frametype = hd_decode (hdp, frame)) + hdp->hd_state) {
	case DM + DISC_SENT:
	case UA + DISC_SENT:
		/*
		 * Link now closed.  Leave timer running
		 * so hd_timer() can periodically check the
		 * status of interface driver flag bit IFF_UP.
		 */
		hdp->hd_state = DISCONNECTED;
		break;

	case DM + INIT:
	case UA + INIT:
		/*
		 * This is a non-standard state change needed for DCEs
		 * that do dynamic link selection.  We can't go into the
		 * usual "SEND DM" state because a DM is a SARM in LAP.
		 */
		hd_writeinternal (hdp, SABM, POLLOFF);
		hdp->hd_state = SABM_SENT;
		SET_TIMER (hdp);
		break;

	case SABM + DM_SENT:
	case SABM + WAIT_SABM:
		hd_writeinternal (hdp, UA, pf);
	case UA + SABM_SENT:
	case UA + WAIT_UA:
		KILL_TIMER (hdp);
		hd_initvars (hdp);
		hdp->hd_state = ABM;
		hd_message (hdp, "Link level operational");
		/* Notify the packet level - to send RESTART. */
		(void) pk_ctlinput (PRC_LINKUP, hdp->hd_pkp);
		break;

	case SABM + SABM_SENT:
		/* Got a SABM collision. Acknowledge the remote's SABM
		   via UA but still wait for UA. */
		hd_writeinternal (hdp, UA, pf);
		break;

	case SABM + ABM:
		/* Request to reset the link from the remote. */
		KILL_TIMER (hdp);
		hd_message (hdp, "Link reset");
#ifdef HDLCDEBUG
		hd_dumptrace (hdp);
#endif
		hd_flush (hdp->hd_ifp);
		hd_writeinternal (hdp, UA, pf);
		hd_initvars (hdp);
		(void) pk_ctlinput (PRC_LINKRESET, hdp->hd_pkp);
		hdp->hd_resets++;
		break;

	case SABM + WAIT_UA:
		hd_writeinternal (hdp, UA, pf);
		break;

	case DM + ABM:
		hd_message (hdp, "DM received: link down");
#ifdef HDLCDEBUG
		hd_dumptrace (hdp);
#endif
		(void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
		hd_flush (hdp->hd_ifp);
	case DM + DM_SENT:
	case DM + WAIT_SABM:
	case DM + WAIT_UA:
		hd_writeinternal (hdp, SABM, pf);
		hdp->hd_state = SABM_SENT;
		SET_TIMER (hdp);
		break;

	case DISC + INIT:
	case DISC + DM_SENT:
	case DISC + SABM_SENT:
		/* Note: This is a non-standard state change. */
		hd_writeinternal (hdp, UA, pf);
		hd_writeinternal (hdp, SABM, POLLOFF);
		hdp->hd_state = SABM_SENT;
		SET_TIMER (hdp);
		break;

	case DISC + WAIT_UA:
		hd_writeinternal (hdp, DM, pf);
		SET_TIMER (hdp);
		hdp->hd_state = DM_SENT;
		break;

	case DISC + ABM:
		hd_message (hdp, "DISC received: link down");
		(void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
	case DISC + WAIT_SABM:
		hd_writeinternal (hdp, UA, pf);
		hdp->hd_state = DM_SENT;
		SET_TIMER (hdp);
		break;

	case UA + ABM:
		hd_message (hdp, "UA received: link down");
		(void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
	case UA + WAIT_SABM:
		hd_writeinternal (hdp, DM, pf);
		hdp->hd_state = DM_SENT;
		SET_TIMER (hdp);
		break;

	case FRMR + DM_SENT:
		hd_writeinternal (hdp, SABM, pf);
		hdp->hd_state = SABM_SENT;
		SET_TIMER (hdp);
		break;

	case FRMR + WAIT_SABM:
		hd_writeinternal (hdp, DM, pf);
		hdp->hd_state = DM_SENT;
		SET_TIMER (hdp);
		break;

	case FRMR + ABM:
		hd_message (hdp, "FRMR received: link down");
		(void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
#ifdef HDLCDEBUG
		hd_dumptrace (hdp);
#endif
		hd_flush (hdp->hd_ifp);
		hd_writeinternal (hdp, SABM, pf);
		hdp->hd_state = WAIT_UA;
		SET_TIMER (hdp);
		break;

	case RR + ABM:
	case RNR + ABM:
	case REJ + ABM:
		process_sframe (hdp, (struct Hdlc_sframe *)frame, frametype);
		break;

	case IFRAME + ABM:
		queued = process_iframe (hdp, fbuf, (struct Hdlc_iframe *)frame);
		break;

	case IFRAME + SABM_SENT:
	case RR + SABM_SENT:
	case RNR + SABM_SENT:
	case REJ + SABM_SENT:
		hd_writeinternal (hdp, DM, POLLON);
		hdp->hd_state = DM_SENT;
		SET_TIMER (hdp);
		break;

	case IFRAME + WAIT_SABM:
	case RR + WAIT_SABM:
	case RNR + WAIT_SABM:
	case REJ + WAIT_SABM:
		hd_writeinternal (hdp, FRMR, POLLOFF);
		SET_TIMER (hdp);
		break;

	case ILLEGAL + SABM_SENT:
		hdp->hd_unknown++;
		hd_writeinternal (hdp, DM, POLLOFF);
		hdp->hd_state = DM_SENT;
		SET_TIMER (hdp);
		break;

	case ILLEGAL + ABM:
		hd_message (hdp, "Unknown frame received: link down");
		(void) pk_ctlinput (PRC_LINKDOWN, hdp->hd_pkp);
	case ILLEGAL + WAIT_SABM:
		hdp->hd_unknown++;
#ifdef HDLCDEBUG
		hd_dumptrace (hdp);
#endif
		hd_writeinternal (hdp, FRMR, POLLOFF);
		hdp->hd_state = WAIT_SABM;
		SET_TIMER (hdp);
		break;
	}

	return (queued);
}

int
process_iframe (hdp, fbuf, frame)
register struct hdcb *hdp;
struct mbuf *fbuf;
register struct Hdlc_iframe *frame;
{
	register int    nr = frame -> nr,
	                ns = frame -> ns,
	                pf = frame -> pf;
	register int    queued = FALSE;

	/*
	 *  Validate the iframe's N(R) value. It's N(R) value must be in
	 *   sync with our V(S) value and our "last received nr".
	 */

	if (valid_nr (hdp, nr, FALSE) == FALSE) {
		frame_reject (hdp, Z, frame);
		return (queued);
	}


	/*
	 *  This section tests the IFRAME for proper sequence. That is, it's
	 *  sequence number N(S) MUST be equal to V(S).
	 */

	if (ns != hdp->hd_vr) {
		hdp->hd_invalid_ns++;
		if (pf || (hdp->hd_condition & REJ_CONDITION) == 0) {
			hdp->hd_condition |= REJ_CONDITION;
			/*
			 * Flush the transmit queue. This is ugly but we
			 * have no choice.  A reject response must be
			 * immediately sent to the DCE.  Failure to do so
			 * may result in another out of sequence iframe
			 * arriving (and thus sending another reject)
			 * before the first reject is transmitted. This
			 * will cause the DCE to receive two or more
			 * rejects back to back, which must never happen.
			 */
			hd_flush (hdp->hd_ifp);
			hd_writeinternal (hdp, REJ, pf);
		}
		return (queued);
	}
	hdp->hd_condition &= ~REJ_CONDITION;

	/*
	 *  This section finally tests the IFRAME's sequence number against
	 *  the window size (K)  and the sequence number of the  last frame
	 *  we have acknowledged.  If the IFRAME is completely correct then
	 *  it is queued for the packet level.
	 */

	if (ns != (hdp -> hd_lasttxnr + hdp -> hd_xcp -> xc_lwsize) % MODULUS) {
		hdp -> hd_vr = (hdp -> hd_vr + 1) % MODULUS;
		if (pf == 1) {
			/* Must generate a RR or RNR with final bit on. */
			hd_writeinternal (hdp, RR, POLLON);
		} else
			/*
			 *  Hopefully we can piggyback the RR, if not we will generate
			 *  a RR when T3 timer expires.
			 */
			if (hdp -> hd_rrtimer == 0)
				hdp->hd_rrtimer = hd_t3;

		/* Forward iframe to packet level of X.25. */
		fbuf -> m_data += HDHEADERLN;
		fbuf -> m_len -= HDHEADERLN;
		fbuf -> m_pkthdr.len -= HDHEADERLN;
		fbuf -> m_pkthdr.rcvif = (struct ifnet *)hdp -> hd_pkp;
#ifdef BSD4_3
		fbuf->m_act = 0;	/* probably not necessary */
#else
		{
			register struct mbuf *m;

			for (m = fbuf; m -> m_next; m = m -> m_next)
				m -> m_act = (struct mbuf *) 0;
			m -> m_act = (struct mbuf *) 1;
		}
#endif
		pk_input (fbuf);
		queued = TRUE;
		hd_start (hdp);
	} else {
		/*
		 *  Here if the remote station has transmitted more iframes then
		 *  the number which have been acknowledged plus K.
		 */
		hdp->hd_invalid_ns++;
		frame_reject (hdp, W, frame);
	}
	return (queued);
}

/*
 *  This routine is used to determine if a value (the middle parameter)
 *  is between two other values. The low value is  the first  parameter
 *  the high value is the last parameter. The routine checks the middle
 *  value to see if it is within the range of the first and last values.
 *  The reason we need this routine is the values are modulo some  base
 *  hence a simple test for greater or less than is not sufficient.
 */

bool
range_check (rear, value, front)
int     rear,
        value,
        front;
{
	register bool result = FALSE;

	if (front > rear)
		result = (rear <= value) && (value <= front);
	else
		result = (rear <= value) || (value <= front);

	return (result);
}

/*
 *  This routine handles all the frame reject conditions which can
 *  arise as a result  of secondary  processing.  The frame reject
 *  condition Y (frame length error) are handled elsewhere.
 */

static void
frame_reject (hdp, rejectcode, frame)
struct hdcb *hdp;
int rejectcode;
struct Hdlc_iframe *frame;
{
	register struct Frmr_frame *frmr = &hd_frmr;

	frmr -> frmr_control = ((struct Hdlc_frame *) frame) -> control;

	frmr -> frmr_ns = frame -> ns;
	frmr -> frmr_f1_0 = 0;
	frmr -> frmr_nr = frame -> nr;
	frmr -> frmr_f2_0 = 0;

	frmr -> frmr_0000 = 0;
	frmr -> frmr_w = frmr -> frmr_x = frmr -> frmr_y =
		frmr -> frmr_z = 0;
	switch (rejectcode) {
	case Z:
		frmr -> frmr_z = 1;/* invalid N(R). */
		break;

	case Y:
		frmr -> frmr_y = 1;/* iframe length error. */
		break;

	case X:
		frmr -> frmr_x = 1;/* invalid information field. */
		frmr -> frmr_w = 1;
		break;

	case W:
		frmr -> frmr_w = 1;/* invalid N(S). */
	}

	hd_writeinternal (hdp, FRMR, POLLOFF);

	hdp->hd_state = WAIT_SABM;
	SET_TIMER (hdp);
}

/*
 *  This procedure is invoked when ever we receive a supervisor
 *  frame such as RR, RNR and REJ. All processing for these
 *  frames is done here.
 */

void
process_sframe (hdp, frame, frametype)
register struct hdcb *hdp;
register struct Hdlc_sframe *frame;
int frametype;
{
	register int nr = frame -> nr, pf = frame -> pf, pollbit = 0;

	if (valid_nr (hdp, nr, pf) == TRUE) {
		switch (frametype) {
		case RR:
			hdp->hd_condition &= ~REMOTE_RNR_CONDITION;
			break;

		case RNR:
			hdp->hd_condition |= REMOTE_RNR_CONDITION;
			hdp->hd_retxcnt = 0;
			break;

		case REJ:
			hdp->hd_condition &= ~REMOTE_RNR_CONDITION;
			rej_routine (hdp, nr);
		}

		if (pf == 1) {
			hdp->hd_retxcnt = 0;
			hdp->hd_condition &= ~TIMER_RECOVERY_CONDITION;

			if (frametype == RR && hdp->hd_lastrxnr == hdp->hd_vs
				&& hdp->hd_timer == 0 && hdp->hd_txq.head == 0)
				hd_writeinternal(hdp, RR, pf);
			else
			/* If any iframes have been queued because of the
			   timer condition, transmit then now. */
			if (hdp->hd_condition & REMOTE_RNR_CONDITION) {
				/* Remote is busy or timer condition, so only
				   send one. */
				if (hdp->hd_vs != hdp->hd_retxqi)
					hd_send_iframe (hdp, hdp->hd_retxq[hdp->hd_vs], pollbit);
			}
			else	/* Flush the retransmit list first. */
				while (hdp->hd_vs != hdp->hd_retxqi)
					hd_send_iframe (hdp, hdp->hd_retxq[hdp->hd_vs], POLLOFF);
		}

		hd_start (hdp);
	} else
		frame_reject (hdp, Z, (struct Hdlc_iframe *)frame);	/* Invalid N(R). */
}

/*
 *  This routine tests the validity of the N(R) which we have received.
 *  If it is ok,  then all the  iframes which it acknowledges  (if any)
 *  will be freed.
 */

bool
valid_nr (hdp, nr, finalbit)
register struct hdcb *hdp;
int nr;
register int finalbit;
{
	/* Make sure it really does acknowledge something. */
	if (hdp->hd_lastrxnr == nr)
		return (TRUE);

	/*
	 *  This section validates the frame's  N(R) value.  It's N(R) value
	 *  must be  in syncronization  with  our V(S)  value and  our "last
	 *  received nr" variable. If it is correct then we are able to send
	 *  more IFRAME's, else frame reject condition is entered.
	 */

	if (range_check (hdp->hd_lastrxnr, nr, hdp->hd_vs) == FALSE) {
		if ((hdp->hd_condition & TIMER_RECOVERY_CONDITION) &&
				range_check (hdp->hd_vs, nr, hdp->hd_xx) == TRUE)
			hdp->hd_vs = nr;

		else {
			hdp->hd_invalid_nr++;
			return (FALSE);
		}
	}

	/*
	 *  If we get to here, we do have a valid frame  but it might be out
	 *  of sequence.  However, we should  still accept the receive state
	 *  number N(R) since it has already passed our previous test and it
	 *  does acknowledge frames which we are sending.
	 */

	KILL_TIMER (hdp);
	free_iframes (hdp, &nr, finalbit);/* Free all acknowledged iframes */
	if (nr != hdp->hd_vs)
		SET_TIMER (hdp);

	return (TRUE);
}

/*
 *  This routine determines how many iframes need to be retransmitted.
 *  It then resets the Send State Variable V(S) to accomplish this.
 */

static void
rej_routine (hdp, rejnr)
register struct hdcb *hdp;
register int rejnr;
{
	register int anchor;

	/*
	 * Flush the output queue.  Any iframes queued for
	 * transmission will be out of sequence.
	 */

	hd_flush (hdp->hd_ifp);

	/*
	 *  Determine how many frames should be re-transmitted. In the case
	 *  of a normal REJ this  should be 1 to K.  In the case of a timer
	 *  recovery REJ (ie. a REJ with the Final Bit on) this could be 0.
	 */

	anchor = hdp->hd_vs;
	if (hdp->hd_condition & TIMER_RECOVERY_CONDITION)
		anchor = hdp->hd_xx;

	anchor = (anchor - rejnr + 8) % MODULUS;

	if (anchor > 0) {

		/* There is at least one iframe to retransmit. */
		KILL_TIMER (hdp);
		hdp->hd_vs = rejnr;

		while (hdp->hd_vs != hdp->hd_retxqi)
			hd_send_iframe (hdp, hdp->hd_retxq[hdp->hd_vs], POLLOFF);

	}
	hd_start (hdp);
}

/*
 *  This routine frees iframes from the retransmit queue. It is called
 *  when a previously written iframe is acknowledged.
 */

static void
free_iframes (hdp, nr, finalbit)
register struct hdcb *hdp;
int *nr;
register int finalbit;

{
	register int    i, k;

	/*
	 *  We  need to do the  following  because  of a  funny quirk  in  the
	 *  protocol.  This case  occures  when  in Timer  recovery  condition
	 *  we get  a  N(R)  which  acknowledges all  the outstanding  iframes
	 *  but with  the Final Bit off. In this case we need to save the last
	 *  iframe for possible retransmission even though it has already been
	 *  acknowledged!
	 */

	if ((hdp->hd_condition & TIMER_RECOVERY_CONDITION) && *nr == hdp->hd_xx && finalbit == 0) {
		*nr = (*nr - 1 + 8) % MODULUS;
/*		printf ("QUIRK\n"); */
	}

	k = (*nr - hdp->hd_lastrxnr + 8) % MODULUS;

	/* Loop here freeing all acknowledged iframes. */
	for (i = 0; i < k; ++i) {
		m_freem (hdp->hd_retxq[hdp->hd_lastrxnr]);
		hdp->hd_retxq[hdp->hd_lastrxnr] = 0;
		hdp->hd_lastrxnr = (hdp->hd_lastrxnr + 1) % MODULUS;
	}

}
