#ident "$Id: x25l2.c,v 1.8 1997/09/24 23:13:47 gary Exp $"
/*****************************************************************************
 * Copyright (c) 1988-95 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. 1995
 * 
 * All Rights Reserved
 * 
 * US   Government   Users   Restricted   Rights   -   Use,   duplication   or
 * disclosure  restricted by GSA ADP Schedule Contract with Digi International
 * Inc.
 * 
 *****************************************************************************/

/*******************************************************************
 *
 *  SpiderX25 X25 PLP Driver
 *
 *  Copyright 1991 Spider Systems Limited
 *
 *  X25L2.C
 *
 *
 *******************************************************************/

/*
 *	x25l2.c
 *	 /projects/common/PBRAIN/SCCS/pbrainF/dev/sys/x25/0/s.x25l2.c
 *	@(#)x25l2.c	1.35
 *
 *	Last delta created	16:33:11 7/9/92
 *	This file extracted	18:32:57 11/19/92
 *
 */

#include "x25_inc.h"

#include "iso.h"
#include "x25pti.h"
#include "x25cause.h"
#include "x25aux.h"
#include "x25form.mac"
#include "x25clock.h"
#include "x25mbu.h"

#ifndef NULL
#define NULL 0
#endif

extern mblk_t *allocb();
extern queue_t *SNMP_trace_q ;


/******************************************************************/
void res_PVCs(lsp, cause, diag)
/******************************************************************/

/*
    Collect and restart all the PVCs for this link.  Called after a
    restart exchange brings the link to L_connected.
*/

struct lsformat FAR *lsp;
unsigned char cause, diag;

{
    int j,k;
    struct x25vcformat FAR *xvc;
    struct wlcfg       *cfg;

    lsp->concount = 0;				/* Zero the conn. count */
    cfg = lsp->lbase;

    if( (k=cfg->NPCchannels) != 0 )
    {
	/*
	   Find the PLP PVC database elements for this net
	*/
	for(j=FIRSTVC; j<=isovcmax && k > 0 ; j++)
	{
	    xvc = (struct x25vcformat FAR *) iso_vc[j];
	    
	    if( xvc->xtag == X25TAG &&
	        xvc->xu_ident == cfg->U_SN_ID && xvc->ampvc!= 0 )
	    {
		k--;			/* Count this pvc out           */
		lsp->concount++;	/* Increment the conn. count    */
		
		/*
		    Now set up the states and send up attach responses which
		    were awaiting the link setup.  Slot the database into
		    queue and set hash table value.
		*/
		xvc->rstcause = cause;	/* Could be RNET_OPERATIONAL */
		xvc->rstdiag  = diag;
		xvc->lsaplink = lsp;
		xvc->xbase    = cfg;
		slot_in(lsp,xvc);

		/*
		    Depending on the original state, send up a reset to the
		    user, or the waiting attach message
		*/
		if( xvc->NSUattached != 0 )
		{
		    xvc->xusrevent &= ~xBUFreq;
		    switch(xvc->xstate)
		    {
		    
		    case DataTransfer:
		    case DXEbusy:
		    case DXErpending:
		    case WRCUpending:
		    case pRESUonly:
		    pRESUcode:
		    
			/*
			    Flush user data awaiting on the upper read queue.
			    This should always be done - this is a super RESET
			*/
			zap_pvc_rdata(xvc,xvc->userref,1);
			stop_vc_timers(xvc);
			tidy_user_side(xvc);
			xvc->xstate = pRESUonly;
			need_buffer(xvc,PVC_RES);
			xvc->bureason = PVC_RES;
			xvc->xusrevent |= xBUFreq;
			x25vccall(xvc);	/* Safe now? */
			
		    break;

		    case DXEresetting:	/* Shift state only */
		    case WtgRCU:
		    
			xvc->xstate = RESUonly;
			
		    break;

		    case P4pending:
		    case D2pending:
		    case D2:

			stop_vc_timers(xvc);
			to_P4D1(xvc,1);	/* Enter transfer with flow */

		    break;

		    case pDTransfer:
		    case WtgRCN:
		    case WtgRCNpending:	/* Confirm the user reset   */

			stop_vc_timers(xvc);
			xvc->xstate = pDTransfer;
			need_buffer(xvc,PVC_RSC);
			x25vccall(xvc);	/* Safe now? */
			
		    break;

		    case RESUonly:	/* Still await the RSC */

			/*
			    Squirrel data to be removed
			*/
			zap_pvc_rdata(xvc,xvc->userref,1);

			/*
			    Check whether RST is confirmed but not done yet
			*/
			if( (xvc->xusrevent&(xRSCout|xRSTout)) != 0 )
			{		
			    xvc->xusrevent &= ~(xRSCout|xRSTout);
			    goto pRESUcode;
			}
			
		    break;


		    default:
		    break;
		    }
		}
		else			/* No user is attached yet */
		{
		    xvc->xusrevent = 0;	/* No relevant events now */
		    stop_vc_timers(xvc);
		    flush_user(xvc,1);	/* To clear all events etc. */
		    to_P4D1(xvc,0);	/* Which will search for user */
		}
	    }
	}
    }
}


