/*****************************************************************************
 * Copyright (c) 1988-2000 Digi International Inc., All Rights Reserved
 * 
 * This  software  contains  proprietary  and  confidential   information   of
 * Digi International  Inc.   By  accepting  transfer  of this copy, Recipient
 * agrees to retain this software in  confidence,  to  prevent  disclosure  to
 * others,  and  to make no  use of this software other than that for which it
 * was delivered.  This is an unpublished copyright work  Digi   International
 * Inc.    Except   as   permitted   by  federal  law,  17 USC 117, copying is
 * strictly prohibited.
 * 
 * Digi  International  Inc.  CONFIDENTIAL  -   (Digi    International    Inc.
 * Confidential  when   combined   with   the   aggregated  modules  for  this
 * product) OBJECT CODE ONLY SOURCE MATERIALS
 * 
 * (C) COPYRIGHT Digi International Inc. 2000
 * 
 * All Rights Reserved
 * 
 * US   Government   Users   Restricted   Rights   -   Use,   duplication   or
 * disclosure  restricted by GSA ADP Schedule Contract with Digi International
 * Inc.
 * 
 *****************************************************************************/

/******************************************************************
 *
 *  SpiderX25 Protocol Streams Component
 *
 *  Copyright 1991 Spider Systems Limited
 *
 *  LAPBSTATE.C
 *
 *    LAPB State Machine.
 *
 ******************************************************************/

/*
 *	 /projects/common/PBRAIN/SCCS/pbrainF/dev/sys/lapb/0/s.lapbstate.c
 *	@(#)lapbstate.c	1.79
 *
 *	Last delta created	12:09:38 7/10/92
 *	This file extracted	22:30:17 11/19/92
 *
 */


/* Converted for STREAMS   1988 DJMM */


#define STATIC static
#define BUFP_T bufp_t
#define DBDIAG(MP, BYTE)        (MP)->b_datap->db_diag=BYTE;

#include "types.h"
#include "errno.h"
#include "stream.h"
#include "strlog.h"
#include "system.h"
#include "ll_proto.h"
#include "ll_control.h"
#include "wan_proto.h"
#include "timer.h"
#include "lapb.h"


/* Stock buffer pointers */
extern mblk_t *lapb_upmp;
extern mblk_t *lapb_cnmp;
extern mblk_t *lapb_dnmp;
extern mblk_t *lapb_dimp;
extern mblk_t *lapb_udmp;

/* PDU values */
#define SABM          0x2F
#define SABME         0x6F
#define SARM          0x0F
#define DISC          0x43
#define UA            0x63
#define DM            0x0F
#define FRMR          0x87
#define CMDR          0x87

#define RR            0x01
#define RNR           0x05
#define REJ           0x09

#define PBIT          0x10

/* Decode table for incoming U format frames */

/* Command switch values */
#define U_SABM  1
#define U_SABME 2
#define U_DISC  3
#define U_SARM  4

/* Response switch values */
#define U_DM	1
#define U_UA	2
#define U_FRMR  3

/* Functional prototypes */
STATIC void I_unexp_ns();
STATIC void report_reset();
STATIC void retry_expired();
STATIC void I_valseq();
STATIC void RR_in();
STATIC void RNR_in();
STATIC void REJ_in();
STATIC void timer_exp();
STATIC void rej_expired();
STATIC int validatecode();
STATIC void rxacked();
STATIC int update_nr();
STATIC void stop_p_timer();
STATIC void stop_all_timers();
STATIC void retransmit();
STATIC void goto_normal();
STATIC void goto_reset();
STATIC void delay_SABM();
STATIC void delay_SARM();
STATIC void start_SABM();
STATIC void start_SARM();
STATIC void set_rem_busy();
STATIC void clear_rem_busy();
STATIC void send_Uframe();
STATIC void send_Srsp();
STATIC void send_SPcmd();
STATIC void send_FRMR();
STATIC void zero_v_vals();
STATIC void DISC_in(lapb_t *);
STATIC void SABM_in(lapb_t *);
STATIC void DM_in(lapb_t *, unsigned int);
STATIC void SARM_in(lapb_t *);
STATIC void FRMR_in(lapb_t *);
void send_wctl(int,lapb_t *,struct ll_msgc FAR *);
STATIC void init_pf_cycle(lapb_t *);

static ucmd_switch [64] =
{
    0,      0,      0, U_SARM,	0,	0,	0, U_SARM,
    0,      0,      0, U_SABM,  0,	0,	0, U_SABM,
    U_DISC, 0,      0, 0,	U_DISC, 0,	0, 0,
    U_UA,   0,      0, U_SABME, U_UA,   0,	0, U_SABME,
    0,      0,      0, 0,	0,	0,	0, 0,
    0,      0,      0, 0,	0,	0,	0, 0,
    0,      0,      0, 0,	0,	0,	0, 0,
    0,      0,      0, 0,	0,	0,	0, 0
};

static ursp_switch [64] =
{
    0,      0,      0, U_DM,	0,	0,	0, U_DM,
    0,      0,      0, 0,	0,	0,	0, 0,
    0,      0,      0, 0,	0,	0,	0, 0,
    U_UA,   0,      0, 0,	U_UA,   0,	0, 0,
    0,      U_FRMR, 0, 0,	0,	U_FRMR, 0, 0,
    0,      0,      0, 0,	0,	0,	0, 0,
    0,      0,      0, 0,	0,	0,	0, 0,
    0,      0,      0, 0,	0,	0,	0, 0
};

#define FRMR_WBIT   0x01
#define FRMR_XBIT   0x02
#define FRMR_YBIT   0x04
#define FRMR_ZBIT   0x08
/* invalid response frames have FRMR_WBIT except when in ERROR state when
   they are ignored - therefore have a special setting to do a check */
#define FRMR_INVRSP 0X10

extern	int getstock();
extern	mblk_t * allocb();


/* REC_I_CMD(P=X) with UNEXP_N(S)  */
/*    *************************************************** I_unexp_ns  */
STATIC void I_unexp_ns(lp)
register lapb_t *lp;
{
    if (lp->busyflag == 0)
    {
	if (!lp->reject)
	{
	    /* NORMAL or AWAIT state */
	    send_Srsp(lp, REJ);
	    set_timer(&lp->rej_timer, (unsigned short) (lp->tunetab.Trej << 1));
	    lp->reject = 1;
	    return;
	}

	/* REJECT or AWAIT_REJECT state */
	if (lp->need_fbit)
	    send_Srsp(lp, RR);
	return;
    }

    /* BUSY or AWAIT_BUSY state */
    send_Srsp(lp, RNR);
    if (lp->dataflag == 0)
	lp->dataflag = 1;
}


/* REC_I_CMD(P=X) and correct sequence */
/*    *************************************************** I_valseq  */
STATIC void I_valseq(lp, bp)
register lapb_t *lp;
mblk_t *bp;
{
    int rejflag;
    DBDIAG(bp, 0x70)

    LAPBMONITOR(I_rx_cmd)

    /*
     * If we were in REJECT, AWAIT REJ or in AWAIT_BUSY
     * with DATAflag == 2, we have received the frame we asked for,
     * so stop REJ_TIMER
     */
    rejflag = lp->reject;
    lp->reject = 0;
    set_timer(&lp->rej_timer, (unsigned short) 0);
    if (lp->busyflag == 0)
    {
	/* Increment V(R) */
	lp->vr++;

	/* L_DATA.indication */
	lp->upcmd = LC_DATA;
	lp->upstatus = LS_SUCCESS;
	DBDIAG(bp, 0x71)
	uplapb(lp, bp);

	if (rejflag)
	{
	    /* Previously in a REJ state, so arrange */
	    /* to inform other end everything now OK */
	    lp->need_ack = 1;
	    return;
	}

	if (lp->need_fbit ||
		(uint16)(lp->vr - lp->ar & lp->seqmask) >= lp->tunetab.notack_max)
	{   /* PBIT set, or too many unacknowledged I frames, so arrange to */
	    /* send RR if no suitable I-frame available */
	    lp->need_ack = 1;
	}
	else
	{
	    /* Start ack delay if not already started */
	    run_timer(&lp->RR_timer, (unsigned short) (lp->tunetab.ack_delay << 1));
	}
	return;
    }

    /* Busy state - discard the data and reject the frame */
    freemsg(bp);
    send_Srsp(lp, RNR);
    lp->dataflag = 1;
}



/* REC_RR_CMD or REC_RR_RSP */
/*    *************************************************** RR_in  */
STATIC void RR_in(lp, fbit)
register lapb_t *lp;
unsigned int fbit;
{
    clear_rem_busy(lp);
    if (fbit)
    {
        LAPBMONITOR(RR_rx_rsp)
	stop_p_timer(lp);
	if (lp->awaitflag)
	{
	    lp->awaitflag = 0;
	    lp->txstopped--;

	    if (lp->vs != lp->as)
		retransmit(lp);
	}
	return;
    }

    if (lp->need_fbit)
    {
	send_Srsp(lp, (lp->busyflag || (lp->i_frame_cnt > 8)) ? RNR : RR);
	LAPBMONITOR(RR_rx_cmd)
    }
    else
	LAPBMONITOR(RR_rx_rsp)
}



