/*	$OpenBSD: llc_subr.c,v 1.7 2003/12/10 07:22:42 itojun Exp $	*/
/*	$NetBSD: llc_subr.c,v 1.5 1996/05/07 02:36:08 thorpej Exp $	*/

/* 
 * Copyright (C) Dirk Husemann, Computer Science Department IV, 
 * 		 University of Erlangen-Nuremberg, Germany, 1990, 1991, 1992
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 * 
 * This code is derived from software contributed to Berkeley by
 * Dirk Husemann and the Computer Science Department (IV) of
 * the University of Erlangen-Nuremberg, Germany.
 *
 * 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. 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.
 *
 *	@(#)llc_subr.c	8.1 (Berkeley) 6/10/93
 */

#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/socketvar.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/kernel.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_llc.h>
#include <net/route.h>

#include <netccitt/dll.h>
#include <netccitt/llc_var.h>

/*
 * Frame names for diagnostic messages
 */
char *frame_names[] = {
	"INFO", "RR", "RNR", "REJ", "DM", "SABME", "DISC",
	"UA", "FRMR", "UI", "XID", "TEST", "ILLEGAL", "TIMER", "N2xT1"
};


/*
 * Trace level
 */
int llc_tracelevel = LLCTR_URGENT;

/*
 * Values for accessing various bitfields
 */
struct bitslice llc_bitslice[] = {
	/* mask, shift value */
	{0x1, 0x0},
	{0xfe, 0x1},
	{0x3, 0x0},
	{0xc, 0x2},
	{0x10, 0x4},
	{0xe0, 0x5},
	{0x1f, 0x0}
};

/*
 * We keep the link control blocks on a doubly linked list -
 * primarily for checking in llc_time()
 */
struct llccb_q  llccb_q = {&llccb_q, &llccb_q};

/*
 * Flag for signalling wether route tree for AF_LINK has been
 * initialized yet.
 */

int af_link_rts_init_done = 0;


/*
 * Functions dealing with struct sockaddr_dl */

/* Compare sdl_a w/ sdl_b */
int
sdl_cmp(sdl_a, sdl_b)
	struct sockaddr_dl *sdl_a;
	struct sockaddr_dl *sdl_b;
{
	if (LLADDRLEN(sdl_a) != LLADDRLEN(sdl_b))
		return (1);
	return (bcmp((caddr_t) sdl_a->sdl_data, (caddr_t) sdl_b->sdl_data,
		     LLADDRLEN(sdl_a)));
}

/* Copy sdl_f to sdl_t */
void
sdl_copy(sdl_f, sdl_t)
	struct sockaddr_dl *sdl_f;
	struct sockaddr_dl *sdl_t;
{
	bcopy((caddr_t) sdl_f, (caddr_t) sdl_t, sdl_f->sdl_len);
}

/* Swap sdl_a w/ sdl_b */
void
sdl_swapaddr(sdl_a, sdl_b)
	struct sockaddr_dl *sdl_a;
	struct sockaddr_dl *sdl_b;
{
	struct sockaddr_dl sdl_tmp;

	sdl_copy(sdl_a, &sdl_tmp);
	sdl_copy(sdl_b, sdl_a);
	sdl_copy(&sdl_tmp, sdl_b);
}

/* Fetch the sdl of the associated if */
struct sockaddr_dl *
sdl_getaddrif(ifp)
	struct ifnet *ifp;
{
	struct ifaddr *ifa;

	for (ifa = ifp->if_addrlist.tqh_first; ifa != 0;
	     ifa = ifa->ifa_list.tqe_next)
		if (ifa->ifa_addr->sa_family == AF_LINK)
			return ((struct sockaddr_dl *) (ifa->ifa_addr));

	return ((struct sockaddr_dl *) 0);
}

/* Check addr of interface with the one given */
int
sdl_checkaddrif(ifp, sdl_c)
	struct ifnet *ifp;
	struct sockaddr_dl *sdl_c;
{
	struct ifaddr *ifa;

	for (ifa = ifp->if_addrlist.tqh_first; ifa != 0;
	     ifa = ifa->ifa_list.tqe_next)
		if (ifa->ifa_addr->sa_family == AF_LINK &&
		    !sdl_cmp((struct sockaddr_dl *) (ifa->ifa_addr), sdl_c))
			return (1);

	return (0);
}

/* Build an sdl from MAC addr, DLSAP addr, and interface */
int
sdl_setaddrif(ifp, mac_addr, dlsap_addr, mac_len, sdl_to)
	struct ifnet *ifp;
	u_char *mac_addr;
	u_char dlsap_addr;
        u_char mac_len;
	struct sockaddr_dl *sdl_to;
{
	struct sockaddr_dl *sdl_tmp;

	if ((sdl_tmp = sdl_getaddrif(ifp))) {
		sdl_copy(sdl_tmp, sdl_to);
		bcopy((caddr_t) mac_addr, (caddr_t) LLADDR(sdl_to), mac_len);
		*(LLADDR(sdl_to) + mac_len) = dlsap_addr;
		sdl_to->sdl_alen = mac_len + 1;
		return (1);
	} else
		return (0);
}

/* Fill out the sdl header aggregate */
int
sdl_sethdrif(ifp, mac_src, dlsap_src, mac_dst, dlsap_dst, mac_len, sdlhdr_to)
	struct ifnet *ifp;
	u_char *mac_src;
	u_char dlsap_src;
	u_char *mac_dst;
	u_char dlsap_dst;
	u_char mac_len;
	struct sdl_hdr *sdlhdr_to;
{
	if (!sdl_setaddrif(ifp, mac_src, dlsap_src, mac_len,
			   &sdlhdr_to->sdlhdr_src) ||
	    !sdl_setaddrif(ifp, mac_dst, dlsap_dst, mac_len,
			   &sdlhdr_to->sdlhdr_dst))
		return (0);
	else
		return (1);
}

static struct sockaddr_dl sap_saddr;
static struct sockaddr_dl sap_sgate = {
	sizeof(struct sockaddr_dl),	/* _len */
	AF_LINK				/* _af */
};

/*
 * Set sapinfo for SAP address, llcconfig, af, and interface
 */
struct npaidbentry *
llc_setsapinfo(ifp, af, sap, llconf)
	struct ifnet *ifp;
	u_char af;
	u_char sap;
	struct dllconfig* llconf;
{
	struct protosw *pp;
	struct sockaddr_dl *ifdl_addr;
	struct rtentry *sirt = (struct rtentry *) 0;
	struct npaidbentry *sapinfo;
	u_char          saploc;
	int             size = sizeof(struct npaidbentry);

	USES_AF_LINK_RTS;

	/*
	 * We rely/assume that only STREAM protocols will make use of
	 * connection oriented LLC2. If this will one day not be the case
	 * this will obviously fail.
	 */
	pp = pffindtype(af, SOCK_STREAM);
	if (pp == 0 || pp->pr_input == 0 || pp->pr_ctlinput == 0) {
		printf("network	level protosw error");
		return 0;
	}
	/*
	 * We need a way to jot down the LLC2 configuration for
	 * a certain LSAP address. To do this we enter
	 * a "route" for the SAP.
	 */
	ifdl_addr = sdl_getaddrif(ifp);
	sdl_copy(ifdl_addr, &sap_saddr);
	sdl_copy(ifdl_addr, &sap_sgate);
	saploc = LLSAPLOC(&sap_saddr, ifp);
	sap_saddr.sdl_data[saploc] = sap;
	sap_saddr.sdl_alen++;

	/* now enter it */
	rtrequest(RTM_ADD, (struct sockaddr *) & sap_saddr,
		  (struct sockaddr *) & sap_sgate, 0, 0, &sirt);
	if (sirt == 0)
		return 0;

	/* Plug in config information in rt->rt_llinfo */

	sirt->rt_llinfo = malloc(size, M_PCB, M_WAITOK);
	sapinfo = (struct npaidbentry *) sirt->rt_llinfo;
	if (sapinfo) {
		bzero((caddr_t) sapinfo, size);
		/*
		 * For the time being we support LLC CLASS II here 	 only
		 */
		sapinfo->si_class = LLC_CLASS_II;
		sapinfo->si_window = llconf->dllcfg_window;
		sapinfo->si_trace = llconf->dllcfg_trace;
		if (sapinfo->si_trace)
			llc_tracelevel--;
		else
			llc_tracelevel++;
		sapinfo->si_input = pp->pr_input;
		sapinfo->si_ctlinput = pp->pr_ctlinput;

		return (sapinfo);
	}
	return 0;
}

/*
 * Get sapinfo for SAP address and interface
 */
struct npaidbentry *
llc_getsapinfo(sap, ifp)
	u_char sap;
	struct ifnet *ifp;
{
	struct sockaddr_dl *ifdl_addr;
	struct sockaddr_dl siaddr;
	struct rtentry *sirt;
	u_char          saploc;

