#ident "$Id: x25touser.c,v 1.7 1997/10/21 20:48:49 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
 *
 *  X25TOUSER.C
 *
 *
 *******************************************************************/

/*
 *	x25touser.c
 *	 /projects/common/PBRAIN/SCCS/pbrainF/dev/sys/x25/0/s.x25touser.c
 *	@(#)x25touser.c	1.28
 *
 *	Last delta created	15:30:17 10/22/92
 *	This file extracted	18:33:05 11/19/92
 *
 */

#include "x25_inc.h"

#include "x25form.mac"
#include "x25cause.h"

#ifndef NULL
#define NULL 0
#endif

extern struct xgldbf xgldb[];
extern queue_t *SNMP_trace_q ;
mblk_t *allocb(), *copymsg();

/******************************************************************/
void sqrl_PVC_data(xvc,buff,expedited)   /* (sqrl == squirrel ) */
/******************************************************************/

/*
    Places data on the glue awaiting response from the user before
    sending up.  Expedited set if not normal data.
*/

struct x25vcformat FAR *xvc;
mblk_t *buff;
int expedited;

{
    struct xgldbf      *xug;

    if( xvc->NSUattached )
    {
	/*
	    Buffer already has More & Q-bit flags set - fill in the others
	*/
	buff->b_datap->db_type = M_PROTO;
	buff->b_wptr = buff->b_rptr + sizeof( struct xdataf );
	
	((struct xdataf FAR *)buff->b_rptr)->xl_type    = XL_DAT;
	((struct xdataf FAR *)buff->b_rptr)->xl_command =
	                         ( expedited ? N_EData : N_Data );
				  
	xug = (&xgldb[xvc->userref]);

	/*
	    Queue it on gdbq - if goes on readq, may get back enable
	*/
	ON(&xug->gdbq,buff);
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
	m_freem(buff);
    }
}


/******************************************************************/
void send_sqrl_data(xvc)
/******************************************************************/

/*
    Sends up any squirrelled data on the glue.  Note this can be
    normal or expedited.
*/

struct x25vcformat FAR *xvc;

{
    struct xgldbf      *xug;
    mblk_t             *buff;
    int datacount, expedited;
#ifndef SOH
    extern mblk_t *OFF();
#endif

    datacount = 0;

    if( xvc->NSUattached )
    {
	/*
	    Buffers held may be data or interrupt and only one expedited can
	    exist on the queue at any time (the EAck must be outstanding).
	*/
	xug = (&xgldb[xvc->userref]);

	while( buff = OFF(&xug->gdbq) )
	{
	    expedited =
	    (((struct xedataf FAR *)buff->b_rptr)->xl_command == N_EData ? 1
	                                                                 : 0 );
	    if( xug->rq->q_first == NULL && canput(xug->rq->q_next) )
	    {
#if ENDIAN_SWAP
		x25apiswap(buff, UPWARD) ;
#endif
		putnext(xug->rq,buff);
		
		if( expedited == 0 )
		    datacount++;
	    }
	    else
	    {
		if( expedited )
		    insq(xug->rq,xug->rq->q_first,buff);
		else
		    putq(xug->rq,buff);
	    }
	}
	if( datacount )
	    N_enableinput( xug->xnetref, datacount);
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
    }
}


/******************************************************************/
void del_sqrl_data(xvc)
/******************************************************************/

/*
    Delete, if attached, any squirrelled data on the glue.
*/

struct x25vcformat FAR *xvc;

{
    if( xvc->NSUattached )
    {
	xpurgeQ((struct Q FAR *)&(xgldb[xvc->userref].gdbq));
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
    }
}


/******************************************************************/
static int do_disc(xvc,clrdata)
/******************************************************************/

/*
    Help routine for disconnect
*/

struct x25vcformat FAR *xvc;
mblk_t *clrdata;

{
    mblk_t *msgbuff, *mt;
    struct xdiscf FAR *disc;
    struct i_xdiscf FAR *idisc;
    struct xgldbf *xug;
#ifndef SOH
    extern mblk_t *scan_ears(int, struct xgldbf * );
#endif

    xug = (&xgldb[xvc->userref]);
    flushq(xug->rq,FLUSHALL);

    if (SNMP_trace_q)
	SNMP_trace_clear_event(xvc) ;

    if( (msgbuff = allocb(sizeof(struct i_xdiscf),BPRI_MED)) != NULL )
    {
	msgbuff->b_cont = clrdata;
	msgbuff->b_datap->db_type = M_PROTO;
	msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct i_xdiscf);
	idisc = (struct i_xdiscf FAR *) msgbuff->b_rptr;
	disc = &(idisc->xdisc);
	disc->xl_type    = XL_CTL;
	disc->xl_command = N_DI;
	disc->indicated_qos = 1;
	disc->conn_id    = xvc->xconnid;
	disc->cause = xvc->discause;
	disc->diag  = xvc->disdiag;

	(void) N_DIdetails(xvc->nsref,(unsigned char FAR *) &disc->originator,
			   (unsigned char FAR *) &disc->reason,&disc->responder,&disc->qos);

	if( xug->gstate == G_ATTACHED )
	{
	    xug->gstate = G_OPEN;
/*	    xug->x_attached = 0; */
	    rmXLDAT(xug->wq);		/* Remove queued data and enable */
	    qenable(xug->wq);		/* to get data held upstream */
#if ENDIAN_SWAP
	    x25apiswap(msgbuff, UPWARD) ;
#endif
	    putnext( xug->rq,msgbuff );
	}
	else if(xug->gstate == G_LISTENING)	/* An abort */
	{
	    /*
		Store the ns reference in the internal DI structure
	    */
	    ((struct i_xdiscf FAR *)msgbuff->b_rptr)->holdns = xvc->nsref;
	    mt=scan_ears(xvc->xconnid,xug);	/* Free CI record */
	    if( mt==NULL )
	    {
		strlog(X25_STID,0,0,SL_ERROR,"X25: N_DI scan listen error");
	    }
	    else
		m_freem(mt);

	    ON(&xug->gdbq,msgbuff);
	    xug->g_earuse++;			/* Count it in */
	}
	else					/* Else whine and go on */
	{
	    strlog(X25_STID,0,0,SL_ERROR,"X25: N_DI bad i/f state");
	    m_freem(msgbuff);
	}
	/*
	    User detached and flush_user(1) called at exit
	*/
    }
    else				/* No buffer for the message */
    {
	/*
	    We have a user - therefore no clear user data is present
	    unless as a result of a previous disconnect attempt
	*/
	flush_user(xvc,1);
	xvc->Udiscdata = clrdata;
	rmXLDAT(xug->wq);		/* Remove queued data and enable */
	qenable(xug->wq);		/* to get data held upstream */
	return(0);
    }

    xvc->NSUattached=0;	/* Detach user */
    return(1);		/* SUCCESS */
}


