#ident "$Id: frscmi.c,v 1.5 1997/10/21 20:48:07 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.
 * 
 *****************************************************************************/

/*****************************************************************************
 *                                                                           *
 * (c) COPYRIGHT 1991, 1992 HARRIS & JEFFRIES, INC.                          *
 *                          888 WASHINGTON STREET                            *
 *                          SUITE 130                                        *
 *                          DEDHAM, MA  02026                                *
 * ALL RIGHTS RESERVED                                                       *
 *                                                                           *
 * This software is the property of Harris & Jeffries, Inc. and is furnished *
 * under license by Harris & Jeffries, Inc. and this software may be used    *
 * only in accordance with the terms of said license.  This copyright notice *
 * may not be removed, modified or obliterated without the prior written     *
 * permission of Harris & Jeffries, Inc.                                     *
 *                                                                           *
 * This software may not be copied, transmitted, provided to or otherwise    *
 * made available to any other person, company, corporation or other entiry  *
 * except as specified in the terms of said license.                         *
 *                                                                           *
 * No right, title, ownership or other interest in the software is hereby    *
 * granted or transferred.                                                   *
 *                                                                           *
 * The information contained herein is subject to change without notice and  *
 * should not be construed as a commitment by Harris & Jeffries, Inc.        *
 *                                                                           *
 *****************************************************************************

  MODULE NAME:

        @@@@@@  @@@@@    @@@@    @@@@   @    @     @             @@@@
        @       @    @  @       @    @  @@  @@     @            @    @
        @@@@@   @    @   @@@@   @       @ @@ @     @            @
        @       @@@@@        @  @       @    @     @     @@@    @
        @       @   @   @    @  @    @  @    @     @     @@@    @    @
        @       @    @   @@@@    @@@@   @    @     @     @@@     @@@@

  ORIGINAL AUTHOR:  David Jeffries
  SUBJECT MATTER
      SPECIALIST:   Deepak Shahane


  REVISION HISTORY:

  Date     Version  Name          Description
  -------- -------  ------------  --------------------------------------------
  08-16-92 2.00A    H&J (DJ)      Original
           Other    H&J           See H&J's bug reports

  $Log: frscmi.c,v $
  Revision 1.5  1997/10/21 20:48:07  gary
  Modifications made to accomodate the SOH (Streams-on-host) folks.

  Revision 1.4  1996/10/22 21:18:50  gary
  A bit of rearranging was required in order to move the FRF9_Stats_t
  structure over to fr_cont.h. (needed by host drivers)

  Revision 1.3  1996/04/26 19:57:35  gary
  First rev of changes: Frame Relay on PRI card.
  Bug fix: sequence number on segmented frames was wrong.

  Revision 1.2  1995/04/07 10:43:47  garyp
  removed FAR/bufp_t definitions

 * Revision 1.7  1994/07/27  20:27:41  stavrosm
 * Release 2.2
 *
 * Revision 1.6  1993/11/10  16:44:19  stavrosm
 * Release 2.1
 *
 * Revision 1.5  93/11/10  16:25:49  stavrosm
 * Cleanup to make lint happy
 * 
 * Revision 1.4  93/11/02  11:13:34  stavrosm
 * 
..............................................................................

  DESCRIPTION:

  This module contains code that implements a simple peer-to-peer Circuit
  Management Interface (CMI) protocol (based on a subset of T1.617 Annex-D)
  within the H&J Frame Relay Stack.

..............................................................................

  CMI REF DOCUMENTS:

  (a) ANSI T1S1/90-213R1 (T1.617) - DSS1 - Signalling Specification for Frame
      Relay Bearer Service, ANSI, July 1990.
  (b) Frame Relay Stack Functional Specification Rel 2.00 Rev G
      Harris & Jeffries Inc.
  (c) H&J MIB extensions for Frame Relay DCE.

..............................................................................

  SPECIAL COMPILE/LINK INSTRUCTIONS:

  If your compiler supports strict prototyping, you may elect to use
  formal parameter syntax verification by defining PROTOTYPE with
  the cc command.

..............................................................................

  FUNCTIONS INCLUDED:

  FRSFR_CMI_Rcv        --- Main receive processor for CMI frames

  Formal Prototypes for all the above functions exist in <frs.h>.
  Strict prototyping is provided.

..............................................................................
*/


 /****************************************************************************
 * 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 of Digi International  *
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly*
 * prohibited.                                                              *
 * Copyright 1994, Digi International Inc.                                  *
 * All rights reserved.                                                     *
 *       $Id: frscmi.c,v 1.5 1997/10/21 20:48:07 gary Exp $I
 ****************************************************************************/