/* REC_RNR_CMD or REC_RNR_RSP */
/*    *************************************************** RNR_in  */
STATIC void RNR_in(lp, fbit)
register lapb_t *lp;
unsigned int fbit;
{
    set_rem_busy(lp);
    if (fbit)
    {
        LAPBMONITOR(RNR_rx_rsp)
	stop_p_timer(lp);
	if (lp->awaitflag)
	{
	    lp->awaitflag = 0;
	    lp->txstopped--;
	}

#ifndef NORETRANSMIT
	/* retransmit unacknowledged frames */
	retransmit(lp);
#endif
	return;
    }

    if (lp->need_fbit)
    {
	send_Srsp(lp, (lp->busyflag || (lp->i_frame_cnt > 8)) ? RNR : RR);
	LAPBMONITOR(RNR_rx_cmd)
    }
    else
	LAPBMONITOR(RNR_rx_rsp)
}



/* REC_REJ_CMD or REC_REJ_RSP */
/*    *************************************************** REJ_in  */
STATIC void REJ_in(lp, fbit)
register lapb_t *lp;
unsigned int fbit;
{
    strlog(LAPB_STID,(short)lp->snid,3,SL_TRACE,"REJ   In  : '%x' N(%03d)",(short)lp->snid,lp->as);

    clear_rem_busy(lp);
    if (fbit)
    {
        LAPBMONITOR(REJ_rx_rsp)
	stop_p_timer(lp);
	if (lp->awaitflag)
	{
	    lp->awaitflag = 0;
	    lp->txstopped--;
	}

	retransmit(lp);
	return;
    }

    if (lp->need_fbit)
    {
	send_Srsp(lp, (lp->busyflag || (lp->i_frame_cnt > 8)) ? RNR : RR);
	LAPBMONITOR(REJ_rx_cmd)
    }
    else
	LAPBMONITOR(REJ_rx_rsp)

    if (lp->awaitflag == 0)
	retransmit(lp);
}



/* ACK or BUSY_TIMER EXPIRED with Pflag = 0 */
/* P_TIMER_EXPIRED */
/*    *************************************************** timer_exp  */
STATIC void timer_exp(lp)
register lapb_t *lp;
{
    lp->upstatus = LS_SUCCESS;

    switch (lp->state)
    {
    case NORMAL:

	if (lp->mode == LAPB_MODE)
	{
	    send_SPcmd(lp, (lp->busyflag || (lp->i_frame_cnt > 8)) ? RNR : RR);
	    if (lp->reject)
		set_timer(&lp->rej_timer, (unsigned short) (lp->tunetab.Trej << 1));
	    if (lp->awaitflag == 0)
		lp->txstopped++;
	    lp->awaitflag = 1;
	}
	else
	{
#ifndef NORETRANSMIT
	    send_SPcmd(lp, (lp->busyflag || (lp->i_frame_cnt > 8))? RNR : RR);
#else
	    retransmit(lp);
#endif
	}
	return;

    case D_CONN:
	send_Uframe(lp, DISC | PBIT);
	return;

    case POLLING:
	lp->retry_count = 0;
	if (lp->pollflag)
	{   /* T3 interval has just ended */
	    lp->pollflag = 0;
	    send_Uframe(lp, DISC);
#if 1
		printf("DISC O1") ;
#endif
	    set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
	    return;
	}
	send_Uframe(lp, DISC | PBIT);
	return;

    case ARM:

    case ADM:
	lp->retry_count = 0;

    case PRESETUP:
	if (lp->clashflag)
	{
		/* SABM(SARM)/DISC collision had occurred */
	    if (lp->mode == LAPB_MODE)
		start_SABM(lp, SETUP);
	    else
		start_SARM(lp, SETUP);
	    return;
	}
	/* remote end isn't replying
	   give it another kick with a DISC */
	send_Uframe(lp, DISC | PBIT);
	return;

    case SETUP:
	if (lp->mode == LAPB_MODE)
	{
	    if (lp->sflag)
	    {
		lp->upcmd = LC_CONCNF;
		lp->upstatus = LS_SUCCESS;
		goto_normal(lp);
		uplapb(lp, (mblk_t *)0);
		return;
	    }
	    /*
	     * In SETUP state, we have sent DM followed by SABM, either of which
	     * may have got lost.  (Or UA response).  If DM, the DCE will have
	     * repeated the DISC after T1 and we will have replied DM.
	     */
	    send_Uframe(lp, lp->extended ? SABME | PBIT : SABM | PBIT);
	}
	else
	    send_Uframe(lp, SARM | PBIT);
	return;

    case RESET:
	if (lp->mode == LAPB_MODE)
	{
	    lp->upcmd = LC_REPORT;
	    lp->upstatus = LS_RESETDONE;
	    if (lp->sflag)
	    {
		goto_normal(lp);
		uplapb(lp, (mblk_t *)0);
		return;
	    }
	    send_Uframe(lp, lp->extended ? SABME | PBIT : SABM | PBIT);
	}
	else
	{
	    if (lp->sflag)
	    {
		lp->upcmd = LC_REPORT;
		lp->upstatus = LS_RESETDONE;
		goto_normal(lp);
		uplapb(lp, (mblk_t *)0);
		return;
	    }
	    send_Uframe(lp, SARM | PBIT);
	}
	return;

    case ERROR:
	send_FRMR(lp);
	set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
	return;
    }
}


/* REJ_TIMER_EXPIRED */
/*    *************************************************** rej_expired  */
STATIC void rej_expired(lp)
register lapb_t *lp;
{
    lp->reject = 0;

    if (lp->pflag)
    {
	if (lp->busyflag || (lp->i_frame_cnt > 8))
	    lp->dataflag = 1;
	else
	{
	    lp->reject = 1;
	    set_timer(&lp->rej_timer, (unsigned short) (lp->tunetab.Trej << 1));
	}
	return;
    }

    if (lp->retry_count >= lp->tunetab.N2)
    {
	retry_expired(lp);
	return;
    }

    lp->retry_count++;
    if (lp->busyflag == 0 || (lp->i_frame_cnt > 8))
    {
	lp->reject = 1;
	set_timer(&lp->rej_timer, (unsigned short) (lp->tunetab.Trej << 1));
	send_SPcmd(lp, REJ);
	return;
    }

    /* BUSY or AWAIT BUSY */
    lp->dataflag = 1;
    send_SPcmd(lp, RNR);
    if (lp->awaitflag == 0)
	lp->txstopped++;
    lp->awaitflag = 1;
}



/* LOCAL BUSY DETECTED */
/*    *************************************************** set_loc_busy  */
void set_loc_busy(lp)
register lapb_t *lp;
{
    if (lp->busyflag == 0)
    {
	lp->busyflag = 1;
#ifdef RESET_RR_TIMER
	set_timer(&lp->RR_timer, (unsigned short) 0);
#endif
	send_Srsp(lp, RNR);
	lp->dataflag = lp->reject ? 2 : 0;
	strlog(LAPB_STID,(short)lp->snid,4,SL_TRACE,"LINK  Bsy : '%x' Local",
							(short)lp->snid);
    }
}



/* LOCAL BUSY CLEARED */
/*    *************************************************** clear_loc_busy  */
void clear_loc_busy(lp)
register lapb_t *lp;
{
    if (lp->busyflag)
    {
	lp->busyflag = 0;
	if (lp->dataflag == 1)
	{
	    send_Srsp(lp, REJ);
	    lp->reject = 1;
	    set_timer(&lp->rej_timer, (unsigned short) (lp->tunetab.Trej << 1));
	}
	else
	    send_Srsp(lp, RR);
    }
}



/*    *************************************************** init_pf_cycle  */
STATIC void init_pf_cycle(lp)
register lapb_t *lp;
{
    if (!lp->pflag)
    {
	send_SPcmd(lp,(lp->busyflag || (lp->i_frame_cnt > 8)) ? RNR : RR);
    }
}