	USES_AF_LINK_RTS;

	ifdl_addr = sdl_getaddrif(ifp);
	sdl_copy(ifdl_addr, &siaddr);
	saploc = LLSAPLOC(&siaddr, ifp);
	siaddr.sdl_data[saploc] = sap;
	siaddr.sdl_alen++;

	if ((sirt = rtalloc1((struct sockaddr *) & siaddr, 0)))
		sirt->rt_refcnt--;
	else
		return (0);

	return ((struct npaidbentry *) sirt->rt_llinfo);
}

/*
 * llc_seq2slot() --- We only allocate enough memory to hold the window. This
 * introduces the necessity to keep track of two ``pointers''
 *
 *        o llcl_freeslot     the next free slot to be used
 *                            this one advances modulo llcl_window
 *        o llcl_projvs       the V(S) associated with the next frame
 *                            to be set via llcl_freeslot
 *                            this one advances modulo LLC_MAX_SEQUENCE
 *
 * A new frame is inserted at llcl_output_buffers[llcl_freeslot], after
 * which both llcl_freeslot and llcl_projvs are incremented.
 *
 * The slot sl(sn) for any given sequence number sn is given by
 *
 *        sl(sn) = (llcl_freeslot + llcl_window - 1 - (llcl_projvs +
 *                  LLC_MAX_SEQUENCE- sn) % LLC_MAX_SEQUENCE) %
 *                  llcl_window
 *
 * i.e. we first calculate the number of frames we need to ``go back''
 * from the current one (really the next one, but that doesn't matter as
 * llcl_projvs is likewise of by plus one) and subtract that from the
 * pointer to the most recently taken frame (llcl_freeslot - 1).
 */

short
llc_seq2slot(linkp, seqn)
	struct llc_linkcb *linkp;
	short seqn;
{
	int sn = 0;

	sn = (linkp->llcl_freeslot + linkp->llcl_window -
	      (linkp->llcl_projvs + LLC_MAX_SEQUENCE - seqn) %
	      LLC_MAX_SEQUENCE) % linkp->llcl_window;

	return sn;
}

/*
 * LLC2 link state handler
 *
 * There is in most cases one function per LLC2 state. The LLC2 standard
 * ISO 8802-2 allows in some cases for ambiguities, i.e. we have the choice
 * to do one thing or the other. Right now I have just chosen one but have also
 * indicated the spot by "multiple possibilities". One could make the behavior
 * in those cases configurable, allowing the superuser to enter a profile word
 * (32/64 bits, whatever is needed) that would suit her needs [I quite like
 * that idea, perhaps I'll get around to it].
 *
 * [Preceeding each state handler function is the description as taken from
 * ISO 8802-2, section 7.9.2.1]
 */

/*
 * ADM --- The connection component is in the asynchronous disconnected mode.
 *         It can accept an SABME PDU from a remote LLC SSAP or, at the request
 *         of the service access point user, can initiate an SABME PDU
 *         transmission to a remote LLC DSAP, to establish a data link
 *         connection. It also responds to a DISC command PDU and to any
 *         command PDU with the P bit set to ``1''.
 */
int
llc_state_ADM(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case NL_CONNECT_REQUEST:
		llc_send(linkp, LLCFT_SABME, LLC_CMD, pollfinal);
		LLC_SETFLAG(linkp,P,pollfinal);
		LLC_SETFLAG(linkp,S,0);
		linkp->llcl_retry = 0;
		LLC_NEWSTATE(linkp,SETUP);
		break;
	case LLCFT_SABME + LLC_CMD:
		/*
		 * ISO 8802-2, table 7-1, ADM state says to set the P flag,
		 * yet this will cause an SABME [P] to be answered with an UA
		 * only, not an UA [F], all other `disconnected' states set
		 * the F flag, so ...
		 */
		LLC_SETFLAG(linkp,F,pollfinal);
		LLC_NEWSTATE(linkp,CONN);
		action = LLC_CONNECT_INDICATION;
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_DM, LLC_RSP, pollfinal);
		break;
	default:
		if (cmdrsp == LLC_CMD && pollfinal == 1)
			llc_send(linkp, LLCFT_DM, LLC_RSP, 1);
		/* remain in ADM state */
	}

	return action;
}

/*
 * CONN --- The local connection component has received an SABME PDU from a
 *          remote LLC SSAP, and it is waiting for the local user to accept or
 *          refuse the connection.
 */
int
llc_state_CONN(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
        int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case NL_CONNECT_RESPONSE:
		llc_send(linkp, LLCFT_UA, LLC_RSP, LLC_GETFLAG(linkp,F));
		LLC_RESETCOUNTER(linkp);
		LLC_SETFLAG(linkp,P,0);
		LLC_SETFLAG(linkp,REMOTE_BUSY, 0);
		LLC_NEWSTATE(linkp,NORMAL);
		break;
	case NL_DISCONNECT_REQUEST:
		llc_send(linkp, LLCFT_DM, LLC_RSP, LLC_GETFLAG(linkp,F));
		LLC_NEWSTATE(linkp,ADM);
		break;
	case LLCFT_SABME + LLC_CMD:
		LLC_SETFLAG(linkp,F,pollfinal);
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
		/* all other frames effect nothing here */
	}

	return action;
}

/*
 * RESET_WAIT --- The local connection component is waiting for the local user
 *                 to indicate a RESET_REQUEST or a DISCONNECT_REQUEST.
 */
int
llc_state_RESET_WAIT(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case NL_RESET_REQUEST:
		if (LLC_GETFLAG(linkp,S) == 0) {
			llc_send(linkp, LLCFT_SABME, LLC_CMD, pollfinal);
			LLC_SETFLAG(linkp,P,pollfinal);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry = 0;
			LLC_NEWSTATE(linkp,RESET);
		} else {
			llc_send(linkp, LLCFT_UA, LLC_RSP,
				 LLC_GETFLAG(linkp,F));
			LLC_RESETCOUNTER(linkp);
			LLC_SETFLAG(linkp,P,0);
			LLC_SETFLAG(linkp,REMOTE_BUSY,0);
			LLC_NEWSTATE(linkp,NORMAL);
			action = LLC_RESET_CONFIRM;
		}
		break;
	case NL_DISCONNECT_REQUEST:
		if (LLC_GETFLAG(linkp,S) == 0) {
			llc_send(linkp, LLCFT_DISC, LLC_CMD, pollfinal);
			LLC_SETFLAG(linkp,P,pollfinal);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry = 0;
			LLC_NEWSTATE(linkp,D_CONN);
		} else {
			llc_send(linkp, LLCFT_DM, LLC_RSP,
				 LLC_GETFLAG(linkp,F));
			LLC_NEWSTATE(linkp,ADM);
		}
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_SABME + LLC_CMD:
		LLC_SETFLAG(linkp,S,1);
		LLC_SETFLAG(linkp,F,pollfinal);
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_DM, LLC_RSP, pollfinal);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	}

	return action;
}

/*
 * RESET_CHECK --- The local connection component is waiting for the local user
 *                 to accept or refuse a remote reset request.
 */
int
llc_state_RESET_CHECK(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case NL_RESET_RESPONSE:
		llc_send(linkp, LLCFT_UA, LLC_RSP, LLC_GETFLAG(linkp,F));
		LLC_RESETCOUNTER(linkp);
		LLC_SETFLAG(linkp,P,0);
		LLC_SETFLAG(linkp,REMOTE_BUSY,0);
		LLC_NEWSTATE(linkp,NORMAL);
		break;
	case NL_DISCONNECT_REQUEST:
		llc_send(linkp, LLCFT_DM, LLC_RSP, LLC_GETFLAG(linkp,F));
		LLC_NEWSTATE(linkp,ADM);
		break;
	case LLCFT_DM + LLC_RSP:
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_SABME + LLC_CMD:
		LLC_SETFLAG(linkp,F,pollfinal);
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_DM, LLC_RSP, pollfinal);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	}

	return action;
}

/*
 * SETUP --- The connection component has transmitted an SABME command PDU to a
 *           remote LLC DSAP and is waiting for a reply.
 */
