#ident "$Id: x25aux.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
 *
 *  X25AUX.C
 *
 *
 *******************************************************************/

/*
 *	x25aux.c
 *	 /projects/common/PBRAIN/SCCS/pbrainF/dev/sys/x25/0/s.x25aux.c
 *	@(#)x25aux.c	1.53
 *
 *	Last delta created	19:12:45 2/24/92
 *	This file extracted	18:32:50 11/19/92
 *
 */

/* ---- Auxiliary routines for the X25 code ---- */
#include "x25_inc.h"

#include "iso.h"
#include "x25pti.h"
#include "x25cause.h"
#include "x25matrix.h"
#include "x25clock.h"
#include "x25form.mac"

extern struct xsndbf xsndb[];
extern int dbuf_low ;

mblk_t *listen_requestQ;	/* listen request queue */

#ifndef  NULL
#define NULL 0
#endif

/* NUI facility table definitions */
#define	NUIHASH_FREE	-1	/* Once used but now deleted	*/
#define NUIHASH_UNUSED	-2	/* Virgin entry			*/

#define	NUIFREE_END	-1
#define FAC_STR		 8	/* length of fac_to_hash string */

/*
 * External declarations from Space.c
 */
extern NUI_ADDR		nui_factab[];	
extern int		nui_nuihash[];
extern int		nui_fachash[];

extern int		max_nuiaddrs;	/* Size of address table	*/
extern int		max_nuihash;	/* Size of hash tables		*/

/*
 * Internal declarations
 */

static int	addrtab_free = 0;	/* Start of free addr entry list*/
static int	nui_adcount = 0;	/* Count of entries in table	*/

/*
 * Required Functional Prototypes
 */
static int	nuihash();

/*
    The "NUI to associated faclities" translation table is realised as
    three arrays.
    The nui_factab array contains NUI_addr structs as entries, these hold
    pairs of Network User Identifiers and Associated Facilities. Each of
    these entries has a pointer to the next free entry in the table. These
    are linked into a list pointed to by addrtab_free, which initially holds
    all entries chained linearly.

    The nui_nuihash & nui_fachash arrays are used to hash NUI and facility
    addresses into this. These contain pointers into the nui_factab,
    and have more entries so the hash table does not run out of slots.
*/

/******************************************************************/
void nuifac_init()
/******************************************************************/

/*
    Initialises entries in address table, and its two hash tables.
*/

{
    int i;

    for(i = 0; i < max_nuihash; i++)
	nui_nuihash[i] = nui_fachash[i] = NUIHASH_UNUSED;

    for(i = 0; i < max_nuiaddrs - 1; i++)
    {
	nui_factab[i].next_free = i + 1;
	bcopy("\0", nui_factab[i].nuid.nui_string, 1);
	nui_factab[i].nuid.nui_len = 0;
    }				/* Put all entries on free list	   */

    bcopy("\0", nui_factab[i].nuid.nui_string, 1);
    nui_factab[i].nuid.nui_len = 0;
    nui_factab[i].next_free = NUIFREE_END;	/* End of free list marker */
    
    addrtab_free = 0;		/* Free list points to first slot  */
    nui_adcount = 0;		/* Table initially empty   */
}

/******************************************************************/
int nuifac_put(ad_entry, status)
/******************************************************************/

/*
    Adds entry to address table, with the given values. Creates hash
    table references using nui_ent and fac_ent. Returns FALSE if no
    room left, or entry already present.
*/

struct nui_put	*ad_entry;
unsigned char	status;

{	
    int free;
    int nui_ent, fac_ent;
    int loop_count;

    if (((free = addrtab_free) == NUIFREE_END) ||    /* End of free list */
	 (ad_entry->nuid.nui_string==NULL))          /* Bad entry        */
	return(FALSE);

    nui_ent = nuihash(ad_entry->nuid.nui_string, ad_entry->nuid.nui_len);


    /*
	Find free slot in NUI hash table
	error if this address already present
    */
    loop_count=0;
    while(nui_nuihash[nui_ent] > NUIHASH_FREE)
    {
	if (x25cmp(nui_factab[nui_nuihash[nui_ent]].nuid.nui_string,
		   ad_entry->nuid.nui_string, ad_entry->nuid.nui_len) == 0)
	    return(FALSE);
	    
	/*
	    prevent infinite loop if initialisation omitted
	*/
	if (loop_count>max_nuihash)
	    return(FALSE);
	    
	nui_ent = (nui_ent + 1) % max_nuihash;
	loop_count++;
    }

    fac_ent=0; 			/* Take 1st free slot */

    /* Find free slot in facility hash table	*/
    while(nui_fachash[fac_ent] > NUIHASH_FREE)
    {
	fac_ent = (fac_ent + 1) % max_nuihash;
    }

    /*
	Point the hash table entries to the next free addr table
	slot, and copy entry into it.
	Take slot off end of free list.
    */
    nui_fachash[fac_ent] = nui_nuihash[nui_ent] = addrtab_free;
    addrtab_free = nui_factab[free].next_free;

    bcopy(&ad_entry->nuid, &nui_factab[free].nuid,
	  sizeof(struct nuiformat));
    bcopy(&ad_entry->nuifacility, &nui_factab[free].nuifacility,
	  sizeof(struct facformat));

    nui_factab[free].status = status;
    nui_adcount++;

    return(TRUE);
}


/******************************************************************/
NUI_ADDR *nuifac_get(nui_ad, nui_length)
/******************************************************************/

/*
    Is passed NUI as a string, and attempts to find matching
    entry in address table.
    Returns pointer to it if found, else NULL.
*/

char	*nui_ad;
unsigned char nui_length;

{
    int nui_index;

    if (nui_ad==NULL)		/* Bad handle	*/
	return((NUI_ADDR *) NULL);

    nui_index = nuihash(nui_ad, nui_length);

    for(;;) 
    {
	if (nui_nuihash[nui_index] == NUIHASH_UNUSED) /* End of bucket */
	    return((NUI_ADDR *) NULL);	  /* => No matches */

	if (x25cmp(nui_factab[nui_nuihash[nui_index]].nuid.nui_string,
	           nui_ad, nui_length) == 0)
	    break;				/* Found */

	nui_index = (nui_index + 1) % max_nuihash;
    }
    return(&nui_factab[nui_nuihash[nui_index]]);
}


/******************************************************************/
int nuifac_del(nui_ad, nui_length)
/******************************************************************/

/*
    Deletes the address table entry with the given NUI
    Returns FALSE if can't find such an entry
*/

char	*nui_ad;
unsigned char nui_length;

{
    int nui_index = nuihash(nui_ad, nui_length);
    int fac_index, nf_index;

    if (nui_ad==NULL)		/* Bad handle	*/
	return(FALSE);

    /* First find ref to entry in NUI hash table	*/
    for(;;)
    {
	if (nui_nuihash[nui_index] == NUIHASH_UNUSED)
	    return(FALSE);		/* No match found */

	if (x25cmp(nui_factab[nui_nuihash[nui_index]].
	           nuid.nui_string, nui_ad, nui_length) == 0)
	    break;

	nui_index = (nui_index + 1) % max_nuihash;
    }

    nf_index  = nui_nuihash[nui_index];	/* Location in addr table */

    fac_index=0; 				/* Take 1st free slot */

    /* Then find ref in DTE hash table	*/
    for(;;) 
    {
	if (nui_fachash[fac_index] == NUIHASH_UNUSED)
	    return(FALSE);		/* No DTE entry	*/

	if (nui_fachash[fac_index] == nf_index)
	    break;

	fac_index = (fac_index + 1) % max_nuihash;
    }

    nui_factab[nf_index].nuid.nui_len = 0;
    bcopy("\0", nui_factab[nf_index].nuid.nui_string, 1); /* Mark as unused & */

    nui_factab[nf_index].next_free = addrtab_free;	/* Put on free list */
    addrtab_free = nf_index;

    nui_nuihash[nui_index] = nui_fachash[fac_index] = NUIHASH_FREE;
    nui_adcount--;				/* Free hash table entries  */

    return(TRUE);
}