/******************************************************************/
void det_PVC(xvc,why)
/******************************************************************/

/*
    Detach attached user - enacted only when user is attached.
*/

struct x25vcformat FAR *xvc;
int why;

{

    struct xgldbf *xug;
    mblk_t        *mp;
    struct pvcdetf FAR *pd;

    if( xvc->NSUattached != 0 )
    {
	xvc->NSUattached = 0;		/* Flush occurs outside this code */
	xug = (&xgldb[xvc->userref]);
	xug->gstate = G_OPEN;
	xug->pvcflg = 0;

	/*
	    Obtain the detach buffer and point at the detach struct
	*/
	mp = OFF(&xug->g_ear); 
	pd = (struct pvcdetf FAR *)mp->b_rptr;	
	pd->reason_code = why;
	
#if ENDIAN_SWAP
	x25apiswap(mp, UPWARD) ;
#endif
	putnext(xug->rq,mp);		/* Send the detach */

	/*
	    Tidy up buffers and queues
	*/
	xpurgeQ((struct Q FAR *)&xug->g_ear);
	xpurgeQ((struct Q FAR *)&xug->gdbq);
	flushq(xug->rq,FLUSHALL);
	rmXLDAT(xug->wq);

	qenable(xug->wq);		/* Start queue moving		*/
    }
}


/******************************************************************/
void unhook_PVCs(lsp,may_detach)
/******************************************************************/

/*
    Detach any PVC records from this link record.  Called before clearing
    all svcs during restart, link down etc. Parameter requests DETACH of
    user.
*/

struct lsformat FAR *lsp;
int may_detach;

{
    int k;
    struct x25vcformat FAR *thisvc;
    struct x25vcformat FAR *xvc;

    if( (k=lsp->lbase->NPCchannels) != 0 )
    {
	/*
	    Find the PLP PVC database elements for this net
	*/
	xvc = lsp->CRqueue;

	while (xvc != NULL && k != 0)
	{
	    thisvc = xvc;
	    xvc = xvc->xvclink;

	    if( thisvc->ampvc!= 0 )
	    {
		k--;			/* Count this pvc out           */
		stop_vc_timers(thisvc);
		thisvc->lsaplink = NULL;
		lsp->concount--;	/* Decrement the conn. count    */
		lsp->slot[thisvc->hashval] = SLOTFREE;
		deqvc(lsp,thisvc);
		
		/*
		    (Hook here for closedown on restart)
		*/
		if( may_detach == PVC_LINKDOWN )
		{
		    if( thisvc->NSUattached )
		    {
			det_PVC(thisvc,PVC_LINKDOWN);
		    }
		    flush_user(thisvc,1);
		    to_P4D1(thisvc,0);
		}
	    }
	}
    }	    
}


/******************************************************************/
void zap_pvc_rdata(xvc,ref,asked)
/******************************************************************/

/*
    Deletes any userdata on the glue including the squirrelled data.
    Write q data freed if asked not 0
*/

struct x25vcformat FAR *xvc;
int ref,asked;

{

    if( xvc->NSUattached )
    {
	xpurgeQ((struct Q FAR *)&xgldb[ref].gdbq);  /* Squirrel data gone  */
	flushq (xgldb[ref].rq,FLUSHALL);	    /* Read queue flushed  */

	if( asked )
	    rmXLDAT(xgldb[ref].wq);		    /* Write queue sifted  */
	    
	qenable(xgldb[ref].wq);		            /* Enable write queue  */
    }

}


/******************************************************************/
static unsigned char map_res_rst(c_in)
/******************************************************************/

/*
    Maps a restarting cause to a reset one for PVC. Inputs the restart
    cause and outputs reset value.
*/

unsigned char c_in;

{
    unsigned char c_out;

    switch(c_in)
    {

    /* Local Procedure Error */
    case 1:
    
	c_out = RLOCAL_PROCEDURE_ERROR;
	
    break;

    /* Network Congestion */
    case 3:
    
	c_out = RNETWORK_CONGESTION;
	
    break;

    /* Network Operational */
    case 7:
        
	c_out = RNET_OPERATIONAL;
	
    break;

    default:
    
	c_out = c_in;
	
    break;
    }

    return(c_out);
}



/******************************************************************/
void L2_datind(lsp,inpkt)
/******************************************************************/

/*
    Handle data from level 2.  
*/

struct lsformat FAR *lsp;
mblk_t *inpkt;