/* ----------------------------------------------------------------------
     Routines interfacing to the user.  Return success/fail (resource)
     or result codes
   ---------------------------------------------------------------------- */

/******************************************************************/
static void xint_err(x,d)
/******************************************************************/

/*
*/

struct x25vcformat FAR *x;
unsigned char d;

{

    L3monitor(xintdisc, x->xu_ident);

    x->nsucause = x->discause = LOCAL_PROCEDURE_ERROR;
    x->nsudiag  = x->disdiag  = d;

    x->xusrevent |= xUSRerr;
}


/******************************************************************/
int disconnect_user(xvc,cause, diagnostic, clrdata)
/******************************************************************/

/*
    Checks user is still present and  disconnects. 1 = OK, 0 - FAIL
    Uses routine do_disc.
*/

register struct x25vcformat FAR *xvc;
unsigned char cause, diagnostic;
mblk_t *clrdata;
{
    if( clrdata != NULL && mb_bulen(clrdata) == 0 )
    {
	m_freem(clrdata);
	clrdata = NULL;
    }

    if( xvc->NSUattached )		/* User is still kicking */
    {
	xvc->discause=cause;
	xvc->disdiag =diagnostic;	/* Reasons copied */

	if( do_disc(xvc,clrdata) == 0 )	/* FAILURE */
	    return(0);
					/* Else OK drop through flush_u */
    }
    else                                /* User has already disconnected */
    {
	/*
	    Kill any clear user data. Disconnect is OK, flush below
	*/
	L3monitor(L3usergone, xvc->xu_ident);
	m_freem(clrdata);
    }

    /*
	Since a disconnect has been sent (or should have), clear
	down the user side completely. NSUattached is zero already.
    */
    flush_user(xvc,1);
    return(1);    

}


/******************************************************************/
int redisconnect_user(xvc)           
/******************************************************************/

/*
    Checks user is still present and redisconnects. 1 = OK, 0 - FAIL
    Uses routine do_disc.
*/

register struct x25vcformat FAR *xvc;

{
    mblk_t *clrdata;

    clrdata = xvc->Udiscdata;
    xvc->Udiscdata = NULL;

    if( clrdata!= NULL && mb_bulen(clrdata) == 0 )
    {
	m_freem(clrdata);
	clrdata = NULL;
    }

    if( xvc->NSUattached )             
    {
	/*
	    User is still kicking. Reasons already loaded in a previous
	    call - retry. If OK drop through flush_user.
	*/
	if( do_disc(xvc,clrdata) == 0 )	/* FAILURE */
	    return(0);
    }
    else
    {
	/*
	    User has already disconnected.  Disconnect is OK,
	    flush below
	*/  
	L3monitor(L3usergone, xvc->xu_ident);
	m_freem(clrdata);     
    }

    /*
	Since a disconnect has been sent (or should have), clear
	down the user side completely. NSUattached is zero already.
    */
    flush_user(xvc,1);
    return(1);
}

#ifndef INTERNAL_DISC
/******************************************************************/
int disc_confirm_user(xvc)
/******************************************************************/

/*
    Checks is user present and passes CLC..
*/

register struct x25vcformat FAR *xvc;

{
    mblk_t *msgbuff;
    struct xdcnff FAR *dcf;
    struct xgldbf *xug;

    if( xvc->NSUattached )
    {
        xug = (&xgldb[xvc->userref]);
        flushq(xug->rq,FLUSHALL);

	/* SNMP Trace */
	if (SNMP_trace_q)
		SNMP_trace_clear_event(xvc) ;

        if( (msgbuff = allocb(sizeof(struct xdcnff),BPRI_MED)) != NULL )
	{
	    msgbuff->b_datap->db_type = M_PROTO;
	    msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct xdcnff);
	    dcf = (struct xdcnff FAR *) msgbuff->b_rptr;
	    dcf->xl_type    = XL_CTL;
	    dcf->xl_command = N_DC;
	    dcf->indicated_qos = 1;

	    /*
	        Load any indication of quality of service parameters
	    */
	    sh_disc_facilities(0,xvc,&dcf->rqos);   

	    if( xug->gstate == G_ATTACHED )
	    {
	        xug->gstate = G_OPEN;
/*	        xug->x_attached = 0; */
	        rmXLDAT(xug->wq);	    /* Remove queued data and enable */
	        qenable(xug->wq);	    /* to get data held upstream */
#if ENDIAN_SWAP
		x25apiswap(msgbuff, UPWARD) ;
#endif
	        putnext( xug->rq,msgbuff );
	    }
	    else			    /* Else whine and go on */
	    {
		strlog(X25_STID,0,0,SL_ERROR,"X25: N_DC bad i/f state");
	        m_freem(msgbuff);
	    }
	}
	else                                /* Ind failed */
	{
	    return(0);
	}
    }
    xvc->NSUattached=0;	/* Detach user */
    return(1);  /* OK */
}
#endif