/*
 * Includes
 */

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

#include "types.h"
#include "errno.h"
#include "stream.h"
#include "strlog.h"
#include "system.h"
#include "ll_proto.h"
#include "ll_control.h"
#include "timer.h"
#include "x25trace.h"
#include "fr_cont.h"
#include "fr.h"
#include "frs.h"
#include "lapb.h"

#if FRS_DCE_SUPPORT

/*
 * Global CMI CCB
 */
CMICCB  gCmiCCB;

/*
 * API functions
 */
/**/
/****************************************************************************
*
*                  RCV PROCESSOR FOR CMI MESSAGES
*
****************************************************************************/

/****************************************************************************

  PROCEDURE NAME:   FRSFR_CMI_Rcv

  DESCRIPTION:

  This procedure processes a CMI frame received from the iterior protocol
  network.

  PARAMETERS:
    USRBUF *         --- Ptr to the rcvd CMI frame.

  RETURNS:

    FRS_SUCCESS      --- No error in frame syntax.
    Else API code    --- Invalid syntax.

  NOTES:

    This function makes the following assumptions:

    (1) The entire CMI frame is contained in the buffer pointed to by pRcv.
    (2) The caller is responsible for freeing the buffer.

*****************************************************************************/
u_short
FRSFR_CMI_Rcv(pRcv)
USRBUF *pRcv;
{
    CMICCB *pCmiCCB = &gCmiCCB;    
    FR_HDR_CMI *cmi_frm =  m_MMGR_DATA(pRcv,FR_HDR_CMI *) ;
    IE_UN *pIe = (IE_UN *)((char *)cmi_frm + sizeof(FR_HDR_CMI));
    u_char NoIeRepTypeFlag = TRUE;
    u_char rBECN, rFECN;
    CCB *pCCB = CCBNULL;
    LCB *pLCB = LCBNULL;
    u_short i, pvc_num, len, remote_node,
            local_link_num, remote_link_num;
    u_char dlciMSB, dlciLSB, pvc_status;

    /* Data length...
     */
    len = (u_short)m_MMGR_FRAME_LEN(pRcv);

    /* Validate Frame Type...
     */
    if(cmi_frm->frame_type != FRS_UNNUM_INFO_FRAME_TYPE)
        return FRS_LMI_INV_FRM_TYPE;

    /* Validate Message Type...
     */
    if(cmi_frm->msg_type != FRS_LMI_STATUS_MSG_TYPE)
    {
        pCmiCCB->cmi_inv_msg_rcvd++;
        return(FRS_LNK_UNKRPT);
    }

    /* Reset timeout timer for this remote node...
     */
     remote_node = (u_short)cmi_frm->call_ref;

    for ( i=0; i < pCmiCCB->remote_node_count; i++ )
    {
        if ( pCmiCCB->rnode[i].node_num == remote_node )
        {
            pCmiCCB->rnode[i].timeout = FRS_CMI_TIMER_IDLE;
            break;
        }
    }

    len -= sizeof(FR_HDR_CMI);

    /* Bump the pIe pointer by a byte to account for LOCK_SHIFT in AnnexD...
     * Also, drop the count by a byte.
     */
    if(*((u_char *)pIe) != FRS_LMI_LOCK_SHIFT)
        {
            pCmiCCB->cmi_proto_err++;
            return(FRS_LNK_LMIPROTOERR);
        }

    pIe = (IE_UN *)((u_char *)pIe + 1);
    len--;

    /* Check for a NULL CMI frame...
     */
    if(len == 0)
    {
        return(FRS_INVALID);
    }

    for (; len > 0;)
    {
        switch (pIe->ie_id)
        {
        case FRS_LMI_IE_REP_TYPE:

            if(pIe->ie_len != (u_char)FRS_LMI_IE_REP_TYPE_LEN)
            {
                pCmiCCB->cmi_proto_err++;
                return(FRS_LNK_LMIPROTOERR);
            }

            NoIeRepTypeFlag = FALSE;

            /* Process Report Type IE...
             */
            switch (pIe->ie_type.ie_rep_type.rep_type)
            {
            case FRS_LMI_ASYNC_PVC_STAT:
                /* Update Statistics...
                 */
                pCmiCCB->my_rev_count++;
                pCmiCCB->cmi_total_status_rcvd++;
                break;

            default:
                /* Unknown Report type received...
                 */
                pCmiCCB->cmi_proto_err++;
                return(FRS_LNK_UNKRPT);
            }
            break;

        case FRS_CMI_IE_REMOTE_PVC_STAT:

                local_link_num = (u_short) 
                   pIe->ie_type.ie_remote_pvc_status.remote_link;
                remote_link_num = (u_short)
                   pIe->ie_type.ie_remote_pvc_status.local_link;

                if ( (local_link_num > gGlobalCfg.frsTotalLinks) ||
                     ((pLCB = frsLCBtable[local_link_num-1]) == LCBNULL) )
                    {
                    pCmiCCB->cmi_unexpected_link_num++;
                    return(FRS_LNK_INVDLCI); /* Ignore this status message */
                    }

                dlciMSB = pIe->ie_type.ie_remote_pvc_status.dlciMSB;
                dlciLSB = pIe->ie_type.ie_remote_pvc_status.dlciLSB;
                pvc_status = pIe->ie_type.ie_remote_pvc_status.pvc_status;

                /* Figure out PVC number, and congestion...
                 */
                    pvc_num = (dlciMSB << 4) | ((dlciLSB & 0x78) >> 3);
                    rBECN = dlciLSB & (u_char)FRS_CMI_PVCS_OC2_BECN;
                    rFECN = dlciLSB & (u_char)FRS_CMI_PVCS_OC2_FECN;

                    /* Verify:
                    (1) IE length
                    (2) Precedence of Rep Type IE
                    (3) EXT & SPARE bits in Octet 1 (MSB)
                    (4) EXT & SPARE bits in Octet 2 (LSB)
                    (5) EXT & SPARE bits in Octet 3 (STATUS)
                     */
                    if((pIe->ie_len != (u_char)FRS_CMI_IE_REMOTE_PVCS_LEN) ||
                        (NoIeRepTypeFlag == TRUE) ||
                        ((dlciMSB & (FRS_ANNEXD_PVCS_EX_MASK | 
                                    FRS_ANNEXD_PVCS_OC1_SPARE)) != 0) ||
                        ((dlciLSB & (FRS_ANNEXD_PVCS_EX_MASK |
                                    FRS_ANNEXD_PVCS_OC2_SPARE)) != 
                                    FRS_ANNEXD_PVCS_EX_MASK) ||
                        ((pvc_status & (FRS_ANNEXD_PVCS_EX_MASK |
                                    FRS_ANNEXD_PVCS_OC3_SPARE)) != 
                                    FRS_ANNEXD_PVCS_EX_MASK))
                    {
                        pCmiCCB->cmi_proto_err++;
                        return(FRS_LNK_LMIPROTOERR);
                    }

                pCCB = FRSLocateCCBbyRemoteDLCI( pLCB, 
                          remote_node, remote_link_num, pvc_num);

                switch(pIe->ie_type.ie_remote_pvc_status.pvc_status &
                       FRS_LMI_NBIT_MASK)
                {
                case FRS_LMI_STATUS_EXIST:
                    /* PVC Exists */

                case FRS_LMI_STATUS_ADDED:
                /* N bit Set
                 * WE DO NOT ALLOW CCBS TO BE CREATED (ADDED) VIA CMI.
                 * ALL CCBS MUST BE CREATED VIA THE API OPEN CALL SUCH THAT A
                 * RESPONSIBLE USER PROCESS WILL SERVICE FRAMES RCVD OVER A
                 * CHANNEL.
                 */
                    break;
                }

                /* Mark the CCB active_or_not
                 */
                if(pCCB != CCBNULL)
                {
                    /* Set congestion status...
                     */
                    if ( rBECN )
                        {mSetBECN(pCCB);}
                    else
                        {mResetBECN(pCCB);}
                        
                    if ( rFECN )
                        {mSetFECN(pCCB);}
                    else
                        {mResetFECN(pCCB);}

                    if ( ((u_char)(pIe->ie_type.ie_remote_pvc_status.pvc_status
                             & FRS_LMI_DBIT_MASK)) )
                    {
                        m_FRS_SET_CCB_UP_PND(pCCB);
                    }
                    else
                    {
                        m_FRS_RESET_CCB_UP_PND(pCCB);
                        FRSLmiMarkCCB( pCCB, FALSE);
                          }

                    pCCB->my_rev_count = pCmiCCB->my_rev_count;
                }
                else
                {
                    pCmiCCB->cmi_unexpected_pvc_num++;
                    return(FRS_LNK_LMIPROTOERR);
                }

            break;

        default:
            /* Unsupported message type. Bail out.
             */
            pCmiCCB->cmi_unsupp_ie_msg++;
            return(FRS_LNK_UNKNOWNIE);
        }

        /* Decrement the length of the packet and update ptr...
         */
        len -= (FRS_LMI_COMMON_HDR_LEN + pIe->ie_len);
        pIe = (IE_UN *)((u_char *)pIe + FRS_LMI_COMMON_HDR_LEN + pIe->ie_len);
    }

    /* Finally, verify if indeed we rcvd IE_REP_TYPE and IE_KEEP_ALIVE...
     */
    if(NoIeRepTypeFlag == TRUE)
    {
        pCmiCCB->cmi_no_ie_rep_type++;
        return(FRS_LNK_LMIPROTOERR);
    }

    /* Whew! Everything checks out...Now update net sequence number 
       for LIV ops.
     */
    return(FRS_SUCCESS);
}