/*    *************************************************** DISC_in  */
STATIC void DISC_in(lp)
register lapb_t *lp;
{
    LAPBMONITOR(DISC_rx_cmd)
    lp->upstatus = LS_DISCONNECT;
    switch (lp->state)
    {
    case POLLING:
	if (lp->mode == LAPB_MODE)
	    send_Uframe(lp, DM);
	else
	    send_Uframe(lp, UA);
	return;

    case ARM:

    case ADM:

    case PRESETUP:

	if (lp->mode == LAPB_MODE)
	{
	    send_Uframe(lp, DM);
            delay_SABM(lp);  /* start a clock to send SABM when it fires */
	}
	else
	{
	    send_Uframe(lp, UA);
            delay_SARM(lp);  /* start a clock to send SARM when it fires */
	}
	return;

    case SETUP:
	if (lp->mode == LAPB_MODE)
	{
	    /*
	     * P timer is running and response may arrive soon, so a second
	     * SABM will only confuse the situation.
	     * As the remote end has not responded to the SABM, give it
	     * a chance to transmit a SABM
	     */
	    send_Uframe(lp, DM);
	    lp->state = lp->causeflag ? PRESETUP : ADM;
            delay_SABM(lp);  /* start a clock to send SABM when it fires */
	}
	else
	{
	    /*
	     * P timer is running and response may arrive soon, so a second
	     * SARM will only confuse the situation.
	     * As the remote end has not responded to the SARM, give it
	     * a chance to transmit a SARM
	     */
	    send_Uframe(lp, UA);
	    lp->state = lp->causeflag ? PRESETUP : ARM;
            delay_SARM(lp);  /* start a clock to send SARM when it fires */
	}
	return;

    case RESET:
	if (lp->mode == LAPB_MODE)
	{
	    /* conflict of requests - reply DM and enter ADM */
	    send_Uframe(lp, DM);
	    if (lp->causeflag)
		lp->upcmd = LC_RSTCNF;
	    else
	    {
		lp->upcmd = LC_REPORT;
		lp->upstatus = LS_RST_REFUSED;
	    }
	}
	else
	{
	    /* conflict of requests - reply UA and enter D_CONN */
	    send_Uframe(lp, UA);
	    if (lp->causeflag)
		lp->upcmd = LC_RSTCNF;
	    else
	    {
		lp->upcmd = LC_REPORT;
		lp->upstatus = LS_RST_REFUSED;
	    }
	}
	break;

    case NORMAL:
    case ERROR:
	send_Uframe(lp, UA);
	lp->upcmd = LC_DISC;
	break;

    case D_CONN:
	if (lp->mode == LAPB_MODE)
	{
	    send_Uframe(lp, DM);
	    lp->upcmd = LC_DISCNF;
	}
	else
	{
	    send_Uframe(lp, UA);
	}
	
	break;

    }
    if (lp->mode == LAPB_MODE)
    {
	uplapb(lp, (mblk_t *)0);

#ifdef NO_WAN_CONTROL
	init_mode(lp, ADM);
	delay_SABM(lp);   /* start a clock and send SABM when it fires */
#else
	if ( lp->state == D_CONN )
	{
	    /*
		Disc initiated from above so send WC_DISC
		request to the WAN.
	    */
		printf("WC_DISC 1");
	    send_wctl(WC_DISC,lp,0);
	    init_mode(lp, OFF);
	}
	else
	{
	    init_mode(lp, ADM);
	    delay_SABM(lp);   /* start a clock and send SABM when it fires */
	}
#endif
    }
    else
    {
	if (lp->state == D_CONN)
	{
	    if (lp->myaddr == B_ADDRESS)
	    {
		init_mode(lp, POLLING);
	    }
	    else
	    {
		init_mode(lp, ARM);
		send_Uframe(lp, DISC | PBIT);
	    }
	}
	else
	{
	    uplapb(lp, (mblk_t *)0);
	    send_Uframe(lp, DISC);
#if 1
		printf("DISC 02") ;
#endif
	    init_mode(lp, (lp->myaddr == B_ADDRESS) ? POLLING : D_CONN);
	}
    }

}


/*    *************************************************** SABM_in  */
STATIC void SABM_in(lp)
register lapb_t *lp;
{
    lp->upcmd = LC_RESET;
    lp->upstatus = LS_SUCCESS;
    switch (lp->state)
    {
    case POLLING:
	if (!lp->pollflag)
	    return;

    case ADM:

    case PRESETUP: /* SABMs are likely to be received from CPSE */

	if (lp->causeflag == 0)
	{
	    lp->upcmd = LC_CONNECT;
	    lp->connected = 1;
	    uplapb(lp, (mblk_t *)0);
	    return;
	}
	lp->upcmd = LC_CONCNF;
    case ERROR:
    case NORMAL:
	send_Uframe(lp, UA);
	goto_normal(lp);
	uplapb(lp, (mblk_t *)0);
	return;

    case SETUP:
    case RESET:
	send_Uframe(lp, UA);
#ifdef SABM_COLLISION
	/*
	    The sflag mechanism is used if a SABM collision is to be
	    handled as per option 3 of CCITT X.25 Section 2.4.4.5.1
	    Otherwise, if no UA response is received to the previous
	    SABM, the SABM will be re-transmitted on timer expiry.
	 */
	lp->sflag = 1;
#endif
	return;

    case D_CONN:
	send_Uframe(lp, DM);
	lp->upcmd = LC_DISCNF;
	uplapb(lp, (mblk_t *)0);
	
#ifdef NO_WAN_CONTROL
	init_mode(lp, ADM);
#else
	init_mode(lp, OFF);
	/*
	    Disconnect iniated from above so
	    send WC_DISC request to the WAN
	*/
	printf("WC_DISC 2") ;
	send_wctl(WC_DISC,lp,0);
#endif
	return;
    }
}



/*    *************************************************** SARM_in  */
STATIC void SARM_in(lp)
register lapb_t *lp;
{
    LAPBMONITOR(SARM_rx_cmd)
    lp->upcmd = LC_RESET;
    lp->upstatus = LS_SUCCESS;
    switch (lp->state)
    {
    case POLLING:
	if (!lp->pollflag)
	    return;
    case ARM:

    case PRESETUP:

	lp->sflag = 1;
	send_Uframe(lp, UA);
	send_Uframe(lp, SARM | PBIT);
	lp->state = SETUP;
	break;

    case SETUP:
	/* Need the sflag mechanism because receiving a UA from remote end
	 * in reply to previous SARM would generate a CMDR and then a reset
	 */
	lp->sflag = 1;
	send_Uframe(lp, UA);
	lp->upcmd = LC_CONNECT;
	lp->connected = 1;
	uplapb(lp, (mblk_t *)0);
	return;

    case ERROR:
	send_Uframe(lp, UA);
	stop_all_timers(lp);
	lp->vr = lp->ar = 0;
	lp->state = RESET;
	send_Uframe(lp, SARM | PBIT);
	lp->upcmd = LC_REPORT;
	lp->upstatus = LS_RESETTING;
	uplapb(lp, (mblk_t *)0);
	return;

    case NORMAL:
	send_Uframe(lp, UA);
	stop_all_timers(lp);
	lp->vr = lp->ar = 0;
	uplapb(lp, (mblk_t *)0);
	return;

    case RESET:
	/* Need the sflag mechanism because receiving a UA from remote end
	 * in reply to previous SABM would generate a FRMR and then a reset
	 */
	lp->sflag = 1;
	send_Uframe(lp, UA);
	lp->upcmd = LC_REPORT;
	lp->upstatus = LS_RESETDONE;
	uplapb(lp, (mblk_t *)0);
	return;

    case D_CONN:
	send_Uframe(lp, UA);
#ifdef REPORT_DISC
	lp->upcmd = LC_DISCNF;
	uplapb(lp, (mblk_t *)0);
#endif
	init_mode(lp, ARM);
	return;
    }
}



/*    *************************************************** UA_in  */
STATIC void UA_in(lp, fbit)
register lapb_t *lp;
unsigned int fbit;
{
    LAPBMONITOR(UA_rx_rsp)
    lp->upstatus = LS_SUCCESS;
	// printf("UA_in state=%x fbit=%x %x %xY", lp->state, fbit,lp->pflag,lp->mode) ;
    switch (lp->state)
    {
    case D_CONN:

	if (lp->mode == LAPB_MODE)
	{
	    if (fbit == lp->pflag)
	    {
		lp->upcmd = LC_DISCNF;
		lp->upstatus = LS_DISCONNECT;
		uplapb(lp, (mblk_t *)0);
		
#ifdef NO_WAN_CONTROL
		init_mode(lp, ADM);
#else
		init_mode(lp, OFF);
		/*
		    Disconnect initiated from above so 
		    send WC_DISC request to the WAN
		*/
		printf("WC_DISC 3") ;
		send_wctl(WC_DISC,lp,0);
#endif
	    }
	}
	else
	{
	    if (fbit == lp->pflag)
	    {
		if (lp->causeflag)
                {
                    lp->upcmd = LC_DISCNF;
                    lp->upstatus = LS_DISCONNECT;
                    uplapb(lp, (mblk_t *)0);
		}
		init_mode(lp, ARM);
	    }
	}
	return;

    case POLLING:
	stop_p_timer(lp);
	lp->pollflag = 1;
	set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1*lp->tunetab.N2 << 1));
	return;

    case ARM:

    case ADM:

    case PRESETUP:

	if (fbit == lp->pflag)
	{
	    if (lp->mode == LAPB_MODE)
		start_SABM(lp, SETUP);
	    else
		start_SARM(lp, SETUP);
	}
	return;

    case SETUP:

	if (lp->mode == LAPB_MODE)
	{
	    if (fbit == 0)
		return;

	    if (lp->causeflag)
	    {
		lp->upcmd =  LC_CONCNF;
		goto_normal(lp);
		uplapb(lp, (mblk_t *)0);
		return;
	    }
	    else
	    {
		lp->upcmd = LC_CONNECT;
		lp->connected = 1;
		stop_all_timers(lp);
		uplapb(lp, (mblk_t *)0);
		return;
	    }
	}
	else
	{
	    if (lp->causeflag)
	    {
		lp->upcmd =  LC_CONCNF;
	    }
	    else
	    {
		if (lp->sflag)
		{
		    lp->upcmd = LC_CONNECT;
		    lp->connected = 1;
		    uplapb(lp, (mblk_t *)0);
		    return;
		}
	    }
	}
	break;


    case RESET:

	if (fbit != lp->pflag)
	    return;

	if (lp->causeflag)
	{
		lp->upcmd = LC_RSTCNF;
	}
	else
	{
		lp->upcmd = LC_REPORT;
		lp->upstatus = LS_RESETDONE;
	}
	goto_normal(lp);
	uplapb(lp, (mblk_t *)0);
	return;
    }
}