/******************************************************************/
int reset_user(xvc,cause, diagnostic)
/******************************************************************/

/*
    Checks is user present and passes RST.
*/

register struct x25vcformat FAR *xvc;
unsigned char cause, diagnostic;

{
    mblk_t *msgbuff;
    struct xrstf FAR *rst;
    struct xgldbf *xug;

    if( xvc->NSUattached )
    {
	xvc->rstcause=cause;
	xvc->rstdiag =diagnostic;
	xug = (&xgldb[xvc->userref]);
	flushq(xug->rq,FLUSHALL);
	rmXLDAT(xug->wq);		/* Flush downstream data and enable */
	qenable(xug->wq);		/* the stream to pick up data msgs  */

	if( (msgbuff = allocb(sizeof(struct xrstf),BPRI_MED)) != NULL )
	{
	    msgbuff->b_datap->db_type = M_PROTO;
	    msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct xrstf);
	    rst = (struct xrstf FAR *) msgbuff->b_rptr;
	    rst->xl_type    = XL_CTL;
	    rst->xl_command = N_RI;
	    rst->cause = cause;
	    rst->diag  = diagnostic;
	    (void) N_RIdetails(xvc->nsref,
			               (unsigned char FAR *) &rst->originator,
						   (unsigned char FAR *) &rst->reason);
#if ENDIAN_SWAP
	    x25apiswap(msgbuff, UPWARD) ;
#endif
	    putnext(xug->rq,msgbuff );
	}
	else                             /* Ind failed */
	{
	    return(0);
	}
    }
    else
    {
	xuser_gone(xvc,diagnostic);
    }
    return(1);  /* OK */
}


/******************************************************************/
void reset_confirm_user(xvc,msgbuff)
/******************************************************************/

/*
    Checks user present and passes RSC.  NB code assumes buffer passed
    i.e. always successful.
*/

register struct x25vcformat FAR *xvc;
mblk_t *msgbuff;

{
    struct xrscf FAR *rsc;
    struct xgldbf *xug;

    if( xvc->NSUattached )
    {
	if( msgbuff != NULL &&
	    (msgbuff->b_datap->db_lim - msgbuff->b_rptr) >= sizeof(struct xrscf) )
	{
	    msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct xrscf);
	    msgbuff->b_datap->db_type = M_PROTO;
	    rsc = (struct xrscf FAR *) msgbuff->b_rptr;
	    rsc->xl_type    = XL_CTL;
	    rsc->xl_command = N_RC;
	    xug = (&xgldb[xvc->userref]);
	    flushq(xug->rq,FLUSHALL);	/* Purge all messages (data) */
	    rmXLDAT(xug->wq);		/* Purge data from write queue */
	    qenable(xug->wq);		/* Enable the write queue */
#if ENDIAN_SWAP
	    x25apiswap(msgbuff, UPWARD) ;
#endif
	    putnext(xug->rq,msgbuff);	/* Send up the reset confirmation */
	}
	else				/* Passed buffer not suitable */
	{
	    strlog(X25_STID,0,0,SL_ERROR,"X25: rc buffer short");
	    xint_err(xvc,0);		/* Should never happen DEBUG only */
	    m_freem(msgbuff);
	}
    }
    else
    {
	m_freem(msgbuff);
	xuser_gone(xvc,(unsigned char) 0);
    }
}


/******************************************************************/
static semicmp( a1, a2, al )
/******************************************************************/

/*
    Internal routine to compare semi-octet strings.  Used by user_on_NSAP.
    Returns 0 for match.
*/

unsigned char FAR *a1, FAR *a2;		/* Pointer to semi-octet strings */
int al;					/* Semi-octet length */

{
    int hl;

    if( al != 0 )
    {
	hl = al>>1;			/* Leading byte length */

	if( hl > 0 )			/* x25cmp leading part */
	{
	    if( x25cmp(a1,a2,hl) != 0 )
		return(1);		/* Failure in leading part */
	}
	
	if( (al&1) != 0 )		/* Odd number semi-octets */
	{
	    if( (a1[hl]&0xF0) != (a2[hl]&0xF0) )
		return(1);		/* Failure in trailing byte */
	}
    }
    return(0);				/* Successful match */
}

/* ----------------------------------------------------------------------
   ---------------------------------------------------------------------- */

/******************************************************************/
int user_on_NSAP( ad, cudf, NSAPflag, id ,qp, bar_non_priv )
/******************************************************************/

/*
    Scan the listeners and identify a match to the address and/or cudf.
    Return 0 and an id value on success, non-zero diagnostic (+ve) otherwise.
*/


struct xaddrf FAR *ad;		/* Pointer to the address structure	*/
mblk_t        *cudf;		/* Pointer to the cudf buffer		*/
int	       NSAPflag;	/* Set if NSAPs (1984) may be present	*/
int           FAR *id;		/* Passing the reference start/found	*/
int           *qp;		/* Passing queue position start/finish  */
ushort	      bar_non_priv;     /* Non privileged listens barred        */