{

    register struct x25vcformat FAR *xvc;
    int           buflen, ref, hashindex;
    unsigned short lci;
    unsigned char gfi, lcn, lgn, pti, byte1, byte2, ptype;
    unsigned char PLRstate, fmterr;
    int pvcflag;

    if ( xcfg[lsp->cfg_index].trace_active )
	x25_trace(xcfg[lsp->cfg_index].trace_queue,lsp,inpkt->b_cont,1);

    if( (buflen=mb_bulen(inpkt))>1 )      /* Proceed if 2 or more present */
    {
	(void) mb_nxtbyte(inpkt,(bufp_t)&byte1);
	gfi=(byte1 & 0XF0);
	lgn=(byte1 & 0X0F);
	
	(void) mb_nxtbyte(inpkt,(bufp_t)&byte2);
	lcn=byte2;
	lci=(lgn<<8) | lcn;
	
	(void) mb_nxtbyte(inpkt,(bufp_t)&pti);
#ifdef MOD8ONLY
	if( (gfi & 0X30) == G_8 )
#else
	if( (gfi & 0X30) == (unsigned char) lsp->lbase->THISGFI )
#endif
	{
	    if( lci )                         /* Non-zero lci */
	    {

		if( vc_in_range(lci,&hashindex,lsp->lbase,&pvcflag) )
		{
		    if( buflen>2 )
			ptype=(unsigned char) IDnnPkt(lsp,gfi,pti);
		    else
		    {
			ptype=X_nnSHORT;
			L3monitor(L3shortframe, lsp->lu_ident);
		    }
		}
		else
		{
		    fmterr=36;
		    goto send_DIAG;
		}
	    }
	    else                                      /* Zero LCI */
	    {
		if( buflen>2 )
		    ptype=(unsigned char) ID00Pkt(lsp,gfi,pti);
		else
		{
		    ptype=X_BAD00PTI;                 /* Correct assignment? */
		    L3monitor(L3shortframe, lsp->lu_ident);
		}
	    }
	}
	else                                          /* Invalid GFI */
	{
	    ptype=X_BADGFI;
	}

	/*
	    Having 'fingered' the packet type, proceed with the analysis,
	    taking the most common case first, and noting the Rstate
	    condition.
	*/
	PLRstate=lsp->lstate;

	if( ptype<=X_VCNOT0 )                        /* Most common */
	{
	    if( PLRstate==L_connected )              /*   ditto      */
	    {
		/*
		    Is this packet on a previously allocated circuit?
		    Use the value returned by vc_in_range.
		*/
		if( (xvc=lcitovc(lsp,lci,hashindex))==NULL )
		{
		    /*
			No, we require to find slots, allocate etc.
		        Pro tem no attempt to acquire PVC db element
		    */
		    if( pvcflag == 0 && (xvc=x25allocvc(&ref)) )
		    {
			xvc->xtag=X25TAG;
			xvc->lsaplink=lsp;
			xvc->xbase    = lsp->lbase;	/* COPY */
			xvc->xdomain  = lsp->ldomain;	/* COPY */
			xvc->xu_ident = lsp->lu_ident;	/* COPY */
			xvc->xstate=P1;
			xvc->nsref=ref;
			xvc->lci=lci;
			xvc->nslim = (ushort) xvc->xbase->MAXNSDULEN;
#ifdef MOD8ONLY
			xvc->xmodulo = 7;
			xvc->lgngfi  =lgn | G_8;
#else
			if( xvc->xbase->THISGFI == G_8 )
			    xvc->xmodulo = 7;
			else
			    xvc->xmodulo = 127;
			    
			xvc->lgngfi=lgn | xvc->xbase->THISGFI;
#endif
			xvc->lcn=lcn;
			xvc->hashval=(unsigned short)hashindex;
			bcopy((bufp_t)&lsp->lladdr,
			      (bufp_t)&xvc->caller.DTE_MAC,
			      sizeof(struct lsapformat));

			/*
			    Mark this channel as now in use - slotin hash
			*/
			slot_in(lsp,xvc);

		    }
		    /* L3nodb event montitored in alloc routine */
		}

		/*
		    Test if xvc is known or allocated
		*/
		if( xvc!=NULL )
		{
		    /*
			Handle this frame.  First adjust buffer length
		    */
		    buflen-=3;

		    /*
			Now we have a xvc initialise character read
			on remainder of buffer
		    */
		    mb_gset(xvc,inpkt);

		    switch( ptype )
		    {

		    case X_DATA:

			Rx_DATA(xvc,gfi,pti,buflen,inpkt);
			inpkt = NULL;

		    break;

		    case X_RR:

			Rx_RR(xvc,pti,buflen);

		    break;

		    case X_RNR:

			Rx_RNR(xvc,pti,buflen);

		    break;

		    case X_CALL:

			if( pvcflag == 0 )
			    Rx_CALL(lsp,xvc,gfi,inpkt);
			else
			    goto PVCcase;

		    break;

		    case X_CAA:

			if( pvcflag == 0 )
			    Rx_CAA(lsp,xvc,gfi,inpkt);
			else
			    goto PVCcase;
			    
		    break;

		    case X_CLR:

			if( pvcflag == 0 )
			    Rx_CLR(lsp,xvc,gfi,buflen,inpkt);
			else
			    goto PVCcase;
				
			 inpkt = NULL;
			 
		    break;

		    case X_CLC:

			if( pvcflag == 0 )
			    Rx_CLC(lsp,xvc,gfi,buflen,inpkt);
			 else
			    goto PVCcase;
			    
		    break;

		    case X_INT:

			Rx_INT(xvc,buflen,inpkt);
			inpkt = NULL;

		    break;

		    case X_INC:

			Rx_INC(xvc,buflen,inpkt);
			inpkt = NULL;
			
		    break;

		    case X_RST:

			Rx_RST(xvc,buflen,inpkt);
			inpkt = NULL;
			
		    break;

		    case X_RSC:

			Rx_RSC(xvc,buflen,inpkt);
			inpkt = NULL;

		    break;

		    case X_REJ:
		    case X_nnSHORT:
		    case X_BADnnPTI:
		    case X_VCNOT0:
		    PVCcase:
		    
			state_matrix(xvc,ptype);

		    break;
		    }

		    if( inpkt != NULL )
		    {
			m_freem(inpkt);
			inpkt=NULL;
		    }
		    
		    /*
			Pick up events caused by any input
		    */
		    if( xvc->xusrevent != 0 ) x_exam_user(lsp,xvc);
		}
		else
		{
		    /*
			No resource, clear this call unless this is a
			CLC.  Thus, a clear will be sent and should
			cause us no trouble when the CLC is received
			even when the no resource condition persists.
			In the case when there is resource available
			the cost will be a further CLR - CLC exchange.
			Note also that clear collisions are handled
			by the protocol.
			Similarly PVCs with RST and RSC.
		    */
		    if( (pvcflag == 0 && ptype!=X_CLC) ||
		        (pvcflag != 0 && ptype!=X_RSC) )
		    {
			/*
			    Delete the data portion, but retain the LL header
			*/
			m_freem(mb_frag(inpkt));

			/*
			    Acquire a data fragment for the CLR
			*/
			if( (inpkt->b_cont = allocb(5,BPRI_MED)) != NULL )
			{
			    /*
				Extended clear is not sent here
			    */
#ifdef MOD8ONLY
			    (*inpkt->b_cont->b_wptr++)=lgn | G_8;
#else
			    (*inpkt->b_cont->b_wptr++)=lgn | lsp->lbase->THISGFI;
#endif
			    (*inpkt->b_cont->b_wptr++)=lcn;
			    (*inpkt->b_cont->b_wptr++)= ( pvcflag ? RST_PTI
			                                          : CLR_PTI);
			    (*inpkt->b_cont->b_wptr++)=0;
			    (*inpkt->b_cont->b_wptr++)=163;

			    if( LL_datareq(lsp,inpkt) == 0 )
				m_freem(inpkt);
				
			    inpkt=NULL;
			}
			else
			    goto disgard_inpkt;    /* No resources */
		    }
		}
	    }
	    else
	    {
		/*
		    Packets <= X_VCNOT0 are discarded
		    If connecting the packets should not arrive
		*/
		if( PLRstate == L_connecting )
		   L3monitor(L3badlstate, lsp->lu_ident);
		   
		goto disgard_inpkt;
	    }
	}

	/*
	    End processing case ptype<= X_VCNOT0.
	    Check PLRstate (to avoid checking in all actions below)
	*/
	else if( PLRstate==L_disconnecting  ||
	         PLRstate==L_dpending       ||
		 PLRstate==L_connecting )
	{
	    if( PLRstate == L_connecting )
		L3monitor(L3badlstate, lsp->lu_ident);
		
	    goto disgard_inpkt;
	}

	/*
	    Process packet types > X_VCNOT0 i.e. LCI==0 types
	*/
	else if( ptype==X_RES || ptype==X_REC )     
	{
	    if( ptype==X_RES )
	    {
	    	/*
		    Restart request Bump length if required.
		*/
		if( buflen==4 && (lsp->lbase->psdn_local.SNMODES&ACC_NODIAG) )
		    buflen=5; 
		    
		fmterr = ( buflen==5 ? 0 : ( buflen>5 ? 39 : 38 ) );

		(void) mb_nxtbyte(inpkt,(bufp_t)&lsp->rescause);
		(void) mb_nxtbyte(inpkt,(bufp_t)&lsp->resdiag );

		if (SNMP_trace_q) SNMP_restart_event(lsp->lu_ident) ;

		if (lsp->rescause == 0x01)
			L3monitor(prc_errors, lsp->lu_ident) ;
#ifdef DEBUG
		strlog(X25_STID,lsp->lu_ident,4,SL_TRACE,"REST  In  : '%x' C/D %04x",
	                          lsp->lu_ident,lsp->rescause<<8|lsp->resdiag);
#endif
	    }
	    else
	    {
	    	/*
		    Restart confirmation. Check length
		*/
		fmterr = ( buflen==3 ? 0 : 39 );

		if (SNMP_trace_q) SNMP_restart_event(lsp->lu_ident) ;

#ifdef DEBUG
		strlog(X25_STID,lsp->lu_ident,4,SL_TRACE,"RESTC In  : '%x'",lsp->lu_ident);
#endif
	    }

	    if(fmterr==0)
	    {
		if( ptype==X_RES )
		{
		    L3monitor(res_in, lsp->lu_ident);
		    if( PLRstate==L_connected || PLRstate==L3rpending)
		    {
			/*
			    Unhook all PVCs. No detach required.
			*/
			unhook_PVCs(lsp,0);
			/*
			    Close all vcs not in P1 state and confirm
			    the restart.
			*/
			close_SVCs(lsp,lsp->rescause,lsp->resdiag,0);
			confirm_restart(lsp,mb_zapdata(inpkt));
			inpkt=NULL;
			
			/*
			    Idle timer started when closedown enacted
			*/
			if( PLRstate == L3rpending )
			{
			    to_Lconnected(lsp);	/* needbuf cleared also */
			    CRs_pending(lsp);
			}
			/*
			    Recollect and restart PVCs
			*/
			res_PVCs(lsp,map_res_rst(lsp->rescause),lsp->resdiag);
		    }
		    else if( PLRstate==WtgRES )      /* Collision */
		    {
			stopT20(lsp);

			if( lsp->lbase->L3PLPMODE == 2 )  /* DXE */
			{
			    if( (lsp->rescause&127) != 0 )
				lsp->DTEmode = 1;
			    else
			    {
				lsp->lstate=WtgDXE;
				start_random_timer(lsp);
				goto disgard_inpkt;	/* To avoid below */
			    }
			}
			else
			    lsp->DTEmode = (unsigned char) lsp->lbase->L3PLPMODE;

			to_Lconnected(lsp);         /* Enter connected     */
			CRs_pending(lsp);           /* Enable ConReq       */
			/*
			    Recollect and restart PVCs
			*/
			res_PVCs(lsp,RNET_OPERATIONAL,0);

		    }
		    else if( PLRstate==WtgDXE || PLRstate == WtgRpending )
		    {
			/*
			    needbuf cleared on going to L_connected
			*/
			confirm_restart(lsp,mb_zapdata(inpkt));
			inpkt=NULL;
			if( lsp->lbase->L3PLPMODE == 2 )
			{
			    if( (lsp->rescause&127) != 0 )
				lsp->DTEmode = 1;   /* YES - behave as DTE */
			    else
				lsp->DTEmode = 0;   /* NO  - behave as DCE */
			}
			else
			    lsp->DTEmode = (unsigned char) lsp->lbase->L3PLPMODE;

			to_Lconnected(lsp);             /* Enter connected     */
			CRs_pending(lsp);
			/*
			    Recollect and restart PVCs
			*/
			res_PVCs(lsp,RNET_OPERATIONAL,0);
		    }
		    else if( PLRstate==L3restarting )
		    {
			to_Lconnected(lsp);         /* Enter connected     */
			CRs_pending(lsp);
			/*
			    Recollect and restart PVCs
			*/
			res_PVCs(lsp,RNET_OPERATIONAL,0);
		    }
		}
		else                              /* RESTART CONFIRM */
		{
		    if( PLRstate==L_connected  ||
		        PLRstate==L3rpending   ||
			PLRstate==WtgRpending  ||
			PLRstate==WtgDXE )      /* Pkt in error for state */
		    {
			L3monitor(rem_perr, lsp->lu_ident);
			lsp->lusrevent&= ~lneedbuf;   /* In case pending */
			if( PLRstate==L_connected || PLRstate==L3rpending )
			{
			    lsp->lstate=L3restarting;
			    unhook_PVCs(lsp,0);		/* No detach required */
			    close_SVCs(lsp,0,17,0); /* Keep pending P1 */
			}
			else
			{
			    lsp->lstate=WtgRES;
			    stop_random_timer(lsp);
			}

			/*
			    Restart, invalid packet for state R1
			*/
			if( restart_request(lsp,0,17,mb_zapdata(inpkt)) )
			    startT20(lsp);
			else
			{
			    if( lsp->lstate==L3restarting )
				lsp->lstate=L3rpending;
			    else
				lsp->lstate=WtgRpending;

			    lsp->lusrevent|= lneedbuf;
			}
			inpkt=NULL;
		    }
		    else if( PLRstate==WtgRES || PLRstate==L3restarting )
		    {
			if( PLRstate==WtgRES )
			{
			    if( lsp->lbase->L3PLPMODE != 2 )
				lsp->DTEmode = (unsigned char) lsp->lbase->L3PLPMODE;
			    else
				lsp->DTEmode = 1; /* DTE */
			}

			to_Lconnected(lsp);              /* Stops clocks */
			CRs_pending(lsp);
			/*
			    Recollect and restart PVCs
			*/
			res_PVCs(lsp,RNET_OPERATIONAL,0);
		    }
		    /*
			Otherwise drop out to disgard the inpkt
		    */
		    goto disgard_inpkt;
		}
	    }
	    else                                  /* RES/REC fmterr */
	    {
		/*
		    'fmterr' has already been assigned
		*/
		L3monitor(res_ferr, lsp->lu_ident);
		if( PLRstate==L_connected || PLRstate==L3rpending  ||
		    PLRstate==WtgRpending || PLRstate==WtgDXE  )
		{
		    goto send_DIAG;
		}
		else if( PLRstate==WtgRES  || PLRstate==L3restarting )
		{
		    if( restart_request(lsp,0,fmterr,mb_zapdata(inpkt)))
			startT20(lsp);           /* I.e. stops and starts */
		    else
		    {
			stopT20(lsp);
			if( PLRstate==WtgRES )
			    lsp->lstate = WtgRpending;
			else
			    lsp->lstate = L3rpending;

			lsp->lusrevent|=lneedbuf;
		    }
		    inpkt=NULL;
		}
		/*
		    Otherwise drop out to disgard inpkt
		*/
		goto disgard_inpkt;
	    }
	}                                     /* End RES & REC */
	else if( ptype==X_BADGFI )
	{
	    L3monitor(L3badgfi, lsp->lu_ident);
	    fmterr=40;
	    goto send_DIAG;
	}
	else if( ptype==X_DIAG || ptype==X_BADDIAG )     /* DIAGNOSTIC */
	{
	    if( ptype!=X_BADDIAG && (buflen>4 && buflen<=7) )
	    {
		L3monitor(dg_in, lsp->lu_ident);
		report_DIAG(inpkt);
	    }
	    else
	    {
		L3monitor(L3baddiag, lsp->lu_ident);
	    }
	    goto disgard_inpkt;
	}
	else if( ptype==X_BAD00PTI )
	{
	    fmterr=36;
	    goto send_DIAG;
	}
	else                                        /* Other types - DISCARD */
	{
	    goto disgard_inpkt;
	}
    }
    else                                            /* Short pkt or NULL */
    {
	if(inpkt)
	{
	    (void) mb_nxtbyte(inpkt,(bufp_t)&byte1);
	    fmterr=38;
	    goto send_DIAG;
	}
	else
	{
	    L3monitor(L3indnodata, lsp->lu_ident);
	}
    }

disgard_inpkt:

    if( inpkt!=NULL )
    {
	m_freem(inpkt);
	inpkt=NULL;
    }
    return;

send_DIAG:

    Tx_DIAG(lsp,fmterr,(unsigned char) (buflen>3 ? 3 : buflen),byte1,byte2,pti);

    goto disgard_inpkt;
}