/*    *************************************************** DM_in  */
STATIC void DM_in(lp, fbit)
register lapb_t *lp;
unsigned int fbit;
{
    LAPBMONITOR(DM_rx_rsp)
    lp->upstatus = LS_DISCONNECT;
    switch (lp->state)
    {
    case POLLING:
	stop_p_timer(lp);
	lp->pollflag = 1;
	set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1*lp->tunetab.N2 << 1));
	return;

    case NORMAL:
    case ERROR:

	/*
	 * Report the Disconnection and then attempt to remake
	 * the connection
	 */
	lp->upcmd = LC_DISC;
	uplapb(lp, (mblk_t *)0);

	init_mode(lp, ADM);
	start_SABM(lp, SETUP); /* send SABM(P), state to SETUP */
        return;

    case ADM:

    case PRESETUP:
	/*
	 * This is in for the CCITT definition of the standard.
	 * In PRESETUP state, causeflag already set to 1
	 */
        if (lp->pflag == fbit)
	    start_SABM(lp, SETUP); /* send SABM(P), state to SETUP */
	return;

    case SETUP:
	/*
	 * The next line is in to cater for the CCITT definition
	 * of the standard which transmits DM(F=0) to solicit
	 * SABM rather than the DISC which PSS uses.
	 */
	if (fbit == 0)
	    return;
	lp->upcmd = LC_CONCNF;
	return;

    case D_CONN:
	lp->upcmd = LC_DISCNF;

	break;

    case RESET:
	if (lp->causeflag)
	    lp->upcmd = LC_RSTCNF;
	else
	{
	    lp->upcmd = LC_REPORT;
	    lp->upstatus = LS_RST_REFUSED;
	}
	return;
    }

#ifdef NO_WAN_CONTROL
    uplapb(lp, (mblk_t *)0);
    init_mode(lp, ADM);
#else
    uplapb(lp, (mblk_t *)0);
    if ( lp->state == D_CONN )
    {
	/*
	    Disconnect initiated from above so 
	    send WC_DISC request to the WAN
	*/
	printf("WC_DISC 4") ;
	init_mode(lp, OFF);
	send_wctl(WC_DISC,lp,0);
    }
    else
	init_mode(lp, ADM);
#endif
}


/*    *************************************************** FRMR_in  */
STATIC void FRMR_in(lp)
register lapb_t *lp;
{
    strlog(LAPB_STID,(short)lp->snid,3,SL_TRACE,"FRMR  In  : '%x'",(short)lp->snid);

    LAPBMONITOR(FRMR_rx_rsp)
    if (lp->state==NORMAL || lp->state==ERROR)
    {
	report_reset(lp);
    }
}


/*    *************************************************** report_reset  */
STATIC void report_reset(lp)
register lapb_t *lp;
{
    if (lp->mode == LAPB_MODE)
	start_SABM(lp, RESET);  /* send SABM, start P timer, state to RESET */
    else
	start_SARM(lp, RESET);  /* send SARM, start P timer, state to RESET */
    lp->causeflag = 0;
    lp->upcmd = LC_REPORT;
    lp->upstatus = LS_RESETTING;
    uplapb(lp, (mblk_t *)0);
}


/*    *************************************************** retry_expired  */
STATIC void retry_expired(lp)
register lapb_t *lp;
{
    lp->upstatus = LS_FAILED;
    switch (lp->state)
    {
    case NORMAL:
    case ERROR:
	if (lp->mode == LAPB_MODE)
	    start_SABM(lp, RESET);  /* send SABM, start P timer, state to RESET */
	else
	    start_SARM(lp, RESET);  /* send SARM, start P timer, state to RESET */
	lp->causeflag = 0;
	lp->upcmd = LC_REPORT;
	lp->upstatus = LS_RESETTING;
	break;

    case PRESETUP:
    case SETUP:
	lp->upcmd = LC_CONCNF;
	lp->state = (lp->mode == LAPB_MODE) ? ADM : ARM;
	/*uplapb(lp, (mblk_t *)0);
	init_mode(lp,OFF);*/
	break;

    case D_CONN:
	lp->upcmd = LC_DISCNF;
#ifndef NO_WAN_CONTROL
	uplapb(lp, (mblk_t *)0);
	init_mode(lp, OFF);
#else
	lp->state = (lp->mode == LAPB_MODE) ? ADM : ARM;
#endif
	break;

    case RESET:
	if (lp->causeflag)
	{
	    lp->upcmd = LC_RSTCNF;
	}
	else
	{
	    lp->upcmd = LC_REPORT;
	    lp->upstatus = LS_RST_FAILED;
	}
	lp->state = (lp->mode == LAPB_MODE) ? ADM : ARM;

	break;

    default:  /*  ADM, POLLING  */
	return;
    }

#ifndef NO_WAN_CONTROL
    if ( lp->state == OFF )
    {
	/*
	    Disconnect initiated from above so
	    send WC_DISC request to the WAN
	*/
	printf("WC_DISC 5") ;
	send_wctl(WC_DISC,lp,0);
	return;
    }
#endif
    uplapb(lp, (mblk_t *)0);

    if ( (lp->state == ADM) && (lp->mode == LAPB_MODE) )
    {
	if ( (lp->tunetab.llconform & NO_DM_ADM) == 0)
	    send_Uframe(lp, DM);
        init_mode(lp, ADM);
    }
    else
    if ( (lp->state == ARM) && (lp->mode == LAP_MODE) )
    {
	send_Uframe(lp, DISC);
#if 1
		printf("DISC 03") ;
#endif
        init_mode(lp, ARM);
    }
}

/*    *************************************************** lapb_CONOK  */
void lapb_CONOK(lp)
register lapb_t *lp;
{

#ifndef NO_WAN_CONTROL
    if (lp->state == OFF ) return;
#endif

    if (lp->mode == LAPB_MODE)
    {
	if (lp->state != SETUP)
	    send_Uframe(lp, UA);
	goto_normal(lp);
	lp->connected = 2;
    }
    else
    {
	goto_normal(lp);
	lp->connected = 2;
    }
    strlog(LAPB_STID,(short)lp->snid,1,SL_TRACE,"LINK  Up  : '%x'",(short)lp->snid);
}

/*    *************************************************** lapb_CONNAK  */
void lapb_CONNAK(lp)
register lapb_t *lp;
{

#ifndef NO_WAN_CONTROL
    if (lp->state == OFF ) return;
#endif

    if (lp->mode == LAPB_MODE)
    {
#if 0
	printf("DISC 04") ;
#endif 
	send_Uframe(lp, lp->state == ADM ? DM : DISC);
        init_mode(lp, ADM);
    }
    else
    {
#if 1
	printf("DISC 05") ;
#endif
	send_Uframe(lp, lp->state == ARM ? UA : DISC);
        init_mode(lp, ARM);
    }
}


/*    *************************************************** lapb_CONNECT  */
#ifdef NO_WAN_CONTROL
void lapb_CONNECT(lp)
register lapb_t *lp;
#else
void lapb_CONNECT(lp,ll_con)
register lapb_t *lp;
struct ll_msgc FAR *ll_con;
#endif
{
    mblk_t *wmp;
    struct wan_ctl FAR *wctl;

#ifndef NO_WAN_CONTROL
    if (lp->WAN_state == WAN_IDLE)
    {
	/*
	    Send WC_CONNECT request to the WAN
	*/
	send_wctl(WC_CONNECT,lp,ll_con);
    }
    else
    {
	/*
	    WAN line connected
	*/
	if (lp->state != SETUP)
	{
	    stop_all_timers(lp);
	    zero_v_vals(lp, PRESETUP);
	    set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
	    lp->retry_count = 0;
	}
	lp->causeflag = 1;    /* eventual indication will be LC_CONCNF */
    }
    

#else
    if (lp->state != SETUP)
    {
	stop_all_timers(lp);
	zero_v_vals(lp, PRESETUP);
	set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
	lp->retry_count = 0;
    }
    lp->causeflag = 1;    /* eventual indication will be LC_CONCNF */
#endif

}


/*    *************************************************** lapb_RESET  */
void lapb_RESET(lp)
register lapb_t *lp;
{

#ifndef NO_WAN_CONTROL
    if (lp->state == OFF ) return;
#endif

    if (lp->mode == LAPB_MODE)
	start_SABM(lp, RESET);  /* send SABM, start P timer, state to RESET */
    else
	start_SARM(lp, RESET);  /* send SARM, start P timer, state to RESET */
    lp->causeflag = 1;
    strlog(LAPB_STID,(short)lp->snid,2,SL_TRACE,"LINK  Rst : '%x' Local",(short)lp->snid);
}