{
    mblk_t		  *mt;
    struct i_xlistenf FAR *lf;
    unsigned char	   cudcmp[16];	/* Holds up to 16 bytes */
    int		cudl;			/* Length cudf in cudcmp */
    int		addlen;			/* DTE or NSAP length */
    bufp_t	addptr;			/* Ptr to DTE or NSAP */
    int		l_seen,			/* Set if listener exists */
		l_busy,			/* Set if a listener busy */
		priv_busy;		/* Priv listen busy       */
    int		cqp;			/* Current queue position */


    l_seen = l_busy = priv_busy = cqp = 0;

    cudl = mb_bulen(cudf);

    if( cudl > 16 )
	cudl = 16;

    if( cudl > 0 )
	getNbytes(cudf, cudl, &cudcmp[0]);

    mt = listen_requestQ;
    
    while ( mt )
    {
	lf = (struct i_xlistenf FAR *)mt->b_rptr;
	
	l_seen = 1;

	/*
	    Skip until at position where we finished the last time
	*/
	if ( ++cqp <= *qp ) goto nxt_mt;
	
	/*
	    Stop the search if we are on to any user listens and either
	     -  non privileged listens have been disabled by the
	        administration.
	     -  a matching privileged listen was busy.
	*/
	if (lf->l_total < ROOT_LISTEN && ( bar_non_priv   ||
	                                   priv_busy == 1 ))
	    break;


	/* Check if there is a Subnet IDentifier match */

	if(lf->l_snid)
	{
		if(lf->l_snid != (int) ad->sn_id)
			goto nxt_mt ;
	}

	if( xgldb[lf->l_gref].g_earuse == xgldb[lf->l_gref].g_earmax )	/* BUSY! */
	{
	    priv_busy = (lf->l_total >= ROOT_LISTEN);   /* Priv listen busy */
	    l_busy = 1;					/* Mark busy        */
	    goto nxt_mt;				/* Try another      */
	}


	/* Examine cudf first */

	if( lf->l_cumode != X25_DONTCARE )		/* Else continue */
	{
		if( (lf->l_cumode == X25_IDENTITY   && lf->l_culength==cudl) ||
		    (lf->l_cumode == X25_STARTSWITH && (int)lf->l_culength<=cudl))
		{
		    /* It is worth doing the comparison */

		    if( lf->l_culength != 0 )
		    {
		        if( x25cmp((bufp_t)&cudcmp[0],
				 &lf->l_cubytes[0],
				 lf->l_culength)!=0 )
			    goto nxt_mt;		/* Match failed */
		    }				/* 0 always matches */
		}
		else
		    goto nxt_mt;
	}

	/* The CUDF 'match' succeeded now try the address. */

	if( lf->l_type == X25_NSAP )
	{
		addlen = ad->nsap_len;
		addptr = (&ad->NSAP[0]);
	}
	else
	{
		addlen = ad->DTE_MAC.lsap_len;
		addptr = (&ad->DTE_MAC.lsap_add[0]);
	}

	if( lf->l_mode != X25_DONTCARE )		/* Else matched */
	{
		if( ((lf->l_type == X25_DTE) ||
		     (lf->l_type == X25_NSAP && NSAPflag != 0 ))  &&
		    ((lf->l_mode == X25_IDENTITY   && lf->l_length==addlen) ||
		     (lf->l_mode == X25_STARTSWITH && (int)lf->l_length<=addlen)) )
		{
		    /* It is worth doing the comparison */

		    if( lf->l_length != 0 )
		    {
			/* Compare semi-octet string */
		        if( semicmp(addptr,
				    &lf->l_add[0],
				    lf->l_length)!=0 )
			    goto nxt_mt;		/* Match failed */
		    }				/* 0 always matches */
		}
		else
		    goto nxt_mt;
	}

        /* We have a match.  Return 0 and set the identifier */

	*id = lf->l_gref;
	*qp = cqp;
	return(0);				/* OK */

nxt_mt:
	mt = mt->b_next;
    }

    /*
	Dropping out of the for loop, no match.  Using l_seen and l_busy,
	cook up an appropriate diagnostic.
    */
    
    if( l_seen == 0 || l_busy == 0 )
    {
	/*
	    Listeners - none busy and no match so connection
	    rejection, permananent
	*/
	return(0xF5);
    }
    else
	return(0xF4);    /* Connection rejection, transient */

}


/******************************************************************/
void CI_touser(xvc,calldata)
/******************************************************************/

/*
    Obtain reference to user waiting on this NSAP and send indication.
*/

register struct  x25vcformat FAR *xvc;
mblk_t *calldata;