/******************************************************************/
void L2_conind(lref,laddr,buff)
/******************************************************************/

/*
    Connect indication from link level
*/

int 			lref;		/* Index to lower stream */
struct lsapformat	FAR *laddr;	/* Calling address       */
mblk_t			*buff;		/* Buffer to respond     */

{
    register struct lsformat FAR *lsp;
    register struct ll_msgc  FAR *msg;
    uint3264 lhandle;                      /* The handle from link  */
    int ref;
	static int cdk_static = 0 ;

    /*
	If resources are available, confirm the connect indication.
	Otherwise, send a CONNAK instead.
    */
    msg     = (struct ll_msgc FAR *)buff->b_rptr;
    lhandle = msg->ll_myhandle;

    if( (lsp = x25alloclsap(&ref)) != NULL )
    {
	/*
	    The 'alloclsap' has already set the connection identifier
	*/
	lsp->ltag = PLPTAG;
	lsp->myref = ref;
	lsp->lref = lref;
	lsp->lowhandle = lhandle;

	bcopy(  (bufp_t)laddr, (bufp_t)&lsp->lladdr, sizeof(struct lsapformat) );

	/*
	    Determine timer set - done on subnetwork (but could? be link addr)
	    Lref == db number
	    iden = subnetwork conf
	*/
	lsp->lu_ident  = map_linkref(lref);
	lsp->lbase     = sn_to_cfg(lsp->lu_ident);
	lsp->cfg_index = sn_to_cfg_index(lsp->lu_ident);

	if( lsp->lbase != NULL )
	{
	    (void) memset((char FAR *)&lsp->slot[0],SLOTFREE,VC_SLOT_LEN);
	    lsp->lstate = WtgDXE;
	    lsp->ldomain = net_domain(lsp->lbase);
	    start_connect_timer(lsp);
	    start_random_timer(lsp);
	    msg->ll_command = LC_CONOK;
	    msg->ll_connID  = lsp->lconnid;
	    msg->ll_myhandle= (unsigned long)lsp->myref;

	    /* LL_conrsp(lref,ref); */
	    L3monitor(l2conin, lsp->lu_ident);
	}
	else
	{
	    L3monitor(l2noconfig, lsp->lu_ident);
	    msg->ll_command = LC_CONNAK;
	    msg->ll_connID  = 0;
//		printf("LC_CONNACK 2") ;
	    /* LL_disreq(lref, 255 );             No config!!! */
	}
    }
    else
    {
	/*
	    L3nodb event monitored in alloc routine
	*/
	msg->ll_command = LC_CONNAK;
	msg->ll_connID  = 0;
	if(cdk_static == 0)
	{
		printf("LC_CONNAK 3") ;	
		cdk_static = 1 ;
	}
	/* LL_disreq(lref, 255 );                   No resource!!! */
    }

    msg->ll_yourhandle = lhandle;

#ifdef DEBUG
    printf("prty msg - lref %d com %d result %d\n",
            lref,msg->ll_command,LL_prtymsg(lref,buff));
#else
    LL_prtymsg(lref,buff);
#endif  

}