/******************************************************************/
int nuifac_dump(q, mp)
/******************************************************************/

/*
    The data buffer is passed down in the ioctl to hold MGET_NMAXENT
    entries in the ioctl. Copies "num_ent" non-null entries starting
    at field "start" in the ioctl table (in arbritrary sequence) into
    the buffer. Then converts the message which contained the dump
    request into an IOCACK and sends it as a reply on the queue specified.
*/

Q_PTR q;
mblk_t *mp;

{
    struct nui_mget *mptr;
    struct nui_addr	*ansptr;
    unsigned int	i;
    unsigned int	ent_sofar = 0;

    mptr = (struct nui_mget *) mp->b_cont->b_rptr;
    ansptr = (struct nui_addr *) mptr->buf;

#if ENDIAN_SWAP
    SWAP_RANGE(mptr, mptr->num_ent) ;
#endif
    /*
	Loop from specified first entry until
	either table exhausted or
	we have the required number of entries
    */
    for (i = mptr->first_ent; (i < (unsigned int)max_nuiaddrs) &&
				(ent_sofar < mptr->num_ent) ; i++)
    {
	/*
	   Is this entry NULL ?
	*/
	if (x25cmp(nui_factab[i].nuid.nui_string, "", 1) !=0)
	{
	    if ((char *)ansptr + sizeof(struct nui_addr) >
				(char *) mp->b_cont->b_wptr)
		break;

	    *ansptr = nui_factab[i];
#if ENDIAN_SWAP
	    SWAP_RANGE(ansptr, &ansptr->nuid.nui_len) ;
	    SWAP32(ansptr->status) ;
#endif
	    ansptr++;
	    ent_sofar++;
	}
    }

    /* Set number of entries returned */
    mptr->num_ent = ent_sofar;
    mptr->last_ent = i;

#if ENDIAN_SWAP
    SWAP_RANGE(mptr, mptr->num_ent) ;
#endif
    mp->b_datap->db_type = M_IOCACK;

    qreply(q, mp);

    return(TRUE);
}


/******************************************************************/
static int nuihash(addr, str_length)
/******************************************************************/

/*
    Takes a string (which is an NUI) and
    returns a value used to index into hash table.
*/

char	*addr;
unsigned char str_length;

{
    int index, test_len, i;
    int nuiaddr_value;
    int maxstrlen = FAC_STR;

    nuiaddr_value = 0;

    /*
	We want a fairly unique (but reproducible)
	number for any string.
    */
    if ((int)str_length < maxstrlen)
	test_len = str_length;
    else
	test_len=maxstrlen;
	
    for (i = 0; i < test_len; i++, addr++)
    {
	nuiaddr_value += *addr;
    }

    index = nuiaddr_value % max_nuihash;	/* max_nuihash is prime and greater */

    if (index < 0)			/* than max_nuiaddrs		   */
	index += max_nuihash;
	
    return(index);
}


/******************************************************************/
void fetch_NUI_fac(xvc)
/******************************************************************/

/*
    Fetch associated override facilities for provided Network
    User Identifier, copy into VC structure + set NUI_override
    if valid.
*/

register struct x25vcformat FAR *xvc;

{
    int nuifaclen, facpos ;
    register struct wlcfg *xx;
    NUI_ADDR        *thisnui;
    unsigned char   nuifac[NUIFACMAXSIZE];
    unsigned char 	temp;
    unsigned short	temp2;


    /*
	By here, can assume NUI override subscribed and an NUI provided.
    */
    xx=xvc->xbase;

    /*
	But does the provided NUI exists in the
	internal NUI-facility association table?
    */
    if ( ( thisnui=(NUI_ADDR *)nuifac_get(xvc->nui_field,xvc->nui_len) ) ==
		                                      (NUI_ADDR *)NULL )
    {
	/* NUI does not exist in mapping file. */
	xvc->NUI_override=0;
    }
    else	/* NUI exists */
    {
	xvc->NUI_override=1;

	/* copy default (configured) values */
	xvc->nuibase.LOCDEFPKTSIZE=xx->LOCDEFPKTSIZE; 
	xvc->nuibase.REMDEFPKTSIZE=xx->REMDEFPKTSIZE; 
	xvc->nuibase.LOCDEFWSIZE=xx->LOCDEFWSIZE; 
	xvc->nuibase.REMDEFWSIZE=xx->REMDEFWSIZE; 
	xvc->nuibase.locdefthclass=xx->locdefthclass; 
	xvc->nuibase.remdefthclass=xx->remdefthclass; 
	xvc->nuibase.SUB_MODES=xx->SUB_MODES; 

	/* Only overwrite if NUI override value is in correct range */
	temp = (unsigned char) thisnui->nuifacility.LOCDEFPKTSIZE;
	if ( (temp >= MIN_PKT) && (temp <= xx->LOCMAXPKTSIZE) )
		xvc->nuibase.LOCDEFPKTSIZE = temp;

	temp = (unsigned char) thisnui->nuifacility.REMDEFPKTSIZE;
	if ( (temp >= MIN_PKT) && (temp <= xx->REMMAXPKTSIZE) )
	    xvc->nuibase.REMDEFPKTSIZE = temp;

	temp = (unsigned char) thisnui->nuifacility.LOCDEFWSIZE;
	if ( (temp >= MIN_WND) && (temp <= xx->LOCMAXWSIZE) )
	    xvc->nuibase.LOCDEFWSIZE = temp;

	temp = (unsigned char) thisnui->nuifacility.REMDEFWSIZE;
	if ( (temp >= MIN_WND) && (temp <= xx->REMMAXWSIZE) )
	    xvc->nuibase.REMDEFWSIZE = temp;

	temp = (unsigned char) thisnui->nuifacility.locdefthclass;
	if ( (temp >= xx->locminthclass) && (temp <= xx->locmaxthclass) )
	    xvc->nuibase.locdefthclass = temp;

	temp = (unsigned char) thisnui->nuifacility.remdefthclass;
	if ( (temp >= xx->remminthclass) && (temp <= xx->remmaxthclass) )
	    xvc->nuibase.remdefthclass = temp;

	temp2 = (ushort) thisnui->nuifacility.SUB_MODES;
	if (temp2 != 0)
	    xvc->nuibase.SUB_MODES = temp2;

	temp = (unsigned char) thisnui->nuifacility.CUG_CONTROL;
	if (temp != 0)
	{
	    /*
		retain any settings that NUI cannot override
	    */
	    xvc->nuibase.CUG_CONTROL=0;
	    if (xx->CUG_CONTROL&BAR_CUG_IN)
		xvc->nuibase.CUG_CONTROL|=BAR_CUG_IN;
	
	    /*
		Load up CUG subscriptions.
		Take first applicable NUI override facility
		or, if none, first applicable existing 	
		CUG subscription (avoids clashes).
	    */
	    if (temp&SUB_CUG)
		xvc->nuibase.CUG_CONTROL|=SUB_CUG;
	    else if (temp&SUB_CUGOA)
		xvc->nuibase.CUG_CONTROL|=SUB_CUGOA;
	    else if (xx->CUG_CONTROL&SUB_CUGIA)
		xvc->nuibase.CUG_CONTROL|=SUB_CUGIA;
	    else if (xx->CUG_CONTROL&SUB_PREF)
		xvc->nuibase.CUG_CONTROL|=SUB_PREF;

	    if (temp&EXTENDED)
		xvc->nuibase.CUG_CONTROL|=EXTENDED;
	    else 
		/* default to Basic Format */
		xvc->nuibase.CUG_CONTROL|=BASIC;
	 }
    }
} 


/******************************************************************/
int vc_in_range(lci,index,xx,pvcf)
/******************************************************************/

/*
    Check lci assigned and return hash index to it in the index
    Note cfg pointer passed. PVC flag returned if enabled.
*/