{
    unsigned char diag;
    mblk_t *header, *hcopy, *ccud;
    struct xgldbf *xug;
    struct xcallf FAR *call;
    struct i_xcallf FAR *icall;
    int bar_non_priv;;

    ccud = NULL;

    /*
	Check whether non privileged user listens are allowed on this
	subnetwork
    */
    bar_non_priv = xvc->xbase->psdn_local.SNMODES & BAR_NONPRIV_LISTEN;

    /*
	Remove zero length call user data buffer
    */
    if( calldata != NULL && mb_bulen(calldata) == 0 )
    {
	m_freem(calldata);
	calldata = NULL;
    }

    /*
	Search for a listener on this address, perhaps with cudf match
    */
    if( (diag=(unsigned char )user_on_NSAP(&xvc->called,
					    calldata,
					    (xvc->xdomain != WAN80 ? 1 : 0),
					    &xvc->userref,
					    &xvc->qposition,
					    (ushort) bar_non_priv))==0 ) /* Yes */
    {
	/*
	    Yes, there is a listener, acquire the resource to set up CI
	    Note the use of the internal CI structure
	*/
	if( (header = allocb(sizeof(struct i_xcallf),BPRI_MED)) != NULL )
	{
	    header->b_datap->db_type = M_PROTO;
	    header->b_wptr = header->b_rptr + sizeof( struct xcallf );
	    icall = (struct i_xcallf FAR *) header->b_rptr;
	    call = &(icall->xcall);
	    call->xl_type    = XL_CTL;
	    call->xl_command = N_CI;
	    call->negotiate_qos = 1;
	    call->CONS_call = 0;
	    call->conn_id = xvc->xconnid;

	    /*
		Store the network reference in the internal CI structure
	    */
	    icall->holdns = xvc->nsref;

	    (void) N_CIdetails(xvc->nsref,
		    	        (unsigned char FAR *) &call->calledaddr,
						(unsigned char FAR *)&call->callingaddr,
						&call->qos);

	    /*
		Now copy (NOT dup) this block, and call user data. Append call
		data and send up
	    */
	    if( (hcopy = allocb(sizeof(struct i_xcallf),BPRI_MED)) != NULL &&
	       ((calldata == NULL) || (ccud  = copymsg(calldata)) != NULL))
	    {
		hcopy->b_datap->db_type = M_PROTO;
		hcopy->b_wptr = hcopy->b_rptr + sizeof( struct i_xcallf );
	    
		bcopy((bufp_t)header->b_rptr,
		      (bufp_t)hcopy->b_rptr, sizeof (struct i_xcallf) );
		  
		hcopy->b_cont  = ccud;
		header->b_cont = calldata;
	    
		xug = (&xgldb[xvc->userref]);
		ON(&xug->gdbq,hcopy);
		xug->g_earuse++;
#if ENDIAN_SWAP
		x25apiswap(header, UPWARD) ;
#endif
		putnext(xug->rq,header);
	    }
	    else
	    {
		diag = 163;
		m_freem(hcopy);		/* May be NULL if failed */
		m_freem(header);	/* calldata freed below  */
	    }
	}
	else
	    diag = 163;				/* Resource low */
    
	/*
	    And wait on the result of this call.
	*/
    }

    if( diag != 0 )
    {
	/*
	    Cannot access called NSAP.  Either the user_on_NSAP routine
	    decided or buffer shortage exists.  Disconnect the call
	*/
	xvc->nsucause=0;
	xvc->nsudiag =diag;
	xvc->xusrevent |= xDRout;
	xvc->NSUattached=0;
	m_freem(calldata);
	
	if( diag != 163 )				/* Resource low */
	    L3monitor(rjc_nouser, xvc->xu_ident);
    }
}

/* ----------------------------------------------------------------------

   ---------------------------------------------------------------------- */

/******************************************************************/
void CC_touser(xvc, buff)
/******************************************************************/

/*
    Connect confirmation for user.  The buffer passed is the cud.
    The message buffer, held in 'calldata' should NEVER be NULL since
    the code assigned this when the call is sent.
*/

register struct x25vcformat FAR *xvc;
mblk_t *buff;

{
    mblk_t *msgbuff;
    struct xccnff FAR *caa;

    if( xvc->NSUattached )				/* No user abort etc. */
    {
	if( mb_bulen(buff)==0 )
	{
	    m_freem(buff);
	    buff=NULL;
	}

	if (SNMP_trace_q)
		SNMP_callparm_event(xvc, buff) ;

	msgbuff = xvc->calldata;
	xvc->calldata = NULL;

	/*
	    Check out the message
	*/
	if( msgbuff  == NULL ||
	   (msgbuff->b_datap->db_lim - msgbuff->b_rptr) < sizeof(struct xccnff) )
	{
	    strlog(X25_STID,0,0,SL_ERROR,"X25: caa message buffer fails");
	    xint_err(xvc,0);
	    m_freem(buff);
	    m_freem(msgbuff);
	    return;
	}

	msgbuff->b_cont = buff;
	msgbuff->b_datap->db_type = M_PROTO;
	msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct xccnff);
	caa = (struct xccnff FAR *) msgbuff->b_rptr;
	caa->xl_type    = XL_CTL;
	caa->xl_command = N_CC;
	caa->conn_id    = xvc->xconnid;
	caa->negotiate_qos = 1;
	(void) N_CCdetails(xvc->nsref,(unsigned char FAR *) &caa->responder,&caa->rqos);
#if ENDIAN_SWAP
	x25apiswap(msgbuff, UPWARD) ;
#endif
	putnext( xgldb[xvc->userref].rq,msgbuff );
	
	/* IND_toCNU(N_CC,xvc->nsref,xvc->userref,msgbuff); */
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
	m_freem(xvc->calldata);		/* Free the held message buffer */
	xvc->calldata = NULL;
	m_freem(buff);			/* Free the data */
    }
}


/******************************************************************/
int DT_touser(xvc,buff,len)
/******************************************************************/

/*
    Send in data. May return acks if sent up.
*/