int
llc_state_SETUP(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case LLCFT_SABME + LLC_CMD:
		LLC_RESETCOUNTER(linkp);
		llc_send(linkp, LLCFT_UA, LLC_RSP, pollfinal);
		LLC_SETFLAG(linkp,S,1);
		break;
	case LLCFT_UA + LLC_RSP:
		if (LLC_GETFLAG(linkp,P) == pollfinal) {
			LLC_STOP_ACK_TIMER(linkp);
			LLC_RESETCOUNTER(linkp);
			LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
			LLC_SETFLAG(linkp,REMOTE_BUSY,0);
			LLC_NEWSTATE(linkp,NORMAL);
			action = LLC_CONNECT_CONFIRM;
		}
		break;
	case LLC_ACK_TIMER_EXPIRED:
		if (LLC_GETFLAG(linkp,S) == 1) {
			LLC_SETFLAG(linkp,P,0);
			LLC_SETFLAG(linkp,REMOTE_BUSY,0),
				LLC_NEWSTATE(linkp,NORMAL);
			action = LLC_CONNECT_CONFIRM;
		} else if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_SABME, LLC_CMD, pollfinal);
			LLC_SETFLAG(linkp,P,pollfinal);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry++;
		} else {
			LLC_NEWSTATE(linkp,ADM);
			action = LLC_DISCONNECT_INDICATION;
		}
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_DM, LLC_RSP, pollfinal);
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	}

	return action;
}

/*
 * RESET --- As a result of a service access point user request or the receipt
 *           of a FRMR response PDU, the local connection component has sent an
 *           SABME command PDU to the remote LLC DSAP to reset the data link
 *           connection and is waiting for a reply.
 */
int
llc_state_RESET(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case LLCFT_SABME + LLC_CMD:
		LLC_RESETCOUNTER(linkp);
		LLC_SETFLAG(linkp,S,1);
		llc_send(linkp, LLCFT_UA, LLC_RSP, pollfinal);
		break;
	case LLCFT_UA + LLC_RSP:
		if (LLC_GETFLAG(linkp,P) == pollfinal) {
			LLC_STOP_ACK_TIMER(linkp);
			LLC_RESETCOUNTER(linkp);
			LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
			LLC_SETFLAG(linkp,REMOTE_BUSY,0);
			LLC_NEWSTATE(linkp,NORMAL);
			action = LLC_RESET_CONFIRM;
		}
		break;
	case LLC_ACK_TIMER_EXPIRED:
		if (LLC_GETFLAG(linkp,S) == 1) {
			LLC_SETFLAG(linkp,P,0);
			LLC_SETFLAG(linkp,REMOTE_BUSY,0);
			LLC_NEWSTATE(linkp,NORMAL);
			action = LLC_RESET_CONFIRM;
		} else if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_SABME, LLC_CMD, pollfinal);
			LLC_SETFLAG(linkp,P,pollfinal);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry++;
		} else {
			LLC_NEWSTATE(linkp,ADM);
			action = LLC_DISCONNECT_INDICATION;
		}
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_DM, LLC_RSP, pollfinal);
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	}

	return action;
}

/*
 * D_CONN --- At the request of the service access point user, the local LLC
 *            has sent a DISC command PDU to the remote LLC DSAP and is waiting
 *            for a reply.
 */
int
llc_state_D_CONN(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case LLCFT_SABME + LLC_CMD:
		llc_send(linkp, LLCFT_DM, LLC_RSP, pollfinal);
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		break;
	case LLCFT_UA + LLC_RSP:
		if (LLC_GETFLAG(linkp,P) == pollfinal) {
			LLC_STOP_ACK_TIMER(linkp);
			LLC_NEWSTATE(linkp,ADM);
		}
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_UA, LLC_RSP, pollfinal);
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		break;
	case LLC_ACK_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_DISC, LLC_CMD, pollfinal);
			LLC_SETFLAG(linkp,P,pollfinal);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry++;
		} else
			LLC_NEWSTATE(linkp,ADM);
		break;
	}

	return action;
}

/*
 * ERROR --- The local connection component has detected an error in a received
 *           PDU and has sent a FRMR response PDU. It is waiting for a reply from
 *           the remote connection component.
 */
int
llc_state_ERROR(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case LLCFT_SABME + LLC_CMD:
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,RESET_CHECK);
		action = LLC_RESET_INDICATION_REMOTE;
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_UA, LLC_RSP, pollfinal);
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_STOP_ACK_TIMER(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_FRMR + LLC_RSP:
		LLC_STOP_ACK_TIMER(linkp);
		LLC_SETFLAG(linkp,S,0);
		LLC_NEWSTATE(linkp,RESET_WAIT);
		action = LLC_FRMR_RECEIVED;
		break;
	case LLC_ACK_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_FRMR, LLC_RSP, 0);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry++;
		} else {
			LLC_SETFLAG(linkp,S,0);
			LLC_NEWSTATE(linkp,RESET_WAIT);
			action = LLC_RESET_INDICATION_LOCAL;
		}
		break;
	default:
		if (cmdrsp == LLC_CMD) {
			llc_send(linkp, LLCFT_FRMR, LLC_RSP, pollfinal);
			LLC_START_ACK_TIMER(linkp);
		}
		break;

	}

	return action;
}

/*
 * NORMAL, BUSY, REJECT, AWAIT, AWAIT_BUSY, and AWAIT_REJECT all share
 * a common core state handler.
 */
int
llc_state_NBRAcore(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = 0;

	switch (frame_kind + cmdrsp) {
	case NL_DISCONNECT_REQUEST:
		llc_send(linkp, LLCFT_DISC, LLC_CMD, pollfinal);
		LLC_SETFLAG(linkp,P,pollfinal);
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_START_ACK_TIMER(linkp);
		linkp->llcl_retry = 0;
		LLC_NEWSTATE(linkp,D_CONN);
		break;
	case NL_RESET_REQUEST:
		llc_send(linkp, LLCFT_SABME, LLC_CMD, pollfinal);
		LLC_SETFLAG(linkp,P,pollfinal);
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_START_ACK_TIMER(linkp);
		linkp->llcl_retry = 0;
		LLC_SETFLAG(linkp,S,0);
		LLC_NEWSTATE(linkp,RESET);
		break;
	case LLCFT_SABME + LLC_CMD:
		LLC_SETFLAG(linkp,F,pollfinal);
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_NEWSTATE(linkp,RESET_CHECK);
		action = LLC_RESET_INDICATION_REMOTE;
		break;
	case LLCFT_DISC + LLC_CMD:
		llc_send(linkp, LLCFT_UA, LLC_RSP, pollfinal);
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLCFT_FRMR + LLC_RSP:
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_SETFLAG(linkp,S,0);
		LLC_NEWSTATE(linkp,RESET_WAIT);
		action = LLC_FRMR_RECEIVED;
		break;
	case LLCFT_DM + LLC_RSP:
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_NEWSTATE(linkp,ADM);
		action = LLC_DISCONNECT_INDICATION;
		break;
	case LLC_INVALID_NR + LLC_CMD:
	case LLC_INVALID_NS + LLC_CMD:
		LLC_SETFRMR(linkp, frame, cmdrsp,
			    (frame_kind == LLC_INVALID_NR ? LLC_FRMR_Z :
			     (LLC_FRMR_V | LLC_FRMR_W)));
		llc_send(linkp, LLCFT_FRMR, LLC_RSP, pollfinal);
		LLC_STOP_ALL_TIMERS(linkp);
		LLC_START_ACK_TIMER(linkp);
		linkp->llcl_retry = 0;
		LLC_NEWSTATE(linkp,ERROR);
		action = LLC_FRMR_SENT;
		break;
	case LLC_INVALID_NR + LLC_RSP:
	case LLC_INVALID_NS + LLC_RSP:
	case LLCFT_UA + LLC_RSP:
	case LLC_BAD_PDU:{
			char            frmrcause = 0;

			switch (frame_kind) {
			case LLC_INVALID_NR:
				frmrcause = LLC_FRMR_Z;
				break;
			case LLC_INVALID_NS:
				frmrcause = LLC_FRMR_V | LLC_FRMR_W;
				break;
			default:
				frmrcause = LLC_FRMR_W;
			}
			LLC_SETFRMR(linkp,frame,cmdrsp,frmrcause);
			llc_send(linkp, LLCFT_FRMR, LLC_RSP, 0);
			LLC_STOP_ALL_TIMERS(linkp);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry = 0;
			LLC_NEWSTATE(linkp,ERROR);
			action = LLC_FRMR_SENT;
			break;
		}
	default:
		if (cmdrsp == LLC_RSP && pollfinal == 1 &&
		    LLC_GETFLAG(linkp,P) == 0) {
			LLC_SETFRMR(linkp,frame,cmdrsp,LLC_FRMR_W);
			LLC_STOP_ALL_TIMERS(linkp);
			LLC_START_ACK_TIMER(linkp);
			linkp->llcl_retry = 0;
			LLC_NEWSTATE(linkp,ERROR);
			action = LLC_FRMR_SENT;
		}
		break;
	case LLC_P_TIMER_EXPIRED:
	case LLC_ACK_TIMER_EXPIRED:
	case LLC_REJ_TIMER_EXPIRED:
	case LLC_BUSY_TIMER_EXPIRED:
		if (linkp->llcl_retry >= llc_n2) {
			LLC_STOP_ALL_TIMERS(linkp);
			LLC_SETFLAG(linkp,S,0);
			LLC_NEWSTATE(linkp,RESET_WAIT);
			action = LLC_RESET_INDICATION_LOCAL;
		}
		break;
	}

	return action;
}