/******************************************************************/
void L2_concnf(lsp)
/******************************************************************/

/*
    Connect confirmation from link level. If the lstate is not 'connecting'
    ignore the confirmation.
*/

register struct lsformat FAR *lsp;

{

    if( lsp->lstate==L_connecting )
    {
	L3monitor(l2conok, lsp->lu_ident);
	lsp->lstate=WtgDXE;
	start_connect_timer(lsp);
	start_random_timer(lsp);
    }
    else
    {
	L3monitor(L2badcc, lsp->lu_ident);
    }
}


/******************************************************************/
void L2_disind(lsp)
/******************************************************************/

/*
    Disconnect from the link level.  The closure event is set in the
    event and x25lscall initiated.
*/

register struct lsformat FAR *lsp;

{
    lsp->lusrevent |= lclosure;
    x25lscall( lsp );
}


/******************************************************************/
void L2_discnf(lsp)
/******************************************************************/

/*
    Disconnect confirmation from the link leve. If the state is
    'disconnecting', free resource, else monitor. The closure event
    is set in the event and x25lscall initiated
*/

register struct lsformat FAR *lsp;

{

    if( lsp->lstate==L_disconnecting )
    {
	lsp->lusrevent |= lclosure;
	x25lscall( lsp );
    }
    else
    {
	L3monitor(L2baddcnf, lsp->lu_ident);
    }
}