/**/
/****************************************************************************

  PROCEDURE NAME:   FRSCmiHeartbeatPoll

  DESCRIPTION:


  PARAMETERS:       none

  RETURNS:          nothing

  NOTES:

*****************************************************************************/
void
FRSCmiHeartbeatPoll(dummy)
u_short dummy;
{
    CMICCB *pCmiCCB = &gCmiCCB;

    /* Send an async status to each remote node with one or more PLLs
       terminated in this node...
     */

    FRSCmiSendStat(pCmiCCB);

    /* Increment remote_node index for next tick...
     */
    pCmiCCB->remote_node++;
    if ( pCmiCCB->remote_node == pCmiCCB->remote_node_count )
      pCmiCCB->remote_node = 0;

    /* Kick the timer...
     */
    FRSTMR_start(pCmiCCB->CmiTimerId, 
            (u_short) ((u_short) (1000 * (u_short) pCmiCCB->cmi_polling_interval)
                            / gCmiCCB.remote_node_count));

    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:   FRSCmiSendStat

  DESCRIPTION:

  This procedure creates a CMI STATUS message and calls the target
  system entrypoint to forward the message to the current remote node.

  PARAMETERS:

    CMICCB         * --- Ptr to the Chan Ctrl Blk (CCB) used for CMI traffic

  RETURNS:

  NOTES:

        This functions assumes that the allocate macro is able to allocate
        a contiguous piece of memory to accomodatethe CMI ststus message:


*****************************************************************************/
void
FRSCmiSendStat(pCmiCCB)
CMICCB *pCmiCCB;
{
    USRBUF *pUsrbuf;
    FR_HDR_CMI *cmi_frm;
    IE_UN  *cmi_data;
    CCB *pScan;
    LCB *pLCB;
    u_short nSize;
    u_short nStats;
    u_short bTimeout = FALSE;
    u_short remote_node;
    u_short link_num;
    u_char *pLkShft;
    u_char stat;

    /* Check for timeout for this remote node...
     */
    if (pCmiCCB->rnode[pCmiCCB->remote_node].timeout
                               == FRS_CMI_TIMER_IDLE)
        pCmiCCB->rnode[pCmiCCB->remote_node].timeout = FRS_CMI_TIMEOUT;
    else if ( --(pCmiCCB->rnode[pCmiCCB->remote_node].timeout) )
                 bTimeout = TRUE;

    /* Find number of local CCBs across all links which have current
       node number in .frs_pll_remote_node field.
    */
    remote_node = pCmiCCB->rnode[pCmiCCB->remote_node].node_num;
    nStats = FRSCmiPLLCount(remote_node);

    /* Allocate appropriate buffer...
     */
    nSize = Q922_ADDR_LEN+FRS_CMI_STATUS_FRAME_LEN+1+nStats *
            sizeof(IE_REMOTE_PVC_STATUS);
    if((pUsrbuf = m_MMGR_ALLOC_BUFR((u_short)nSize)) == BNULL)
        return;

    /* Make room for header...
     */
    m_MMGR_INC_OFF(pUsrbuf,Q922_ADDR_LEN);

    cmi_frm = m_MMGR_DATA( pUsrbuf, FR_HDR_CMI *);

    /* Populate the frame header...
     */
    cmi_frm->frame_type = FRS_UNNUM_INFO_FRAME_TYPE;
    cmi_frm->prot_disc =
        (u_char)FRS_LMI_ANNEXD_PROT_DISC;
    cmi_frm->call_ref = (u_char)gGlobalCfg.frsNodeNum;
    cmi_frm->msg_type = FRS_LMI_STATUS_MSG_TYPE;

    /* Insert locking shift...
     */
    pLkShft = ((u_char *)cmi_frm + sizeof(FR_HDR_CMI));
    *pLkShft++ = FRS_LMI_LOCK_SHIFT;
    cmi_data = (IE_UN *)pLkShft;

    /* Populate mgmt layer data consisting of a REPORT TYPE element
     */
    cmi_data->ie_id = FRS_LMI_IE_REP_TYPE;
    cmi_data->ie_len = FRS_LMI_IE_REP_TYPE_LEN;

    cmi_data->ie_type.ie_rep_type.rep_type = FRS_LMI_ASYNC_PVC_STAT;

    cmi_data = (IE_UN *)((u_char *)cmi_data + 
        FRS_LMI_COMMON_HDR_LEN + 
        FRS_LMI_IE_REP_TYPE_LEN);

    /* Update data count...
     */
    m_MMGR_INC_LEN( pUsrbuf, FRS_CMI_STATUS_FRAME_LEN);

    for (link_num = 1; link_num <= gGlobalCfg.frsTotalLinks; link_num++ )
    {
        /* Get LCB pointer...
         */
        if ( (pLCB = frsLCBtable[link_num - 1]) == LCBNULL )
            continue;

        /* Get first CCB...
         */
        pScan = pLCB->ccbList;

        /* For each CCB on this link...
         */
        while ( pScan)
        {
            if ( pScan->fwd_node != (u_char)remote_node )
            {
                pScan = pScan->next_ccb;
                continue;
            }

            stat = (u_char)FRS_LMI_STATUS_EXIST;

            /*
               Declare active if link is up and no timeout.
               LMI on remote link will handle actual status
               when DTE sends status enquiry.
             */
            if( !bTimeout && (pLCB->State == FRS_LKUP))
                stat = (u_char)(FRS_LMI_STATUS_ACT | FRS_LMI_NBIT_MASK);
            else
                if ( bTimeout )
                    {
                    /* Timeout ! Mark local CCB "down"...
                     */
                    FRSLmiMarkCCB(pScan,FALSE);
                    }

            /* Populate the IE...
             */
            cmi_data->ie_id = (u_char)FRS_CMI_IE_REMOTE_PVC_STAT;
            cmi_data->ie_len = (u_char)FRS_CMI_IE_REMOTE_PVCS_LEN;
            cmi_data->ie_type.ie_remote_pvc_status.local_link =
                                                      (u_char)link_num;
            cmi_data->ie_type.ie_remote_pvc_status.remote_link =
                                                       pScan->fwd_link;
            cmi_data->ie_type.ie_remote_pvc_status.dlciMSB =
                (u_char)(pScan->fwd_dlci >> 4 & 0x3F);
            cmi_data->ie_type.ie_remote_pvc_status.dlciLSB =
                (u_char)(((pScan->fwd_dlci & 0x0F) << 3) | 0x80);
            cmi_data->ie_type.ie_remote_pvc_status.pvc_status = 
                  (stat | (u_char)0x80);
            nSize = FRS_CMI_IE_REMOTE_PVCS_LEN;

            /* Set congestion status...
             */
            if ( m_FRS_IS_CCB_FALLBACK(pScan) )
                cmi_data->ie_type.ie_remote_pvc_status.dlciLSB
                    |= FRS_CMI_PVCS_OC2_BECN;

            /* Update pointers & counters...
             */
            cmi_data = (IE_UN *)((u_char *)cmi_data +                 
                FRS_LMI_COMMON_HDR_LEN + nSize);
            m_MMGR_INC_LEN( pUsrbuf, (FRS_LMI_COMMON_HDR_LEN + nSize));

            pScan = pScan->next_ccb;
        }
    }

    /* Ship it...
     */
    if ( sys_send_cmi(remote_node, pUsrbuf) == FRS_SUCCESS )
        pCmiCCB->cmi_total_status_sent++;
    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:   FRSCmiPLLCount

  DESCRIPTION:

  This procedure scans for local CCBs linked to the specified remote node.

  PARAMETERS:

    u_short           --- node number of remote node

  RETURNS: count of CCBs with .fwd_node == remote_node

  NOTES:


*****************************************************************************/
u_short
FRSCmiPLLCount(remote_node)
u_short remote_node;
{
u_short  link_num;
u_short  count = 0;
LCB      *pLCB;
CCB      *pScan;

    for (link_num = 1; link_num <= gGlobalCfg.frsTotalLinks; link_num++ )
    {
        /* Get LCB pointer...
         */
        if ( (pLCB = frsLCBtable[link_num - 1]) == LCBNULL )
            continue;

        /* Get first CCB...
         */
        pScan = pLCB->ccbList;

        /* For each CCB on this link...
         */
        while ( pScan)
        {
            /* If it's linked to the specified remote node, count it.
             */
            if ( pScan->fwd_node == (u_char)remote_node )
                count++;

            /* next...
             */
            pScan = pScan->next_ccb;
        }
    }
    return (count);
}

/**/
/****************************************************************************

  PROCEDURE NAME:   FRSCmiInsertRemoteNode

  DESCRIPTION:

  This procedure adds the specified node to CMI polling list.

  PARAMETERS:

    u_short           --- node number of remote node

  RETURNS: Nothing.

  NOTES:


*****************************************************************************/
void
FRSCmiInsertRemoteNode(remote_node)
u_short remote_node;
{
u_short i;

    /* See if it's already here...
     */
    for (i = 0; i < gCmiCCB.remote_node_count; i++ )
    {
    if ( gCmiCCB.rnode[i].node_num == remote_node )
        return;
    }

    /* Not found, so add it...
     */
    gCmiCCB.rnode[gCmiCCB.remote_node_count].node_num = remote_node;
    gCmiCCB.rnode[gCmiCCB.remote_node_count].timeout = FRS_CMI_TIMER_IDLE;
    gCmiCCB.remote_node_count++;

    /* If it's the first one, create CMI timer...
     */
    if ( gCmiCCB.remote_node_count == 1 )
        {
        gCmiCCB.CmiTimerId = 
               FRSTMR_create( 0,
                  (void ( * )( u_long ))FRSCmiHeartbeatPoll,0 );
        FRSTMR_start(gCmiCCB.CmiTimerId, 
            (u_short) (1000 * (u_short) gCmiCCB.cmi_polling_interval));
        }

    return;
}

/**/
/****************************************************************************

  PROCEDURE NAME:   FRSLocateCCBbyRemoteDLCI

  DESCRIPTION:

  This procedure locates a CCB using a remote DLCI number

  PARAMETERS:

    LCB *        - pointer to LCB to scan
    u_short      - remote node number
    u_short      - remote link number
    u_short      - remote DLCI

  RETURNS: pointer to CCB ( CCBNULL if not found ).

  NOTES:


*****************************************************************************/
CCB *
FRSLocateCCBbyRemoteDLCI( pLCB, remote_node_num, remote_link_num, pvc_num)
LCB     *pLCB;
u_short remote_node_num;
u_short remote_link_num;
u_short pvc_num;
{
CCB      *pScan;

    /* Get first CCB...
     */
    pScan = pLCB->ccbList;

    /* For each CCB on this link...
     */
    while ( pScan)
    {
        /* If it's linked to the specified remote node/link,
           return it.
         */
        if ( (pScan->fwd_node == (u_char)remote_node_num)
                    && (pScan->fwd_link == (u_char)remote_link_num)
              && (pScan->fwd_dlci == pvc_num) )
            break;

            /* next...
         */
        pScan = pScan->next_ccb;
    }

    return (pScan);
}
#endif /* FRS_DCE_SUPPORT */