/*
 * NORMAL --- A data link connection exists between the local LLC service access
 *            point and the remote LLC service access point. Sending and
 *            reception of information and supervisory PDUs can be performed.
 */
int
llc_state_NORMAL(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = LLC_PASSITON;

	switch (frame_kind + cmdrsp) {
	case NL_DATA_REQUEST:
		if (LLC_GETFLAG(linkp,REMOTE_BUSY) == 0) {
#ifdef not_now
			if (LLC_GETFLAG(linkp,P) == 0) {
				/* multiple possibilities */
				llc_send(linkp, LLCFT_INFO, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				if (LLC_TIMERXPIRED(linkp,ACK) !=
				    LLC_TIMER_RUNNING)
					LLC_START_ACK_TIMER(linkp);
			} else {
#endif
				/* multiple possibilities */
				llc_send(linkp, LLCFT_INFO, LLC_CMD, 0);
				if (LLC_TIMERXPIRED(linkp,ACK) !=
				    LLC_TIMER_RUNNING)
					LLC_START_ACK_TIMER(linkp);
#ifdef not_now
			}
#endif
			action = 0;
		}
		break;
	case LLC_LOCAL_BUSY_DETECTED:
		if (LLC_GETFLAG(linkp,P) == 0) {
			/* multiple possibilities --- action-wise */
			/* multiple possibilities --- CMD/RSP-wise */
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
			LLC_START_P_TIMER(linkp);
			LLC_SETFLAG(linkp,DATA,0);
			LLC_NEWSTATE(linkp,BUSY);
			action = 0;
		} else {
			/* multiple possibilities --- CMD/RSP-wise */
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
			LLC_SETFLAG(linkp,DATA,0);
			LLC_NEWSTATE(linkp,BUSY);
			action = 0;
		}
		break;
	case LLC_INVALID_NS + LLC_CMD:
	case LLC_INVALID_NS + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr =
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_REJ, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_START_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,REJECT);
				action = 0;
			} else if (pollfinal == 0 && p == 1) {
				llc_send(linkp, LLCFT_REJ, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_START_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,REJECT);
				action = 0;
			} else if ((pollfinal == 0 && p == 0) ||
			  (pollfinal == 1 && p == 1 && cmdrsp == LLC_RSP)) {
				llc_send(linkp, LLCFT_REJ, LLC_CMD, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_START_P_TIMER(linkp);
				LLC_START_REJ_TIMER(linkp);
				if (cmdrsp == LLC_RSP && pollfinal == 1) {
					LLC_CLEAR_REMOTE_BUSY(linkp,action);
				} else
					action = 0;
				LLC_NEWSTATE(linkp,REJECT);
			}
			break;
		}
	case LLCFT_INFO + LLC_CMD:
	case LLCFT_INFO + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = 
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				LLC_INC(linkp->llcl_vr);
				LLC_SENDACKNOWLEDGE(linkp,LLC_RSP,1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				action = LLC_DATA_INDICATION;
			} else if (pollfinal == 0 && p == 1) {
				LLC_INC(linkp->llcl_vr);
				LLC_SENDACKNOWLEDGE(linkp,LLC_CMD,0);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				action = LLC_DATA_INDICATION;
			} else if ((pollfinal == 0 && p == 0 &&
				    cmdrsp == LLC_CMD) ||
				   (pollfinal == p && cmdrsp == LLC_RSP)) {
				LLC_INC(linkp->llcl_vr);
				LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
				LLC_SENDACKNOWLEDGE(linkp,LLC_CMD,0);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (cmdrsp == LLC_RSP && pollfinal == 1)
					LLC_CLEAR_REMOTE_BUSY(linkp,action);
				action = LLC_DATA_INDICATION;
			}
			break;
		}
	case LLCFT_RR + LLC_CMD:
	case LLCFT_RR + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr =
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				LLC_SENDACKNOWLEDGE(linkp,LLC_RSP,1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			} else if ((pollfinal == 0) ||
			  (cmdrsp == LLC_RSP && pollfinal == 1 && p == 1)) {
				LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case LLCFT_RNR + LLC_CMD:
	case LLCFT_RNR + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = 
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			} else if ((pollfinal == 0) ||
			  (cmdrsp == LLC_RSP && pollfinal == 1 && p == 1)) {
				LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case LLCFT_REJ + LLC_CMD:
	case LLCFT_REJ + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr =
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				llc_resend(linkp,LLC_RSP,1);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			} else if (pollfinal == 0 && p == 1) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			} else if ((pollfinal == 0 && p == 0 &&
				    cmdrsp == LLC_CMD) ||
				   (pollfinal == p && cmdrsp == LLC_RSP)) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_START_P_TIMER(linkp);
				llc_resend(linkp, LLC_CMD, 1);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case NL_INITIATE_PF_CYCLE:
		if (LLC_GETFLAG(linkp,P) == 0) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			action = 0;
		}
		break;
	case LLC_P_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			LLC_NEWSTATE(linkp,AWAIT);
			action = 0;
		}
		break;
	case LLC_ACK_TIMER_EXPIRED:
	case LLC_BUSY_TIMER_EXPIRED:
		if ((LLC_GETFLAG(linkp,P) == 0)
		    && (linkp->llcl_retry < llc_n2)) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			LLC_NEWSTATE(linkp,AWAIT);
			action = 0;
		}
		break;
	}
	if (action == LLC_PASSITON)
		action = llc_state_NBRAcore(linkp, frame, frame_kind,
					    cmdrsp, pollfinal);

	return action;
}

/*
 * BUSY --- A data link connection exists between the local LLC service access
 *          point and the remote LLC service access point. I PDUs may be sent.
 *          Local conditions make it likely that the information feld of
 *          received I PDUs will be ignored. Supervisory PDUs may be both sent
 *          and received.
 */