unsigned short lci;
int *index;
register struct wlcfg *xx;
int *pvcf;
{
    register int inx;


    *pvcf = 0;			/* Default returned value */

    if( (unsigned short)xx->HIC>=lci && (unsigned short)xx->LIC<=lci )
    {
	inx=(lci - xx->LIC + xx->NPCchannels);
    }
    else if( (unsigned short)xx->HTC>=lci && (unsigned short)xx->LTC<=lci )
    {
	inx=(lci - xx->LTC + xx->NICchannels + xx->NPCchannels);
    }
    else if( (unsigned short)xx->HOC>=lci && (unsigned short)xx->LOC<=lci )
    {
	inx=(lci - xx->LOC + xx->NICchannels + xx->NTCchannels + xx->NPCchannels);
    }
    else if( (unsigned short)xx->HPC>=lci && (unsigned short)xx->LPC<=lci )
    {
	inx=(lci - xx->LPC);
	*pvcf = 1;			/* Mark is a PVC channel */
    }
    else
	return(0);        /* LCI OUT-OF-RANGE */

    /* Compute the hash value for this lci */
    if( inx > (HASHLEN/2) )
    {
	/*
	    Hash inx is abs( inx - xx->Nochnls + HASHLEN ) % HASHLEN
	*/
	if( (inx = inx - xx->Nochnls + HASHLEN )<0 )
	    inx = (-inx);
	inx = inx % HASHLEN;
    }

    /*
	else inx is already the hash value
    */
    *index = inx;

    return(1);                /* LCI IN-RANGE     */
}


/******************************************************************/
struct x25vcformat FAR *lcitovc(lsp, tlci,index)
/******************************************************************/

/*
    Takes lci and maps to a vc database record if one
    exists.  Uses hash index returned by vc_in_range.
*/

register struct lsformat FAR *lsp;
unsigned short tlci;
int            index;

{
    register struct x25vcformat FAR *xvc;
    register unsigned short   i;

    xvc = NULL;

    if( (i=lsp->slot[index]) != SLOTFREE )
    {
	/*
	    This lci is in use. Check lci identity
	*/
	xvc = (struct x25vcformat FAR *) iso_vc[i];
	if( xvc->lci != tlci )
	{                           /* Sigh, we have to hunt for it */
	    xvc = lsp->CRqueue;
	    while( xvc != NULL )
	    {
		if( xvc->lci == tlci )
		    break;              /* To return value of xvc */
		else if ( xvc->lci > tlci )
		    xvc = NULL;
		else
		    xvc = xvc->xvclink;
	    }
	}
    }
    return(xvc);
}


/******************************************************************/
int hunt_low( lsp, lowv, highv, lval)
/******************************************************************/

/*
    Scans the linked list for thislsap to find
    and appropriate free channel for conreq.
*/

register struct lsformat FAR *lsp;
int lowv, highv, *lval;

{

    register struct x25vcformat FAR *st;
    int test, testl, curr, eocycle;

    /*
	Take an ordered set and provide the lowest free within
	the range lowv and highv (inclusive). 0 if failure
	
	Skim range for starting test region.
    */

    st = lsp->CRqueue;
    while ( st != NULL && ((unsigned int)st->lci < (unsigned int)lowv ))
	st = st->xvclink;

    curr = eocycle = 0;
    testl = lowv;

    while( eocycle == 0 )
    {
	if( st == NULL || (test = st->lci) > highv )
	    test = highv + 1;

	if( test == testl )
	{
	    if( testl != highv )
	    {
		testl = testl + 1;
		st = st->xvclink;
	    }
	    else
		eocycle = 1;
	}
	else
	{
	    /* test != testl : must be a gap */
	    curr = testl;
	    eocycle = 1;
	}
    }
    *lval = curr;
    return( curr != 0 ? 1 : 0 );
}


/******************************************************************/
int hunt_high( lsp, lowv, highv, hval )
/******************************************************************/

/*
   Take an ordered set and provide the highest free within
   the range lowv and highv (inclusive). 0 if failure
*/

register struct lsformat FAR *lsp;
int lowv, highv, *hval;

{
    register struct x25vcformat FAR *st;
    int test, testl, curr, eocycle;

    /*
	Skim range for starting test region
    */
    st = lsp->CRqueue;
    while ( st != NULL && ((unsigned int)st->lci < (unsigned int)lowv ))
	st = st->xvclink;

    curr = eocycle = 0;                   /* When non-zero, last value */
    testl = lowv - 1;

    while( eocycle == 0 )
    {
	/* Obtain the test value */
	if( st == NULL || (test=st->lci) > highv )
	    test = highv + 1;

        if( test >= highv )
	{
	    /* The cycle ends here */
	    eocycle = 1;
	    if( (test - testl) > 1 )
		curr = test - 1;
	}
	else
	{
	    /* Another cycle may be required */
	    if( (test - testl) > 1 )
		curr = test - 1;

	    testl = test;
	    st = st->xvclink;
	}
    }
    *hval = curr;
    return( curr != 0 ? 1 : 0);
}


/******************************************************************/
int assign_lci(xvc)
/******************************************************************/

/*
    Ascertain the next free lgn/lcn pair for an outgoing connection.
    Return 1/0 - OK/FAIL
*/

register struct x25vcformat FAR *xvc;

{
    register struct wlcfg       *xx;
    struct lsformat    FAR *lsp;
    int i, j;
    int dummy;

    lsp = xvc->lsaplink;
    xx  = xvc->xbase;

    i = 0;                      /* Maintains result of function call */
    j = 0;
#if 0
	printf("lsp->DTEmode = %x NOCchannels = %x LOC = %x HOC = %x",lsp->DTEmode, xx->NOCchannels, xx->LOC, xx->HOC) ;
	printf("lsp->DTEmode = %x NICchannels = %x LIC = %x HIC = %x",lsp->DTEmode, xx->NICchannels, xx->LIC, xx->HIC) ;
	printf("lsp->DTEmode = %x NTCchannels = %x LTC = %x HTC = %x",lsp->DTEmode, xx->NTCchannels, xx->LTC, xx->HTC) ;
#endif

    if( lsp->DTEmode != 0 )
    {
	if( xx->NOCchannels != 0 )
	    i = hunt_high( lsp, xx->LOC, xx->HOC, &j);
    } else
    {
	if( xx->NICchannels != 0 )
	    i = hunt_low ( lsp, xx->LIC, xx->HIC, &j);
    }

    if( i == 0 && xx->NTCchannels != 0 )
    {
	i = ( lsp->DTEmode != 0 ?
	    hunt_high( lsp, xx->LTC, xx->HTC, &j ) :
	    hunt_low ( lsp, xx->LTC, xx->HTC, &j ) );
    }

    if( i != 0 )
    {
	xvc->lci = (unsigned short) j;
#ifdef MOD8ONLY
	xvc->lgngfi = (( j >> 8 ) & 255) | G_8;
#else
	xvc->lgngfi = (( j >> 8 ) & 255) | xvc->xbase->THISGFI;
#endif
	xvc->lcn = ( j & 255 );

	(void) vc_in_range(xvc->lci,&j,xx,&dummy);

	xvc->hashval = (unsigned short) j;
	
	/* Set the hash table entry when slotted in */
	return(1);
    }
    return(0);                       /* No channels available */
}


/******************************************************************/
void slot_in(lsp, xvc)
/******************************************************************/

/*
    Links database record into the lsap queue and sets
    the hash table entry appropriately
*/

struct lsformat FAR *lsp;
struct x25vcformat FAR *xvc;

{
    register struct x25vcformat FAR *temp;

    temp = lsp->CRqueue;

    if( temp == NULL || temp->lci >= xvc->lci)
    {
	xvc->xvclink = temp;
	lsp->CRqueue = xvc;
    }
    else
    {
	/*
	    Hunt for value higher - note that, on entry there will be at
	    least one element with lci lower than the test value
	*/

	while( 1 )
	{
	    if( temp->xvclink == NULL || temp->xvclink->lci > xvc->lci )
	    {
		xvc->xvclink = temp->xvclink;
		temp->xvclink = xvc;
		break;
	    }
	    else
	    {
		temp = temp->xvclink;        /* Chain to next */
	    }
	}
    }