register struct x25vcformat FAR *xvc;
mblk_t *buff;
int len ;
{
    struct xgldbf *xug;

    if( xvc->NSUattached )
    {
	/*
	    Buffer already has More & Q-bit flags set - fill in the others
	*/
	buff->b_datap->db_type = M_PROTO;
	buff->b_wptr = buff->b_rptr + sizeof( struct xdataf );
	((struct xdataf FAR *)buff->b_rptr)->xl_type    = XL_DAT;
	((struct xdataf FAR *)buff->b_rptr)->xl_command = N_Data;
	xug = (&xgldb[xvc->userref]);

	if( xug->rq->q_first == NULL && canput(xug->rq->q_next) )
	{
#if ENDIAN_SWAP
	    x25apiswap(buff, UPWARD) ;
#endif
	    putnext(xug->rq,buff);
	    return(1);			/* Acknowledge */
	}
	else
	{
	    putq(xug->rq,buff);	/* For later */
	}
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
	m_freem(buff);                   /* Free the data */
    }
    return(0);
}


/******************************************************************/
void ED_touser(xvc,buff)
/******************************************************************/

/*
    Send in expedited data.
*/

register struct x25vcformat FAR *xvc;
mblk_t *buff;

{
    struct xgldbf *xug;

    if( xvc->NSUattached )
    {
	buff->b_datap->db_type = M_PROTO;
	buff->b_wptr = buff->b_rptr + sizeof( struct xedataf );
	((struct xedataf FAR *)buff->b_rptr)->xl_type    = XL_DAT;
	((struct xedataf FAR *)buff->b_rptr)->xl_command = N_EData;
	xug = (&xgldb[xvc->userref]);

	if( xug->rq->q_first == NULL && canput(xug->rq->q_next) )
	{
#if ENDIAN_SWAP
	    x25apiswap(buff, UPWARD) ;
#endif
	    putnext(xug->rq,buff);
	}
	else
	{
	    /*
		If the head pointer is NULL then this is just putq
	    */
	    insq(xug->rq,xug->rq->q_first,buff);	/* For later */
	}
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
	m_freem(buff);                   /* Free the data */
    }
}


/******************************************************************/
void FL_touser(xvc)
/******************************************************************/

/*
    Sends user a flow indication - qenable wq.
*/

register struct x25vcformat FAR *xvc;

{
    if( xvc->NSUattached )
    {
	qenable( xgldb[xvc->userref].wq );
    }
    else
    {
	xuser_gone(xvc,(unsigned char) 0);
    }
}


/******************************************************************/
void EDA_touser(xvc,msgbuff)
/******************************************************************/

/*
    Checks user present and passes EDflow.  NB code assumes buffer passed
    i.e. always successful.
*/

register struct x25vcformat FAR *xvc;
mblk_t *msgbuff;

{
    struct xgldbf *xug;

    if( xvc->NSUattached )
    {
	if( msgbuff != NULL &&
	    (msgbuff->b_datap->db_lim - msgbuff->b_rptr) >= sizeof(struct xedatacf) )
	{
	    msgbuff->b_datap->db_type = M_PROTO;
	    msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct xedatacf);
	    ((struct xedatacf FAR *) msgbuff->b_rptr)->xl_type    = XL_DAT;
	    ((struct xedatacf FAR *) msgbuff->b_rptr)->xl_command = N_EAck;
	    xug = (&xgldb[xvc->userref]);
	    if( xug->rq->q_first == NULL && canput(xug->rq->q_next) )
	    {
#if ENDIAN_SWAP
		x25apiswap(msgbuff, UPWARD) ;
#endif
		putnext(xug->rq,msgbuff);
	    }
	    else
	    {
		/*
		    If the head pointer is NULL then this is just putq
		*/
		insq(xug->rq,xug->rq->q_first,msgbuff);	/* For later */
	    }
	}
	else
	{
	    strlog(X25_STID,0,0,SL_ERROR,"X25: ea buffer short");
	    xint_err(xvc,0);
	    m_freem(msgbuff);
	}
    }
    else
    {
	m_freem(msgbuff);
	xuser_gone(xvc,(unsigned char) 0);
    }
}


/******************************************************************/
void DA_touser(xvc)
/******************************************************************/

/*
    Checks user present and passes Data Ack.  No user info.
*/

register struct x25vcformat FAR *xvc;

{
    struct xgldbf *xug;
    mblk_t	      *msgbuff;

    if( xvc->NSUattached )
    {
        if( (msgbuff = allocb(sizeof(struct xdatacf),BPRI_MED)) != NULL )
        {
	    msgbuff->b_datap->db_type = M_PROTO;
	    msgbuff->b_wptr = msgbuff->b_rptr + sizeof(struct xdatacf);

	    ((struct xdatacf FAR *) msgbuff->b_rptr)->xl_type    = XL_DAT;
	    ((struct xdatacf FAR *) msgbuff->b_rptr)->xl_command = N_DAck;
	    xug = (&xgldb[xvc->userref]);

	    if( xug->rq->q_first == NULL && canput(xug->rq->q_next) ) {
#if ENDIAN_SWAP
		x25apiswap(msgbuff, UPWARD) ;
#endif
	        putnext(xug->rq,msgbuff);
	    }
	    else
	    {
	        /*
		    If the head pointer is NULL then this is just putq
		*/
	        insq(xug->rq,xug->rq->q_first,msgbuff);	/* For later */
	    }
        }
        else
        {
	    strlog(X25_STID,0,0,SL_ERROR,"X25: cannot allocate data ack buffer");
	    xint_err(xvc,0);
        }
    }
    else
    {
        xuser_gone(xvc,(unsigned char) 0);
    }
}

/*
 * Sends an SNMP Cleared Circuit event on the SNMP trace queue
 */
int SNMP_trace_clear_event(xvc)
struct x25vcformat *xvc ;
{
	mblk_t *mp ;
	SNMP_ClearedCircuit_Event_t *event ;

	if (!(mp = allocb(sizeof(SNMP_ClearedCircuit_Event_t), BPRI_HI)))
		return 0 ;