int
llc_state_BUSY(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = LLC_PASSITON;

	switch (frame_kind + cmdrsp) {
	case NL_DATA_REQUEST:
		if (LLC_GETFLAG(linkp,REMOTE_BUSY) == 0) {
			if (LLC_GETFLAG(linkp,P) == 0) {
				llc_send(linkp, LLCFT_INFO, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				if (LLC_TIMERXPIRED(linkp,ACK) !=
				    LLC_TIMER_RUNNING)
					LLC_START_ACK_TIMER(linkp);
				action = 0;
			} else {
				llc_send(linkp, LLCFT_INFO, LLC_CMD, 0);
				if (LLC_TIMERXPIRED(linkp,ACK) !=
				    LLC_TIMER_RUNNING)
					LLC_START_ACK_TIMER(linkp);
				action = 0;
			}
		}
		break;
	case LLC_LOCAL_BUSY_CLEARED:{
			int    p = LLC_GETFLAG(linkp,P);
			int    df = LLC_GETFLAG(linkp,DATA);

			switch (df) {
			case 1:
				if (p == 0) {
					/* multiple possibilities */
					llc_send(linkp, LLCFT_REJ, LLC_CMD, 1);
					LLC_START_REJ_TIMER(linkp);
					LLC_START_P_TIMER(linkp);
					LLC_NEWSTATE(linkp,REJECT);
					action = 0;
				} else {
					llc_send(linkp, LLCFT_REJ, LLC_CMD, 0);
					LLC_START_REJ_TIMER(linkp);
					LLC_NEWSTATE(linkp,REJECT);
					action = 0;
				}
				break;
			case 0:
				if (p == 0) {
					/* multiple possibilities */
					llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
					LLC_START_P_TIMER(linkp);
					LLC_NEWSTATE(linkp,NORMAL);
					action = 0;
				} else {
					llc_send(linkp, LLCFT_RR, LLC_CMD, 0);
					LLC_NEWSTATE(linkp,NORMAL);
					action = 0;
				}
				break;
			case 2:
				if (p == 0) {
					/* multiple possibilities */
					llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
					LLC_START_P_TIMER(linkp);
					LLC_NEWSTATE(linkp,REJECT);
					action = 0;
				} else {
					llc_send(linkp, LLCFT_RR, LLC_CMD, 0);
					LLC_NEWSTATE(linkp,REJECT);
					action = 0;
				}
				break;
			}
			break;
		}
	case LLC_INVALID_NS + LLC_CMD:
	case LLC_INVALID_NS + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = 
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (LLC_GETFLAG(linkp,DATA) == 0)
					LLC_SETFLAG(linkp,DATA,1);
				action = 0;
			} else if ((cmdrsp == LLC_CMD && pollfinal == 0 &&
				    p == 0) ||
				   (cmdrsp == LLC_RSP && pollfinal == p)) {
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
				LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (LLC_GETFLAG(linkp,DATA) == 0)
					LLC_SETFLAG(linkp,DATA,1);
				if (cmdrsp == LLC_RSP && pollfinal == 1) {
					LLC_CLEAR_REMOTE_BUSY(linkp,action);
				} else
					action = 0;
			} else if (pollfinal == 0 && p == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (LLC_GETFLAG(linkp,DATA) == 0)
					LLC_SETFLAG(linkp,DATA,1);
				action = 0;
			}
			break;
		}
	case LLCFT_INFO + LLC_CMD:
	case LLCFT_INFO + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr =
				LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				LLC_INC(linkp->llcl_vr);
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (LLC_GETFLAG(linkp,DATA) == 2)
					LLC_STOP_REJ_TIMER(linkp);
				LLC_SETFLAG(linkp,DATA,0);
				action = LLC_DATA_INDICATION;
			} else if ((cmdrsp == LLC_CMD && pollfinal == 0 && p == 0) ||
				   (cmdrsp == LLC_RSP && pollfinal == p)) {
				LLC_INC(linkp->llcl_vr);
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (LLC_GETFLAG(linkp,DATA) == 2)
					LLC_STOP_REJ_TIMER(linkp);
				if (cmdrsp == LLC_RSP && pollfinal == 1)
					LLC_CLEAR_REMOTE_BUSY(linkp,action);
				action = LLC_DATA_INDICATION;
			} else if (pollfinal == 0 && p == 1) {
				LLC_INC(linkp->llcl_vr);
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (LLC_GETFLAG(linkp,DATA) == 2)
					LLC_STOP_REJ_TIMER(linkp);
				LLC_SETFLAG(linkp,DATA,0);
				action = LLC_DATA_INDICATION;
			}
			break;
		}
	case LLCFT_RR + LLC_CMD:
	case LLCFT_RR + LLC_RSP:
	case LLCFT_RNR + LLC_CMD:
	case LLCFT_RNR + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr =
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (frame_kind == LLCFT_RR) {
					LLC_CLEAR_REMOTE_BUSY(linkp,action);
				} else {
					LLC_SET_REMOTE_BUSY(linkp,action);
				}
			} else if (pollfinal == 0 ||
				   (cmdrsp == LLC_RSP && pollfinal == 1)) {
				LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				if (frame_kind == LLCFT_RR) {
					LLC_CLEAR_REMOTE_BUSY(linkp,action);
				} else {
					LLC_SET_REMOTE_BUSY(linkp,action);
				}
			}
			break;
		}
	case LLCFT_REJ + LLC_CMD:
	case LLCFT_REJ + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr =
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			} else if ((cmdrsp == LLC_CMD && pollfinal == 0 &&
				    p == 0) ||
				   (cmdrsp == LLC_RSP && pollfinal == p)) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				LLC_UPDATE_P_FLAG(linkp,cmdrsp,pollfinal);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			} else if (pollfinal == 0 && p == 1) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp,nr);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case NL_INITIATE_PF_CYCLE:
		if (LLC_GETFLAG(linkp,P) == 0) {
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			action = 0;
		}
		break;
	case LLC_P_TIMER_EXPIRED:
		/* multiple possibilities */
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			LLC_NEWSTATE(linkp,AWAIT_BUSY);
			action = 0;
		}
		break;
	case LLC_ACK_TIMER_EXPIRED:
	case LLC_BUSY_TIMER_EXPIRED:
		if (LLC_GETFLAG(linkp,P) == 0 && linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			LLC_NEWSTATE(linkp,AWAIT_BUSY);
			action = 0;
		}
		break;
	case LLC_REJ_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			if (LLC_GETFLAG(linkp,P) == 0) {
				/* multiple possibilities */
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				linkp->llcl_retry++;
				LLC_SETFLAG(linkp,DATA,1);
				LLC_NEWSTATE(linkp,AWAIT_BUSY);
				action = 0;
			} else {
				LLC_SETFLAG(linkp,DATA,1);
				LLC_NEWSTATE(linkp,BUSY);
				action = 0;
			}
		}
		break;
	}
	if (action == LLC_PASSITON)
		action = llc_state_NBRAcore(linkp, frame, frame_kind,
					    cmdrsp, pollfinal);

	return action;
}

/*
 * REJECT --- A data link connection exists between the local LLC service
 *            access point and the remote LLC service access point. The local
 *            connection component has requested that the remote connection
 *            component resend a specific I PDU that the local connection
 *            componnent has detected as being out of sequence. Both I PDUs and
 *            supervisory PDUs may be sent and received.
 */
int
llc_state_REJECT(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = LLC_PASSITON;

	switch (frame_kind + cmdrsp) {
	case NL_DATA_REQUEST:
		if (LLC_GETFLAG(linkp,P) == 0) {
			llc_send(linkp, LLCFT_INFO, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			if (LLC_TIMERXPIRED(linkp,ACK) != LLC_TIMER_RUNNING)
				LLC_START_ACK_TIMER(linkp);
			LLC_NEWSTATE(linkp,REJECT);
			action = 0;
		} else {
			llc_send(linkp, LLCFT_INFO, LLC_CMD, 0);
			if (LLC_TIMERXPIRED(linkp,ACK) != LLC_TIMER_RUNNING)
				LLC_START_ACK_TIMER(linkp);
			LLC_NEWSTATE(linkp,REJECT);
			action = 0;
		}
		break;
	case NL_LOCAL_BUSY_DETECTED:
		if (LLC_GETFLAG(linkp,P) == 0) {
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			LLC_SETFLAG(linkp,DATA,2);
			LLC_NEWSTATE(linkp,BUSY);
			action = 0;
		} else {
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
			LLC_SETFLAG(linkp,DATA,2);
			LLC_NEWSTATE(linkp,BUSY);
			action = 0;
		}
		break;
	case LLC_INVALID_NS + LLC_CMD:
	case LLC_INVALID_NS + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				action = 0;
			} else if (pollfinal == 0 ||
			  (cmdrsp == LLC_RSP && pollfinal == 1 && p == 1)) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_UPDATE_P_FLAG(linkp, cmdrsp, pollfinal);
				if (cmdrsp == LLC_RSP && pollfinal == 1) {
					LLC_CLEAR_REMOTE_BUSY(linkp, action);
				} else
					action = 0;
			}
			break;
		}
	case LLCFT_INFO + LLC_CMD:
	case LLCFT_INFO + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				LLC_INC(linkp->llcl_vr);
				LLC_SENDACKNOWLEDGE(linkp, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_STOP_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,NORMAL);
				action = LLC_DATA_INDICATION;
			} else if ((cmdrsp = LLC_RSP && pollfinal == p) ||
			  (cmdrsp == LLC_CMD && pollfinal == 0 && p == 0)) {
				LLC_INC(linkp->llcl_vr);
				LLC_SENDACKNOWLEDGE(linkp, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				if (cmdrsp == LLC_RSP && pollfinal == 1)
					LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_STOP_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,NORMAL);
				action = LLC_DATA_INDICATION;
			} else if (pollfinal == 0 && p == 1) {
				LLC_INC(linkp->llcl_vr);
				LLC_SENDACKNOWLEDGE(linkp, LLC_CMD, 0);
				LLC_STOP_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,NORMAL);
				action = LLC_DATA_INDICATION;
			}
			break;
		}
	case LLCFT_RR + LLC_CMD:
	case LLCFT_RR + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				LLC_SENDACKNOWLEDGE(linkp, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			} else if (pollfinal == 0 ||
			  (cmdrsp == LLC_RSP && pollfinal == 1 && p == 1)) {
				LLC_UPDATE_P_FLAG(linkp, cmdrsp, pollfinal);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			}
			break;
		}
	case LLCFT_RNR + LLC_CMD:
	case LLCFT_RNR + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			} else if (pollfinal == 0 ||
			  (cmdrsp == LLC_RSP && pollfinal == 1 && p == 1)) {
				LLC_UPDATE_P_FLAG(linkp, cmdrsp, pollfinal);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				action = 0;
			}
			break;
		}
	case LLCFT_REJ + LLC_CMD:
	case LLCFT_REJ + LLC_RSP:{
			int    p = LLC_GETFLAG(linkp,P);
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				llc_resend(linkp, LLC_RSP, 1);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			} else if ((cmdrsp == LLC_CMD && pollfinal == 0 && p == 0) ||
				   (cmdrsp == LLC_RSP && pollfinal == p)) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_UPDATE_P_FLAG(linkp, cmdrsp, pollfinal);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			} else if (pollfinal == 0 && p == 1) {
				linkp->llcl_vs = nr;
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			}
			break;
		}
	case NL_INITIATE_PF_CYCLE:
		if (LLC_GETFLAG(linkp,P) == 0) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			action = 0;
		}
		break;
	case LLC_REJ_TIMER_EXPIRED:
		if (LLC_GETFLAG(linkp,P) == 0 && linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_REJ, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			LLC_START_REJ_TIMER(linkp);
			linkp->llcl_retry++;
			action = 0;
		}
	case LLC_P_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			LLC_START_REJ_TIMER(linkp);
			linkp->llcl_retry++;
			LLC_NEWSTATE(linkp,AWAIT_REJECT);
			action = 0;
		}
		break;
	case LLC_ACK_TIMER_EXPIRED:
	case LLC_BUSY_TIMER_EXPIRED:
		if (LLC_GETFLAG(linkp,P) == 0 && linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			LLC_START_REJ_TIMER(linkp);
			linkp->llcl_retry++;
			/*
			 * I cannot locate the description of RESET_V(S) in
			 * ISO 8802-2, table 7-1, state REJECT, last event,
			 * and  assume they meant to set V(S) to 0 ...
			 */
			linkp->llcl_vs = 0;	/* XXX */
			LLC_NEWSTATE(linkp,AWAIT_REJECT);
			action = 0;
		}
		break;
	}
	if (action == LLC_PASSITON)
		action = llc_state_NBRAcore(linkp, frame, frame_kind,
					    cmdrsp, pollfinal);

	return action;
}