    /*
	Now set the hash table with the reference
    */
    lsp->slot[xvc->hashval] = (VCREF) xvc->nsref;
}


static int last_connid = 0;

/******************************************************************/
static int new_connid()
/******************************************************************/

/*
    Obtain a new and unique connection identifier
    (in a pedestrian manner)
*/

{
    register int i;
    register struct x25vcformat FAR *xvc;

    while(1)
    {
	last_connid = ((last_connid + 1)&0x7FFF);

	if( last_connid == 0 )
	    last_connid = 1;
	    
	for( i=1; i<=isovcmax; i++)
	{
	    xvc = iso_vc[i];
	    if( (xvc->xtag != FREETAG) && (xvc->xconnid == last_connid) )
		break;
	}

	/*
	    Scan complete? If so, return this value
	*/
	if( i>isovcmax )
	    return(last_connid);
    }
}


static int last_lsapid = 0;

/******************************************************************/
static int new_lsapid()
/******************************************************************/

/*
    Obtain a new and unique link identifier
    (in a pedestrian manner)
*/

{
    register int i;
    register struct lsformat FAR *lsp;

    while(1)
    {
	last_lsapid = ((last_lsapid+ 1)&0x7FFF);

	if( last_lsapid == 0 )
	    last_lsapid = 1;
	    
	for( i=FIRSTLSAP; i<=isolsapmax; i++)
	{
	    lsp = iso_lsap[i];
	    if ( (lsp->ltag != FREETAG) && (lsp->lconnid == last_lsapid) )
		break;
	}

	/*
	    Scan complete? If so, return this value
	*/
	if( i > isolsapmax)
	    return(last_lsapid);
    }
}


/******************************************************************/
struct x25vcformat FAR *x25allocvc(ref)
/******************************************************************/

/*
    Allocates a vc database record - returns  pointer to the database
    and (int)index. If no database elements, monitor event.
*/

int *ref;

{
    struct x25vcformat FAR *xvc;
    int i;

    for( i=FIRSTVC; i<=isovcmax; i++)
    {
	xvc = iso_vc[i];

	if( xvc->xtag == FREETAG )
	{
	    *ref = i;
	    xvc->xconnid = new_connid();
	    return(xvc);
	}
    }
    L3monitor(L3nodb,0);
    return((struct x25vcformat FAR *)NULL);
}


/******************************************************************/
struct lsformat *x25alloclsap(ref)
/******************************************************************/

/*
    Allocates a link database record - returns pointer to the database
    and (int)index. If no database elements, monitor event.
*/

int *ref;

{
    struct lsformat FAR *lsp;
    int i;

    for( i=FIRSTLSAP; i<=isolsapmax; i++)
    {
	lsp = iso_lsap[i];
	if (lsp->ltag == FREETAG)
	{
		printf("x25alloclsap SUC i=%x, lsp=%x ",i, lsp) ;
	    *ref = i;
	    lsp->lconnid = new_lsapid();
	    return(lsp);
	}
    }
    L3monitor(L3nodb,0);
    return((struct lsformat FAR *)NULL);
}


/******************************************************************/
void tidy_user_side(xvc)
/******************************************************************/

/*
    Tidies user in reset situation
*/

register struct x25vcformat FAR *xvc;
{

    register mblk_t        *buf;

    xvc->acksoutstanding=0;
    xvc->ftoack         = xvc->remcredit    =0;
    xvc->Udatawaiting   =xvc->Uexdatawaiting=0;
    xvc->lastQin        =xvc->lastMin       =0;
    xvc->DTEbusy        =0;
    xvc->xusrevent     &= ~(xAKout | xDTout | xINTout | xINCout);
    xvc->xflag         &= ~(INTOUTSTANDING|INCwaited);
    xvc->lastnr=xvc->psexpected=xvc->nrtogo=xvc->nrsent=xvc->nstogo=0;

    xvc->hold_nr = 0;
    xvc->Dreq_rcvd_cnt = 0;
    xvc->Dreq_sent_cnt = 0;
    (void)bzero(xvc->Dreq_rcvd_seq,128);
    (void)bzero(xvc->Dreq_sent_seq,128);

    while( (buf=xvc->x25txqhead)!=NULL )
    {
	xvc->x25txqhead=buf->b_next;
	buf->b_next = (mblk_t *) 0 ;
	m_freem(buf);
    }

    xvc->x25txqtail=NULL;
    xvc->x25txqsize=0;
    m_freem(xvc->xEDbuff);
    xvc->xEDbuff = NULL;
    
    if( xvc->datatogo )
    {
	m_freem(xvc->datatogo);
    }

    xvc->datatogo = NULL;
    m_freem(xvc->reassemble);
    xvc->reassemble = NULL;
    xvc->nsdulen=0;
}


/******************************************************************/
void flush_user(xvc,killdata)
/******************************************************************/

/*
    Clear out all of user side.  Kill user disconnect data if
    killdata!=0. NSUattached is not cleared by this routine!
*/

register struct x25vcformat FAR *xvc;
int killdata;

{
    tidy_user_side(xvc);                /* Clears user queues etc. */

    /* xvc->NSUattached = 0;*/          /* Detach user             */
    xvc->xusrevent  &= ~ALLXUSREVENTS;  /* Clear any user events   */

    if( xvc->calldata != NULL )         /* Free call data          */
    {
	m_freem(xvc->calldata);
	xvc->calldata=NULL;
    }

    if( killdata != 0 )                 /* Free user disc data?    */
    {
	if( xvc->Udiscdata != NULL )
        {
	    m_freem(xvc->Udiscdata);
	    xvc->Udiscdata=NULL;
        }
    }
}


/******************************************************************/
void clr_procedure(xvc,entering,c,d,OKstate,NOKstate)
/******************************************************************/

/*
    Called when the vc is continuing or entering the clearing procedure
    (see the macros 'into' and 'cont' clr_procedure)
*/

register struct x25vcformat FAR *xvc;
unsigned char entering, c, d, OKstate, NOKstate;

{

    xvc->discause = c;
    xvc->disdiag  = d;

    if( entering != '\0' )
	xvc->xretx = 0;       /* Prime retx var   */

    if( xvc->xretx <= xvc->xbase->R23value )     /* Send clear again */
    {
	if( clear_request(xvc) )                 /* X25 clear sent? */
        {
	    xvc->Udiscdata=NULL;
	    xvc->xusrevent&= ~xBUFreq;
	    xvc->xstate=OKstate;                 /* Enter procedure proper */
	    xvc->xretx++;                        /* And increment */
	    startT23(xvc);
	}
	else
	{
	    need_buffer(xvc,X_CLR);
	    xvc->xstate=NOKstate;                /* Buffer crisis on */
	}
    }
    else
    {
	/*
	    Specification says one should stay in P6, but this
	    implementation may decide to acquire the resource.
	*/
	L3monitor(r23exp, xvc->xu_ident);

#ifndef INTERNAL_DISC
	/*
	    Still have a user waiting on a CLC, so send one up and
	    free the stream. Leave XVC state as is.
	*/
	if ( disc_confirm_user(xvc) == 0 )
	    need_buffer(xvc,X_CLC);
#endif
    }
}


/******************************************************************/
void rst_procedure(xvc,entering,c,d,OKstate,NOKstate)
/******************************************************************/

/*
    Called when the vc is continuing or entering the resetting procedure
    (see the macros 'into' and 'cont' rst_procedure
*/

register struct x25vcformat FAR *xvc;
unsigned char entering, c, d, OKstate, NOKstate;