/*    *************************************************** lapb_DISC  */
void lapb_DISC(lp)
register lapb_t *lp;
{

#ifndef NO_WAN_CONTROL
    if (lp->state == OFF ) return;
#endif

    stop_all_timers(lp);
    zero_v_vals(lp, D_CONN);
    send_Uframe(lp, DISC | PBIT);
    lp->retry_count = 0;
    lp->causeflag = 1;
    strlog(LAPB_STID,(short)lp->snid,1,SL_TRACE,"LINK  Dwn : '%x' Local",(short)lp->snid);
}



/*  ***************************************************
    **  actions required to support the event code   **
    *************************************************** */

/*    *************************************************** goto_normal  */
STATIC void goto_normal(lp)
lapb_t *lp;
{
    stop_all_timers(lp);
    zero_v_vals(lp, NORMAL);
    set_timer(&lp->idle_timer, (unsigned short) (lp->tunetab.Tidle << 1));
    lp->txstopped = 0;
    qenable(lp->downq);
}

/*    *************************************************** goto_reset  */
STATIC void goto_reset(lp)
lapb_t *lp;
{
    stop_all_timers(lp);
    lp->vr = lp->ar = 0;
    lp->state = RESET;
}

/*    *************************************************** delay_SABM  */
STATIC void delay_SABM(lp)
register lapb_t *lp;
{
    stop_all_timers(lp);
    set_timer(&lp->ack_timer, (unsigned short) 4);
    lp->clashflag = 1;
    lp->retry_count = 0;
}


/*    *************************************************** delay_SARM  */
STATIC void delay_SARM(lp)
register lapb_t *lp;
{
    stop_all_timers(lp);
    set_timer(&lp->ack_timer, (unsigned short) 4);
    lp->clashflag = 1;
    lp->retry_count = 0;
}


/*    *************************************************** start_SABM  */
STATIC void start_SABM(lp, newstate)
register lapb_t *lp;
{
    stop_all_timers(lp);
    zero_v_vals(lp, newstate);
    send_Uframe(lp, lp->extended ? SABME | PBIT : SABM | PBIT);
}



/*    *************************************************** start_SARM  */
STATIC void start_SARM(lp, newstate)
register lapb_t *lp;
{
    stop_all_timers(lp);
    zero_v_vals(lp, newstate);
    send_Uframe(lp, SARM | PBIT);
}



/*    *************************************************** set_rem_busy  */
STATIC void set_rem_busy(lp)
register lapb_t *lp;
{
    lp->retry_count = 0;
    if (lp->rem_busy == 0)
    {
	lp->rem_busy = 1;
	lp->txstopped++;

	set_timer(&lp->busy_timer, (unsigned short) (lp->tunetab.Tbusy << 1));
	/*
	 * lp->upcmd = LC_REPORT;
	 * lp->upstatus = REMOTE_BUSY;
	 * uplapb(lp, (mblk_t *)0);
	 */
	strlog(LAPB_STID,(short)lp->snid,4,SL_TRACE,"LINK  Bsy : '%x' Remote",
							(short)lp->snid);
    }
}


/*    *************************************************** clear_rem_busy  */
STATIC void clear_rem_busy(lp)
register lapb_t *lp;
{
    if (lp->rem_busy)
    {
	lp->retry_count = 0;
	set_timer(&lp->busy_timer, (unsigned short) 0);
	/*
	 * lp->upcmd = LC_REPORT;
	 * lp->upstatus = REMOTE_NOT BUSY;
	 * uplapb(lp, (mblk_t *)0);
	 */

	lp->rem_busy = 0;
	lp->txstopped--;
    }
}


/*  *************************************************** send_Uframe  */
STATIC void send_Uframe(lp, pdu)
register lapb_t *lp;
{
    register bufp_t fp;
    mblk_t *mp;

    /* Get a message to send U frame: try for auxiliary U-format first */
    if (mp = lapb_udmp)
    {   /* U-format buffer in stock */
	lapb_udmp = (mblk_t *)0;
    }
    else
    {   /* Collect main buffer from stock */
	if (!(mp = lapb_dnmp))
	{   /* Shouldn't happen! */
	    strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no Uframe buffer");
	    return;
	}
	lapb_dnmp = (mblk_t *)0;
    }

    /* PDU buffer */
    fp = mp->b_cont->b_wptr;

    if ( ((lp->mode == LAP_MODE) && (pdu != UA)) ||
 	 ((lp->mode == LAPB_MODE) && (pdu != UA && pdu != DM)) )
    {   /* SABM[E] or DISC command (or SARM command if LAP) */
	switch (pdu & ~0x10)
	{
	case SABM:
		LAPBMONITOR(SABM_tx_cmd)
		break;

	case SABME:
		LAPBMONITOR(SABME_tx_cmd)
		break;

	case DISC:
		LAPBMONITOR(DISC_tx_cmd)
		break;

	case SARM:
		LAPBMONITOR(SARM_tx_cmd)

	default:
		break;
	}

	fp[0] = lp->myaddr ^ 2;
	fp[1] = (unchar) pdu;
	if (lp->pflag = pdu & 0x10)
	{   /* P-bit set: start P timer */
	    set_timer(&lp->p_timer, (unsigned short) (lp->tunetab.Tpf << 1));
	}
    }
    else
    {   /* UA or DM response */
	if (pdu == UA)
	    LAPBMONITOR(UA_tx_rsp)
	else
	    LAPBMONITOR(DM_tx_rsp)

	fp[0] = lp->myaddr;
	fp[1] = (unchar)pdu;
	if (lp->need_fbit)
	    fp[1] |= 0x10;   /* F-bit has value '0x10' */
	lp->need_fbit = 0;
    }

    /* Transmit frame */
    txlapb(lp, mp, 2);
}


/*  *************************************************** send_Srsp  */
STATIC void send_Srsp(lp, pdu)
register lapb_t *lp;
{
    register bufp_t fp;
    mblk_t *mp;

    /* Check we have a message to send RR, RNR or REJ response */
    if (!(mp = lapb_dnmp))
    {   /* Shouldn't happen! */
        strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no Sframe buffer");
	return;
    }

    /* Detach message */
    lapb_dnmp = (mblk_t *)0;

    /* PDU buffer */
    fp = mp->b_cont->b_wptr;

    /* Address for response is local address */
    fp[0] = lp->myaddr;
    if (lp->extended)
    {
	fp[1] = (unchar) pdu;
	fp[2] = lp->vr<<1;
	if (lp->need_fbit) fp[2] |= 0x01;   /* F-bit has value '0x01' */
    }
    else
    {
	fp[1] = (unchar)pdu | lp->vr<<5;
	if (lp->need_fbit) fp[1] |= 0x10;   /* F-bit has value '0x10' */
    }
    lp->need_fbit = 0;

    /* RR, REJ or RNR all acknowledge current V(R) */
    rxacked(lp);

    /* Statistics */
    switch (pdu)
    {
    case RR:
	LAPBMONITOR(RR_tx_rsp)
	break;

    case RNR:
	LAPBMONITOR(RNR_tx_rsp)
	break;

    case REJ:
	LAPBMONITOR(REJ_tx_rsp)
	break;

    default:
	break;
    }

    /* Transmit frame */
    txlapb(lp, mp, 2 + lp->extended);

    if ( pdu == REJ )
	strlog(LAPB_STID,(short)lp->snid,3,SL_TRACE,"REJ   Out : '%x' N(%03d)",(short)lp->snid,lp->vr);
}



/*  *************************************************** send_SPcmd  */
STATIC void send_SPcmd(lp, pdu)
register lapb_t *lp;
{
    register bufp_t fp;
    mblk_t *mp;

    /* Check we have a message to send RR, RNR or REJ command */
    if (!(mp = lapb_dnmp))
    {   /* Shouldn't happen! */
    	strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no Sframe buffer");
	return;
    }

    /* Detach message */
    lapb_dnmp = (mblk_t *)0;

    /* PDU buffer */
    fp = mp->b_cont->b_wptr;

    /* Address for command is remote address */
    fp[0] = lp->myaddr ^ 2;
    if (lp->extended)
    {
	fp[1] = (unchar)pdu;
	fp[2] = lp->vr<<1 | 0x01;           /* P-bit has value '0x01' */
    }
    else
	fp[1] = (unchar)pdu | lp->vr<<5 | 0x10;     /* P-bit has value '0x10' */

    /* RR, REJ or RNR all acknowledge current V(R) */
    rxacked(lp);

    /* Set pflag and start P timer */
    lp->pflag = 1;
    set_timer(&lp->p_timer, (unsigned short) (lp->tunetab.Tpf << 1));

    /* Statistics */
    switch (pdu)
    {
    case RR:
	LAPBMONITOR(RR_tx_cmd)
	break;

    case RNR:
	LAPBMONITOR(RNR_tx_cmd)
	break;

    case REJ:
	LAPBMONITOR(REJ_tx_cmd)
	break;

    default:
	break;
    }

    /* Transmit frame */
    txlapb(lp, mp, 2 + lp->extended);
}