/*
 * AWAIT --- A data link connection exists between the local LLC service access
 *           point and the remote LLC service access point. The local LLC is
 *           performing a timer recovery operation and has sent a command PDU
 *           with the P bit set to ``1'', and is awaiting an acknowledgement
 *           from the remote LLC. I PDUs may be received but not sent.
 *           Supervisory PDUs may be both sent and received.
 */
int
llc_state_AWAIT(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = LLC_PASSITON;

	switch (frame_kind + cmdrsp) {
	case LLC_LOCAL_BUSY_DETECTED:
		llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
		LLC_SETFLAG(linkp,DATA,0);
		LLC_NEWSTATE(linkp,AWAIT_BUSY);
		action = 0;
		break;
	case LLC_INVALID_NS + LLC_CMD:
	case LLC_INVALID_NS + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_REJ, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_START_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,AWAIT_REJECT);
				action = 0;
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				llc_send(linkp, LLCFT_REJ, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_START_REJ_TIMER(linkp);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,REJECT);
			} else if (pollfinal == 0) {
				llc_send(linkp, LLCFT_REJ, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_START_REJ_TIMER(linkp);
				LLC_NEWSTATE(linkp,AWAIT_REJECT);
				action = 0;
			}
			break;
		}
	case LLCFT_INFO + LLC_RSP:
	case LLCFT_INFO + LLC_CMD:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			LLC_INC(linkp->llcl_vr);
			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				action = LLC_DATA_INDICATION;
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				llc_resend(linkp, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,NORMAL);
				action = LLC_DATA_INDICATION;
			} else if (pollfinal == 0) {
				llc_send(linkp, LLCFT_RR, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				action = LLC_DATA_INDICATION;
			}
			break;
		}
	case LLCFT_RR + LLC_CMD:
	case LLCFT_RR + LLC_RSP:
	case LLCFT_REJ + LLC_CMD:
	case LLCFT_REJ + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,NORMAL);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			}
			break;
		}
	case LLCFT_RNR + LLC_CMD:
	case LLCFT_RNR + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (pollfinal == 1 && cmdrsp == LLC_CMD) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			} else if (pollfinal == 1 && cmdrsp == LLC_RSP) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				LLC_SET_REMOTE_BUSY(linkp,action);
				LLC_NEWSTATE(linkp,NORMAL);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case LLC_P_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			action = 0;
		}
		break;
	}
	if (action == LLC_PASSITON)
		action = llc_state_NBRAcore(linkp, frame, frame_kind,
					    cmdrsp, pollfinal);

	return action;
}

/*
 * AWAIT_BUSY --- A data link connection exists between the local LLC service
 *                access point and the remote LLC service access point. The
 *                local LLC is performing a timer recovery operation and has
 *                sent a command PDU with the P bit set to ``1'', and is
 *                awaiting an acknowledgement from the remote LLC. I PDUs may
 *                not be sent. Local conditions make it likely that the
 *                information feld of receoved I PDUs will be ignored.
 *                Supervisory PDUs may be both sent and received.
 */
int
llc_state_AWAIT_BUSY(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = LLC_PASSITON;

	switch (frame_kind + cmdrsp) {
	case LLC_LOCAL_BUSY_CLEARED:
		switch (LLC_GETFLAG(linkp,DATA)) {
		case 1:
			llc_send(linkp, LLCFT_REJ, LLC_CMD, 0);
			LLC_START_REJ_TIMER(linkp);
			LLC_NEWSTATE(linkp,AWAIT_REJECT);
			action = 0;
			break;
		case 0:
			llc_send(linkp, LLCFT_RR, LLC_CMD, 0);
			LLC_NEWSTATE(linkp,AWAIT);
			action = 0;
			break;
		case 2:
			llc_send(linkp, LLCFT_RR, LLC_CMD, 0);
			LLC_NEWSTATE(linkp,AWAIT_REJECT);
			action = 0;
			break;
		}
		break;
	case LLC_INVALID_NS + LLC_CMD:
	case LLC_INVALID_NS + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SETFLAG(linkp,DATA,1);
				action = 0;
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				/* optionally */
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				LLC_SETFLAG(linkp,DATA,1);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_NEWSTATE(linkp,BUSY);
			} else if (pollfinal == 0) {
				/* optionally */
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SETFLAG(linkp,DATA,1);
				action = 0;
			}
		}
	case LLCFT_INFO + LLC_CMD:
	case LLCFT_INFO + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_INC(linkp->llcl_vr);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SETFLAG(linkp,DATA,0);
				action = LLC_DATA_INDICATION;
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
				LLC_INC(linkp->llcl_vr);
				LLC_START_P_TIMER(linkp);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_SETFLAG(linkp,DATA,0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_NEWSTATE(linkp,BUSY);
				action = LLC_DATA_INDICATION;
			} else if (pollfinal == 0) {
				llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
				LLC_INC(linkp->llcl_vr);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SETFLAG(linkp,DATA,0);
				action = LLC_DATA_INDICATION;
			}
			break;
		}
	case LLCFT_RR + LLC_CMD:
	case LLCFT_REJ + LLC_CMD:
	case LLCFT_RR + LLC_RSP:
	case LLCFT_REJ + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,BUSY);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			}
			break;
		}
	case LLCFT_RNR + LLC_CMD:
	case LLCFT_RNR + LLC_RSP:{
#if 0
			int    p = LLC_GETFLAG(linkp,P);
#endif
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RNR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				LLC_SET_REMOTE_BUSY(linkp,action);
				LLC_NEWSTATE(linkp,BUSY);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case LLC_P_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_RNR, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			action = 0;
		}
		break;
	}
	if (action == LLC_PASSITON)
		action = llc_state_NBRAcore(linkp, frame, frame_kind,
					    cmdrsp, pollfinal);

	return action;
}

/*
 * AWAIT_REJECT --- A data link connection exists between the local LLC service
 *                  access point and the remote LLC service access point. The
 *                  local connection component has requested that the remote
 *                  connection component re-transmit a specific I PDU that the
 *                  local connection component has detected as being out of
 *                  sequence. Before the local LLC entered this state it was
 *                  performing a timer recovery operation and had sent a
 *                  command PDU with the P bit set to ``1'', and is still
 *                  awaiting an acknowledgment from the remote LLC. I PDUs may
 *                  be received but not transmitted. Supervisory PDUs may be
 *                  both transmitted and received.
 */