{
    if( entering !=0 ) xvc->xretx = 0;           /* Prime retx var  */

    if( xvc->xretx <= xvc->xbase->R22value )     /* Send reset or clear? */
    {
	if( Tx_RST(xvc,c,d) )                    /* X25 reset sent? */
        {
	    xvc->xusrevent&= ~xBUFreq;
	    xvc->xstate=OKstate;                 /* Enter procedure proper */
	    xvc->xretx++;                        /* And increment */
	    startT22(xvc);
	}
	else
	{
	    need_buffer(xvc,X_RST);
	    xvc->xstate=NOKstate;            /* Buffer crisis on */
	}
    }
    else
    {                                        /* Enter clear proc  */
	if( xvc->ampvc )
	{
	    flush_user(xvc,1);		/* Free buffers and events */
	    xvc->xusrevent = 0;		/* No events to handle */
	    det_PVC(xvc,PVC_RMTERROR);	/* Detach user if present */
	    to_P4D1(xvc,0);		/* Return to ready */
	}
	else
	{
	    ERR_clrU(xvc,(REMOTE_PROCEDURE_ERROR<<8)|51);/* To log rem_perr   */
	}
	L3monitor(r22exp, xvc->xu_ident) ;
    }
}


/******************************************************************/
void ERR_clrnoU(xvc,cd)
/******************************************************************/

/*
    Clear when no user.
*/

register struct x25vcformat FAR *xvc;
int cd;

{
    unsigned char d;

    d=(cd&255);
    stop_vc_timers(xvc);
    L3monitor(rem_perr, xvc->xu_ident);
    into_clr_procedure(xvc,0,d,P6,P6pending);
}


/******************************************************************/
void ERR_clrU(xvc,cd)
/******************************************************************/

/*
    Clear when user attached.
*/

register struct x25vcformat FAR *xvc;
int cd;

{
    unsigned char c,d;
  
    L3monitor(rem_perr, xvc->xu_ident);
    
    c=(cd>>8);
    d=(cd&255);
    stop_vc_timers(xvc);

    if( disconnect_user(xvc,c,d,(mblk_t *)NULL) )  /* Which clears down user */
	into_clr_procedure(xvc,0,d,P6,P6pending);  /* Enter clearing         */
    else
    {
	xvc->xstate=WUNcpending;              /* Clear is pending */
	need_buffer(xvc,Usr_Req);
    }

}


/******************************************************************/
void ERR_rstU(xvc,cd)
/******************************************************************/

/*
    Reset to user, and wait for the response.
*/

register struct x25vcformat FAR *xvc;
int cd;

{
    unsigned char c,d;

    L3monitor(rem_perr, xvc->xu_ident);

    stop_vc_timers(xvc);
    tidy_user_side(xvc);     /* Tidy BEFORE calling user */

    if( (xvc->ampvc) && (xvc->NSUattached) )
	xpurgeQ((struct Q FAR *)&(xgldb[xvc->userref].gdbq));

    c=(cd>>8);
    d=(cd&255);
    if( reset_user(xvc,c,d) )
    {
	xvc->xstate=WtgRCU;
	startT22(xvc);
    }
    else
    {
	xvc->xstate=WRCUpending;
	need_buffer(xvc,Usr_Req);
    }
}


/******************************************************************/
void ERR_rstnoU(xvc,cd)
/******************************************************************/

/*
    Reset to network only.
*/

register struct x25vcformat FAR *xvc;
int cd;

{
    unsigned char d;

    xvc->xusrevent&= ~xBUFreq; /* Might not need it */
    L3monitor(rem_perr, xvc->xu_ident);
    if( xvc->ampvc )	       /* Delete squirrel data */
	del_sqrl_data(xvc);
    d=(cd&255);
    into_rst_procedure(xvc,0,d,D2,D2pending);
}


/******************************************************************/
static void beWtgRCU(xvc,d)
/******************************************************************/

/*
    Called to shift state in error conditions
*/

register struct x25vcformat FAR *xvc;
int d;

{
    xvc->xstate=WtgRCU;
    xvc->rstcause=0;
    xvc->rstdiag =(d&255);
    if( xvc->ampvc )		/* RSTU emulated, delete sqrl data */
	del_sqrl_data(xvc);
}


/******************************************************************/
static void beWRCUpending(xvc,d)
/******************************************************************/

/*
    Called to shift state in error conditions
*/

register struct x25vcformat FAR *xvc;
int d;

{
    xvc->xstate=WRCUpending;
    xvc->rstcause=0;
    xvc->rstdiag =(d&255);
    need_buffer(xvc,Usr_Req);
    if( xvc->ampvc )
	del_sqrl_data(xvc);
}


/******************************************************************/
static void beWUNcpending(xvc,d)
/******************************************************************/

/*
    Called to shift state in error conditions
*/

register struct x25vcformat FAR *xvc;
int d;

{
    xvc->xstate=WUNcpending;
    xvc->discause=0;
    xvc->disdiag =(d&255);
    need_buffer(xvc,Usr_Req);
}

/******************************************************************/
void beRCNpending(xvc,d)
/******************************************************************/

/*
    Called to shift state in error conditions
*/

register struct x25vcformat FAR *xvc;
int d;

{
    xvc->xstate=WtgRCNpending;
    xvc->rstcause=0;
    xvc->rstdiag=(d&255);
    need_buffer(xvc,X_RST);
    
    if( xvc->ampvc )
	del_sqrl_data(xvc);
}


/******************************************************************/
void state_matrix(xvc,ptype)
/******************************************************************/

/*
    Called when packet is unacceptable to the current state or discarded.
    Indexes through tables L3amatrix and L3adiagmatrix to provide action.
*/

register struct x25vcformat FAR *xvc;
unsigned char ptype;

{
    unsigned char errtype;
    int errdiag;

    if( xvc->ampvc )
    {
	errtype = L3pamatrix[ptype][xvc->xstate];
	errdiag = L3padiagmatrix[ptype][xvc->xstate];
    }
    else
    {
	errtype = L3amatrix[ptype][xvc->xstate];
	errdiag = L3adiagmatrix[ptype][xvc->xstate];
    }
    if( errtype != 0 )

    {

    switch(errtype)
    {
	case cnou:
	
	    ERR_clrnoU(xvc,(REMOTE_PROCEDURE_ERROR<<8) | errdiag);
	    
	break;

	case reju:
	
	    ERR_rejU(xvc,(REMOTE_PROCEDURE_ERROR<<8) | errdiag);
	    
	break;

	case cu:
	
	    ERR_clrU(xvc,(REMOTE_PROCEDURE_ERROR<<8) | errdiag);
	    
	break;

	case ru:
	
	    ERR_rstU(xvc,(RREMOTE_PROCEDURE_ERROR<<8) | errdiag);
	    
	break;

	case rnou:
	
	    ERR_rstnoU(xvc,(RREMOTE_PROCEDURE_ERROR<<8) | errdiag);
	    
	break;

	case disg:
	    /*
		DISCARD buffer only required
	    */	
	break;

	case brcu:
	
	    beWtgRCU(xvc,(RREMOTE_PROCEDURE_ERROR<<8) | errdiag); /* State shift */
	    
	break;

	case bunc:
	
	    beWUNcpending(xvc,(REMOTE_PROCEDURE_ERROR<<8)| errdiag);
	    
	break;

	case brup:
	
	    beWRCUpending(xvc,(RREMOTE_PROCEDURE_ERROR<<8)| errdiag);
	    
	break;

	case rcnp:
	
	    beRCNpending(xvc,(RREMOTE_PROCEDURE_ERROR<<8)| errdiag);
	    
	break;

	case  0:
	default:

	    system_error(L3tabfault);
	    
	break;

	}
    }
    else
	system_error(L3matrixcall);
}


/******************************************************************/
void P4_FMT_error(xvc,diag)
/******************************************************************/

/*
    Called in P4 to cater for case when pti is acceptable, but format
    in error.  Care to taken to avoid rst/bad rsc loops.
*/

register struct x25vcformat FAR *xvc;
unsigned char diag;