	mp->b_datap->db_type = M_PROTO ;

	event = (SNMP_ClearedCircuit_Event_t *) mp->b_rptr ;
	mp->b_wptr += sizeof(SNMP_ClearedCircuit_Event_t) ;

	event->Event = SNMP_EVENT_CLEARED_CIRCUIT ;
	event->X25ClearedCircuitIndex = xvc->xu_ident ;
	event->X25ClearedCircuitPleIndex = 0 ;
	event->X25ClearedCircuitTimeEstablished = xvc->est_time ;
	event->X25ClearedCircuitTimeCleared = curtime() ;
	event->X25ClearedCircuitChannel = xvc->lci ;
	event->X25ClearedCircuitClearingCause = xvc->discause ;
	event->X25ClearedCircuitDiagnosticCode = xvc->disdiag ;
	event->X25ClearedCircuitInPDUs = xvc->in_pdu ;
	event->X25ClearedCircuitOutPDUs = xvc->out_pdu ;
	bcd_to_str(xvc->called.DTE_MAC.lsap_add, xvc->called.DTE_MAC.lsap_len,
		event->X25ClearedCircuitCalledAddress) ;
	bcd_to_str(xvc->caller.DTE_MAC.lsap_add, xvc->caller.DTE_MAC.lsap_len,
		event->X25ClearedCircuitCallingAddress) ;
	event->X25ClearedCircuitClearFacilities[0] = '\0' ;

	get_xvc_circuit_info(xvc, &event->X25ClearedCircuitFinalStats) ;

#if ENDIAN_SWAP
	SWAP_RANGE(event, &event->X25ClearedCircuitOutPDUs) ;
	SWAP_RANGE(&event->X25ClearedCircuitFinalStats, &event->X25ClearedCircuitFinalStats.X25CircuitCallParamId) ;
#endif
	putnext(SNMP_trace_q, mp) ;

	return 0 ;
}

/*
 * Sends an SNMP CallParm event on the SNMP trace queue
 */
int SNMP_callparm_event(xvc, cud)
struct x25vcformat *xvc ;
mblk_t *cud ;
{
	SNMP_CallParm_Event_t *event ;
	mblk_t *mp ;
	unsigned long i ;
	struct wlcfg *cfg = (struct wlcfg *) 0 ;

	if (!(mp = allocb(sizeof(SNMP_CallParm_Event_t), BPRI_HI)))
		return 0 ;

	mp->b_datap->db_type = M_PROTO ;

	event = (SNMP_CallParm_Event_t *) mp->b_rptr ;
	mp->b_wptr += sizeof(SNMP_CallParm_Event_t) ;
	bzero(event, sizeof(SNMP_CallParm_Event_t)) ;

	if ((i = map_snident(xvc->xu_ident)) != (unsigned long)-1) {
		cfg = &(xcfg[i].net_cfg) ;
	}

	event->Event = SNMP_EVENT_CALL_PARM ;
	event->X25CallParmIndex = xvc->xu_ident ;
	event->X25CallParmChannel = xvc->lci ;
	event->X25CallParmInPacketSize = xvc->lpkt ;
	event->X25CallParmOutPacketSize = xvc->rpkt ;
	event->X25CallParmInWindowSize = xvc->lwsize ;
	event->X25CallParmOutWindowSize = xvc->rwsize ;

	/* Reverse Charging? */
	if (cfg && !(cfg->SUB_MODES & SUB_REVCHARGE))
		event->X25CallParmAcceptReverseCharging = 4 ;
	else
		event->X25CallParmAcceptReverseCharging = 1 ;

	event->X25CallParmProposeReverseCharging = 1 ;

	/* Fast Selct? */
	if (!cfg)
		event->X25CallParmFastSelect = 2 ;
	else if (cfg->SUB_MODES & SUB_FSELECT)
		if (cfg->SUB_MODES & SUB_FSRRESP)
			event->X25CallParmFastSelect = 4 ;
		else
			event->X25CallParmFastSelect = 3 ;
	else
		if (cfg->SUB_MODES & SUB_FSRRESP)
			event->X25CallParmFastSelect = 5 ;
		else
			event->X25CallParmFastSelect = 6 ;

	event->X25CallParmInThruputClassSize = xvc->lthru ;
	event->X25CallParmOutThruputClassSize = xvc->rthru ;
	bcopy(xvc->cug_index, event->X25CallParmCUG, 8) ;
	bzero(event->X25CallParmCUGOA, 8) ;
	bzero(event->X25CallParmBCUG, 4) ;
	bcopy(xvc->nui_field, event->X25CallParmNUI, xvc->nui_len) ;
	event->X25CallParmChargingInfo = 1 ;
	bcopy(xvc->rpoa_field, event->X25CallParmRPOA, xvc->rpoa_len) ;
	event->X25CallParmTrnstDly = xvc->ltrans ;
	bzero(event->X25CallParmCallingExt, 44) ;
	bzero(event->X25CallParmCalledExt, 44) ;
	event->X25CallParmInMinThruputCls = xvc->lminthru ;
	event->X25CallParmOutMinThruputCls = xvc->rminthru ;
	bzero(event->X25CallParmEndTrnsDly, 8) ;
	bcopy(xvc->prty, event->X25CallParmPriority, xvc->prty_len) ;
	bcopy(xvc->protection, event->X25CallParmProtection, xvc->prot_len) ;
	event->X25CallParmExptData = (xvc->CCITTflag & EXPINUSE) ? 3 : 2 ;
	if (cud) bcopy(cud->b_rptr, event->X25CallParmUserData, cud->b_wptr - cud->b_rptr) ;
	if ( xvc->x_fac_len ) {
		bcopy( (bufp_t)(xvc->fac_field + xvc->cd_fac_len),
		(bufp_t)event->X25CallParmCallingNetworkFacilities,
		xvc->cg_fac_len );

		bcopy( (bufp_t)xvc->fac_field,
		(bufp_t)event->X25CallParmCalledNetworkFacilities,
		xvc->cd_fac_len );
	} else {
		bcopy( (bufp_t)(xvc->fac_field),
		(bufp_t)event->X25CallParmCallingNetworkFacilities,
		xvc->cg_fac_len );

		bcopy( (bufp_t)(xvc->fac_field + xvc->cg_fac_len),
		(bufp_t)event->X25CallParmCalledNetworkFacilities,
		xvc->cd_fac_len );
	}

#if ENDIAN_SWAP
	SWAP_RANGE(event, &event->X25CallParmOutThruputClassSize) ;
	SWAP32(event->X25CallParmChargingInfo) ;
	SWAP32(event->X25CallParmTrnstDly) ;
	SWAP32(event->X25CallParmInMinThruputCls) ;
	SWAP32(event->X25CallParmOutMinThruputCls) ;
	SWAP32(event->X25CallParmExptData) ;
#endif
	putnext(SNMP_trace_q, mp) ;

	return 0 ;
}