int
llc_state_AWAIT_REJECT(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int             action = LLC_PASSITON;

	switch (frame_kind + cmdrsp) {
	case LLC_LOCAL_BUSY_DETECTED:
		llc_send(linkp, LLCFT_RNR, LLC_CMD, 0);
		LLC_SETFLAG(linkp,DATA,2);
		LLC_NEWSTATE(linkp,AWAIT_BUSY);
		action = 0;
		break;
	case LLC_INVALID_NS + LLC_CMD:
	case LLC_INVALID_NS + LLC_RSP:{
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				action = 0;
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				llc_resend(linkp, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,REJECT);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				action = 0;
			}
			break;
		}
	case LLCFT_INFO + LLC_CMD:
	case LLCFT_INFO + LLC_RSP:{
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				LLC_INC(linkp->llcl_vr);
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_STOP_REJ_TIMER(linkp);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_NEWSTATE(linkp,AWAIT);
				action = LLC_DATA_INDICATION;
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_INC(linkp->llcl_vr);
				LLC_STOP_P_TIMER(linkp);
				LLC_STOP_REJ_TIMER(linkp);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				llc_resend(linkp, LLC_CMD, 0);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,NORMAL);
				action = LLC_DATA_INDICATION;
			} else if (pollfinal == 0) {
				LLC_INC(linkp->llcl_vr);
				llc_send(linkp, LLCFT_RR, LLC_CMD, 0);
				LLC_STOP_REJ_TIMER(linkp);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_NEWSTATE(linkp,AWAIT);
				action = LLC_DATA_INDICATION;
			}
			break;
		}
	case LLCFT_RR + LLC_CMD:
	case LLCFT_REJ + LLC_CMD:
	case LLCFT_RR + LLC_RSP:
	case LLCFT_REJ + LLC_RSP:{
			int    nr = LLCGBITS(frame->llc_control_ext, s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				llc_resend(linkp, LLC_CMD, 1);
				LLC_START_P_TIMER(linkp);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
				LLC_NEWSTATE(linkp,REJECT);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_CLEAR_REMOTE_BUSY(linkp, action);
			}
			break;
		}
	case LLCFT_RNR + LLC_CMD:
	case LLCFT_RNR + LLC_RSP:{
			int    nr =
				LLCGBITS(frame->llc_control_ext,s_nr);

			if (cmdrsp == LLC_CMD && pollfinal == 1) {
				llc_send(linkp, LLCFT_RR, LLC_RSP, 1);
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			} else if (cmdrsp == LLC_RSP && pollfinal == 1) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				linkp->llcl_vs = nr;
				LLC_STOP_P_TIMER(linkp);
				LLC_SET_REMOTE_BUSY(linkp,action);
				LLC_NEWSTATE(linkp,REJECT);
			} else if (pollfinal == 0) {
				LLC_UPDATE_NR_RECEIVED(linkp, nr);
				LLC_SET_REMOTE_BUSY(linkp,action);
			}
			break;
		}
	case LLC_P_TIMER_EXPIRED:
		if (linkp->llcl_retry < llc_n2) {
			llc_send(linkp, LLCFT_REJ, LLC_CMD, 1);
			LLC_START_P_TIMER(linkp);
			linkp->llcl_retry++;
			action = 0;
		}
		break;
	}
	if (action == LLC_PASSITON)
		action = llc_state_NBRAcore(linkp, frame, frame_kind,
					    cmdrsp, pollfinal);

	return action;
}


/*
 * llc_statehandler() --- Wrapper for llc_state_*() functions.
 *                         Deals with action codes and checks for
 *                         ``stuck'' links.
 */

int
llc_statehandler(linkp, frame, frame_kind, cmdrsp, pollfinal)
	struct llc_linkcb *linkp;
	struct llc *frame;
	int frame_kind;
	int cmdrsp;
	int pollfinal;
{
	int    action = 0;

	/*
	 * To check for ``zombie'' links each time llc_statehandler() gets called
	 * the AGE timer of linkp is reset. If it expires llc_timer() will
	 * take care of the link --- i.e. kill it 8=)
	 */
	LLC_STARTTIMER(linkp,AGE);

	/*
	 * Now call the current statehandler function.
	 */
	action = (*linkp->llcl_statehandler) (linkp, frame, frame_kind,
					      cmdrsp, pollfinal);
once_more_and_again:
	switch (action) {
	case LLC_CONNECT_INDICATION:{
			int             naction;

			LLC_TRACE(linkp, LLCTR_INTERESTING, "CONNECT INDICATION");
			linkp->llcl_nlnext =
				(*linkp->llcl_sapinfo->si_ctlinput)
				(PRC_CONNECT_INDICATION,
				 (struct sockaddr *) & linkp->llcl_addr, (caddr_t) linkp);
			if (linkp->llcl_nlnext == 0)
				naction = NL_DISCONNECT_REQUEST;
			else
				naction = NL_CONNECT_RESPONSE;
			action = (*linkp->llcl_statehandler) (linkp, frame, naction, 0, 0);
			goto once_more_and_again;
		}
	case LLC_CONNECT_CONFIRM:
		/* llc_resend(linkp, LLC_CMD, 0); */
		llc_start(linkp);
		break;
	case LLC_DISCONNECT_INDICATION:
		LLC_TRACE(linkp, LLCTR_INTERESTING, "DISCONNECT INDICATION");
		(*linkp->llcl_sapinfo->si_ctlinput)
			(PRC_DISCONNECT_INDICATION,
		(struct sockaddr *) & linkp->llcl_addr, linkp->llcl_nlnext);
		break;
		/* internally visible only */
	case LLC_RESET_CONFIRM:
	case LLC_RESET_INDICATION_LOCAL:
		/*
		 * not much we can do here, the state machine either makes it or
		 * brakes it ...
		 */
		break;
	case LLC_RESET_INDICATION_REMOTE:
		LLC_TRACE(linkp, LLCTR_SHOULDKNOW, "RESET INDICATION (REMOTE)");
		action = (*linkp->llcl_statehandler) (linkp, frame,
						   NL_RESET_RESPONSE, 0, 0);
		goto once_more_and_again;
	case LLC_FRMR_SENT:
		LLC_TRACE(linkp, LLCTR_URGENT, "FRMR SENT");
		break;
	case LLC_FRMR_RECEIVED:
		LLC_TRACE(linkp, LLCTR_URGEN, "FRMR RECEIVED");
		action = (*linkp->llcl_statehandler) (linkp, frame,
						    NL_RESET_REQUEST, 0, 0);

		goto once_more_and_again;
	case LLC_REMOTE_BUSY:
		LLC_TRACE(linkp, LLCTR_SHOULDKNOW, "REMOTE BUSY");
		break;
	case LLC_REMOTE_NOT_BUSY:
		LLC_TRACE(linkp, LLCTR_SHOULDKNOW, "REMOTE BUSY CLEARED");
		/*
		 * try to get queued frames out
		 */
		llc_start(linkp);
		break;
	}

	/*
         * Only LLC_DATA_INDICATION is for the time being
	 * passed up to the network layer entity.
	 * The remaining action codes are for the time
	 * being visible internally only.
         * However, this can/may be changed if necessary.
	 */

	return action;
}


/*
 * Core LLC2 routines
 */

/*
 * The INIT call. This routine is called once after the system is booted.
 */

void
llc_init()
{
	llcintrq.ifq_maxlen = IFQ_MAXLEN;
}


/*
 * In case of a link reset we need to shuffle the frames queued inside the
 * LLC2 window.
 */

void
llc_resetwindow(linkp)
	struct llc_linkcb *linkp;
{
	struct mbuf *mptr = (struct mbuf *) 0;
	struct mbuf *anchor = (struct mbuf *) 0;
	short  i;

	/* Pick up all queued frames and collect them in a linked mbuf list */
	if (linkp->llcl_slotsfree != linkp->llcl_window) {
		i = llc_seq2slot(linkp, linkp->llcl_nr_received);
		anchor = mptr = linkp->llcl_output_buffers[i];
		for (; i != linkp->llcl_freeslot;
		     i = llc_seq2slot(linkp, i + 1)) {
			if (linkp->llcl_output_buffers[i]) {
				mptr->m_nextpkt = linkp->llcl_output_buffers[i];
				mptr = mptr->m_nextpkt;
			} else
				panic("LLC2 window broken");
		}
	}
	/* clean closure */
	if (mptr)
		mptr->m_nextpkt = (struct mbuf *) 0;

	/* Now --- plug 'em in again */
	if (anchor != (struct mbuf *) 0) {
		for (i = 0, mptr = anchor; mptr != (struct mbuf *) 0; i++) {
			linkp->llcl_output_buffers[i] = mptr;
			mptr = mptr->m_nextpkt;
			linkp->llcl_output_buffers[i]->m_nextpkt = (struct mbuf *) 0;
		}
		linkp->llcl_freeslot = i;
	} else
		linkp->llcl_freeslot = 0;

	/* We're resetting the link, the next frame to be acknowledged is 0 */
	linkp->llcl_nr_received = 0;

	/*
	 * set distance between LLC2 sequence number and the top of window to
	 * 0
	 */
	linkp->llcl_projvs = linkp->llcl_freeslot;

	return;
}

/*
 * llc_newlink() --- We allocate enough memory to contain a link control block
 *                   and initialize it properly. We don't intiate the actual
 *		     setup of the LLC2 link here.
 */
struct llc_linkcb *
llc_newlink(dst, ifp, nlrt, nlnext, llrt)
	struct sockaddr_dl *dst;
	struct ifnet *ifp;
	struct rtentry *nlrt;
	caddr_t nlnext;
	struct rtentry *llrt;
{
	struct llc_linkcb *nlinkp;
	u_char          sap = LLSAPADDR(dst);
	short           llcwindow;


	/* allocate memory for link control block */
	MALLOC(nlinkp, struct llc_linkcb *, sizeof(struct llc_linkcb),
	       M_PCB, M_DONTWAIT);
	if (nlinkp == 0)
		return (NULL);
	bzero((caddr_t) nlinkp, sizeof(struct llc_linkcb));