/******************************************************************/
void L2_rstind(lsp)
/******************************************************************/

/*
    Reset indication from link level. The reset is merely monitored,
    no action is required here.
*/

register struct lsformat FAR *lsp;

{
    L3monitor(L2reset, lsp->lu_ident);
}


/******************************************************************/
static int lfirst(z)
/******************************************************************/

/*
    Returns the first set event value
*/

register unsigned short z;

{
    register int i;

    for( i=1; i<16; i++, z>>=1)
	if( (z&1) != 0 ) return(i);

    return(0);
}


/******************************************************************/
void l_exam_user(lsp)
/******************************************************************/

/*
    Examine events caused and enact them
*/

register struct lsformat  FAR *lsp;

{
    register int lfunction;

    if( lsp->lusrevent != 0 )
    {
	lfunction = lfirst(lsp->lusrevent);

	switch( lfunction )
	{
	
	case lbye:

	    /*
		A disconnect or a disconnect confirmation was received
		and is enacted using this event.
	    */
	    if( lsp->lstate==L_connecting )
		L3monitor(l2conrej, lsp->lu_ident);
	    unhook_PVCs(lsp,PVC_LINKDOWN);	/* May detach here */
	    close_SVCs(lsp,OUT_OF_ORDER,0,1);   /* ALL flag set!   */

	    lfreedb(lsp);                       /* Clears events   */
	    
	break;


	case lzap:

	    /*
		A request has been made to disconnect an LLC connection.
		This can be done except for connecting and disconnecting
	    */
	    lsp->lusrevent &= ~lopkills;

	    if( lsp->lstate != L_connecting && lsp->lstate != L_disconnecting )
	    {
		L3monitor(l2lzap, lsp->lu_ident);
		dis_lsap_and_vcs( lsp, 0, 122 );  /* 'Maintenance' action */
	    }
	    else
		L3monitor(Cantlzap, lsp->lu_ident);

	break;


	case lBUFrequest:

	    /*
		The only reason that the PLP level requires a buffer
		is for transmission of a restart. Otherwise if a link
		disconnection is pending, do it
	    */
	    lsp->lusrevent&= ~lneedbuf;

	    if( lsp->lstate == WtgRES      || lsp->lstate == WtgRpending ||
	        lsp->lstate == L3rpending  || lsp->lstate == L3restarting )
	    {
		if( restart_request(lsp,lsp->rescause,lsp->resdiag,NULL) != 0 )
		{
		    lsp->lretx++;
		    startT20(lsp);

		    if( lsp->lstate == WtgRpending )
			lsp->lstate =  WtgRES;
		    else if( lsp->lstate == L3rpending )
			lsp->lstate = L3restarting;
		}
		else
		{
		    lsp->lusrevent|= lneedbuf;
		    lsp->lbureason = X_RES;
		}
	    }
	    else if( lsp->lstate==L_dpending )
	    {
		/*
		    Diag is stored from before
		*/	    
		dis_lsap_and_vcs(lsp,lsp->rescause,lsp->resdiag);
	    }
	    else
		L3monitor(LNeednotneeded, lsp->lu_ident);

	break;

	}  /* End switch for L EVENTS */
    }
}          /* End L_EXAM_USER */