/*  *************************************************** send_FRMR  */
STATIC void send_FRMR(lp)
register lapb_t *lp;
{
    register bufp_t fp;
    mblk_t *mp;

    /* Get a buffer to send FRMR response */
    if (!(mp = lapb_dnmp))
    {   /* No main buffer in stock! */
    	strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no FRMR buffer");
	return;
    }

    /* Detach message */
    lapb_dnmp = (mblk_t *)0;

    /* FRMR buffer */
    fp = mp->b_cont->b_wptr;

    fp[0] = lp->myaddr;
    fp[1] = FRMR;
    if (lp->need_fbit)
    {
	fp[1] |= 0x10;      /* F-bit in U response */
	lp->need_fbit = 0;
    }

    fp[2] = lp->frmr_ctl1;
    if (lp->extended)
    {
	fp[3] = lp->frmr_ctl2;
	fp[4] = lp->vs<<1;
	fp[5] = lp->vr<<1;
	if ( (lp->mode == LAPB_MODE) && (lp->frmr_addr != lp->myaddr) )
		fp[5] |= 0x01; /* Response */
	fp[6] = lp->frmr_reason;
    }
    else
    {
	fp[3] = lp->vr<<5 | (lp->vs & 7)<<1;
	if ( (lp->mode == LAPB_MODE) && (lp->frmr_addr != lp->myaddr) )
		fp[3] |= 0x10; /* Response */
	fp[4] = lp->frmr_reason;
    }

    /* Statistics */
    LAPBMONITOR(FRMR_tx_rsp)

    /* Transmit frame */
    txlapb(lp, mp, 5 + (lp->extended<<1));

    strlog(LAPB_STID,(short)lp->snid,3,SL_TRACE,"FRMR  Out : '%x' Rsn %02d",(short)lp->snid,lp->frmr_reason);
}



/*  *************************************************** lapb_wdata  */
void lapb_wdata(lp)
register lapb_t *lp;
{
    mblk_t *mp, *bp;
    register bufp_t fp;

    /* Get the main downward message */
    if (!(mp = lapb_dnmp) || !(bp = mp->b_cont))
    {   /* Shouldn't happen! */
    	strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no I-frame buffer");
	return;
    }

    /* Attach the duplicate information part */
    if (!(bp->b_cont = lapb_dimp))
    {   /* Shouldn't happen! */
    	strlog(LAPB_STID,0,0,SL_ERROR,"LAPB no duplicate I-data");
	return;
    }

    /* All OK, so remove from stock */
    lapb_dnmp = (mblk_t *)0;
    lapb_dimp = (mblk_t *)0;

    /* Pdu pointer */
    fp = bp->b_wptr;

    /* Remote address */
    fp[0] = lp->myaddr ^ 2;

    /* this implementation NEVER uses P_BIT in an I frame */
    if (lp->extended)
    {
	fp[1] = lp->vs<<1;
	fp[2] = lp->vr<<1;
    }
    else
	fp[1] = (lp->vs & 7)<<1 | lp->vr<<5;

    /* I-frame acknowledges all received data */
    rxacked(lp);

    /* Statistics */
    LAPBMONITOR(I_tx_cmd)

    /* Transmit frame */
    txlapb(lp, mp, 2 + lp->extended);

    /* Update sequence number, check that next data will lie within window */
    if ((uint16)(++lp->vs - lp->as & lp->seqmask) >= lp->tunetab.tx_window)
    {
	if (!lp->wclosed)
	{
	    lp->wclosed = 1;
	    lp->txstopped++;
	}
    }
    run_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
}


/*  *************************************************** discard_data  */
void discard_data(lp)
register lapb_t *lp;
{
    register mblk_t *bp;

    /* Discard data frames */
    while (bp = lp->ackq)
    {
	lp->ackq = bp->b_next;
	bp->b_next = (mblk_t *)0;
	freemsg(bp);
    }
    lp->datq = (mblk_t *)0;
}


/*  *************************************************** retransmit  */
STATIC void retransmit(lp)
register lapb_t *lp;
{
    if (lp->vs != lp->as)
    {   /* There are unacknowledged I-frames to retransmit */
	lp->datq = lp->ackq;
	lp->vs = lp->as;
	if (lp->wclosed)
	{
	    lp->wclosed = 0;
	    lp->txstopped--;
	}
    }
}

/*  *************************************************** rxacked  */
STATIC void rxacked(lp)
register lapb_t *lp;
{   /* All received data now acked */
    lp->ar = lp->vr;
    set_timer(&lp->RR_timer, (unsigned short) 0);
    lp->need_ack = 0;
}



/*  *************************************************** update_nr  */
STATIC int update_nr(lp, nr_in)
register lapb_t *lp;
unsigned int nr_in;
{
    unsigned int num_acked;

    /* Check for out-of-range number */
    if ((num_acked = nr_in - lp->as & lp->seqmask) >
				(unsigned int)(lp->vs - lp->as & lp->seqmask))
	return 0;

    if (num_acked > 0)
    {   /* Waiting frames are acknowledged by nr_in */

	/* Release newly acknowleged frames from ack queue */
	do
	{   /* Release message from ack queue */
	    register mblk_t *bp = lp->ackq;
	    lp->ackq = bp->b_next;
	    bp->b_next = (mblk_t *)0;
	    freemsg(bp);

	    /* Step ack sequence number */
	    lp->as++;
	}
	while (--num_acked);

	/* Stop ack timer if all frames now acked, otherwise restart it */
	set_timer(&lp->ack_timer, (unsigned short) (lp->ackq ? (lp->tunetab.T1 << 1) : 0)); 

	/* Start acknowledgement retries again */
	lp->retry_count = 0;

	/* Restart Tx if window was closed */
	if (lp->wclosed)
	{
	    lp->wclosed = 0;
	    lp->txstopped--;
	}
    }

    /* Valid sequence number */
    return 1;
}



/*    *************************************************** stop_p_timer  */
STATIC void stop_p_timer(lp)
register lapb_t *lp;
{
    set_timer(&lp->p_timer, (unsigned short) 0);
    lp->pflag = 0;
}



/*    *************************************************** stop_all_timers  */
STATIC void stop_all_timers(lp)
register lapb_t *lp;
{
    lp->pflag = 0;
    lp->reject = 0;
    stop_timers(&lp->thead, lp->tmtab, NTIMERS);
}


/*    *************************************************** zero_v_vals  */
STATIC void zero_v_vals(lp, newstate)
register lapb_t *lp;
{
    register mblk_t *bp;

    if (lp->mode == LAPB_MODE)
    {
	lp->vr = 0;
	lp->ar = 0;
    }
    lp->vs = 0;
    lp->as = 0;
    lp->need_ack = 0;
    lp->retry_count = 0;
    lp->rem_busy = 0;
    lp->busyflag = 0;
    lp->i_frame_cnt = 0 ;
    lp->awaitflag = 0;
    lp->pollflag = 0;
    lp->clashflag = 0;
    lp->sflag = 0;
    lp->wclosed = 0;
    lp->dataflag = 0;
    lp->txstopped = 1;
    lp->state = (unchar)newstate;

    /* Discard unacknowledged frames */
    while ((bp = lp->ackq) != lp->datq)
    {
	lp->ackq = bp->b_next;
	bp->b_next = (mblk_t *)0;
	freemsg(bp);
    }
}



/*    *************************************************** init_mode  */
void init_mode(lp, state)
register lapb_t *lp;
{
    lp->causeflag = 0;  /* causeflag always zero in ADM state */

    /* Clear timers and pflag */
    stop_all_timers(lp);

    /* Reset sequences and retry_count and empty queues */
    zero_v_vals(lp, state);

    discard_data(lp);

    /* Disconnect from network */
    lp->nethandle = (uint32)0;
    lp->netcid = (uint16)0;
    lp->connected = 0;

    switch (lp->state)
    {
#ifndef NO_WAN_CONTROL
    	case OFF:
	break;
#endif
	
	case POLLING:
	    /* Set ack time to T3 */
	    set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1*lp->tunetab.N2 << 1));
	    lp->pollflag = 1;
	    break;

	case D_CONN:
	case ARM:
	case ADM:

	default:
	    /* Set ack time to T1 */
	    set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
	    break;
    }

}


/*    *************************************************** lapb_expired */
void lapb_expired(lp, id)
lapb_t *lp;
unchar id;
{
    switch (id)
    {
    default:
	if (lp->pflag)
	    break;

	/* Run into P timer .. */

    case PTIMER:
        if (lp->retry_count++ >= lp->tunetab.N2)
        {
	    retry_expired(lp);
	    break;
        }
	timer_exp(lp);
	break;

    case IDLETIMER:
	if (lp->pflag)
	    break;		/* Ptimer already running */
	timer_exp(lp);
	break;

    case REJTIMER:
	rej_expired(lp);
	break;

    case RRTIMER:
	send_Srsp(lp, RR);
	break;
    }
}