{

    L3monitor(p4_ferr, xvc->xu_ident);
    xvc->xusrevent &= ~xBUFreq;               /* Clear buffer request */
    switch(xvc->xstate)
    {

	case DataTransfer:
	case DXEbusy:

	    /*
		Reset user first, this is a remote procedure error
	    */
	    ERR_rstU(xvc,(RREMOTE_PROCEDURE_ERROR<<8)|diag);
	    
	break;

	case pRESUonly:

	    /*
		Change diagnostic, reset - ERR deletes squirrel data
	    */
	    ERR_rstU(xvc,(RREMOTE_PROCEDURE_ERROR<<8)|diag);
	    
	break;

	case pDTransfer:

	    del_sqrl_data(xvc);
	    tidy_user_side(xvc);    /* To drop interrupt flags etc. */
	    into_rst_procedure(xvc,0,diag,WtgRCN,WtgRCNpending);
	    
	break;

	case RESUonly:

	    /*
		Delete squirrel data, change state and
		drop thru' WtgRCU code
	    */
	    del_sqrl_data(xvc);
	    tidy_user_side(xvc);    /* To drop interrupt flags etc. */
	    xvc->xstate = WtgRCU;


	case WtgRCU:
	
	    /*
		No action required.  However, change the original
		diagnostic
	    */
	    xvc->rstcause=0;
	    xvc->rstdiag =diag;
	    
	break;

	case D2:
	case WtgRCN:

	    /*
		Continue the reset procedure.  If the retx count expires
		then the 'cont' will clear the connection. Note no state
		change,	but the diagnostic sent may change.
	    */
	    cont_rst_procedure(xvc,0,diag,xvc->xstate,xvc->xstate);
	    
	break;

	case WtgRCNpending:
	case D2pending:

	    /*
		Attempt to send the reset, and enter reset procedure
	    */
	    if( xvc->xstate==WtgRCNpending )
		into_rst_procedure(xvc,0,diag,WtgRCN,WtgRCNpending);
	    else
		into_rst_procedure(xvc,0,diag,D2,D2pending);
		
	break;

	case P4pending:
	
	    /*
		Attempt to enter the reset procedure. The buffer request
		is cleared in the 'into' routine
	    */
	    into_rst_procedure(xvc,0,diag,D2,D2pending);
	    
	break;

	case DXErpending:

	    /*
		Change state to WRCUpending & drop thru to BUFreq event
	    */
	    xvc->xstate=WRCUpending;

	case WRCUpending:
	
	    /*
		Reset the buffer request event
	    */
	    need_buffer(xvc,Usr_Req);

	break;

	case DXEresetting:

	    /*
		Change state to WtgRCU, and send the reset when
		the confirmation arrives from the user
	    */
	    beWtgRCU(xvc,diag);
	    
	break;
    }
}


/******************************************************************/
void close_SVCs(lsp, c,d,allflag)
/******************************************************************/

/*
    Called when link disconnected, restart etc. allflag set means that
    all vcs aborted, including those in P1 state waiting.
    PVCs MUST be unhooked before this rtn call!
*/

struct lsformat FAR *lsp;
unsigned char c,d;
int allflag;

{
    register struct x25vcformat FAR *xvc, FAR *temp;

    temp = NULL;                          /* Q for P1 states              */
    while( (xvc = lsp->CRqueue) != NULL )
    {
	if( allflag == 0 && xvc->lci == 0 )
	{
	    /*
		Save this entry, allflag is not set
	    */
	    lsp->CRqueue = xvc->xvclink;
	    xvc->xvclink = temp;
	    temp = xvc;
	}
	else
	{
	    xvc->discause = c;
	    xvc->disdiag  = d;
	    xvc->lsaplink = NULL;    /* User goes into 'limbo' */
	    xvc->xusrevent|=labort;
	    
	    /*
		NB hash slot freed below
	    */
	    lsp->CRqueue = xvc->xvclink;
	    xvc->xvclink = NULL;
	}
    }

    lsp->CRqueue = temp;
    lsp->concount= 0;

    if( lsp->lstate == L_connected )
	start_idle_timer(lsp);

    /*
	All vcs have been cleared out, tidy the hash table
    */
    (void) memset((char FAR *)&lsp->slot[0],SLOTFREE,VC_SLOT_LEN);
}


/******************************************************************/
void dis_lsap_and_vcs( lsp, r, d )
/******************************************************************/

/*
    Disconnect to link level, abort vcs with reason & diag r,d.
*/

struct lsformat FAR *lsp;
unsigned char r, d;

{
    /*
	N.B. State change before 'close' call - prevents timer restart
    */
    lsp->lstate = L_dpending;
    stop_ls_timers(lsp);

    unhook_PVCs(lsp, PVC_LINKDOWN);

    close_SVCs( lsp, r, d, 1);     /* All vcs flag is set */

    /*
	If not already disconnecting, do so now
    */
    if( (lsp->lusrevent&lclosure)==0 )
    {
	if( LL_disreq( lsp ) )
	    lsp->lstate=L_disconnecting;
	else
	{
	    /*
		The state remains as disconnect pending
	    */
	    lsp->rescause= r;
	    lsp->resdiag = d;
	    lsp->lusrevent|=lneedbuf;
	}
    }
    else
    {
	/*
	    The state remains as L_dpending and the event collected later
	*/
    }

}


/******************************************************************/
void CRs_pending(lsp)
/******************************************************************/

/*
    Connect requests waiting may now be enabled
*/

struct lsformat FAR *lsp;

{
    register struct x25vcformat FAR *xvc;

    xvc = lsp->CRqueue;
    while( xvc != NULL && xvc->lci == 0 )
    {
	xvc->xusrevent |= xCRout;
	xvc = xvc->xvclink;
    }
}


/******************************************************************/
void xuser_gone(xvc,diag)
/******************************************************************/

/*
    Called when trying to send something to user
    but the user has disappeared.  Diagnostic passed.
*/

register struct x25vcformat FAR *xvc;
unsigned char diag;

{
    if( (xvc->xusrevent&xDRout)==0 )
    {
	xvc->xusrevent|=xDRout;
	if( xvc->ampvc )
	{
	    xvc->rstcause=xvc->nsucause=0;
	    xvc->rstdiag=xvc->nsudiag=diag;	/* Shift the diagnostic */
	    L3monitor(PVCusergone, xvc->xu_ident);   /* Normal (mostly) */
	}
	else
	{
	    xvc->nsucause=0;
	    xvc->nsudiag =224;       /* OSI service problem */
	    L3monitor(L3usergone, xvc->xu_ident);
	}
    }
}


/******************************************************************/
void dec_ls_concount(lsp,xvc)
/******************************************************************/

/*
    Count out a connection in ls record and start idle if necessary
*/

struct lsformat    FAR *lsp;
struct x25vcformat FAR *xvc;

{
    if( (xvc->xflag&vc_counted) != 0 )
    {
	if( --lsp->concount < 1 )
	{
	    start_idle_timer(lsp);
	    if( lsp->concount<0 )
	    {
		system_error(L3conlt0);
		lsp->concount=0;
	    }
	}
    }
}


/******************************************************************/
void deqvc(lsp,xvc)
/******************************************************************/

/*
    Called to unqueue a vc from the CR queue
*/

struct lsformat FAR *lsp;
register struct x25vcformat FAR *xvc;

{
    register struct x25vcformat FAR *temp2;

    temp2=lsp->CRqueue;

    if( temp2 == xvc )
    {
	lsp->CRqueue = temp2->xvclink;
	temp2->xvclink = NULL;
    }
    else
    {
	while( temp2 != NULL )
	{
	    if( temp2->xvclink == xvc )
	    {
		temp2->xvclink = xvc->xvclink;
		xvc->xvclink= NULL;
		break;
	    }
	    else
	    {
		temp2=temp2->xvclink;
	    }
	}
    }

    if( temp2 == NULL )
	system_error(L3deqfailed);
}


/******************************************************************/
void xfreedb(xvc)
/******************************************************************/

/*
    Clears out slots, channels and the vc database
*/

register struct x25vcformat FAR *xvc;