	/* copy link address */
	sdl_copy(dst, &nlinkp->llcl_addr);

	/* hold on to the network layer route entry */
	nlinkp->llcl_nlrt = nlrt;

	/* likewise the network layer control block */
	nlinkp->llcl_nlnext = nlnext;

	/* jot down the link layer route entry */
	nlinkp->llcl_llrt = llrt;

	/* reset writeq */
	nlinkp->llcl_writeqh = nlinkp->llcl_writeqt = NULL;

	/* setup initial state handler function */
	nlinkp->llcl_statehandler = llc_state_ADM;

	/* hold on to interface pointer */
	nlinkp->llcl_if = ifp;

	/* get service access point information */
	nlinkp->llcl_sapinfo = llc_getsapinfo(sap, ifp);

	/* get window size from SAP info block */
	if ((llcwindow = nlinkp->llcl_sapinfo->si_window) == 0)
		llcwindow = LLC_MAX_WINDOW;

	/* allocate memory for window buffer */
	MALLOC(nlinkp->llcl_output_buffers, struct mbuf **,
	       llcwindow * sizeof(struct mbuf *), M_PCB, M_DONTWAIT);
	if (nlinkp->llcl_output_buffers == 0) {
		FREE(nlinkp, M_PCB);
		return (NULL);
	}
	bzero((caddr_t) nlinkp->llcl_output_buffers,
	      llcwindow * sizeof(struct mbuf *));

	/* set window size & slotsfree */
	nlinkp->llcl_slotsfree = nlinkp->llcl_window = llcwindow;

	/* enter into linked listed of link control blocks */
	insque(nlinkp, &llccb_q);

	return (nlinkp);
}

/*
 * llc_dellink() --- farewell to link control block
 */
void
llc_dellink(linkp)
	struct llc_linkcb *linkp;
{
	struct mbuf *m;
	struct mbuf *n;
	struct npaidbentry *sapinfo = linkp->llcl_sapinfo;
	int    i;

	/* notify upper layer of imminent death */
	if (linkp->llcl_nlnext && sapinfo->si_ctlinput)
		(*sapinfo->si_ctlinput)
			(PRC_DISCONNECT_INDICATION,
		(struct sockaddr *) & linkp->llcl_addr, linkp->llcl_nlnext);

	/* pull the plug */
	if (linkp->llcl_llrt)
		((struct npaidbentry *) (linkp->llcl_llrt->rt_llinfo))->np_link
			= (struct llc_linkcb *) 0;

	/* leave link control block queue */
	remque(linkp);

	/* drop queued packets */
	for (m = linkp->llcl_writeqh; m;) {
		n = m->m_act;
		m_freem(m);
		m = n;
	}

	/* drop packets in the window */
	for (i = 0; i < linkp->llcl_window; i++)
		if (linkp->llcl_output_buffers[i])
			m_freem(linkp->llcl_output_buffers[i]);

	/* return the window space */
	FREE((caddr_t) linkp->llcl_output_buffers, M_PCB);

	/* return the control block space --- now it's gone ... */
	FREE((caddr_t) linkp, M_PCB);
}

int
llc_decode(frame, linkp)
	struct llc *frame;
	struct llc_linkcb *linkp;
{
	int    ft = LLC_BAD_PDU;

	if ((frame->llc_control & 01) == 0) {
		ft = LLCFT_INFO;
		/* S or U frame ? */
	} else
		switch (frame->llc_control) {

			/* U frames */
		case LLC_UI:
		case LLC_UI_P:
			ft = LLC_UI;
			break;
		case LLC_DM:
		case LLC_DM_P:
			ft = LLCFT_DM;
			break;
		case LLC_DISC:
		case LLC_DISC_P:
			ft = LLCFT_DISC;
			break;
		case LLC_UA:
		case LLC_UA_P:
			ft = LLCFT_UA;
			break;
		case LLC_SABME:
		case LLC_SABME_P:
			ft = LLCFT_SABME;
			break;
		case LLC_FRMR:
		case LLC_FRMR_P:
			ft = LLCFT_FRMR;
			break;
		case LLC_XID:
		case LLC_XID_P:
			ft = LLCFT_XID;
			break;
		case LLC_TEST:
		case LLC_TEST_P:
			ft = LLCFT_TEST;
			break;

			/* S frames */
		case LLC_RR:
			ft = LLCFT_RR;
			break;
		case LLC_RNR:
			ft = LLCFT_RNR;
			break;
		case LLC_REJ:
			ft = LLCFT_REJ;
			break;
		}		/* switch */

	if (linkp) {
		switch (ft) {
		case LLCFT_INFO:
			if (LLCGBITS(frame->llc_control,i_ns) !=
			    linkp->llcl_vr) {
				ft = LLC_INVALID_NS;
				break;
			}
			/* fall thru --- yeeeeeee */
		case LLCFT_RR:
		case LLCFT_RNR:
		case LLCFT_REJ:
			/* splash! */
			if (LLC_NR_VALID(linkp,
					 LLCGBITS(frame->llc_control_ext,s_nr))
					 == 0)
				ft = LLC_INVALID_NR;
			break;
		}
	}
	return ft;
}

/*
 * llc_anytimersup() --- Checks if at least one timer is still up and running.
 */
int
llc_anytimersup(linkp)
	struct llc_linkcb *linkp;
{
	int    i;

	FOR_ALL_LLC_TIMERS(i)
		if (linkp->llcl_timers[i] > 0)
		break;
	if (i == LLC_AGE_SHIFT)
		return 0;
	else
		return 1;
}

/*
 * llc_link_dump() - dump link info
 */

#define SAL(s) ((struct sockaddr_dl *)&(s)->llcl_addr)
#define CHECK(l,s) if (LLC_STATEEQ(l,s)) return __STRING(s)

char *timer_names[] = {"ACK", "P", "BUSY", "REJ", "AGE"};

char *
llc_getstatename(linkp)
	struct llc_linkcb *linkp;
{
	CHECK(linkp,ADM);
	CHECK(linkp,CONN);
	CHECK(linkp,RESET_WAIT);
	CHECK(linkp,RESET_CHECK);
	CHECK(linkp,SETUP);
	CHECK(linkp,RESET);
	CHECK(linkp,D_CONN);
	CHECK(linkp,ERROR);
	CHECK(linkp,NORMAL);
	CHECK(linkp,BUSY);
	CHECK(linkp,REJECT);
	CHECK(linkp,AWAIT);
	CHECK(linkp,AWAIT_BUSY);
	CHECK(linkp,AWAIT_REJECT);

	return "UNKNOWN - eh?";
}

void
llc_link_dump(linkp, message)
	struct llc_linkcb *linkp;
	const char *message;
{
	int    i;

	/* print interface */
	printf("if %s\n", linkp->llcl_if->if_xname);

	/* print message */
	printf(">> %s <<\n", message);

	/* print MAC and LSAP */
	printf("llc addr ");
	for (i = 0; i < (SAL(linkp)->sdl_alen) - 2; i++)
		printf("%x:", (char) *(LLADDR(SAL(linkp)) + i) & 0xff);
	printf("%x,", (char) *(LLADDR(SAL(linkp)) + i) & 0xff);
	printf("%x\n", (char) *(LLADDR(SAL(linkp)) + i + 1) & 0xff);

	/* print state we're in and timers */
	printf("state %s, ", llc_getstatename(linkp));
	for (i = LLC_ACK_SHIFT; i < LLC_AGE_SHIFT; i++)
		printf("%s-%c %d/", timer_names[i],
		       (linkp->llcl_timerflags & (1 << i) ? 'R' : 'S'),
		       linkp->llcl_timers[i]);
	printf("%s-%c %d\n", timer_names[i], (linkp->llcl_timerflags & (1 << i) ?
					 'R' : 'S'), linkp->llcl_timers[i]);

	/* print flag values */
	printf("flags P %d/F %d/S %d/DATA %d/REMOTE_BUSY %d\n",
	       LLC_GETFLAG(linkp,P), LLC_GETFLAG(linkp,F),
	       LLC_GETFLAG(linkp,S),
	       LLC_GETFLAG(linkp,DATA), LLC_GETFLAG(linkp,REMOTE_BUSY));

	/* print send and receive state variables, ack, and window */
	printf("V(R) %d/V(S) %d/N(R) received %d/window %d/freeslot %d\n",
	       linkp->llcl_vs, linkp->llcl_vr, linkp->llcl_nr_received,
	       linkp->llcl_window, linkp->llcl_freeslot);

	/* further expansions can follow here */

}

void
llc_trace(linkp, level, message)
	struct llc_linkcb *linkp;
	int level;
	const char *message;
{
	if (linkp->llcl_sapinfo->si_trace && level > llc_tracelevel)
		llc_link_dump(linkp, message);
}