/*  *************************************************** lapb_rxframe  */
void lapb_rxframe(lp, mp)
register lapb_t *lp;
mblk_t *mp;
{   /* Process message from WAN */

    register bufp_t fp = mp->b_cont->b_rptr;
    unsigned int len = (unsigned int)(mp->b_cont->b_wptr - fp);
    unsigned int tlen = (unsigned int) msgdsize(mp->b_cont);
    unsigned int nr, ns, pf_bit, is_rsp, reason = 0;
    register unsigned char c, f;

    DBDIAG(mp, 0x75)

    if (len < 2)	/*  Must contain at least Address and Control bytes */
    {   /* Discard any frame which was too small */
	if (tlen == 0)
	{   /* Signals overflowed frame! */
	    reason = FRMR_YBIT;
	    goto faulty;
	}
	goto discard;
    }

    /*
     * Check incoming frame address.
     * If the incoming frame address is the receiving station's address
     * then the incoming frame is a command.
     * Hence the expression
     *    (incoming frame address ^ lp->myaddr)
     * gives values
     *    0 => command,
     *    2 => response;
     * any other value is invalid.
     */
    if ((is_rsp = fp[0] ^ lp->myaddr) & ~2)
    {   /* frame with address neither A nor B */
	goto discard;
    }

    /* Restart idle timer if NORMAL */
    if (lp->state == NORMAL)
    	set_timer(&lp->idle_timer, (unsigned short) (lp->tunetab.Tidle << 1));

    switch ((c = fp[1]) & 3)
    {
    case 0:	/* I format */
    case 2:
	if (lp->extended)
	{
	    if (len < 3)
	    {   /* Discard any frame which was too small */
		goto discard;
	    }

	    ns = c>>1;
	    nr = (c = fp[2])>>1;
	    lp->need_fbit = c & 0x01;
	}
	else
	{
	    if (len < 2)
	    {   /* Discard any frame which was too small */
		goto discard;
	    }

	    ns = c>>1 & 7;
	    nr = c>>5;
	    lp->need_fbit = c & 0x10;
	}

	if (lp->state != NORMAL)
	{
	    if (lp->state == ERROR)
		break;
	    if ( (lp->state != SETUP) && (lp->state != RESET) )
	    {
	    	/* CCITT states that DM RSP must be made if Pbit set */
		if ( lp->need_fbit  && (is_rsp == 0) )
		{
		    freemsg(mp);
		    send_Uframe(lp, DM);
		    return;
		}
	    }
	    lp->need_fbit = 0;
	    goto ignore;
	}

	if (is_rsp)
	{   /* I RSP not permitted */
	    lp->need_fbit = 0;
	    reason = FRMR_INVRSP;
	    break;
	}

	if (tlen > lp->tunetab.max_I_len)
	{   /* Information field too large */
	    reason = FRMR_YBIT;
	    break;
	}

	if (!update_nr(lp, nr))
	{
	    reason = FRMR_ZBIT;
	    break;
	}

	if (ns == (unsigned int) (lp->vr & lp->seqmask))
	{
	    register mblk_t *bp = mp->b_cont;

	    /* Discard header block */
	    mp->b_cont = (mblk_t *)0;
	    freeb(mp);

	    /* Strip off lapb protocol */
	    adjmsg(bp, 2 + lp->extended);

	    DBDIAG(bp, 0x76)
	    I_valseq(lp, bp);

	    return;
	}
	/* Out of sequence */
	freemsg(mp);

	I_unexp_ns(lp);
	return;

    case 1:	/* S format */

	if (lp->extended)
	{
	    if (len < 3)
	    {   /* Discard any frame which was too small */
		goto discard;
	    }
	    nr = fp[2]>>1;
	    pf_bit = fp[2] & 0x01;
	}
	else
	{
	    if (len < 2)
	    {   /* Discard any frame which was too small */
		goto discard;
	    }
	    nr = c>>5;
	    pf_bit = c & 0x10;
	}

	if (lp->state != NORMAL)
	{
	    if (lp->state == ERROR)
	    {
		reason = FRMR_INVRSP;
		if (is_rsp == 0)
		    lp->need_fbit = (unchar)pf_bit;
		break;
	    }
            if ( (lp->state != SETUP) && (lp->state != RESET) &&
					(lp->mode == LAPB_MODE) )
            {
                /* CCITT states that DM RSP must be made if Pbit set */
                if (pf_bit && (is_rsp == 0))
                {
                    lp->need_fbit = 1;
                    freemsg(mp);
                    send_Uframe(lp, DM);
		    return;
		}
	    }
	    goto ignore;
	}

	if (is_rsp)
	{   /* S format response */
	    if (pf_bit)
	    {
		if (!lp->pflag)
		{
                    if (lp->state == ERROR)
                    {
                        /* unsolicite F bit in ERROR state */
                        reason = FRMR_INVRSP;
                        break;
                    }
                    if (validatecode(lp, c, is_rsp) == 0)
                    {
                        /* invalid response code */
                        reason = FRMR_INVRSP;
                        break;
                    }
                    if (len != (unsigned int) (lp->extended ? 3 : 2))
                    {
                        /* invalid length */
                        reason = (FRMR_WBIT + FRMR_XBIT);
			printf("DISC 1 at %x ", mp->b_cont->b_rptr ) ;
                        break;
                    }
                    freemsg(mp);
                    report_reset(lp);
                    return;
		}
	    }
	    else
	    {
		if (lp->pflag)
		{
		    goto ignore;
		}
	    }
	}
	else
	{   /* S format command */

	    /*
		PSS acceptance tests require FRMR for RR,
		RNR, REJ commands without the Pbit set
	    */

            if ( (lp->tunetab.llconform & SFRAME_PBIT) && (pf_bit == 0) )
            {
                reason = FRMR_WBIT;
                break;
            }

	    lp->need_fbit = (unchar)pf_bit;
	    pf_bit = 0;
	}

	/* PSS acceptance tests require FRMR for RR, RNR, REJ frames
							of invalid length  */
        if ( len != (unsigned int) (lp->extended ? 3 : 2) )
        {
	printf("DISC 2 at %x ", mp->b_cont->b_rptr ) ;
            reason = (FRMR_WBIT | FRMR_XBIT);
            /* check for invalid code which will require a different reason */
            if (validatecode(lp, c, is_rsp) == 0)
		reason = FRMR_INVRSP;
	    break;
	}
	if (!update_nr(lp, nr))
	{
	    reason = FRMR_ZBIT;
	    break;
	}

	switch (c>>2 & 3)
	{
	case 0: /* RR */
	    freemsg(mp);
	    RR_in(lp, pf_bit);
	    return;

	case 1: /* RNR */
	    freemsg(mp);
	    RNR_in(lp, pf_bit);
	     return;

	case 2: /* REJ */
	    freemsg(mp);
	    REJ_in(lp, pf_bit);
	    return;

	case 3: /* XXX */
	    reason = FRMR_INVRSP;
	    break;
	}
	break;

    case 3:	/* U format */
	f = (c & ~0x10);
	pf_bit = c & 0x10;
        if ( (lp->state == ERROR) && (f == UA) )
        {
            /*
		Certain PSDNs (e.g.DDN) say ignore the UA
		Others say retransmit FRMR
	    */
            if (lp->tunetab.llconform & IGN_UA_ERROR)
		goto ignore;
            break;
        }

        if (len != (unsigned int) (f != FRMR ? 2 : lp->extended ? 7 : 5))
        {   /* Wrong length */
            if (is_rsp == 0)
		lp->need_fbit = (unchar)pf_bit;
	    printf("DISC 3 at %x ", mp->b_cont->b_rptr ) ;
            reason = (FRMR_WBIT | FRMR_XBIT);
            /* check for invalid code which will require a different reason */
            if (validatecode(lp, c, is_rsp) == 0)
		reason = FRMR_INVRSP;
            break;
        }


	if (is_rsp)
	{   /* U format response */
	    if (pf_bit)
	    {
		if ( (!lp->pflag) && (f != FRMR ) )
                {
                    if ( (f != UA) && (f != DM) )
                    {
                    /* invalid U format */
                        reason = FRMR_INVRSP;
                        break;
                    }
                    if ( lp->state == NORMAL )
                    {
                        freemsg(mp);
                        report_reset(lp);
                        return;
                    }
                    /* what to do in other states ?? - currently ignore */
                    goto ignore;
                }
            }
	    else
	    {
		if (lp->pflag)
		{
		    goto ignore;
		}
	    }

	    switch (ursp_switch[c>>2])
	    {
	    case U_DM:
		freemsg(mp);
		DM_in(lp, pf_bit);
		return;

	    case U_UA:
		if (lp->state == NORMAL)
		{
                    freemsg(mp);
                    report_reset(lp);
                    return;
		}
		freemsg(mp);
		UA_in(lp, pf_bit);
		return;

	    case U_FRMR:
                if ((lp->state == ERROR) && 
		    (lp->tunetab.llconform & FRMR_FRMR_ERROR))
		    break;
		freemsg(mp);
		FRMR_in(lp);
		return;

	    default:
		reason = FRMR_INVRSP;
		break;
	    }
	}
	else
	{   /* U format command */
	    lp->need_fbit = (unchar)pf_bit;

	    switch (ucmd_switch[c>>2])
	    {
	    case U_SABM:
		if (lp->extended)
		{
		    reason = FRMR_WBIT;
		    break;
		}
		if (lp->mode == LAP_MODE)
		{
		    reason = FRMR_WBIT;
		    break;
		}

		freemsg(mp);
                LAPBMONITOR(SABM_rx_cmd)
		SABM_in(lp);
		return;

	    case U_SABME:
		if (lp->extended == 0)
		{
		    reason = FRMR_WBIT;
		    break;
		}
		if (lp->mode == LAP_MODE)
		{
		    reason = FRMR_WBIT;
		    break;
		}
		freemsg(mp);
		LAPBMONITOR(SABME_rx_cmd)
		SABM_in(lp);
		return;

	    case U_DISC:
		freemsg(mp);
		DISC_in(lp);
		return;

	    case U_SARM:
		if (lp->extended)
		{
		    reason = FRMR_WBIT;
		    break;
		}
		if (lp->mode == LAPB_MODE)
		{
		    reason = FRMR_WBIT;
		    break;
		}
		freemsg(mp);
		SARM_in(lp);
		return;

	    default:
		reason = FRMR_WBIT;
		break;
	    }
	}
	break;
    }

faulty:
    /* We only drop through on error: send FRMR */

    if ( reason == FRMR_INVRSP )
    {
        if ( is_rsp && (lp->state == ERROR) )
        {
            /* Certain PSDNs (e.g.TELENET) always want a FRMR back */
            if ((lp->tunetab.llconform & FRMR_INVRSP_ERROR) == 0)
		goto ignore;
        }
        reason = FRMR_WBIT;
    }

    if (lp->state != ERROR)
    {
	if (lp->state != NORMAL)
	{
	    freemsg(mp);
	    return;
	}

	/* If incoming frame has this station's address, it is a command */
	lp->frmr_addr = fp[0];   /* copy failing command */
	lp->frmr_ctl1 = fp[1];
	if (lp->extended)
	    lp->frmr_ctl2 = fp[2];

	lp->frmr_reason = (unchar)reason;
    }
    freemsg(mp);
    send_FRMR(lp);
    stop_all_timers(lp);
    /* save vr and vs for possible repeat of FRMR */
    nr = lp->vr;
    ns = lp->vs;
    zero_v_vals(lp, ERROR);
    lp->vr = (unchar)nr;
    lp->vs = (unchar)ns;
    set_timer(&lp->ack_timer, (unsigned short) (lp->tunetab.T1 << 1));
    lp->retry_count = 0;
    return;

discard:
    freemsg(mp);
    return;

ignore:
    freemsg(mp);
}