/******************************************************************/
void IND_toNS(linkref,buff)
/******************************************************************/

/*
    Externally called routine for passing indications from the link to
    the network level.
*/

unsigned int linkref;           /* Index to the physical sub-net */
mblk_t	 *buff;

{
    register struct lsformat FAR *lsp;
    struct lsapformat r_add;
    int indtype, nref;
    int3264 in_status;
    struct ll_msg FAR *msg;
    struct ll_msgc FAR *msgc;
    extern int dllcall;				/* THE DB LOCKING DEVICE */


    if( buff != NULL && buff->b_rptr != NULL )    /* Assume message is OK */
    {
	msg     = (struct ll_msg FAR *) buff->b_rptr;
	nref    = (int) msg->ll_yourhandle;
	indtype = msg->ll_command;
	if( msg->ll_type == LL_DAT )
	{
	    if( indtype == LC_DATA )
	    {
		if(  ( nref>0 && nref<=isolsapmax)           &&
	             ((lsp=iso_lsap[nref])->ltag == PLPTAG ) &&
	             ( msg->ll_connID ==  (uint32) lsp->lconnid )      &&
		     ( lsp->prgcnt == 0 ) )
		{
		    dllcall = 1;		/* LOCK_DBELEMENTS */
		    L2_datind(lsp,buff);
		    buff=NULL;
		    dllcall = 0;		/* UNLOCK_DBELEMENTS */
		}
		/*
		    ELSE Mismatch or purging - discard at the bottom
		*/
	    }
	}
	else if( msg->ll_type == LL_CTL )         /* ll_type == LL_CTL? */
	{
	    in_status = msg->ll_status;
	    if( indtype!=LC_CONNECT )
	    {
		if( ( nref>0 && nref<=isolsapmax)          &&
		    ((lsp=iso_lsap[nref])->ltag == PLPTAG) &&
		    (msg->ll_connID ==  (uint32) lsp->lconnid ) )
		{
		    switch( indtype )
	            {

		    case LC_CONCNF:

			if( in_status==LS_SUCCESS )
			    L2_concnf(lsp);
			else
			{
			    /*
				Connect Request refused for some reason.
				Treat this as a disconnect indication
			    */
			    L2_disind(lsp);
			}

		    break;

		    case LC_CONOK:

			/* Reference exchange */
			lsp->lowhandle = ((struct ll_msgc FAR *)msg)->ll_myhandle;
			
		    break;

		    case LC_CONNAK:

			/*
			    This reference exchange failed - treat as
			    a disconnect
			*/
			L2_disind(lsp);

		    break;

		    case LC_DISC:

			/*
			    Ignore any details
			*/
			L2_disind(lsp);
			
		    break;

		    case LC_DISCNF:

			/*
			    Ignore any details
			*/
			L2_discnf(lsp);

		    break;

		    case LC_RESET:

			/*
			    Ignore any reason parameter
			*/
			L2_rstind(lsp);

		    break;

		    case LC_REPORT:

			/*
			    Must examine report status to determine if the
			    link has been disconnected
			*/
			if( in_status==LS_CONFLICT        ||
			    in_status==LS_DISCONNECT      ||
			    in_status==LS_FAILED          ||
			    in_status==LS_RST_FAILED      ||
			    in_status==LS_RST_DECLINED    ||
			    in_status==LS_RST_REFUSED )
			{
			    /*
				Treat this as a disconnect indication
			    */
			    L2_disind(lsp);
			}
			else
			{
			    /*
				Merely monitor the report
			    */
			    L3monitor(L2report, lsp->lu_ident);
			}
			
		    break;

		    case LC_PRGBRA:

			/*
			    Count the (ahem!) BRAs
			*/
			lsp->prgcnt++;

		    break;

		    case LC_PRGKET:

			/*
			    Decrement no. of BRAs
			*/
			if( lsp->prgcnt )
			    lsp->prgcnt--;
			    
		    break;

		    case LC_RSTCNF:     /* This code does not use RESET */
		    default:

			system_error(BadL2func);

		    }                       /* End switch on indtype        */
		}                           /* End range / connid check     */
		else                        /* Bad tag value hook      */
		{
		}
	    }                               /* END M_CTL != LC_CONNECT */
	    else
	    {
		/*
		     LC_CONNECT Message. Obtain the remote
		     station address
		*/
		msgc = (struct ll_msgc FAR *)buff->b_rptr;
		r_add.lsap_len = (msgc->ll_remsize);
		bcopy((bufp_t)&msgc->ll_remaddr[0],
		      (bufp_t)&r_add.lsap_add[0],9); /* MAX 9 (NPI/TOA) */

		L2_conind(linkref, &r_add,buff);     /* Handle connect   */
		buff=NULL;                        /* Use buff to respond */
	    }
	}    
    }

    /*
	Now discard the incoming buffer if still present.  Also discards
	cases unrecognised (silently)
    */
    if(buff)
	m_freem(buff);
}


/******************************************************************/
void kill_subnet(id)
/******************************************************************/

/*
    Disconnect all link connections with a subnetwork given
    identifier - called by I_UNLINK code.
*/

int id;

{
    int i;
    struct lsformat FAR *lsp;

    for(i=FIRSTLSAP; i<=isolsapmax; i++)
    {
	if( (lsp=iso_lsap[i])->ltag == PLPTAG && lsp->lref == id )
	{
	    L2_disind(lsp);	/* Calls x25lscall to lock db */
	}
    }
}