{
    register struct lsformat    FAR *lsp;
    register struct x25vcformat FAR *temp;
    unsigned short              index;

    /* register struct vcircuit    *vcptr; */

    lsp=xvc->lsaplink;

    /* Free the channel etc if the vc is linked to the lsap */

    if( lsp != NULL )                         /* Is it linked? (Not limbo) */
    {
	deqvc(lsp,xvc);                       /* Remove from lsap queue    */
	if( xvc->lci != 0 )                   /* A channel is assigned?    */
	{
	    index = xvc->hashval;
	    lsp->slot[index]=SLOTFREE;
	    
	    /* Scan thru for collisions */
	    temp = lsp->CRqueue;
	    while( temp != NULL )
	    {
		if( (temp->hashval == index) && (temp->lci != 0) )
		{
		    lsp->slot[index]=(VCREF)temp->nsref;
		    break;
		}
		else
		    temp = temp->xvclink;
	    }
	}

    }

    /*
	ls resources returned.  Clear out all data etc and deallocate.
	NSUattached cleared in the memset below.
    */
    flush_user(xvc,1);
    (void)memset((char FAR *)xvc,0,sizeof(struct x25vcformat));
}


/******************************************************************/
void lfreedb(lsp)
/******************************************************************/

/*
    Clears out slot and ls database
*/

register struct lsformat  FAR *lsp;

{
    if( lsp != NULL )
    {
	(void) memset((char FAR *)lsp,0,sizeof(struct lsformat));
    }
}


/******************************************************************/
void to_P4D1(xvc,floflag)
/******************************************************************/

/*
    Enter the data transfer state.  data transmission is started if
    waiting.
*/

register struct x25vcformat FAR *xvc;
int floflag;

{
    xvc->xstate = DataTransfer;
    xvc->xusrevent &= ~xBUFreq;		/* Drop any buffer request */
    startT24(xvc);

    if( xvc->xEDbuff != NULL )
	floflag = Tx_EDdata(xvc);
    else
	floflag = 1;

    if( floflag != 0 )
	Tx_Qdata(xvc);

    /*
	Going to data transfer, check status to see if we need to find
	a new PVC user.  Query optimised since almost always attached
    */
    if( xvc->NSUattached == 0 && xvc->ampvc && xvc->lsaplink
                              && xvc->lsaplink->lstate == L_connected )
	hunt_PVC_user(xvc);
}


/******************************************************************/
void to_Lconnected(lsp)
/******************************************************************/

/*
    PLP enters the connected phase. Stop clocks and start
    the idle timer.
*/

struct lsformat FAR *lsp;

{
    lsp->lretx = 0;			/* Clear restart counter */
    lsp->lusrevent &= ~lneedbuf;	/* No needbuf event now  */
    lsp->lstate = L_connected;
    stop_ls_timers(lsp);
    if( lsp->concount == 0 )		/* Check if PVCs there first */
	start_idle_timer(lsp);
}


/******************************************************************/
int qos_available(xvc,direction)
/******************************************************************/

/*
    Checks the availablity of transit delay and throughput. The
    encoding of the transit delay is performed here.  Note no
    'real' checks but could be enhanced to do 'real' checking.
    Returns zero if OK
*/

register struct x25vcformat FAR *xvc;
int direction;

{
    register struct wlcfg *xx;

    xx =xvc->xbase;

    if( (xvc->CCITTflag&ENDTOEND) != 0 )
    {
	if( direction == X_INWARD )
	{
	    if( (xvc->ISOflag&TRDELAY) != 0 ) 
	    {
		/*
		    Must also have TRDELAY - 8878
		*/
printf("cum = %d , local = %d , trans = %d \n",xvc->etoecumulative,xx->localdelay,xvc->ltrans);
		xvc->etoecumulative += (xx->localdelay + xvc->ltrans);
	    }
	    else
		return(76);    /* Facility not present and expected */
	}
	else                
	{
	    /*
		Call going out - set up fields
	    */	
	    xvc->etoecumulative = xx->localdelay + xx->accessdelay;
	    xvc->ltrans         = xvc->etoetarget - xvc->etoecumulative;
	    xvc->ISOflag       |= TRDELAY;
	}
	
	/*
	    Now check the value of the qos
	*/
	if( (xvc->CCITTflag&ENDMAX) != 0
	                         && xvc->etoecumulative > xvc->etoemaxaccept )
	    return(NS_QOSNAPERMANENT);
    }

    /*
	Now the throughput value but only if not used to set w/p sizes
    */
    if( (xvc->ISOflag&TCLASS) != 0 && (xvc->xflag&thruforced)==0 )
    {
	if( xvc->lthru < xvc->lminthru || xvc->rthru < xvc->rminthru )
	    return(NS_QOSNAPERMANENT);
    }
    else if( (xvc->CCITTflag&MTCLASS) != 0 )  /* I.e. TCLASS unspecified */
    {
	if( xvc->lminthru > xx->locmaxthclass ||
	                    xvc->rminthru > xx->remmaxthclass )
	    return(NS_QOSNAPERMANENT);
    }
    return(0);                    /* OK QOS */
}


/******************************************************************/
void map_dbit_control(xvc)
/******************************************************************/

/*
    Works out and copies the required Dbit control information
    for data packets into the Dctl field of the vc record, for
    ease of access and use.
*/

register struct x25vcformat FAR *xvc;

{
    register struct wlcfg *xx;

    xx = xvc->xbase;

    xvc->Dctl = 0; /* Initialise no action to be taken */
    if( xvc->CONS_call && (!xvc->rem_Dsupport || !xvc->loc_Dsupport) )
    {
	xvc->rem_Dsupport = xvc->loc_Dsupport = 0;  /* Both ends have to */
	xvc->Dctl |= (DT_OUT_RST | DT_IN_RST);      /* agree for CONS    */
    }
    else
    {
	if( !xvc->rem_Dsupport )
	    xvc->Dctl |= (xx->psdn_local.dbit_control&(DT_OUT_RST|DT_OUT_ZERO));

	if( !xvc->loc_Dsupport )
	    xvc->Dctl |= (xx->psdn_local.dbit_control&(DT_IN_RST|DT_IN_ZERO));
    }
}

/******************************************************************/
unsigned char next_Dreq(xvc)
/******************************************************************/

/*
    Returns the sequence number of the next Dbit request that was
    received from the network. If none then the sequence number of
    the next ps expected is returned.
*/

register struct x25vcformat FAR *xvc;

{
    int j;

    j = (xvc->hold_nr + 1)&xvc->xmodulo;

    while ( j != xvc->psexpected ) 
    {
	if (xvc->Dreq_rcvd_seq[j])
	    break;

	j = (j + 1)&xvc->xmodulo;
    }

    return((unsigned char)j);
}

/******************************************************************/
int Dsent_acked(xvc, nr)
/******************************************************************/

/*
    Returns the number of Dbit requests that are acknowledged by
    advancing the window to the given nr. Assumes that the lastnr
    received value has not been updated yet.
*/

register struct x25vcformat FAR *xvc;
int nr;

{
    int j, dacked;

    j = (xvc->hold_nr + 1)&xvc->xmodulo;
    dacked = 0;
    
    for ( j = xvc->lastnr ; j != nr ; j = (j+1)&xvc->xmodulo ) 
    {
	if (xvc->Dreq_sent_seq[j])
	{
	    xvc->Dreq_sent_seq[j] = 0;
	    dacked++;
	}
    }

    return(dacked);
}


/******************************************************************/
unsigned long map_linkref(lref)
/******************************************************************/

/*
    Maps linkref (for conind) to a subnetwork id.
*/

int lref;

{
    return( xsndb[lref].u_snid );
}


/******************************************************************/
unsigned long map_snident(snid)
/******************************************************************/

/*
    Returns the linkref for the subnetwork identifier
*/

unsigned long snid;

{
    int i;

    for(i=0; i<x25submax; i++)
    {
	if( xsndb[i].regok != 0 && xsndb[i].u_snid == snid )
	    return(i);
    }
    
    return(-1);			/* Not found */
}


/******************************************************************/
struct wlcfg *sn_to_cfg(in_ref)
/******************************************************************/