/*
 * Sends an SNMP Restart event on the SNMP trace queue
 */
int SNMP_restart_event(snid)
unsigned long snid ;
{
	SNMP_Restart_Event_t *event ;
	mblk_t *mp ;

	if (!(mp = allocb(sizeof(SNMP_Restart_Event_t), BPRI_HI)))
		return 0 ;

	mp->b_datap->db_type = M_PROTO ;

	event = (SNMP_Restart_Event_t *) mp->b_rptr ;
	mp->b_wptr += sizeof(SNMP_Restart_Event_t) ;

	event->Event = SNMP_EVENT_RESTART ;
	event->X25OperIndex = snid ;

#if ENDIAN_SWAP
	SWAP_THIS(event, sizeof(SNMP_Restart_Event_t)) ;
#endif
	putnext(SNMP_trace_q, mp) ;

	return 0 ;
}

/*
 * Sends an SNMP Reset event on the SNMP trace queue
 */
int SNMP_reset_event(xvc)
struct x25vcformat *xvc ;
{
	SNMP_Reset_Event_t *event ;
	mblk_t *mp ;

	if (!(mp = allocb(sizeof(SNMP_Reset_Event_t), BPRI_HI)))
		return 0 ;

	mp->b_datap->db_type = M_PROTO ;

	event = (SNMP_Reset_Event_t *) mp->b_rptr ;
	mp->b_wptr += sizeof(SNMP_Reset_Event_t) ;

	event->Event = SNMP_EVENT_RESET ;
	event->X25CircuitIndex = xvc->xu_ident ;
	event->X25CircuitChannel = xvc->lci ;

#if ENDIAN_SWAP
	SWAP_THIS(event, sizeof(SNMP_Reset_Event_t)) ;
#endif
	putnext(SNMP_trace_q, mp) ;

	return 0 ;
}

void get_xvc_circuit_info(xvc, circ)
struct x25vcformat *xvc ;
rfc_1382_circuit_t *circ ;
{
	switch (xvc->xstate) {
	case P1:
		circ->X25CircuitStatus = 2 ;
		break ;
	case P2:
	case P3:
	case P5:
		circ->X25CircuitStatus = 3 ;
		break ;
	case DataTransfer:
	case DXEbusy:
		circ->X25CircuitStatus = 4 ;
		break ;
	case P6:
	case P6pending:
	case WUcpending:
	case WUNcpending:
	case DXEcpending:
	case DXEcfpending:
	case DXEclearing:
		circ->X25CircuitStatus = 5 ;
		break ;
	default:
		circ->X25CircuitStatus = 10 ;
		break ;
	}

	if (xvc->ampvc)
		circ->X25CircuitStatus = 6 ;

	circ->X25CircuitEstablishTime = xvc->est_time ;
	circ->X25CircuitDirection = xvc->direction ;
	circ->X25CircuitInOctets = xvc->in_octets ;
	circ->X25CircuitInPDUs = xvc->in_pdu ;
	circ->X25CircuitRemotelyInitiatedResets = xvc->remote_resets ;
	circ->X25CircuitProviderInitiatedResets = xvc->provider_resets ;
	circ->X25CircuitInInterrupts = xvc->rx_int_pkts ;
	circ->X25CircuitOutOctets = xvc->out_octets ;
	circ->X25CircuitOutPDUs = xvc->out_pdu ;
	circ->X25CircuitOutInterrupts = xvc->tx_int_pkts ;
	circ->X25CircuitDataRxmtTimeouts = xvc->T25_expire ;
	circ->X25CircuitResetTimeouts = xvc->T22_expire ;
	circ->X25CircuitInterruptTimeouts = xvc->T26_expire ;
	bcd_to_str(xvc->called.DTE_MAC.lsap_add, xvc->called.DTE_MAC.lsap_len, circ->X25CircuitCalledDteAddress) ;
	bcd_to_str(xvc->caller.DTE_MAC.lsap_add, xvc->caller.DTE_MAC.lsap_len, circ->X25CircuitCallingDteAddress) ;
	bcd_to_str(xvc->rcalled.DTE_MAC.lsap_add, xvc->rcalled.DTE_MAC.lsap_len, circ->X25CircuitOriginallyCalledAddress) ;
}