/*  *************************************************** validatecode  */
STATIC int validatecode(lp, codeval, rspflag)
lapb_t *lp;
unsigned char codeval;
unsigned int rspflag;
{
    register unsigned char f;

    f = codeval & 0X0F;
    if ( (f == RR) || (f == RNR) || (f == REJ) )
    {
	return(1);
    }

    f = codeval & 0XEF;
    if (rspflag)
    {
	/* response */
	if ( (f == UA) || (f == DM) || (f == FRMR) )
	    return(1);
	return(0);
    }

    /* command */
    if (f == DISC)
	return(1);
    if (lp->extended && (f == SABME) )
	return(1);
    if ( !lp->extended && (f == SABM) )
	return(1);

    return(0);
}


/*  *************************************************** lapb_init  */
int lapb_init(lp, extended, myaddr, mode)
register lapb_t *lp;
{
    lp->myaddr = (unchar) myaddr;
    lp->mode = (unchar) mode;

    if (extended) {
	lp->extended = 1;
	lp->seqmask = 127;
    }
    else {
	lp->extended = 0;
	lp->seqmask = 7;
    }

    return(1);
}

/*  *************************************************** lapb_start  */
void lapb_start(lp)
register lapb_t *lp;
{
    /* Restart: start polling if DCE */
    if (lp->mode == LAPB_MODE)
    {
	if (lp->myaddr == B_ADDRESS)
	{
#if 1
		printf("DISC 05") ;
#endif
	    send_Uframe(lp, DISC);
	}
	init_mode(lp, (lp->myaddr == B_ADDRESS) ? POLLING : ADM );
    }
    else
    {
	if (lp->myaddr == B_ADDRESS)
	{
#if 1
		printf("DISC 06") ;
#endif
	    send_Uframe(lp, DISC);
	}
	init_mode(lp, (lp->myaddr == B_ADDRESS) ? POLLING : D_CONN );
    }
}

/*  *************************************************** lapb_stop  */
void lapb_stop(lp)
register lapb_t *lp;
{
    /* Send final DM if not already stopped */
    if ( (lp->state > ADM) && (lp->mode == LAPB_MODE) &&
			(getstock(lp, (mblk_t *)0, 0)) )
	send_Uframe(lp, DISC);

    /* Stop all timers */
    stop_all_timers(lp);

    /* Mark stopped */
    zero_v_vals(lp, OFF);

    /* Discard any data messages currently awaiting transmission */
    discard_data(lp);
}

/*  *************************************************** lapb_txack  */
void lapb_txack(lp)
register lapb_t *lp;
{
    send_Srsp(lp, RR);
}


#ifndef NO_WAN_CONTROL
/*  *************************************************** lapb_rxwctl  */
void lapb_rxwctl(lp,mp)
register lapb_t *lp;
register mblk_t *mp;
{

    union WAN_primitives FAR *wp;
    wp = (union WAN_primitives FAR *)mp->b_rptr;
	
    if ( wp->wan_type == WAN_CTL )
    {
	struct wan_ctl FAR *wctl;

	wctl = (struct wan_ctl FAR *)wp;
		    
	switch(wctl->wan_command)
	{
	    case WC_CONNECT:

		wctl->wan_command = WC_CONCNF;
			
		if ( lp->WAN_state == WAN_IDLE )
		{
		    /*
			Copy remote address details for future
			use and kick off link set up.
		    */
		    lp->WAN_remsize = wctl->wan_remsize;
		    bcopy((BUFP_T)wctl->wan_remaddr,
		          (BUFP_T)lp->WAN_remaddr, wctl->wan_remsize);
		    
		    lp->state     = START;
		    lp->WAN_state = WAN_ESTB;
		    
		    wctl->wan_status = WAN_SUCCESS;
		}
		else
		    wctl->wan_status = WAN_FAIL;

		putnext(lp->downq,mp);
		mp = (mblk_t *)NULL;
		
	    break;


	    case WC_CONCNF:

		/*
		    Confirmation to WC_CONNECT request. Status
		    field holds result. Ignore if not expected.
		*/
		if ( lp->WAN_state == WAN_CONN )
		{
		    if (wctl->wan_status == WAN_SUCCESS)
		    {
			/*
			    WAN now established so proceed with
			    link set up.
			*/
			lp->WAN_state = WAN_ESTB;
			lapb_CONNECT(lp,0);
		    }
		    else
		    {
			/*
			    WAN connect failed so inform upper
			    layer and revert state to OFF.
			*/
			lp->upstatus = LS_DISCONNECT;
			lp->upcmd    = LC_CONCNF;
			uplapb(lp, (mblk_t *)0);
			init_mode(lp, OFF);
			lp->WAN_state = WAN_IDLE;
		    }
		}

	    break;

	    case WC_DISC:

		/*
		    Disconnect indication fron the WAN.
		    Return failure if not expected.
		*/
		wctl->wan_command = WC_DISCCNF;

		if ( lp->WAN_state != WAN_IDLE )
		{
		    wctl->wan_status = WAN_SUCCESS;

		    /*
			WAN disconnect so inform upper layer
			if connected and revert state to OFF.
		    */
		    if ( lp->connected )
		    {
			lp->upstatus = LS_DISCONNECT;
			lp->upcmd    = LC_DISC;
			uplapb(lp, (mblk_t *)0);
		    }
		    init_mode(lp, OFF);
		    lp->WAN_state = WAN_IDLE;
		}
		else
		    wctl->wan_status = WAN_FAIL;

		putnext(lp->downq,mp);
		mp = (mblk_t *)NULL;
	    
	    break;

	    case WC_DISCCNF:
			
		/*
		    Confirmation to WC_DISC request.
		    Ignore if not expected.
		*/
		if ( lp->WAN_state == WAN_DISC )
		{
		    lp->WAN_state = WAN_IDLE;
		}

	    break;

	    default:
	    
	         /* Ignore */
		 
	    break;
	}
    }
	if (mp)
	{
		freemsg(mp);
	}
}


/*  *************************************************** send_wctl  */
void send_wctl(cmd,lp,lctl)
int cmd;
lapb_t *lp;
struct ll_msgc FAR *lctl;

{
    mblk_t *wmp;
    register struct wan_ctl FAR *wctl;
    
    /*
	Send control message to the WAN
    */
    if ( (wmp = allocb(sizeof(struct wan_ctl),BPRI_HI) ))
    {
	wmp->b_datap->db_type = M_PCPROTO;
	wctl = (struct wan_ctl FAR *)wmp->b_wptr;
	wctl->wan_type    = WAN_CTL;
	wctl->wan_command = (unchar) cmd;

	if ( cmd == WC_CONNECT )
	{
	    wctl->wan_remtype = WAN_TYPE_BCD;    
	    wctl->wan_remsize = lctl->ll_remsize;
	    bcopy((BUFP_T)lctl->ll_remaddr,
		  (BUFP_T)wctl->wan_remaddr,lctl->ll_remsize);
		      
	    lp->WAN_state = WAN_CONN;
	}
	else
	   lp->WAN_state = WAN_DISC;
	    
	wmp->b_wptr += sizeof(struct wan_ctl);

	putnext(lp->downq, wmp);

    }
    else
	strlog(LAPB_STID,0,0,SL_ERROR,"LAPB: No buffer for WAN ctl message (%x)!",cmd);
}
#endif