/*
    Map a subnetwork id to a configuration database. Direction ignored
    in this version but .. Directions can be X_UTWARD: soft id->db; or
    X_INWARD: hard id->db. Returns pointer or NULL.
*/

unsigned long in_ref;

{
    int i;

    for(i=0; i<x25cfgmax; i++ )
    {
	if( xcfg[i].net_ok != 0 )
	{
	    if( in_ref == xcfg[i].net_cfg.U_SN_ID )
		return(&xcfg[i].net_cfg);
	}
    }

    return( (struct wlcfg *) 0 );
}


/******************************************************************/
unsigned long sn_to_cfg_index(in_ref)
/******************************************************************/

/*
    Map a subnetwork id to an index into the configuration database.
*/

unsigned long in_ref;

{
    int i;

    for( i = 0 ; i < x25cfgmax ; i++ )
    {
	if( xcfg[i].net_ok != 0 )
	{
	    if( in_ref == xcfg[i].net_cfg.U_SN_ID )
	    return(i);
	}
    }

    return(0);
}


/******************************************************************/
unsigned char net_domain(cf)
/******************************************************************/

/*
    From a configuration database, determine the LAN84, WAN84 or
    WAN80 nature of the 'domain'.
*/

struct wlcfg *cf;

{
    unsigned char mode;

    if( cf->NET_MODE == X25_LLC )
    {
	if ( cf->X25_VSN == 2 )
	   mode = LAN88;
	else
	    mode = LAN84;
    }
    else
    {
	switch( cf->X25_VSN )
	{
	    case 1:
		mode = WAN84;
	    break;
	
	    case 2:
		mode = WAN88;
	    break;
	
	    case 0:
	    default:
		mode = WAN80;		/* Default */
	    break;
	}
    }
    return(mode);
}


/************************************************************************/
void queue_listen(mp,gref)
/************************************************************************/

/*
    Adds the given listen request to the listen queue. Assumes next and prev
    pointers of the supplied message are NULL.
*/

register mblk_t *mp;
int gref;

{
    register struct i_xlistenf *lreq;
    mblk_t *qreq;
    mblk_t *qlast;

    lreq  = (struct i_xlistenf *)mp->b_rptr;
    qreq  = listen_requestQ;
    qlast = NULL;
	
    /*
	Find position in queue ordered by amount of listen
	data supplied.
    */
    while ( qreq &&
	    lreq->l_total <= ((struct i_xlistenf *)qreq->b_rptr)->l_total )
    {
	qlast = qreq;
	qreq  = qreq->b_next;
    }		

    /*
	Insert supplied listen request into queue
    */
    if ( qreq )
    {
	if ( qreq->b_prev )
	{
	    mp->b_prev = qreq->b_prev;	/* Middle of Queue */
	    qreq->b_prev->b_next = mp;
	}
	else
	    listen_requestQ = mp;	/* Head of Queue   */

	qreq->b_prev = mp;
	mp->b_next = qreq;
    }
    else
    {
	if ( qlast )
	{
	    mp->b_prev = qlast;		/* End of Queue    */
	    qlast->b_next = mp;
	}
	else
	    listen_requestQ = mp;	/* Queue Empty     */
    }

    /*
	Fill in reference of associated glue module.
    */
    lreq->l_gref = gref;
}


/************************************************************************/
void purge_listens(gref)
/************************************************************************/

/*
    Deletes all listen requests for the given glue database
    from the listen request queue.
*/

int gref;

{
    register mblk_t *mp;
    register mblk_t *nmp;
	
    mp = listen_requestQ;

    while (mp)
    {
	if( ((struct i_xlistenf *)(mp->b_rptr))->l_gref != gref )
	{
	    mp = mp->b_next;
	    continue;
	}

		
	/*
	    Only special case is when removing head of list
	    then have to update module list head pointer.
	*/
	if ( mp->b_prev )
		mp->b_prev->b_next = mp->b_next;
	else
		listen_requestQ = mp->b_next;
		
	if ( mp->b_next )
	    mp->b_next->b_prev = mp->b_prev;

	nmp = mp->b_next;
	mp->b_next = mp->b_prev = (mblk_t *)0;
	freemsg(mp);
	mp = nmp;
    }
}



   
/* ----------------------------------------------------------------------
	Queue routines - OFF, ON and xpurgeQ
   ---------------------------------------------------------------------- */

/************************************************************************/
mblk_t *OFF(chain)
/************************************************************************/

/*
    Return the fisrt message on the given queue or NULL if empty.
*/

struct Q FAR *chain;

{
    mblk_t *temp;

    if( chain->head==NULL ) return NULL;

    temp=chain->head;
    chain->head=chain->head->b_next;
    
    if( chain->head==NULL )
	chain->tail=NULL;
	
    temp->b_next  = NULL;
    return temp;
}


/************************************************************************/
void ON(chain,ptr)
/************************************************************************/

/*
    Put the given message on the given queue, adjusting pointers
    accordingly
*/

struct Q FAR *chain;
mblk_t *ptr;

{

    if( chain->head==NULL )
	chain->head=ptr;
    else
	chain->tail->b_next=ptr;
	
    ptr->b_next=NULL;
    chain->tail=ptr;
}


/************************************************************************/
void xpurgeQ(chain)
/************************************************************************/

/*
    Remove all messages from the given queue
*/

struct Q FAR *chain;

{
    mblk_t *temp;

    while( temp = OFF(chain) )
	freemsg(temp);
}


/* ----------------------------------------------------------------------
            Routines for general use but which should exist outside
   ---------------------------------------------------------------------- */

/************************************************************************/
void system_error(val)
/************************************************************************/

/*
    Record in error log and increment stats.
*/

{
    strlog(X25_STID,0,0,SL_ERROR,"X25: SYSTEM ERROR - %d",val);
    L3monitor(val, 0);
}


/************************************************************************/
void delete_NRS_request()
/************************************************************************/

/*
    Hook if this functionality ever required
*/

{
#ifdef DEBUG
    printf("Delete NRS request called\n");
#endif
}


/************************************************************************/
int x25cmp(str1, str2, n)
/************************************************************************/

/*
    This is a copy of the standard memcmp() function
*/

register char FAR *str1, FAR *str2;
register int n;

{
    if (str1 == str2)
	return (0);

    while (--n >= 0)  
    {
	if (*str1++ != *str2++)
	    break;
    }

    return((n < 0) ? 0 : (*--str1 - *--str2));
}


/************************************************************************/
int buffs_status()
/************************************************************************/

/*
    Hook if this functionality ever required
*/

{

/*	return(dbuf_low) ; */
	    return 0; 
}


/************************************************************************/
void report_DIAG(x)
/************************************************************************/

/*
    Hook if this functionality ever required
*/

{
}

/************************************************************************/
void L3monitor(item, sn_id)
/************************************************************************/
int item;
unsigned long sn_id;
{
	/*
	 * Increment the appropriate L3monarray element based on sn_id.
	 * If sn_id is NULL, then it is a global stat, in which case
	 * the sn_id is treated as a wildcard.
	 */
	register int i;
	register int *MonPtr;

	for(i = 0; i < iMAXXSUBNETS; i++) {
		if (xsndb[i].regok && (!sn_id || (xsndb[i].u_snid == sn_id))) {
			MonPtr = xsndb[i].statptr ;
			MonPtr[item]++ ;
		}
	}
}

/************************************************************************/
void bcd_to_str(bcd_addr, len, cptr)
/************************************************************************/
uint8 *bcd_addr ;
int len ;
uint8 *cptr ;
{
	/*
	 * Convert an BCD-encoded address (of "len" digits, or
	 * "len"/2 octets) to a string of "len" ASCII characters.
	 */
	register int i ;

	for(i = 0; i < len; i++) {
		if (i & 0x01) {
			cptr[i] = '0' + ((*bcd_addr++) & 0x0F) ;
		} else {
			cptr[i] = '0' + ((*bcd_addr) >> 4) ;
		}
	}

	cptr[i] = '\0' ;
}

