/*************************************************************************
  (c) Copyright.  Digital Equipment Corporation, 1995.  All Rights
  Reserved.

  Permission is hereby granted to use, copy, modify, or enhance this 
  software freely, as long as the foregoing copyright of Digital Equipment
  Corporation and this notice are retained on the software.  This 
  software may not be distributed or sublicensed for a fee.  Digital      
  makes this software available "AS IS" and without warranties of any
  kind.  
 *************************************************************************/
/*
 * Marko Kiiskila carnil@cs.tut.fi 
 * 
 * Tampere University of Technology - Telecommunications Laboratory
 *
 * Permission to use, copy, modify and distribute this
 * software and its documentation is hereby granted,
 * provided that both the copyright notice and this
 * permission notice appear in all copies of the software,
 * derivative works or modified versions, and any portions
 * thereof, that both notices appear in supporting
 * documentation, and that the use of this software is
 * acknowledged in any publications resulting from using
 * the software.
 * 
 * TUT ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION AND DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS
 * SOFTWARE.
 * 
 */

/*
* Module Name:
*   lec_ctrl.c
*   
* Overview:
*   This module handles the control aspects of LAN Emulation Clienthood
*   (with the exception of LE-ARP functions which are handled by the
*   lec_arp.c module).  This module is responsible for the following:
*
*   - Setup and Teardown of all LAN Emulation SVCs.
*   - Handling of all received LE Control Frames.
*   - Composition and Transmission of all LE Control Frames.
*   - Sequencing of Emulated LAN Join procedures.
*
*   One of the central parts of this module is the ELAN Join state machine.
*   The state machine controls the sequencing of actions necessary to join
*   an Emulated LAN.  The state machine provides the option to join with or
*   without the help of a LAN Emulation Configuration Server.
*
* Authors:
*   TLR - Theodore L. Ross
*
* Modification History:
*   Date       Name  Description 
*   18-Jan-95  TLR   Created.
*    1-Aug-95  TLR   Proxy hooks added.
*   21-Sep-95  TLR   Added address registration feature.
*
*/

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#include <atm.h>

/* Include General Headers. */

#include "g_types.h"    /* General Purpose Types                 */
#include "codes.h"      /* Return Status Codes                   */
#include "g_endian.h"   /* Host/Network Data Conversion Routines */
#include "utl.h"        /* General Purpose Utilities             */
#include "utl_os.h"     /* Operating System Utilities            */
#include "atm.h"        /* ATM Specific Definitions and Macros   */
#include "atmsap.h"	/* ATM SAP definition			 */
#include "system.h"     /* ATM Subsystem Specific Stuff          */
#include "g_event.h"

/* Include Headers for Mapping Module Interfaces. */

#include "af_lane.h"    /* ATM Forum LAN Emulation Definitions   */
#include "cm.h"
#include "svc_info.h"   /* SVC Data Structures                   */
#include "cm_sap.h"     /* CM SAP Interface                      */
#include "addr_reg.h"   /* Address Registration Interface        */
#include "lec.h"        /* LAN Emulation Protocol Interface      */
#include "lec_arp.h"    /* LEC_ARP Module Interface              */
#include "lec_ctrl.h"   /* LEC_CTRL Module Interface             */
#include "le_disp.h"

#define EMOD   MOD_LEC_CTRL
#define EINST  "lec_ctrl.c"
#include "emask.h"

typedef struct _reg_dest {
  ESI                mac_addr;
  BOOLEAN            registered;
  BOOLEAN            unregistering;
  UINT32             tran_id;
} REG_DEST;

/*  LEC_STATE
 *
 *    This structure contains the state values defined in the ATM Forum LAN
 *    Emulation Specification V1.0 (see section 5.1.1).  The values in this
 *    structure are prefixed with their C# designation used in the
 *    specification.  Refer to the specification for detailed descriptions of
 *    these values.
 */
typedef struct {
  ADDR_ATM       c1_my_atm_addr;
  LAN_TYPE       c2_lan_type;
  LAN_MTU        c3_lan_mtu;
  BOOLEAN        c4_proxy_flag;
  UINT8          elan_name_size; /* For config request */
  char           c5_elan_name[32]; /* For config request */
  UINT8          elan_name_size_join;   /* For join request */
  char           c5_elan_name_join[32]; /* For join request */
  REG_DEST       c6_my_mac_addr; 
  UINT16         c7_control_timeout;
  ADDR_ATM       c9_les_atm_addr;
  UINT16         c10_max_unknown_frame_count;
  UINT16         c11_max_unknown_frame_time;
  UINT32         c12_vcc_timeout;
  UINT16         c13_max_retry_count;
  UINT16         c14_lec_id;
  UINT32         c17_aging_time;
  UINT16         c18_forward_delay_time;
  BOOLEAN        c19_topology_change_flag;
  UINT16         c20_le_arp_response_time;
  UINT16         c21_flush_timeout;
  UINT16         c22_path_switching_delay;
} LEC_STATE;

typedef struct
   {
   UINT16   new_state;
   UINT16   event;
   } SM_STATE_HISTORY;

#define SM_STATE_HISTORY_DEPTH   4

/*  LC_ELAN_CONTEXT
 *
 *    This structure contains the data required for each ELAN that this module
 *    is joined to.  An instance of this structure is created each time
 *    lc_register is executed.
 *
 *  p_next
 *    Pointer to enother instance of this structure.  This allows the struture
 *    to be stored in a simple list.
 *
 *  lc_handle
 *    Handle of the created LEC_CTRL module.  This handle is generated by the
 *    lc_create function.
 *
 *  la_elan_handle
 *    The handle of the LEC_ARP ELAN instance generated by la_register.  The
 *    LEC_ARP ELAN instance is a peer to this LEC_CTRL instance.
 *
 *  ld_elan_handle
 *    The handle of the LEC_DATA ELAN instance that called lc_register.  The
 *    LEC_DATA ELAN instance is a peer to this LEC_CTRL instance.
 *
 *  cm_handle
 *    The Connection Manager handle generated by cm_create.  This handle
 *    represents the instance of the CM that is to be used by this LEC_CTRL
 *    instance.
 *
 *  addr_reg_handle
 *    The Address Registration handle to be used when taking advantage of the
 *    services of the Address Registration Module.
 *
 *  addr_reg_client_handle
 *    The handle returned by the address registration client registration.
 *
 *  sap_handle
 *    The handle returned by cm_sap_register for the LAN Emulation NSAP.
 *    This SAP instance carries all LAN Emulation traffic.
 *
 *  direct_conn_context
 *    Connection context for data-direct VCCs.
 *
 *  mcast_conn_context
 *    Connection context for multicast VCCs.
 *
 *  ctrl_conn_context
 *    Connection context for control VCCs (both LECS and LES VCCs).
 *
 *  ctrl_direct_conn_handle
 *    Connection handle of the Control Direct VCC.
 *
 *  ctrl_dist_conn_handle
 *    Connection handle of the Control Distribute VCC.
 *
 *  mcast_send_conn_handle
 *    Connection handle of the Multicast Send VCC.
 *
 *  mcast_rcv_conn_handle
 *    Connection handle of the Multicast Receive VCC.
 *
 *  lecs_conn_handle
 *    Connection handle of the Configuration Direct VCC.
 *
 *  sm_timer
 *    Handle of the State-Machine timer.  This timer is used to implement
 *    time-outs in the state machine.
 *
 *  sm_state
 *    The current state of the LEC_CTRL state machine.
 *
 *  sm_timer_retry_count
 *    The number of remaining retries for the current state machine state.
 *
 *  sm_timer_duration
 *    The time (in seconds) that was used in the last timer set.
 *
 *  lec_state
 *    The current LEC state (see LEC_STATE structure).
 *
 *  lecs_addr
 *    The ATM address of the LECS that is currently being contacted.
 *
 *  skip_lecs
 *    Boolean flag that indicates that the LECS should not be contacted in the
 *    provecc of joining the emulated LAN.  Instead, it should be assumed that
 *    the lec_state.c9_les_atm_addr has been pre-loaded with the correct ATM
 *    address for the LAN Emulation Server.
 *
 *  bus_addr
 *    The ATM Address of the BUS being contacted.
 *
 *  qos_spec
 *    The QOS specification string for SVCs. NULL if default values (i.e. UBR
 *    at link speeed) should be used. This data is obtained from the UME.
 *
 *  current_tran_id
 *    Current transaction ID to be used for the next LE Control frame.
 *
 *  vc_ready_mask
 *    Bit mask of required LAN Emulation VCCs.  This is used to get a quick
 *    indication of which VCCs are ready for use.
 *
 *  illegal_frame_rcv_count
 *    Count of illegal frames received.
 *
 *  ctrl_xmt_failure_count
 *    Count of failures during control frame transmits
 *
 *  illegal_transition_count
 *    Count of illegal state machine transitions.
 */
typedef struct _lc_elan
   {
   struct _lc_elan  *p_next;
   char             *p_text;
   HANDLE            lc_handle;
   HANDLE            la_elan_handle;
   HANDLE            ld_elan_handle;
   HANDLE            addr_reg_handle;
   HANDLE            addr_reg_client_handle;
   HANDLE            lport_handle;
   ATM_LINK_STATE    link_state;
   HANDLE            sap_handle;
   HANDLE            direct_conn_context;
   HANDLE            mcast_conn_context;
   HANDLE            ctrl_conn_context;
   HANDLE            ctrl_direct_conn_handle;
   HANDLE            ctrl_dist_conn_handle;
   HANDLE            mcast_send_conn_handle;
   HANDLE            mcast_rcv_conn_handle;
   HANDLE            lecs_conn_handle;
   HANDLE            sm_timer;
   UINT16            sm_state;
   UINT8             sm_timer_retry_count;
   BOOLEAN           sm_timer_retry_flag;
   UINT16            sm_timer_duration;
   LEC_STATE         lec_state;
   ADDR_ATM          lecs_addr;
   INIT_METHOD       init_method;
   ADDR_ATM          bus_addr;
   HANDLE            reg_timer;
   SM_STATE_HISTORY  sm_state_history[SM_STATE_HISTORY_DEPTH];
   UINT8             sm_state_history_index;
   const char       *qos_spec;
   UINT32            current_tran_id;
   UINT8             vc_ready_mask;
   UINT32            illegal_frame_rcv_count;
   UINT32            ctrl_xmt_failure_count;
   UINT32            illegal_transition_count;
   UINT32            ctrl_frames_sent;
   UINT32            ctrl_frames_rcvd;
   UINT32            arps_sent;
   UINT32            arps_rcvd;
   } LC_ELAN_CONTEXT;

SIMPLE_LIST (LC_ELAN_CONTEXT, ELAN_LIST);

typedef void (*ACTION_ROUTINE) (HANDLE lc_elan_handle);

typedef struct _sm_table
   {
   struct _sm_table  *p_next;
   UINT16             curr_state;
   UINT16             sm_event;
   UINT16             next_state;
   ACTION_ROUTINE     action1;
   ACTION_ROUTINE     action2;
   ACTION_ROUTINE     action3;
   } STATE_TBL_ENTRY;

SIMPLE_LIST (STATE_TBL_ENTRY, STATE_LIST);

/* LC_CONTEXT
 *
 *  This structure contains data used by the LEC_CTRL super-component.  An
 *  instance of this strutcure is created each time lc_create is executed.
 *
 *  os_handle
 *    Handle of the OS utilities.  Used for memory allocation and timer
 *    operations.
 *
 *  cm_handle
 *    Handle of the Connection Manager instance to be used by this LEC_CTRL
 *    instance.
 *
 *  la_handle
 *    Handle of the peer LEC_ARP module instance used by this LEC_CTRL
 *    instance.
 *
 *  elan_status_callback
 *    Callback to indicate changes in the ELAN availability.
 *
 *  elan_list
 *    List of ELAN Context blocks under this LEC_CTRL instance.  There is one
 *    for every ELAN that is joined to.
 */
typedef struct _lc_context
   {
   HANDLE                  line_up_handle;
   HANDLE                  la_handle;
   ELAN_STATUS_CALLBACK    elan_status_callback;
   ELAN_LIST               elan_list;
   STATE_LIST              sm_use_lecs;
   STATE_LIST              sm_skip_lecs;
   } LC_CONTEXT;

/* Bit mask definitions for the vc_ready_mask. */

#define READY_LECS         1
#define READY_CTRL_DIRECT  2
#define READY_CTRL_DIST    4
#define READY_MCAST_SEND   8
#define READY_MCAST_RCV    16


/* Protos */
static const char *state_name (UINT16 s);
static const char *event_name (UINT16 s);
static void tlv_parse (LC_ELAN_CONTEXT  *p_elan,
		       UINT8             buffer[],
		       UINT32            length,
		       UINT8            *p_tlv_index);
/*++
* =====================
* = lc_conn_info_make =
* =====================
*
* Status:   IMPLEMENTATION COMPLETE
*
* Overview:
*   Generates a CONN_INFO block for SVC setup.
*
* Arguments:
*   lc_elan_handle   - (IN)  Handle returned by lc_register.
*   called_party     - (IN)  ATM address of called party.
*   blli_codepoint   - (IN)  16-bit BLLI codepoint for the connection.
*   calling_party    - (IN)  ATM address of calling party.
*   p_conn_info      - (OUT) Generated CONN_INFO block.
*
* Returns:
*   None
*
* Description:
*   Fill the required SVC information elements based on the currently
*   supported UNI version.
*
* Issues:
*   None
*
--*/
static void lc_conn_info_make (HANDLE      lc_elan_handle,
                               ADDR_ATM    called_party,
                               UINT16      blli_codepoint,
                               BOOLEAN     pmp,
                               CONN_INFO  *p_conn_info)
{
  LC_ELAN_CONTEXT   *p_elan;
  unsigned int bllicode;
  int i;
  
  EVENT(EM_DEBUG,("Lc_conn_info_make, blli codepoint %x\n",blli_codepoint));

  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  
  svcinit_conn_info (p_conn_info);
  
  p_conn_info->addr.sas_family = AF_ATMSVC;
  
    /* Set the forward and backward Max CPCS-SDU Size */
  switch(blli_codepoint) {
  case BLLI_CONTROL:
  case BLLI_DIRECT_802_3:
  case BLLI_BUS_802_3:
    p_conn_info->conqos.rxtp.max_sdu = 1516;
    p_conn_info->conqos.txtp.max_sdu = 1516;
    break;
  case BLLI_DIRECT_802_5:
  case BLLI_BUS_802_5:
    p_conn_info->conqos.rxtp.max_sdu = 4544;
    p_conn_info->conqos.txtp.max_sdu = 4544;
    break;
  default:
    EVENT(EM_ASSERT,("Unknown BLLI codepoint %x\n",blli_codepoint));
  }
  /* ATM User Cell Rate/ATM Traffic Descriptor. */
  p_conn_info->conqos.txtp.traffic_class = ATM_UBR;
  p_conn_info->conqos.rxtp.traffic_class = ATM_UBR;
   
  if (p_elan->qos_spec)
    if (text2qos(p_elan->qos_spec,&p_conn_info->conqos,T2Q_DEFAULTS) < 0)
      EVENT(EM_ASSERT,("text2qos failed in lc_conn_info_make\n"));

  if (pmp==TRUE) {
    /* Asdf */
  } else {
    /* Fdsa */
  }

  /* Broadband Lower Layer Information. */
  p_conn_info->blli.l3_proto = ATM_L3_TR9577;
  p_conn_info->blli.l3.tr9577.ipi = NLPID_IEEE802_1_SNAP;
  p_conn_info->blli.l3.tr9577.snap[0] = 0x00;
  p_conn_info->blli.l3.tr9577.snap[1] = 0xa0;
  p_conn_info->blli.l3.tr9577.snap[2] = 0x3e;
  bllicode = hton16(blli_codepoint);
  p_conn_info->blli.l3.tr9577.snap[3] = (unsigned char)(0xff&bllicode);
  p_conn_info->blli.l3.tr9577.snap[4] = (unsigned char)(0xff&(bllicode>>8));

  if (EMASK & EM_DEBUG)
    for(i=0;i<5;i++) {
      printf("snap[%d] = 0x%2.2x\n",i,p_conn_info->blli.l3.tr9577.snap[i]);
    }
   /* Called Party Number. */
  p_conn_info->addr.sas_addr.prv[0] = called_party.prefix[0];
  p_conn_info->addr.sas_addr.prv[1] = called_party.prefix[1];
  p_conn_info->addr.sas_addr.prv[2] = called_party.prefix[2];
  p_conn_info->addr.sas_addr.prv[3] = called_party.prefix[3];
  p_conn_info->addr.sas_addr.prv[4] = called_party.prefix[4];
  p_conn_info->addr.sas_addr.prv[5] = called_party.prefix[5];
  p_conn_info->addr.sas_addr.prv[6] = called_party.prefix[6];
  p_conn_info->addr.sas_addr.prv[7] = called_party.prefix[7];
  p_conn_info->addr.sas_addr.prv[8] = called_party.prefix[8];
  p_conn_info->addr.sas_addr.prv[9] = called_party.prefix[9];
  p_conn_info->addr.sas_addr.prv[10] = called_party.prefix[10];
  p_conn_info->addr.sas_addr.prv[11] = called_party.prefix[11];
  p_conn_info->addr.sas_addr.prv[12] = called_party.prefix[12];
  p_conn_info->addr.sas_addr.prv[13] = called_party.esi[0];
  p_conn_info->addr.sas_addr.prv[14] = called_party.esi[1];
  p_conn_info->addr.sas_addr.prv[15] = called_party.esi[2];
  p_conn_info->addr.sas_addr.prv[16] = called_party.esi[3];
  p_conn_info->addr.sas_addr.prv[17] = called_party.esi[4];
  p_conn_info->addr.sas_addr.prv[18] = called_party.esi[5];
  p_conn_info->addr.sas_addr.prv[19] = called_party.sel;
   /* Calling Party Number. */
}


/*++
* ======================
* = lc_conn_info_check =
* ======================
*
* Status:   IMPLEMENTATION COMPLETE
*
* Overview:
*   Check the information elements of an incoming SVC request and determine if
*   it is valid to be accepted.  Furthermore, extract certain information from
*   the IEs.
*
* Arguments:
*   lc_elan_handle    - (IN)  Handle returned by lc_register.
*   p_conn_info       - (IN)  Conn Info block containing the IEs.
*   p_blli_codepoint  - (OUT) The BLLI codepoint of the received request.
*   p_pmp             - (OUT) Point-to-Multipoint indication.  Iff true, this
*                             is a point-to-multipoint connection.
*
* Returns:
*   STATUS_K_SUCCESS    - The connection info is valid and appropriate for
*                         this ELAN instance.
*   STATUS_K_MISMATCH   - The connection is not appropriate for this ELAN
*                         instance but might be for another NSAP.
*   STATUS_K_REJECT     - The connection is destined for this ELAN instance
*                         but has a problem (i.e. missing information) that
*                         makes it necessary to reject the connection.
*
* Description:
*   Check the information elements based on the currently supported UNI
*   version.
*
* Issues:
*   None
*
--*/
static STATUS lc_conn_info_check (HANDLE      lc_elan_handle,
                                  CONN_INFO  *p_conn_info,
                                  UINT16     *p_blli_codepoint,
                                  BOOLEAN    *p_pmp)
{
   LC_ELAN_CONTEXT   *p_elan;
/*   int                len, i, ie_count;
   unsigned int       rate;
*/
   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   assert(p_conn_info);
   /* Check the called party address.  If it is not equal to our address,
    * return STATUS_K_MISMATCH.
    */


   /* Check the BLLI.  If it is not relevant to LAN Emulation, return
    * STATUS_K_MISMATCH.
    */

      /* Make sure the first bytes are 6B.40.80.80.00.A0.3E */


   /* Extract the BLLI Codepoint from the BLLI data. */
   
      /* Put codepoint in *p_blli_codepoint */

   *p_blli_codepoint = 
     ((unsigned short)0xff00&(p_conn_info->blli.l3.tr9577.snap[3]<<8)) |
     ((unsigned short)0xff&(p_conn_info->blli.l3.tr9577.snap[4]));

   /* Check the AAL Parameters. */

   /* Check the ATM Traffic Descriptor. */

   /* Check Broadband Bearer Capability. */

   /* Check QoS. */

   return STATUS_K_SUCCESS;
}


/*++
* ====================
* = lc_ctrl_hdr_make =
* ====================
*
* Status:   IMPLEMENTATION COMPLETE
*
* Overview:
*   Initialize the general header of a LEC control frame.  The status and
*   flags fields are set to zero.
*
* Arguments:
*   lc_elan_handle  - (IN)  Handle returned by lc_elan_register.
*   p_hdr           - (OUT) Header of a LEC frame.
*   op_code         - (IN)  Op code to be placed in the LEC header.
*
* Returns:
*   The transaction ID that was placed in this header (in host byte-order).
*
* Preconditions:
*   Sufficient memory is allocated at p_hdr to hold the header.
*
* Postconditions:
*   The header contains the op_code, success status, null flags, the correct
*   lec_id, and appropriate constant data.
*
* Description:
*   Data is placed in the header structure using the endian conversion
*   routines.
*
* Issues:
*   None
*
--*/
static UINT32 lc_ctrl_hdr_make (HANDLE       lc_elan_handle,
                                LE_CTRL_HDR *p_hdr,
                                UINT16       op_code)
   {
   LC_ELAN_CONTEXT   *p_elan;
   UINT32             tran_id;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   p_hdr->marker     = hton16 (LE_CTRL_MARKER);
   p_hdr->protocol   = LE_CTRL_PROTOCOL;
   p_hdr->version    = LE_CTRL_VERSION;
   p_hdr->op_code    = hton16 (op_code);
   p_hdr->status     = hton16 (0);
   p_hdr->tran_id    = hton32 (p_elan->current_tran_id);
   p_hdr->req_lec_id = hton16 (p_elan->lec_state.c14_lec_id);
   p_hdr->flags      = hton16 (0);

   tran_id = p_elan->current_tran_id;
   p_elan->current_tran_id += 1;

   return tran_id;
   }

/****************************************************************************
 *  LAN Destination Registration Code
 ****************************************************************************/

static void reg_req_xmt (HANDLE     lc_elan_handle,
                         REG_DEST  *p_dest,
                         BOOLEAN    reg)
   {
   LC_ELAN_CONTEXT   *p_elan;
   LE_REG_FRAME       frame;
   STATUS             status;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   memset(&frame, 0, sizeof (LE_REG_FRAME));

   if (reg)
      {
      p_dest->tran_id = lc_ctrl_hdr_make (lc_elan_handle,
                                          &frame.hdr,
                                          LE_REGISTER_REQ);
      }
   else
      {
      p_dest->tran_id = lc_ctrl_hdr_make (lc_elan_handle,
                                          &frame.hdr,
                                          LE_UNREGISTER_REQ);
      }

   frame.src_lan_dst.tag = hton16 (TAG_MAC_ADDR);
   ESI_COPY (p_dest->mac_addr, frame.src_lan_dst.mac_addr);
   ATM_COPY (p_elan->lec_state.c1_my_atm_addr, frame.src_atm_addr);

   if (reg)
      {
      EVENT (EM_XCTRL, ("Sending Register Request.\n"));
      }
   else
      {
      EVENT (EM_XCTRL, ("Sending Unregister Request.\n"));
      }

   if (p_elan->ctrl_direct_conn_handle != NULL)
      {
      status =  cm_sap_xmt_vc (p_elan->ctrl_direct_conn_handle,
                               &frame,
                               sizeof (frame),
                               USER_DATA_INTERNAL,
                               NULL);
      if (status != STATUS_K_SUCCESS)
         p_elan->ctrl_xmt_failure_count += 1;
      else
         p_elan->ctrl_frames_sent += 1;
      }
   }

static void 
reg_timer_handler (HANDLE lc_elan_handle)
{
  LC_ELAN_CONTEXT   *p_elan;
  BOOLEAN            request_sent;
  
  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  
  request_sent = FALSE;
  
  /* Our LAN address. If it has
   * not been successfully registered, send a register-request to the LES.
   * If it is flagged as unregistering, send
   * an unregister-request.
   */
  if ((!p_elan->lec_state.c6_my_mac_addr.registered) && 
      (!p_elan->lec_state.c6_my_mac_addr.unregistering)) {
    reg_req_xmt (lc_elan_handle, 
		 &p_elan->lec_state.c6_my_mac_addr, TRUE);
    request_sent = TRUE;
  }
  if ((!p_elan->lec_state.c6_my_mac_addr.registered) && 
      (p_elan->lec_state.c6_my_mac_addr.unregistering)) {
    reg_req_xmt (lc_elan_handle, 
		 &p_elan->lec_state.c6_my_mac_addr, FALSE);
    request_sent = TRUE;
  }
  if (request_sent) {
    os_timer_set (p_elan->reg_timer,
		  p_elan->lec_state.c7_control_timeout * 1000);
  }
}

/****************************************************************************
 *  State Machine Definitions
 ****************************************************************************/

/*
 * State Values
 */
#define S_INITIAL             101
#define S_ATM_WAIT            102
#define S_ILMI_WAIT           103
#define S_LECS_SVC_WAIT       104
#define S_LECS_WSVC_WAIT      105
#define S_LECS_PVC_WAIT       106
#define S_CONFIG_WAIT         107
#define S_LES_SVC_WAIT        108
#define S_JOIN_WAIT           109
#define S_BUS_ARP_WAIT        110
#define S_BUS_SVC_WAIT        111
#define S_OPERATIONAL         112
#define S_DISABLED            113

/*
 * Event Values
 */
#define E_JOIN_START          201
#define E_AREG_DONE           202
#define E_ILMI_NEW_ADDR       203
#define E_ILMI_NO_MORE        204
#define E_LECS_SVC_READY      205
#define E_LECS_SVC_RELEASE    206
#define E_LECS_PVC_READY      207
#define E_LES_SVC_READY       208
#define E_LES_SVC_RELEASE     209
#define E_BUS_SVC_READY       210
#define E_BUS_SVC_REL_NORM    211
#define E_BUS_SVC_REL_ABNORM  212
#define E_TIMEOUT_RETRY       213
#define E_TIMEOUT_NORETRY     214
#define E_RCV_CONFIG_RSP      215
#define E_RCV_JOIN_RSP        216
#define E_RCV_BUS_ARP_RSP     217
#define E_AREG_CANCELED       218

/*
 * Event Handlers (Function Prototypes)
 */
static void act_atm_addr_req        (HANDLE lc_elan_handle);
static void act_atm_addr_cancel     (HANDLE lc_elan_handle);
static void act_ilmi_ld_1st_get     (HANDLE lc_elan_handle);
static void act_ilmi_next_get       (HANDLE lc_elan_handle);
static void act_lecs_svc_setup      (HANDLE lc_elan_handle);
static void act_lecs_wsvc_setup     (HANDLE lc_elan_handle);
static void act_config_req_xmt      (HANDLE lc_elan_handle);
static void act_les_svc_setup       (HANDLE lc_elan_handle);
static void act_join_req_xmt        (HANDLE lc_elan_handle);
static void act_bus_arp_req_xmt     (HANDLE lc_elan_handle);
static void act_bus_svc_setup       (HANDLE lc_elan_handle);
static void act_lecs_vc_teardown    (HANDLE lc_elan_handle);
static void act_les_svc_teardown    (HANDLE lc_elan_handle);
static void act_bus_svc_teardown    (HANDLE lc_elan_handle);
static void act_timer_cancel        (HANDLE lc_elan_handle);
static void act_ctrl_list_close     (HANDLE lc_elan_handle);

#define TRAN(curr, in_event, next, act1, act2, act3)                       \
   {                                                                       \
   p_tran = os_mem_alloc (sizeof (STATE_TBL_ENTRY)); \
   if (p_tran == NULL)                                                     \
      return STATUS_K_RESOURCES;                                           \
                                                                           \
   p_tran->curr_state = curr;                                              \
   p_tran->sm_event   = in_event;                                          \
   p_tran->next_state = next;                                              \
   p_tran->action1    = act1;                                              \
   p_tran->action2    = act2;                                              \
   p_tran->action3    = act3;                                              \
                                                                           \
   utl_list_add (*p_sm, p_tran);                                           \
   }

const char *state_name (UINT16 s)
   {
   switch (s)
      {
      case S_INITIAL:         return ("S_INITIAL");
      case S_ATM_WAIT:        return ("S_ATM_WAIT");
      case S_ILMI_WAIT:       return ("S_ILMI_WAIT");
      case S_LECS_SVC_WAIT:   return ("S_LECS_SVC_WAIT");
      case S_LECS_WSVC_WAIT:  return ("S_LECS_WSVC_WAIT");
      case S_LECS_PVC_WAIT:   return ("S_LECS_PVC_WAIT");
      case S_CONFIG_WAIT:     return ("S_CONFIG_WAIT");
      case S_LES_SVC_WAIT:    return ("S_LES_SVC_WAIT");
      case S_JOIN_WAIT:       return ("S_JOIN_WAIT");
      case S_BUS_ARP_WAIT:    return ("S_BUS_ARP_WAIT");
      case S_BUS_SVC_WAIT:    return ("S_BUS_SVC_WAIT");
      case S_OPERATIONAL:     return ("S_OPERATIONAL");
      case S_DISABLED:        return ("S_DISABLED");
      default:                return ("<unknown>");
      }
   }

const char *event_name (UINT16 s)
   {
   switch (s)
      {
      case E_JOIN_START:         return ("E_JOIN_START");
      case E_AREG_DONE:          return ("E_AREG_DONE");
      case E_ILMI_NEW_ADDR:      return ("E_ILMI_NEW_ADDR");
      case E_ILMI_NO_MORE:       return ("E_ILMI_NO_MORE");
      case E_LECS_SVC_READY:     return ("E_LECS_SVC_READY");
      case E_LECS_SVC_RELEASE:   return ("E_LECS_SVC_RELEASE");
      case E_LECS_PVC_READY:     return ("E_LECS_PVC_READY");
      case E_LES_SVC_READY:      return ("E_LES_SVC_READY");
      case E_LES_SVC_RELEASE:    return ("E_LES_SVC_RELEASE");
      case E_BUS_SVC_READY:      return ("E_BUS_SVC_READY");
      case E_BUS_SVC_REL_NORM:   return ("E_BUS_SVC_REL_NORM");
      case E_BUS_SVC_REL_ABNORM: return ("E_BUS_SVC_REL_ABNORM");
      case E_TIMEOUT_RETRY:      return ("E_TIMEOUT_RETRY");
      case E_TIMEOUT_NORETRY:    return ("E_TIMEOUT_NORETRY");
      case E_RCV_CONFIG_RSP:     return ("E_RVC_CONFIG_RSP");
      case E_RCV_JOIN_RSP:       return ("E_RCV_JOIN_RSP");
      case E_RCV_BUS_ARP_RSP:    return ("E_RCV_BUS_ARP_RSP");
      case E_AREG_CANCELED:      return ("E_AREG_CANCELED");
      default:                   return ("<unknown>");
      }
   }


/*++
* ==================
* = state_tbl_init =
* ==================
*
* Status:   IMPLEMENTATION COMPLETE
*
* Overview:
*   Initialize the control state machine table.  This table contains entries
*   for each of the transitions in the LEC state machine.
*
* Arguments:
*   None
*
* Returns:
*   None
*
* Preconditions:
*   None
*
* Postconditions:
*   The state machine is ready to operate
*
* Description:
*
* Issues:
*   None
*
--*/
static STATUS state_tbl_init (HANDLE lc_handle)
   {
   LC_CONTEXT       *p_context;
   STATE_TBL_ENTRY  *p_tran;
   STATE_LIST       *p_sm;

   p_context = (LC_CONTEXT *) lc_handle;

   utl_list_init (p_context->sm_use_lecs);
   utl_list_init (p_context->sm_skip_lecs);

   p_sm = &p_context->sm_use_lecs;

   /*    Current State     Event                  Next State        Action 1              Action 2              Action 3
    *-------------------------------------------------------------------------------------------------------------------*/
   TRAN (S_INITIAL,        E_JOIN_START,          S_ATM_WAIT,       act_atm_addr_req,     NULL,                 NULL);
   TRAN (S_ATM_WAIT,       E_AREG_DONE,           S_ILMI_WAIT,      act_ilmi_ld_1st_get,  NULL,                 NULL);
   TRAN (S_ILMI_WAIT,      E_ILMI_NEW_ADDR,       S_LECS_SVC_WAIT,  act_timer_cancel,     act_lecs_svc_setup,   NULL);
   TRAN (S_ILMI_WAIT,      E_ILMI_NO_MORE,        S_LECS_WSVC_WAIT, act_timer_cancel,     act_lecs_wsvc_setup,  NULL);
   TRAN (S_ILMI_WAIT,      E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_ilmi_ld_1st_get,  NULL,                 NULL);
   TRAN (S_LECS_SVC_WAIT,  E_LECS_SVC_READY,      S_CONFIG_WAIT,    act_timer_cancel,     act_config_req_xmt,   NULL);
   TRAN (S_LECS_SVC_WAIT,  E_LECS_SVC_RELEASE,    S_ILMI_WAIT,      act_timer_cancel,     act_ilmi_next_get,    NULL);
   TRAN (S_LECS_SVC_WAIT,  E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_ilmi_next_get,    NULL,                 NULL);
   TRAN (S_LECS_WSVC_WAIT, E_LECS_SVC_READY,      S_CONFIG_WAIT,    act_timer_cancel,     act_config_req_xmt,   NULL);
   TRAN (S_LECS_WSVC_WAIT, E_LECS_SVC_RELEASE,    S_ILMI_WAIT,      act_timer_cancel,     act_ilmi_ld_1st_get,  NULL);
   TRAN (S_LECS_WSVC_WAIT, E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_ilmi_ld_1st_get,  NULL,                 NULL);
   TRAN (S_CONFIG_WAIT,    E_RCV_CONFIG_RSP,      S_LES_SVC_WAIT,   act_timer_cancel,     act_lecs_vc_teardown, act_les_svc_setup);
   TRAN (S_CONFIG_WAIT,    E_TIMEOUT_RETRY,       S_CONFIG_WAIT,    act_config_req_xmt,   NULL,                 NULL);
   TRAN (S_CONFIG_WAIT,    E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_lecs_vc_teardown, act_ilmi_next_get,    NULL);
   TRAN (S_CONFIG_WAIT,    E_LECS_SVC_RELEASE,    S_ILMI_WAIT,      act_timer_cancel,     act_ilmi_next_get,    NULL);
   TRAN (S_LES_SVC_WAIT,   E_LES_SVC_READY,       S_JOIN_WAIT,      act_timer_cancel,     act_join_req_xmt,     NULL);
   TRAN (S_LES_SVC_WAIT,   E_LES_SVC_RELEASE,     S_ILMI_WAIT,      act_timer_cancel,     act_ilmi_next_get,    NULL);
   TRAN (S_LES_SVC_WAIT,   E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_ilmi_next_get,    NULL,                 NULL);
   TRAN (S_JOIN_WAIT,      E_RCV_JOIN_RSP,        S_BUS_ARP_WAIT,   act_timer_cancel,     act_bus_arp_req_xmt,  NULL);
   TRAN (S_JOIN_WAIT,      E_TIMEOUT_RETRY,       S_JOIN_WAIT,      act_join_req_xmt,     NULL,                 NULL);
   TRAN (S_JOIN_WAIT,      E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_les_svc_teardown, act_ilmi_next_get,    NULL);
   TRAN (S_JOIN_WAIT,      E_LES_SVC_RELEASE,     S_ILMI_WAIT,      act_timer_cancel,     act_ilmi_next_get,    NULL);
   TRAN (S_BUS_ARP_WAIT,   E_RCV_BUS_ARP_RSP,     S_BUS_SVC_WAIT,   act_timer_cancel,     act_bus_svc_setup,    NULL);
   TRAN (S_BUS_ARP_WAIT,   E_TIMEOUT_RETRY,       S_BUS_ARP_WAIT,   act_bus_arp_req_xmt,  NULL,                 NULL);
   TRAN (S_BUS_ARP_WAIT,   E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_les_svc_teardown, act_ilmi_next_get,    NULL);
   TRAN (S_BUS_ARP_WAIT,   E_LES_SVC_RELEASE,     S_ILMI_WAIT,      act_timer_cancel,     act_ilmi_ld_1st_get,  NULL);
   TRAN (S_BUS_SVC_WAIT,   E_BUS_SVC_READY,       S_OPERATIONAL,    act_timer_cancel,     act_ctrl_list_close,  NULL);
   TRAN (S_BUS_SVC_WAIT,   E_BUS_SVC_REL_NORM,    S_ILMI_WAIT,      act_timer_cancel,     act_les_svc_teardown, act_ilmi_next_get);
   TRAN (S_BUS_SVC_WAIT,   E_BUS_SVC_REL_ABNORM,  S_ILMI_WAIT,      act_timer_cancel,     act_les_svc_teardown, act_ilmi_next_get);
   TRAN (S_BUS_SVC_WAIT,   E_TIMEOUT_NORETRY,     S_ILMI_WAIT,      act_les_svc_teardown, act_ilmi_next_get,    NULL);
   TRAN (S_BUS_SVC_WAIT,   E_LES_SVC_RELEASE,     S_ILMI_WAIT,      act_timer_cancel,     act_bus_svc_teardown, act_ilmi_ld_1st_get);
   TRAN (S_OPERATIONAL,    E_BUS_SVC_REL_ABNORM,  S_BUS_ARP_WAIT,   act_bus_arp_req_xmt,  NULL,                 NULL);
   TRAN (S_OPERATIONAL,    E_BUS_SVC_REL_NORM,    S_ATM_WAIT,       act_les_svc_teardown, act_atm_addr_cancel,  act_atm_addr_req);
   TRAN (S_OPERATIONAL,    E_LES_SVC_RELEASE,     S_ATM_WAIT,       act_bus_svc_teardown, act_atm_addr_cancel,  act_atm_addr_req);

   TRAN (S_ILMI_WAIT,      E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LECS_SVC_WAIT,  E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LECS_WSVC_WAIT, E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LECS_PVC_WAIT,  E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_CONFIG_WAIT,    E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LES_SVC_WAIT,   E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_JOIN_WAIT,      E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_BUS_ARP_WAIT,   E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_BUS_SVC_WAIT,   E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_OPERATIONAL,    E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);

   TRAN (S_DISABLED,       E_JOIN_START,          S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_AREG_DONE,           S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_ILMI_NEW_ADDR,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_ILMI_NO_MORE,        S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LECS_SVC_READY,      S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LECS_SVC_RELEASE,    S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LECS_PVC_READY,      S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LES_SVC_READY,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LES_SVC_RELEASE,     S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_BUS_SVC_READY,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_BUS_SVC_REL_NORM,    S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_BUS_SVC_REL_ABNORM,  S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_TIMEOUT_RETRY,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_TIMEOUT_NORETRY,     S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_RCV_CONFIG_RSP,      S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_RCV_JOIN_RSP,        S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_RCV_BUS_ARP_RSP,     S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_AREG_CANCELED,       S_DISABLED,       NULL,                 NULL,                 NULL);


   p_sm = &p_context->sm_skip_lecs;

   /*    Current State     Event                  Next State        Action 1              Action 2              Action 3
    *-------------------------------------------------------------------------------------------------------------------*/
   TRAN (S_INITIAL,        E_JOIN_START,          S_ATM_WAIT,       act_atm_addr_req,     NULL,                 NULL);
   TRAN (S_ATM_WAIT,       E_AREG_DONE,           S_LES_SVC_WAIT,   act_les_svc_setup,    NULL,                 NULL);
   TRAN (S_LES_SVC_WAIT,   E_LES_SVC_READY,       S_JOIN_WAIT,      act_timer_cancel,     act_join_req_xmt,     NULL);
/* TRAN (S_LES_SVC_WAIT,   E_LES_SVC_RELEASE,     S_LES_SVC_WAIT,   act_timer_cancel,     act_les_svc_setup,    NULL); */
   TRAN (S_LES_SVC_WAIT,   E_TIMEOUT_NORETRY,     S_LES_SVC_WAIT,   act_les_svc_setup,    NULL,                 NULL);
   TRAN (S_JOIN_WAIT,      E_RCV_JOIN_RSP,        S_BUS_ARP_WAIT,   act_timer_cancel,     act_bus_arp_req_xmt,  NULL);
   TRAN (S_JOIN_WAIT,      E_TIMEOUT_RETRY,       S_JOIN_WAIT,      act_join_req_xmt,     NULL,                 NULL);
   TRAN (S_JOIN_WAIT,      E_TIMEOUT_NORETRY,     S_LES_SVC_WAIT,   act_les_svc_teardown, act_les_svc_setup,    NULL);
   TRAN (S_JOIN_WAIT,      E_LES_SVC_RELEASE,     S_LES_SVC_WAIT,   act_timer_cancel,     act_les_svc_setup,    NULL);
   TRAN (S_BUS_ARP_WAIT,   E_RCV_BUS_ARP_RSP,     S_BUS_SVC_WAIT,   act_timer_cancel,     act_bus_svc_setup,    NULL);
   TRAN (S_BUS_ARP_WAIT,   E_TIMEOUT_RETRY,       S_BUS_ARP_WAIT,   act_bus_arp_req_xmt,  NULL,                 NULL);
   TRAN (S_BUS_ARP_WAIT,   E_TIMEOUT_NORETRY,     S_LES_SVC_WAIT,   act_les_svc_teardown, act_les_svc_setup,    NULL);
   TRAN (S_BUS_ARP_WAIT,   E_LES_SVC_RELEASE,     S_LES_SVC_WAIT,   act_timer_cancel,     act_les_svc_setup,    NULL);
   TRAN (S_BUS_SVC_WAIT,   E_BUS_SVC_READY,       S_OPERATIONAL,    act_timer_cancel,     act_ctrl_list_close,  NULL);
   TRAN (S_BUS_SVC_WAIT,   E_BUS_SVC_REL_NORM,    S_LES_SVC_WAIT,   act_timer_cancel,     act_les_svc_teardown, act_les_svc_setup);
   TRAN (S_BUS_SVC_WAIT,   E_BUS_SVC_REL_ABNORM,  S_LES_SVC_WAIT,   act_timer_cancel,     act_les_svc_teardown, act_les_svc_setup);
   TRAN (S_BUS_SVC_WAIT,   E_TIMEOUT_NORETRY,     S_LES_SVC_WAIT,   act_les_svc_teardown, act_les_svc_setup,    NULL);
   TRAN (S_BUS_SVC_WAIT,   E_LES_SVC_RELEASE,     S_LES_SVC_WAIT,   act_timer_cancel,     act_bus_svc_teardown, act_les_svc_setup);
   TRAN (S_OPERATIONAL,    E_BUS_SVC_REL_ABNORM,  S_BUS_ARP_WAIT,   act_bus_arp_req_xmt,  NULL,                 NULL);
   TRAN (S_OPERATIONAL,    E_BUS_SVC_REL_NORM,    S_ATM_WAIT,       act_les_svc_teardown, act_atm_addr_cancel,  act_atm_addr_req);
   TRAN (S_OPERATIONAL,    E_LES_SVC_RELEASE,     S_ATM_WAIT,       act_bus_svc_teardown, act_atm_addr_cancel,  act_atm_addr_req);

   TRAN (S_ILMI_WAIT,      E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LECS_SVC_WAIT,  E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LECS_WSVC_WAIT, E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LECS_PVC_WAIT,  E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_CONFIG_WAIT,    E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_LES_SVC_WAIT,   E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_JOIN_WAIT,      E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_BUS_ARP_WAIT,   E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_BUS_SVC_WAIT,   E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);
   TRAN (S_OPERATIONAL,    E_AREG_CANCELED,       S_ATM_WAIT,       act_timer_cancel,     act_atm_addr_req,     NULL);

   TRAN (S_DISABLED,       E_JOIN_START,          S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_AREG_DONE,           S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_ILMI_NEW_ADDR,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_ILMI_NO_MORE,        S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LECS_SVC_READY,      S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LECS_SVC_RELEASE,    S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LECS_PVC_READY,      S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LES_SVC_READY,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_LES_SVC_RELEASE,     S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_BUS_SVC_READY,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_BUS_SVC_REL_NORM,    S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_BUS_SVC_REL_ABNORM,  S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_TIMEOUT_RETRY,       S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_TIMEOUT_NORETRY,     S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_RCV_CONFIG_RSP,      S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_RCV_JOIN_RSP,        S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_RCV_BUS_ARP_RSP,     S_DISABLED,       NULL,                 NULL,                 NULL);
   TRAN (S_DISABLED,       E_AREG_CANCELED,       S_DISABLED,       NULL,                 NULL,                 NULL);

   return STATUS_K_SUCCESS;
   }

/*++
* ===========
* = sm_exec =
* ===========
*
* Status:   IMPLEMENTATION COMPLETE
*
* Overview:
*   Execute one transition of the state machine.
*
* Arguments:
*   lc_elan_handle - (IN) Handle returned by lc_register.
*   sm_event       - (IN) Event value to be handled by the state machine.
*
* Returns:
*   none
*
* Description:
*   Search the appropriate state machine transition list based on the
*   skip_lecs value.
*
* Issues:
*   None
*
--*/
static void sm_exec (HANDLE   lc_elan_handle,
                     UINT16   sm_event)
   {
   LC_CONTEXT        *p_context;
   LC_ELAN_CONTEXT   *p_elan;
   STATE_TBL_ENTRY   *p_tran;
   STATE_LIST        *p_sm;

   p_elan    = (LC_ELAN_CONTEXT *) lc_elan_handle;
   p_context = (LC_CONTEXT      *) p_elan->lc_handle;

   /* Choose the appropriate state machine representation to use. */

   if (p_elan->init_method == INIT_MANUAL_LES)
      p_sm = &p_context->sm_skip_lecs;
   else
      p_sm = &p_context->sm_use_lecs;

   /* Search the state machine list for a matching state-event pair. */

   utl_list_traverse (*p_sm, p_tran)
      {
      if ((p_tran->curr_state == p_elan->sm_state) &&
          (p_tran->sm_event   == sm_event))
         {
         p_elan->sm_state = p_tran->next_state;

         EVENT (EM_TRAN, ("(%s, %s) -> %s\n",
                          state_name (p_tran->curr_state),
                          event_name (sm_event),
                          state_name (p_tran->next_state)));

         p_elan->sm_state_history[p_elan->sm_state_history_index].new_state =
            p_tran->next_state;
         p_elan->sm_state_history[p_elan->sm_state_history_index].event =
            sm_event;
         p_elan->sm_state_history_index = (p_elan->sm_state_history_index + 1)
            % SM_STATE_HISTORY_DEPTH;


         /* If the ELAN state goes from non-operational to operational,
          * notify the upper layers that the ELAN is now available for use.
          */
         if ((p_tran->curr_state != S_OPERATIONAL) &&
             (p_tran->next_state == S_OPERATIONAL))
            p_context->elan_status_callback (p_elan->ld_elan_handle,
                                             TRUE,
                                             p_elan->lec_state.c3_lan_mtu,
                                             p_elan->lec_state.c5_elan_name,
                                             p_elan->lec_state.c14_lec_id);

         /* If the ELAN state goes from operational to non-operational,
          * reset the le-arp cache and notify the upper layers that the ELAN
          * is no longer available.  The le-arp cache must be reset to ensure
          * that arp-retries are not attempted on a closed control VCC.
          */
         if ((p_tran->curr_state == S_OPERATIONAL) &&
             (p_tran->next_state  != S_OPERATIONAL))
            {
	      /*
		la_elan_reset (p_elan->la_elan_handle);
		*/
            lc_elan_reset (p_elan);

            p_context->elan_status_callback (p_elan->ld_elan_handle,
                                             FALSE,
                                             p_elan->lec_state.c3_lan_mtu,
                                             p_elan->lec_state.c5_elan_name,
                                             p_elan->lec_state.c14_lec_id);
            }


         /* Execute the transition's action routines. */

         if (p_tran->action1 != NULL)
            p_tran->action1 (lc_elan_handle);

         if (p_tran->action2 != NULL)
            p_tran->action2 (lc_elan_handle);

         if (p_tran->action3 != NULL)
            p_tran->action3 (lc_elan_handle);

         return;
         }
      }

   p_elan->illegal_transition_count += 1;
   }

/*++
* ================
* = sm_timer_set =
* ================
--*/
static void sm_timer_set (HANDLE lc_elan_handle,
                          UINT16 duration,
                          UINT8  retry_count)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   if (!p_elan->sm_timer_retry_flag)
      {
      p_elan->sm_timer_retry_count = retry_count;
      p_elan->sm_timer_duration    = duration;
      }

   os_timer_set (p_elan->sm_timer, duration * 1000);
   }

/*++
* ====================
* = sm_timer_handler =
* ====================
--*/
static void sm_timer_handler (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   if (p_elan->sm_timer_retry_count > 0)
      {
      p_elan->sm_timer_retry_count -= 1;
      p_elan->sm_timer_retry_flag   = TRUE;
      os_timer_set (p_elan->sm_timer, p_elan->sm_timer_duration * 1000);
      sm_exec (lc_elan_handle, E_TIMEOUT_RETRY);
      }
   else
      {
      p_elan->sm_timer_retry_flag = FALSE;
      sm_exec (lc_elan_handle, E_TIMEOUT_NORETRY);
      }
   }

static void 
act_ctrl_list_close(HANDLE lc_elan_handle)
{
  /*
  cm_kill_ctrl_listens();
  */
}

/*++
* ====================
* = act_atm_addr_req =
* ====================
* Description:
*  Request a new ATM address from the Address-Registration Service.
--*/
static void 
act_atm_addr_req (HANDLE lc_elan_handle)
{
  LC_ELAN_CONTEXT   *p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  STATUS             status;
  
  status = addr_reg_atm_addr_alloc (p_elan->addr_reg_client_handle);
  
  assert ((status == STATUS_K_PENDING) || (status == STATUS_K_SUCCESS));
}

/*++
* =======================
* = act_atm_addr_cancel =
* =======================
* Description:
*  Cancel registration of an ATM address.
--*/
static void 
act_atm_addr_cancel (HANDLE lc_elan_handle)
{
  LC_ELAN_CONTEXT   *p_elan = (LC_ELAN_CONTEXT*) lc_elan_handle;
  
  (void) addr_reg_atm_addr_dealloc (p_elan->addr_reg_client_handle,
				    &p_elan->lec_state.c1_my_atm_addr); 
}

/*++
* =======================
* = act_ilmi_ld_1st_get =
* =======================
--*/
static void act_ilmi_ld_1st_get (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   if ((p_elan->init_method == INIT_WELL_KNOWN_LECS) ||
       (p_elan->init_method == INIT_LECS_PVC))
      {
      sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
      sm_exec (lc_elan_handle, E_ILMI_NO_MORE);
      return;
      }

   if (p_elan->init_method == INIT_MANUAL_LECS)
      {
      sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
      sm_exec (lc_elan_handle, E_ILMI_NEW_ADDR);
      return;
      }

   if (p_elan->init_method == INIT_NORMAL)
      {
      /* This is temporary!!! */

      sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
      sm_exec (lc_elan_handle, E_ILMI_NO_MORE);
      return;
      }

   assert (FALSE);
   }


/*++
* =====================
* = act_ilmi_next_get =
* =====================
--*/
static void act_ilmi_next_get (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   sm_exec (lc_elan_handle, E_ILMI_NO_MORE);

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
   }

/*++
* ======================
* = act_lecs_svc_setup =
* ======================
--*/
static void act_lecs_svc_setup (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   CONN_INFO          conn_info;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   lc_conn_info_make (lc_elan_handle,
                      p_elan->lecs_addr,
                      BLLI_CONTROL,
                      FALSE,           /* point to point */
                      &conn_info);

   (void) cm_sap_svc_setup (p_elan->sap_handle,
                            p_elan->ctrl_conn_context,
                            &conn_info,
                            0,
                            &p_elan->lecs_conn_handle);   

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
   }

/*++
* =======================
* = act_lecs_wsvc_setup =
* =======================
*
--*/
static void act_lecs_wsvc_setup (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   ADDR_ATM           addr;
   CONN_INFO          conn_info;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   if (p_elan->init_method == INIT_LECS_PVC)
      {
      sm_exec (lc_elan_handle, E_TIMEOUT_NORETRY);
      return;
      }

   addr.prefix[0]  = 0x47;
   addr.prefix[1]  = 0x00;
   addr.prefix[2]  = 0x79;
   addr.prefix[3]  = 0x00;
   addr.prefix[4]  = 0x00;
   addr.prefix[5]  = 0x00;
   addr.prefix[6]  = 0x00;
   addr.prefix[7]  = 0x00;
   addr.prefix[8]  = 0x00;
   addr.prefix[9]  = 0x00;
   addr.prefix[10] = 0x00;
   addr.prefix[11] = 0x00;
   addr.prefix[12] = 0x00;
   addr.esi[0]     = 0x00;
   addr.esi[1]     = 0xa0;
   addr.esi[2]     = 0x3e;
   addr.esi[3]     = 0x00;
   addr.esi[4]     = 0x00;
   addr.esi[5]     = 0x01;
   addr.sel        = 0x00;

   lc_conn_info_make (lc_elan_handle,
                      addr,
                      BLLI_CONTROL,
                      FALSE,           /* point to point */
                      &conn_info);

   (void) cm_sap_svc_setup (p_elan->sap_handle,
                            p_elan->ctrl_conn_context,
                            &conn_info,
                            0,
                            &p_elan->lecs_conn_handle);   

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
   }

/*++
* ======================
* = act_config_req_xmt =
* ======================
--*/
static void act_config_req_xmt (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   LE_CONFIG_FRAME    frame;
   STATUS             status;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   memset(&frame, 0, sizeof (LE_JOIN_FRAME));

   lc_ctrl_hdr_make (lc_elan_handle, &frame.hdr, LE_CONFIG_REQ);
   frame.hdr.req_lec_id = hton16 (0);

   ATM_COPY (p_elan->lec_state.c1_my_atm_addr, frame.src_atm_addr);

   switch (p_elan->lec_state.c2_lan_type) {
   case LAN_802_3 :  
     frame.lan_type = 1; 
     break;
   case LAN_802_5 :  
     frame.lan_type = 2;  
     break;
   default: 
     frame.lan_type =1;
   }
   
   switch (p_elan->lec_state.c3_lan_mtu)
      {
      case MTU_UNSPEC : frame.max_frame_size = 0;  break;
      case MTU_1516   : frame.max_frame_size = 1;  break;
      case MTU_4544   : frame.max_frame_size = 2;  break;
      case MTU_9234   : frame.max_frame_size = 3;  break;
      case MTU_18190  : frame.max_frame_size = 4;  break;
      }

   frame.tlv_count = 0;

   frame.elan_name_size = p_elan->lec_state.elan_name_size;   
   memcpy(frame.elan_name, p_elan->lec_state.c5_elan_name,                 
	  MIN (frame.elan_name_size, 32));

   sleep(1);
   status =  cm_sap_xmt_vc (p_elan->lecs_conn_handle,
                            &frame,
                            sizeof (frame),
                            USER_DATA_INTERNAL,
                            NULL);
   if (status != STATUS_K_SUCCESS)
      p_elan->ctrl_xmt_failure_count += 1;
   else
      p_elan->ctrl_frames_sent += 1;

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 1);
   }

/*++
* =====================
* = act_les_svc_setup =
* =====================
--*/
static void act_les_svc_setup (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   CONN_INFO          conn_info;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   lc_conn_info_make (lc_elan_handle,
                      p_elan->lec_state.c9_les_atm_addr,
                      BLLI_CONTROL,
                      FALSE,           /* point to point */
                      &conn_info);

   (void) cm_sap_svc_setup (p_elan->sap_handle,
                            p_elan->ctrl_conn_context,
                            &conn_info,
                            0,
                            &p_elan->ctrl_direct_conn_handle);   

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
   }

/*++
* ====================
* = act_join_req_xmt =
* ====================
--*/
static void 
act_join_req_xmt(HANDLE lc_elan_handle)
{
  LC_ELAN_CONTEXT   *p_elan;
  LE_JOIN_FRAME      frame;
  STATUS             status;

  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  
  /* Mark MAC address as unregistered so it will be re-registered
   * with the ELAN we are now joining.
   */
  p_elan->lec_state.c6_my_mac_addr.registered = FALSE;

  memset(&frame, 0, sizeof (LE_JOIN_FRAME));
  
  p_elan->lec_state.c14_lec_id = 0;
  lc_ctrl_hdr_make (lc_elan_handle, &frame.hdr, LE_JOIN_REQ);
  
  frame.hdr.flags = p_elan->lec_state.c4_proxy_flag ?
    hton16 (LE_FLAG_PROXY) : 0;
  
  p_elan->lec_state.c6_my_mac_addr.registered    = TRUE;
  frame.src_lan_dst.tag = hton16 (TAG_MAC_ADDR);
  ESI_COPY (p_elan->lec_state.c6_my_mac_addr.mac_addr, 
	    frame.src_lan_dst.mac_addr);

  ATM_COPY (p_elan->lec_state.c1_my_atm_addr, frame.src_atm_addr);
  
  switch (p_elan->lec_state.c2_lan_type) {
  case LAN_802_3 : 
    frame.lan_type = 1;  
    break;
  case LAN_802_5 : 
    frame.lan_type = 2;  
    break;
  default:
    frame.lan_type = 1;
  }
  
  switch (p_elan->lec_state.c3_lan_mtu) {
  case MTU_UNSPEC: 
    frame.max_frame_size = 0;  
    break;
  case MTU_1516  : frame.max_frame_size = 1;  break;
  case MTU_4544  : frame.max_frame_size = 2;  break;
  case MTU_9234  : frame.max_frame_size = 3;  break;
  case MTU_18190 : frame.max_frame_size = 4;  break;
  }
  
   frame.tlv_count      = 0;

   frame.elan_name_size = p_elan->lec_state.elan_name_size_join;   
   memcpy(frame.elan_name, p_elan->lec_state.c5_elan_name_join,
	  MIN (frame.elan_name_size, 32));
   EVENT(EM_DEBUG,("ELAN NAME:%s len:%d\n",p_elan->lec_state.c5_elan_name_join,
		   MIN(frame.elan_name_size, 32)));

   sleep(1);
   status =  cm_sap_xmt_vc (p_elan->ctrl_direct_conn_handle,
                            &frame,
                            sizeof (frame),
                            USER_DATA_INTERNAL,
                            NULL);

   if (status != STATUS_K_SUCCESS)
      p_elan->ctrl_xmt_failure_count += 1;
   else
      p_elan->ctrl_frames_sent += 1;

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 1);
   }

/*++
* =======================
* = act_bus_arp_req_xmt =
* =======================
--*/
static void act_bus_arp_req_xmt (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   LE_ARP_FRAME       frame;
   STATUS             status;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   memset(&frame, 0, sizeof (LE_ARP_FRAME));

   lc_ctrl_hdr_make (lc_elan_handle, &frame.hdr, LE_ARP_REQ);

   frame.src_lan_dst.tag            = hton16 (TAG_NOT_PRESENT);
   frame.target_lan_dst.tag         = hton16 (TAG_MAC_ADDR);
   frame.target_lan_dst.mac_addr[0] = 0xff;
   frame.target_lan_dst.mac_addr[1] = 0xff;
   frame.target_lan_dst.mac_addr[2] = 0xff;
   frame.target_lan_dst.mac_addr[3] = 0xff;
   frame.target_lan_dst.mac_addr[4] = 0xff;
   frame.target_lan_dst.mac_addr[5] = 0xff;

   ATM_COPY (p_elan->lec_state.c1_my_atm_addr, frame.src_atm_addr);

   p_elan->arps_sent += 1;

   status =  cm_sap_xmt_vc (p_elan->ctrl_direct_conn_handle,
                            &frame,
                            sizeof (frame),
                            USER_DATA_INTERNAL,
                            NULL);

   if (status != STATUS_K_SUCCESS)
      p_elan->ctrl_xmt_failure_count += 1;
   else
      p_elan->ctrl_frames_sent += 1;

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 1);
   }

/*++
* =====================
* = act_bus_svc_setup =
* =====================
--*/
static void act_bus_svc_setup (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   CONN_INFO          conn_info;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   lc_conn_info_make (lc_elan_handle,
                      p_elan->bus_addr,
                      BLLI_BUS_802_3,
                      FALSE,           /* Point to Point */
                      &conn_info);

   (void) cm_sap_svc_setup (p_elan->sap_handle,
                            p_elan->mcast_conn_context,
                            &conn_info,
                            0,
                            &p_elan->mcast_send_conn_handle);   

   sm_timer_set (lc_elan_handle, p_elan->lec_state.c7_control_timeout, 0);
   }

/*++
* ========================
* = act_lecs_vc_teardown =
* ========================
--*/
static void act_lecs_vc_teardown (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   EVENT(EM_DEBUG,("LECS_VC_TEARDOWN :%p\n", p_elan->lecs_conn_handle));
   if (p_elan->lecs_conn_handle != NULL)
      {
	if (p_elan->link_state == LINK_SIG_UP)
	  {
            cm_sap_svc_teardown (p_elan->lecs_conn_handle);
	  }

      p_elan->lecs_conn_handle = NULL;
      }

   }

/*++
* ========================
* = act_les_svc_teardown =
* ========================
--*/
static void act_les_svc_teardown (HANDLE lc_elan_handle)
{
  LC_ELAN_CONTEXT   *p_elan;
  HANDLE teardown;
  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  
  EVENT(EM_DEBUG,("LES_SVC_TEARDOWN :%p %p\n", 
		  p_elan->ctrl_direct_conn_handle,
		  p_elan->ctrl_dist_conn_handle));
  
  if (p_elan->ctrl_direct_conn_handle != NULL) {
    teardown = p_elan->ctrl_direct_conn_handle;
    p_elan->ctrl_direct_conn_handle = NULL;
    cm_sap_svc_teardown (teardown);
  }
  
  if (p_elan->ctrl_dist_conn_handle != NULL) {
    teardown = p_elan->ctrl_dist_conn_handle;
    p_elan->ctrl_dist_conn_handle = NULL;
    cm_sap_svc_teardown (teardown);
  }
}

/*++
* ========================
* = act_bus_svc_teardown =
* ========================
--*/
static void act_bus_svc_teardown (HANDLE lc_elan_handle)
{
  LC_ELAN_CONTEXT   *p_elan;
  
  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  
  EVENT(EM_DEBUG,("BUS_SVC_TEARDOWN :%p %p\n",
		  p_elan->mcast_send_conn_handle,
		  p_elan->mcast_rcv_conn_handle));
  
  if (p_elan->mcast_send_conn_handle != NULL) {
    cm_sap_svc_teardown (p_elan->mcast_send_conn_handle);
    p_elan->mcast_send_conn_handle = NULL;
  }
  
  if (p_elan->mcast_rcv_conn_handle != NULL) {
    cm_sap_svc_teardown (p_elan->mcast_rcv_conn_handle);
    p_elan->mcast_rcv_conn_handle = NULL;
  }
}


/*++
* ====================
* = act_timer_cancel =
* ====================
--*/
static void act_timer_cancel (HANDLE lc_elan_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   p_elan->sm_timer_retry_count = 0;
   p_elan->sm_timer_retry_flag  = FALSE;

   os_timer_cancel (p_elan->sm_timer);
   }


/*++
* =====================
* = addr_reg_callback =
* =====================
--*/
static void addr_reg_callback (HANDLE               context,
                               ADDR_ATM            *p_atm_addr,
                               ADDR_REG_ADDR_EVENT  ar_event)
   {
   LC_ELAN_CONTEXT   *p_elan;

   p_elan = (LC_ELAN_CONTEXT *) context;

   EVENT(EM_DEBUG,("Addr_reg_callback:%s\n",disp_atm_text(*p_atm_addr)));
   switch (ar_event)
      {
      case ADDR_REG_EVENT_ATM_ADDR_ALLOCATED :
      
         ATM_COPY (*p_atm_addr, p_elan->lec_state.c1_my_atm_addr);
         sm_exec (context, E_AREG_DONE);
         break;

      case ADDR_REG_EVENT_ATM_ADDR_INVALIDATED :

         sm_exec (context, E_AREG_CANCELED);
         break;

      default :
      
         assert (FALSE);
      }
   }

/*++
* ==============
* = lc_arp_xmt =
* ==============
--*/
static void lc_arp_xmt (HANDLE   lc_elan_handle,
                        ESI      esi)
   {
   LC_ELAN_CONTEXT   *p_elan;
   LE_ARP_FRAME       frame;
   STATUS             status;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   memset(&frame, 0, sizeof (LE_ARP_FRAME));

   lc_ctrl_hdr_make (lc_elan_handle, &frame.hdr, LE_ARP_REQ);

   frame.src_lan_dst.tag    = hton16 (TAG_NOT_PRESENT);
   frame.target_lan_dst.tag = hton16 (TAG_MAC_ADDR);
   ESI_COPY (esi, frame.target_lan_dst.mac_addr);
   ATM_COPY (p_elan->lec_state.c1_my_atm_addr, frame.src_atm_addr);

   EVENT (EM_XDATA, ("Sending LE-ARP for address: %02x-%02x-%02x-%02x-%02x-%02x\n",
           esi[0], esi[1], esi[2], esi[3], esi[4], esi[5]));

   p_elan->arps_sent += 1;

   if (p_elan->sm_state == S_OPERATIONAL) {
     status =  cm_sap_xmt_vc (p_elan->ctrl_direct_conn_handle,
			      &frame,
			      sizeof (frame),
			      USER_DATA_INTERNAL,
			      NULL);
   }
   if (status != STATUS_K_SUCCESS)
      p_elan->ctrl_xmt_failure_count += 1;
   else
      p_elan->ctrl_frames_sent += 1;
   }

/*++
* ================
* = lc_flush_xmt =
* ================
--*/
static void lc_flush_xmt (HANDLE    lc_elan_handle,
                          unsigned char *addr_atm,
                          HANDLE    flush_tran_id)
   {
   LC_ELAN_CONTEXT   *p_elan;
   LE_FLUSH_FRAME     frame;
   STATUS             status;
   UINT32            *p_tran_id;
   int i;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
   p_tran_id  = (UINT32 *)flush_tran_id;

   memset(&frame, 0, sizeof (LE_FLUSH_FRAME));

   *p_tran_id = lc_ctrl_hdr_make (lc_elan_handle,
				  &frame.hdr,
				  LE_FLUSH_REQ);

   ATM_COPY (p_elan->lec_state.c1_my_atm_addr, frame.src_atm_addr);
   for(i=0;i<sizeof(PREFIX);i++)
     frame.target_atm_addr.prefix[i] = addr_atm[i];
   for(;i<sizeof(PREFIX)+sizeof(ESI);i++)
     frame.target_atm_addr.esi[i-sizeof(PREFIX)] = addr_atm[i];
   frame.target_atm_addr.sel = addr_atm[19];

   /*
   ATM_COPY (addr_atm,                         frame.target_atm_addr);
   */
   EVENT (EM_XDATA, ("Sending Flush Request for %s.\n",
          disp_atm_text (frame.target_atm_addr)));

   if (p_elan->sm_state == S_OPERATIONAL) {
     status =  cm_sap_xmt_vc (p_elan->mcast_send_conn_handle,
			      &frame,
			      sizeof (frame),
			      USER_DATA_INTERNAL,
			      NULL);
   }
     if (status != STATUS_K_SUCCESS)
       p_elan->ctrl_xmt_failure_count += 1;
     else
       p_elan->ctrl_frames_sent += 1;
   }

/*++
* ================
* = lc_svc_setup =
* ================
--*/
static void lc_svc_setup (HANDLE    lc_elan_handle,
                          ADDR_ATM  addr_atm)
{
  LC_ELAN_CONTEXT   *p_elan;
  CONN_INFO          conn_info;
  HANDLE             unused_handle;
  
  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;
  
  lc_conn_info_make (lc_elan_handle,
		     addr_atm,
		     BLLI_DIRECT_802_3,
		     FALSE,        /* point to point */
		     &conn_info);
  
  if (p_elan->sm_state == S_OPERATIONAL) {
    (void) cm_sap_svc_setup (p_elan->sap_handle,
			     p_elan->direct_conn_context,
			     &conn_info,
			     p_elan->lec_state.c12_vcc_timeout,
			     &unused_handle);   
  }
}


/*++
* ====================
* = lc_ready_ind_xmt =
* ====================
--*/
static void lc_ready_ind_xmt (HANDLE   lc_elan_handle,
                              HANDLE   conn_handle)
   {
   LC_ELAN_CONTEXT   *p_elan;
   LE_READY_FRAME     frame;
   UINT32             user_data;
   STATUS             status;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   frame.hdr.marker     = hton16 (LE_CTRL_MARKER);
   frame.hdr.protocol   = LE_CTRL_PROTOCOL;
   frame.hdr.version    = LE_CTRL_VERSION;
   frame.hdr.op_code    = hton16 (READY_IND);

   user_data = USER_DATA_INTERNAL;
   status = cm_sap_xmt_vc (conn_handle,
                           (void *) &frame,
                           sizeof (LE_READY_FRAME),
                           user_data,
                           NULL);
   if (status != STATUS_K_SUCCESS)
      {
      p_elan->ctrl_xmt_failure_count += 1;
      }
   else
      {
      p_elan->ctrl_frames_sent += 1;
      }
   }

/****************************************************************************
 *  Implementations of LC Interface Functions
 ****************************************************************************/

/*++
* =============
* = lc_create =
* =============
--*/
STATUS lc_create (HANDLE                line_up_handle,
                  ELAN_STATUS_CALLBACK  elan_status_callback,
                  HANDLE               *p_lc_handle)
   {
   LC_CONTEXT  *p_context;
   STATUS       status;

   /* Allocate memory for the module context. */

   p_context = (LC_CONTEXT *) os_mem_alloc (sizeof (LC_CONTEXT));
   if (p_context == NULL)
      {
      return STATUS_K_RESOURCES;
      }

   EVENT(EM_DEBUG,("Lc_create: lc_handle:%p\n",p_context));
   /* Initialize the fields of the context structure. */

   p_context->line_up_handle       = line_up_handle;
   p_context->elan_status_callback = elan_status_callback;
   utl_list_init (p_context->elan_list);

   /* Create an instance of the LE_ARP module. */

   status = la_create ((HANDLE) p_context,
                       lc_arp_xmt,
                       lc_flush_xmt,
                       lc_svc_setup,
                       &p_context->la_handle);
   if (status != STATUS_K_SUCCESS)
      {
      os_mem_dealloc (p_context);
      return STATUS_K_RESOURCES;
      }

   status = state_tbl_init((HANDLE) p_context);

   /* Return the handle with success status. */

   *p_lc_handle = (HANDLE) p_context;
   return STATUS_K_SUCCESS;
   }

/*++
* ==============
* = lc_destroy =
* ==============
--*/
void lc_destroy (HANDLE  lc_handle)
   {
   LC_CONTEXT        *p_context;
   LC_ELAN_CONTEXT   *p_elan;

   /* Do nothing if the handle is already NULL. */

   if (lc_handle == NULL)
      return;

   p_context = (LC_CONTEXT *) lc_handle;

   /* Deregister each registered elan instance. */

   p_elan = p_context->elan_list.p_root;
   while (p_elan != NULL)
      {
      lc_deregister ((HANDLE) p_elan);
      p_elan = p_context->elan_list.p_root;
      }

   /* Destroy the LEC_ARP instance. */

   la_destroy (p_context->la_handle);

   /* Destroy the State Tables. */

   utl_list_dealloc (p_context->sm_use_lecs);
   utl_list_dealloc (p_context->sm_skip_lecs);

   /* Free the memory used for this instance context. */

   os_mem_dealloc (p_context);
   return;
   }

/*++
* ===============
* = lc_register =
* ===============
--*/
STATUS lc_register (HANDLE       lc_handle,
                    HANDLE       ld_elan_handle,
                    HANDLE       addr_reg_handle,
                    ESI          mac_addr,
                    LAN_TYPE     lan_type,
                    LAN_MTU      max_frame_size,
                    char        *p_elan_name,
                    INIT_METHOD  init_method,
                    ADDR_ATM     manual_atm_addr,
                    BOOLEAN      proxy_flag,
                    HANDLE       lport_handle,
                    char        *p_text,
                    HANDLE      *p_lc_elan_handle)
{
  LC_CONTEXT        *p_context;
  LC_ELAN_CONTEXT   *p_elan;
  STATUS             status;
  int                i;
  
  p_context = (LC_CONTEXT *) lc_handle;
  
  /* Allocate Memory for this ELAN instance. */
  
  p_elan = (LC_ELAN_CONTEXT *) os_mem_alloc (sizeof (LC_ELAN_CONTEXT));
  
  memset(p_elan, 0, sizeof(LC_ELAN_CONTEXT));
  
  if (p_elan == NULL) {
    return STATUS_K_RESOURCES;
  }
  
  EVENT(EM_DEBUG,("Lc_register, lc_handle:%p p_elan:%p\n",lc_handle, p_elan));
  
  /* Initialize the fields of the elan block. */
  
  p_elan->lc_handle       = lc_handle;
  p_elan->ld_elan_handle  = ld_elan_handle;
  p_elan->addr_reg_handle = addr_reg_handle;
  p_elan->link_state      = LINK_DOWN;
  p_elan->sm_state        = S_INITIAL;
  p_elan->current_tran_id = 0x640305;
  p_elan->vc_ready_mask   = 0;
  p_elan->init_method     = init_method;
  p_elan->p_text          = p_text;
  p_elan->lport_handle    = lport_handle;
  
  p_elan->sm_state_history_index = 0;
  for (i = 0; i < SM_STATE_HISTORY_DEPTH; i++) {
    p_elan->sm_state_history[i].new_state = 0;
    p_elan->sm_state_history[i].event     = 0;
  }
  
  
  if (init_method == INIT_MANUAL_LES) {
    ATM_COPY (manual_atm_addr, p_elan->lec_state.c9_les_atm_addr);
  }
  
  if (init_method == INIT_MANUAL_LECS) {
    ATM_COPY (manual_atm_addr, p_elan->lecs_addr);
  }
  
  /* Initialize the fields of the lec state. */
  
  p_elan->lec_state.c2_lan_type    = lan_type;
  p_elan->lec_state.c3_lan_mtu     = max_frame_size;
  p_elan->lec_state.c4_proxy_flag  = proxy_flag;
  p_elan->lec_state.elan_name_size = strlen (p_elan_name);
  strncpy(p_elan->lec_state.c5_elan_name, p_elan_name, 
	  MIN(32, p_elan->lec_state.elan_name_size));
  if (init_method == INIT_MANUAL_LES) {
    p_elan->lec_state.elan_name_size_join = strlen (p_elan_name);
    strncpy(p_elan->lec_state.c5_elan_name_join, p_elan_name, 
	    MIN(32, p_elan->lec_state.elan_name_size_join));
  }
  p_elan->lec_state.c7_control_timeout            = 10;
  p_elan->lec_state.c10_max_unknown_frame_count   = 1;
  p_elan->lec_state.c11_max_unknown_frame_time    = 1;
  p_elan->lec_state.c12_vcc_timeout               = 1200;
  p_elan->lec_state.c13_max_retry_count           = 2;
  p_elan->lec_state.c17_aging_time                = 300;
  p_elan->lec_state.c18_forward_delay_time        = 15;
  p_elan->lec_state.c19_topology_change_flag      = FALSE;
  p_elan->lec_state.c20_le_arp_response_time      = 1;
  p_elan->lec_state.c21_flush_timeout             = 4;
  p_elan->lec_state.c22_path_switching_delay      = 6;
  
  p_elan->illegal_frame_rcv_count  = 0;
  p_elan->ctrl_xmt_failure_count   = 0;
  p_elan->illegal_transition_count = 0;
  p_elan->ctrl_frames_sent         = 0;
  p_elan->ctrl_frames_rcvd         = 0;
  p_elan->arps_sent                = 0;
  p_elan->arps_rcvd                = 0;
  
  /* If a MAC address has been supplied by the caller, add it to the
   * destination list and flag it as not-registered.
   */
  if ((mac_addr[0] != 0) || (mac_addr[1] != 0) ||
      (mac_addr[2] != 0) || (mac_addr[3] != 0) ||
      (mac_addr[4] != 0) || (mac_addr[5] != 0)) {
    ESI_COPY(mac_addr, p_elan->lec_state.c6_my_mac_addr.mac_addr);
    p_elan->lec_state.c6_my_mac_addr.registered    = FALSE;
    p_elan->lec_state.c6_my_mac_addr.unregistering = FALSE;
  }

  /* Allocate the state machine timer. */
  status = os_timer_alloc (sm_timer_handler,
			   (HANDLE) p_elan,
			   &p_elan->sm_timer);
  if (status != STATUS_K_SUCCESS) {
    EVENT (EM_SERR, ("LC_ELAN SM timer could not be allocated\n"));
    os_mem_dealloc (p_elan);
    return STATUS_K_RESOURCES;
  }
  
  /* Allocate the address registration timer. */
  
  status = os_timer_alloc (reg_timer_handler,
			   (HANDLE) p_elan,
			   &p_elan->reg_timer);
  if (status != STATUS_K_SUCCESS) {
    EVENT (EM_SERR, ("LC_ELAN registration timer could not be allocated\n"));
    os_timer_dealloc (p_elan->sm_timer);
    os_mem_dealloc (p_elan);
    return STATUS_K_RESOURCES;
  }

  /* Register with the Arp Cache module. */
  
  status = la_register (p_context->la_handle,
			(HANDLE) p_elan,
			p_elan->lport_handle,
			p_elan->p_text,
			&p_elan->la_elan_handle);
  if (status != STATUS_K_SUCCESS) {
    EVENT (EM_SERR, ("la_register failed\n"));
    os_timer_dealloc (p_elan->reg_timer);
    os_timer_dealloc (p_elan->sm_timer);
    os_mem_dealloc (p_elan);
    return STATUS_K_RESOURCES;
  }
  
  la_config (p_elan->la_elan_handle,
	     p_elan->lec_state.c2_lan_type,
	     p_elan->lec_state.c4_proxy_flag,
	     p_elan->lec_state.c10_max_unknown_frame_count,
	     p_elan->lec_state.c11_max_unknown_frame_time,
	     p_elan->lec_state.c12_vcc_timeout,
	     p_elan->lec_state.c13_max_retry_count,
	     p_elan->lec_state.c17_aging_time,
	     p_elan->lec_state.c18_forward_delay_time,
	     p_elan->lec_state.c20_le_arp_response_time,
	     p_elan->lec_state.c21_flush_timeout,
	     p_elan->lec_state.c22_path_switching_delay);
  
  /* Register as a client to the Address Registration module. */
  
  status = addr_reg_client_register (p_elan->addr_reg_handle,
				     addr_reg_callback,
				     (HANDLE) p_elan,
				     "LAN Emulation Control",
				     &p_elan->addr_reg_client_handle);
   if (status != STATUS_K_SUCCESS) {
     EVENT (EM_SERR, ("Address Registration Client register failed\n"));
     la_deregister (p_elan->la_elan_handle);
     os_timer_dealloc (p_elan->reg_timer);
     os_timer_dealloc (p_elan->sm_timer);
     os_mem_dealloc (p_elan);
     return STATUS_K_RESOURCES;
      }
   
   utl_list_add (p_context->elan_list, p_elan);
   
   *p_lc_elan_handle = (HANDLE) p_elan;
   EVENT(EM_DEBUG,("p_elan->les_svc_handles :%p %p\n",
		   p_elan->ctrl_direct_conn_handle,
		   p_elan->ctrl_dist_conn_handle));
   return STATUS_K_SUCCESS;
}

/*++
* =================
* = lc_deregister =
* =================
--*/
void lc_deregister (HANDLE lc_elan_handle)
   {
   LC_CONTEXT       *p_context;
   LC_ELAN_CONTEXT  *p_elan;
   STATUS            status;

   p_elan    = (LC_ELAN_CONTEXT *) lc_elan_handle;
   p_context = (LC_CONTEXT      *) p_elan->lc_handle;

   p_elan->sm_state = S_DISABLED;

   os_timer_dealloc (p_elan->sm_timer);
   os_timer_dealloc (p_elan->reg_timer);

#if 0
   act_bus_svc_teardown (lc_elan_handle);
   act_les_svc_teardown (lc_elan_handle);
   act_lecs_vc_teardown (lc_elan_handle);
#endif

   status = addr_reg_atm_addr_dealloc (p_elan->addr_reg_client_handle,
                                       &p_elan->lec_state.c1_my_atm_addr);

   addr_reg_client_deregister(p_elan->addr_reg_client_handle);
   
   la_deregister (p_elan->la_elan_handle);

   utl_list_delete (p_context->elan_list, p_elan);
   os_mem_dealloc (p_elan);
   }

/*++
* =========================
* = lc_dest_is_registered =
* =========================
--*/
BOOLEAN 
lc_dest_is_registered (HANDLE  lc_elan_handle,
		       ESI     dst_addr)
{
  LC_ELAN_CONTEXT  *p_elan;
  
  p_elan    = (LC_ELAN_CONTEXT *) lc_elan_handle;

  /* If there is a matching entry that has been successfully registered,
   * return TRUE.  Otherwise, return FALSE.
   */
  if (!memcmp(dst_addr, p_elan->lec_state.c6_my_mac_addr.mac_addr,
	      sizeof(ESI)) &&
      p_elan->lec_state.c6_my_mac_addr.registered)
    return TRUE;
  else
    return FALSE;
}

/*++
* =================
* = lc_elan_reset =
* =================
--*/
void lc_elan_reset (HANDLE lc_elan_handle)
{
  LC_CONTEXT        *p_context;
  LC_ELAN_CONTEXT   *p_elan;
  
  p_elan    = (LC_ELAN_CONTEXT *) lc_elan_handle;
  p_context = (LC_CONTEXT      *) p_elan->lc_handle;
  
  EVENT (EM_EVENT, ("LC_ELAN resetting\n"));
  
  p_elan->sm_state = S_INITIAL;
  p_context->elan_status_callback (p_elan->ld_elan_handle,
				   FALSE,
				   p_elan->lec_state.c3_lan_mtu,
				   p_elan->lec_state.c5_elan_name,
				   p_elan->lec_state.c14_lec_id);
  
  act_bus_svc_teardown (lc_elan_handle);
  act_les_svc_teardown (lc_elan_handle);
  act_lecs_vc_teardown (lc_elan_handle);
  
  p_elan->vc_ready_mask = 0;
  /*
    cm_reset(p_elan->sap_handle); 
    la_elan_reset (p_elan->la_elan_handle);
    */
  (void) addr_reg_atm_addr_dealloc (p_elan->addr_reg_client_handle,
				    &p_elan->lec_state.c1_my_atm_addr);

  sig_reset(42);
  /*  
  cm_sap_link_status_get (p_elan->sap_handle, &p_elan->link_state);
  if (p_elan->link_state == LINK_SIG_UP) {
    sm_exec (lc_elan_handle, E_JOIN_START);
  }
  */
}

/*++
* =================
* = lc_join_start =
* =================
--*/
void lc_join_start (HANDLE  lc_elan_handle,
                    HANDLE  sap_handle,
                    HANDLE  direct_conn_context,
                    HANDLE  mcast_conn_context,
		    const char *qos_spec,
                    HANDLE  ctrl_conn_context)
{
  LC_CONTEXT        *p_context;
  LC_ELAN_CONTEXT   *p_elan;
  
  p_elan    = (LC_ELAN_CONTEXT *) lc_elan_handle;
  p_context = (LC_CONTEXT      *) p_elan->lc_handle;
  
  p_elan->qos_spec = qos_spec;
  
  p_elan->sap_handle            = sap_handle;
  p_elan->direct_conn_context   = direct_conn_context;
  p_elan->mcast_conn_context    = mcast_conn_context;
  p_elan->ctrl_conn_context     = ctrl_conn_context;
  
  cm_sap_link_status_get (p_elan->sap_handle, &p_elan->link_state);
  
  if (p_elan->link_state == LINK_SIG_UP) {
    sm_exec (lc_elan_handle, E_JOIN_START);
  }
  EVENT(EM_DEBUG,("p_elan->les_svc_handles :%p %p\n",
		  p_elan->ctrl_direct_conn_handle,
		  p_elan->ctrl_dist_conn_handle));  
}

/*++
* ====================
* = lc_sap_vc_notify =
* ====================
--*/
void lc_sap_vc_notify (HANDLE           conn_context,
                       HANDLE           conn_handle,
                       NOTIFY_EVENT     vc_event,
                       UINT16           reason,
                       UINT32           endpoint_ref,
                       BOOLEAN          calling_party)
{
  LC_ELAN_CONTEXT   *p_elan;
  LEC_CONN_CONTEXT  *p_conn;
  
  p_conn = (LEC_CONN_CONTEXT *) conn_context;
  p_elan = (LC_ELAN_CONTEXT  *) p_conn->lc_elan_handle;
  
  switch (vc_event) {
  case CONN_VC_READY :
    switch (p_conn->conn_type) {
    case LEC_DIRECT :
      
      /* Send a READY_IND message on the new connection. */
      
      EVENT (EM_EVENT, ("New Data Direct VCC Ready\n"));
      lc_ready_ind_xmt ((HANDLE) p_elan, conn_handle);
      break;
      
    case LEC_MCAST :
      
      if (calling_party) {
	assert (conn_handle == p_elan->mcast_send_conn_handle);
	EVENT (EM_EVENT, ("Multicast Send VCC Ready\n"));
	p_elan->vc_ready_mask |= READY_MCAST_SEND;
#ifdef PROXY
	proxy_mcast_send_add (p_elan->lport_handle,
			      vpi,
			      vci,
			      p_elan->lec_state.c14_lec_id,
			      PROXY_FORMAT_802_3,
			      conn_handle);
#endif
	sm_exec ((HANDLE) p_elan, E_BUS_SVC_READY);
      } else {
	assert (conn_handle == p_elan->mcast_rcv_conn_handle);
	EVENT (EM_EVENT, ("Multicast Forward VCC Ready\n"));
	p_elan->vc_ready_mask |= READY_MCAST_RCV;
#ifdef PROXY
	proxy_mcast_rcv_add (p_elan->lport_handle,
			     vpi,
			     vci,
			     p_elan->lec_state.c14_lec_id,
			     PROXY_FORMAT_802_3,
			     conn_handle);
#endif
      }
      
      break;
      
    case LEC_CTRL :
      
      if (calling_party) {
	if (conn_handle == p_elan->lecs_conn_handle) {
	  EVENT (EM_EVENT, ("Configuration Direct VCC Ready\n"));
	  p_elan->vc_ready_mask |= READY_LECS;
	  sm_exec ((HANDLE) p_elan, E_LECS_SVC_READY);
	} else {
	  assert (conn_handle == p_elan->ctrl_direct_conn_handle);
	  EVENT (EM_EVENT, ("Control Direct VCC Ready\n"));
	  p_elan->vc_ready_mask |= READY_CTRL_DIRECT;
	  sm_exec ((HANDLE) p_elan, E_LES_SVC_READY);
	}
      } else {
	assert (conn_handle == p_elan->ctrl_dist_conn_handle);
	EVENT (EM_EVENT, ("Control Distribute VCC Ready\n"));
	p_elan->vc_ready_mask |= READY_CTRL_DIST;
      }
      break;
      
    default:
      assert (FALSE);
    }
    break;
    
  case CONN_RELEASE :
    switch (p_conn->conn_type) {
    case LEC_DIRECT :
      
      /* No Action Necessary. */
#ifdef PROXY
      proxy_vcc_del (p_elan->lport_handle, vpi, vci);
#endif
      EVENT (EM_EVENT, ("Data Direct VCC Torn Down\n"));
      cm_sap_svc_teardown(conn_handle);
      break;
      
    case LEC_MCAST :
      
      EVENT (EM_EVENT, ("Multicast VCC Torn Down\n"));
#ifdef PROXY
      proxy_vcc_del (p_elan->lport_handle, vpi, vci);
#endif
      act_bus_svc_teardown ((HANDLE) p_elan);
      sm_exec ((HANDLE) p_elan, E_BUS_SVC_REL_NORM);
      break;
      
    case LEC_CTRL :
      
      if (conn_handle == p_elan->lecs_conn_handle) {
	EVENT (EM_EVENT, ("Configuration Direct VCC Torn Down\n"));
	act_lecs_vc_teardown ((HANDLE) p_elan);
	sm_exec ((HANDLE) p_elan, E_LECS_SVC_RELEASE);
      }
      else if ((conn_handle == p_elan->ctrl_direct_conn_handle) ||
	       (conn_handle == p_elan->ctrl_dist_conn_handle)) {
	EVENT (EM_EVENT, ("Control VCC Torn Down\n"));
	act_les_svc_teardown ((HANDLE) p_elan);
	sm_exec ((HANDLE) p_elan, E_LES_SVC_RELEASE);
      }
      break;
      
    default:
      assert (FALSE);
    }
  default:
    break;
  }
}

/*++
* ==================
* = lc_sap_connect =
* ==================
--*/
BOOLEAN lc_sap_connect (HANDLE       lc_elan_handle,
                        CONN_INFO   *p_conn_info,
                        HANDLE       conn_handle,
                        HANDLE      *p_conn_context,
                        UINT32      *p_age_limit,
                        UINT32      *p_reject_reason)
   {
   LC_ELAN_CONTEXT   *p_elan;
   UINT16             blli_codepoint;
   BOOLEAN            pmp;
   STATUS             status;

   p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

   status = lc_conn_info_check ((HANDLE) p_elan,
                                p_conn_info,
                                &blli_codepoint,
                                &pmp);

   switch (status)
      {
      case STATUS_K_SUCCESS :

         switch (blli_codepoint)
            {
            case BLLI_CONTROL :

               /* This must be an incoming Control Distribute VCC.  Make sure
                * we are in the correct state to accept this connection and
                * that there isn't already a control distribute VCC open.
                */
               if ((p_elan->sm_state == S_JOIN_WAIT) &&
                   (p_elan->ctrl_dist_conn_handle == NULL))
                  {
                  p_elan->ctrl_dist_conn_handle = conn_handle;
                  *p_conn_context               = p_elan->ctrl_conn_context;
                  *p_age_limit                  = 0;
                   EVENT (EM_EVENT, ("Accepting Control Distribute VCC.\n"));
                  return TRUE;
                  }
               else
                  {
                  *p_reject_reason = CAUSE_NORMAL;
                  EVENT (EM_EVENT, ("Rejecting Spurious BLLI_control VCC.\n"));
                  return FALSE;
                  }

            case BLLI_DIRECT_802_3 :

               /* This is an incoming data-direct VCC.  If we are in the
                * operational state, accept the connection.
                */
               if (p_elan->sm_state == S_OPERATIONAL)
                  {
                  *p_conn_context   = p_elan->direct_conn_context;
                  *p_age_limit      = p_elan->lec_state.c12_vcc_timeout;
                  EVENT (EM_EVENT, ("Accepting Data Direct VCC.\n"));
                  return TRUE;
                  }
               else
                  {
                  *p_reject_reason = CAUSE_NORMAL;
                  EVENT (EM_EVENT,
                         ("Rejecting Data Direct VCC (not operational).\n"));
                  return FALSE;
                  }

            case BLLI_BUS_802_3 :

               /* This is an incoming BUS distribute VCC.  If we are in the
                * S_BUS_SVC_WAIT state and there is no currently open BUS
                * distribute VCC, accept the connection.
                *
                * Also accept incoming BUS connections if we're in the
                * operational state.  Some BUS implementations may take a long
                * time to establish a BUS distribute VCC due to an ambiguity
                * in the LUNI spec.
                */
               if (((p_elan->sm_state == S_BUS_SVC_WAIT) ||
                    (p_elan->sm_state == S_OPERATIONAL)) &&
                   (p_elan->mcast_rcv_conn_handle == NULL))
                  {
                  p_elan->mcast_rcv_conn_handle = conn_handle;
                  *p_conn_context         = p_elan->mcast_conn_context;
                  *p_age_limit            = 0;
                  EVENT (EM_EVENT, ("Accepting Multicast Distribute VCC.\n"));
                  return TRUE;
                  }
               else
                  {
                  *p_reject_reason = CAUSE_NORMAL;
                  EVENT (EM_EVENT, ("Rejecting Spurious BLLI_mcast VCC.\n"));
                  return FALSE;
                  }

            case BLLI_DIRECT_802_5 :
            case BLLI_BUS_802_5 :

               EVENT (EM_EVENT, ("Rejecting 802.5 VCC - not supported.\n"));
               *p_reject_reason = CAUSE_NORMAL;
               return FALSE;

            default :
               assert (FALSE);
            }

      case STATUS_K_MISMATCH :

         return FALSE;

      case STATUS_K_REJECT :

         *p_reject_reason = CAUSE_NORMAL; /*???*/
         return FALSE;

      default :
         assert (FALSE);
      }

   return FALSE;
   }


/*++
* ==================
* = lec_config_req =
* ==================
--*/
STATIC void 
lec_config_req (LC_ELAN_CONTEXT *p_elan,
		void            *p_packet,
		UINT32           length)
{
  p_elan->illegal_frame_rcv_count += 1;
  EVENT (EM_EVENT, ("Configure Request received - Illegal.\n"));
  return;
}

/*++
* =============
* = tlv_parse =
* =============
*
* Status:
*
* Description:
*
* Issues:
*   None
*
--*/
void 
tlv_parse (LC_ELAN_CONTEXT  *p_elan,
	   UINT8             buffer[],
	   UINT32            length,
	   UINT8            *p_tlv_index)
{
  UINT32   tlv_type;
  UINT8    tlv_length;
  UINT8    tlv_value1;
  UINT16   tlv_value2;
  UINT32   tlv_value4;
  UINT8    i;
  
  if (length - *p_tlv_index < 5) {
    EVENT (EM_EVENT, ("TLV too short.\n"));
    return;
  }
  
  /* Extract the tlv_type, length, and value from the buffer. */
  
  i = *p_tlv_index;
  
  tlv_type = (buffer [i]     << 24) |
    (buffer [i + 1] << 16) |
    (buffer [i + 2] << 8)  |
    (buffer [i + 3]);
  tlv_length = buffer[i + 4];
  i += 5;
  
  if ((UINT32) (i + tlv_length) > length) {
    EVENT (EM_EVENT, ("TLV value missing, type = %08lx.\n", tlv_type));
    return;
  }

  tlv_value1 = 0;
  tlv_value2 = 0;
  tlv_value4 = 0;
  
  switch (tlv_length) {
  case 1 :
    tlv_value1   = buffer [i];
    *p_tlv_index = i + 1;
    break;
    
  case 2 :
    tlv_value2   = (buffer [i] << 8) | buffer [i + 1];
    *p_tlv_index = i + 2;
    break;
    
  case 4 :
    tlv_value4   = (buffer [i]     << 24) |
      (buffer [i + 1] << 16) |
      (buffer [i + 2] << 8 ) |
      (buffer [i + 3]);
    *p_tlv_index = i + 4;
    break;
  }

  /* Verify correct TVL lengths */

  switch (tlv_type) {
  case TLV_CTRL_TIMEOUT :
  case TLV_MAX_UNKNOWN_COUNT :
  case TLV_MAX_UNKNOWN_TIME :
  case TLV_MAX_RETRY :
  case TLV_FORW_DELAY_TIME :
  case TLV_ARP_RSP_TIME :
  case TLV_FLUSH_TIMEOUT :
  case TLV_PATH_SWITCH_DELAY :
  case TLV_LOCAL_SEG_ID :
  case TLV_MCAST_VCC_TYPE :
  case TLV_CONNECT_COMPLETION_TIMER :
    if (tlv_length != 2) {
      EVENT (EM_EVENT, ("Incorrect TLV length=%d for type=%08lx.\n",
			tlv_length, tlv_type));
      return;
    }
    break;
    
  case TLV_VCC_TIMEOUT :
  case TLV_AGING_TIME :
  case TLV_MCAST_VCC_AVG_RATE :
  case TLV_MCAST_VCC_PEAK_RATE :
    if (tlv_length != 4) {
      EVENT (EM_EVENT, ("Incorrect TLV length=%d for type=%08lx.\n",
			tlv_length, tlv_type));
      return;
    }
    break;
  }
  
  switch (tlv_type) {
  case TLV_CTRL_TIMEOUT :
    p_elan->lec_state.c7_control_timeout = tlv_value2;
    break;
    
  case TLV_MAX_UNKNOWN_COUNT :
    p_elan->lec_state.c10_max_unknown_frame_count = tlv_value2;
    break;
    
  case TLV_MAX_UNKNOWN_TIME :
    p_elan->lec_state.c11_max_unknown_frame_time = tlv_value2;
    break;
    
  case TLV_VCC_TIMEOUT :
    p_elan->lec_state.c12_vcc_timeout = tlv_value4;
    break;
    
  case TLV_MAX_RETRY :
    p_elan->lec_state.c13_max_retry_count = tlv_value2;
    break;
    
  case TLV_AGING_TIME :
    p_elan->lec_state.c17_aging_time = tlv_value4;
    break;
    
  case TLV_FORW_DELAY_TIME :
    p_elan->lec_state.c18_forward_delay_time = tlv_value2;
    break;
    
  case TLV_ARP_RSP_TIME :
    p_elan->lec_state.c20_le_arp_response_time = tlv_value2;
    break;
    
  case TLV_FLUSH_TIMEOUT :
    p_elan->lec_state.c21_flush_timeout = tlv_value2;
    break;
    
  case TLV_PATH_SWITCH_DELAY :
    p_elan->lec_state.c22_path_switching_delay = tlv_value2;
    break;
    
  case TLV_LOCAL_SEG_ID :
  case TLV_MCAST_VCC_TYPE :
  case TLV_MCAST_VCC_AVG_RATE :
  case TLV_MCAST_VCC_PEAK_RATE :
  case TLV_CONNECT_COMPLETION_TIMER :
  default :
    EVENT (EM_EVENT, ("Unsupported TLV received, type=%08lx.\n",
		      tlv_type));
    break;
  }
}

/*++
* ==================
* = lec_config_rsp =
* ==================
--*/
STATIC void 
lec_config_rsp (LC_ELAN_CONTEXT *p_elan,
		void            *p_packet,
		UINT32           length)
{
  LE_CONFIG_FRAME  *p_frame;
  UINT8             tlv_index;
  
  p_frame = (LE_CONFIG_FRAME *)p_packet;

  if (length < sizeof (LE_CONFIG_FRAME)) {
    EVENT (EM_EVENT, ("Received runt Config Response frame - %ld bytes.\n",
		      length));
    return;
  }

  if (p_frame->hdr.status != hton16 (LE_STATUS_SUCCESS)) {
    EVENT (EM_EVENT, ("Received Config Response with error %d.\n",
		      ntoh16 (p_frame->hdr.status)));
    return;
  }
  
  switch (p_frame->max_frame_size) {
  case 1  : p_elan->lec_state.c3_lan_mtu = MTU_1516;     break;
  case 2  : p_elan->lec_state.c3_lan_mtu = MTU_4544;     break;
  case 3  : p_elan->lec_state.c3_lan_mtu = MTU_9234;     break;
  case 4  : p_elan->lec_state.c3_lan_mtu = MTU_18190;    break;
  default : break;
  }
  
  ATM_COPY (p_frame->target_atm_addr, p_elan->lec_state.c9_les_atm_addr);
  
  switch(p_frame->lan_type) {
  case 1:
    p_elan->lec_state.c2_lan_type = LAN_802_3;
    break;
  case 2:
    p_elan->lec_state.c2_lan_type = LAN_802_5;
    break;
  default:
    break;
  }
   
  EVENT(EM_DEBUG,("Config response, elan_name:%s len:%d\n",
		  p_frame->elan_name, p_frame->elan_name_size));
  strncpy (p_elan->lec_state.c5_elan_name_join, p_frame->elan_name, 
	   MIN(p_frame->elan_name_size,32));
  p_elan->lec_state.elan_name_size_join = MIN(p_frame->elan_name_size, 32);
  
  /* Parse TLVs */
  
  tlv_index = sizeof (LE_CONFIG_FRAME);

  if (tlv_index < length) {
    while (tlv_index < length) {
      tlv_parse(p_elan, p_packet, length, &tlv_index);
    }
    la_config(p_elan->la_elan_handle,
	      p_elan->lec_state.c2_lan_type,
	      p_elan->lec_state.c4_proxy_flag,
	      p_elan->lec_state.c10_max_unknown_frame_count,
	      p_elan->lec_state.c11_max_unknown_frame_time,
	      p_elan->lec_state.c12_vcc_timeout,
	      p_elan->lec_state.c13_max_retry_count,
	      p_elan->lec_state.c17_aging_time,
	      p_elan->lec_state.c18_forward_delay_time,
	      p_elan->lec_state.c20_le_arp_response_time,
	      p_elan->lec_state.c21_flush_timeout,
	      p_elan->lec_state.c22_path_switching_delay);
  }
  sm_exec ((HANDLE) p_elan, E_RCV_CONFIG_RSP);
}

/*++
* ================
* = lec_join_req =
* ================
--*/
STATIC void 
lec_join_req (LC_ELAN_CONTEXT *p_elan,
	      void            *p_packet,
	      UINT32           length)
{
  p_elan->illegal_frame_rcv_count += 1;
  EVENT (EM_EVENT, ("Join Request received - Illegal.\n"));
  return;
}

/*++
* ================
* = lec_join_rsp =
* ================
--*/
STATIC void 
lec_join_rsp (LC_ELAN_CONTEXT *p_elan,
	      void            *p_packet,
	      UINT32           length)
{
  LE_JOIN_FRAME   *frame;

  frame = (LE_JOIN_FRAME *)p_packet;
  if (length<sizeof(LE_JOIN_FRAME))
    return;

  /* Check the completion status of the frame.  If it isn't success, handle
   * the error.
   */
  if (frame->hdr.status != ntoh16 (LE_STATUS_SUCCESS)) {
    EVENT (EM_NERR, ("Join Request Rejected by LES. Error = %s.\n",
		     disp_status_text (ntoh16 (frame->hdr.status))));
    return;
  }
  
  /* Get our new LEC_ID. */
  
  p_elan->lec_state.c14_lec_id = ntoh16 (frame->hdr.req_lec_id);

  /* Copy the joined ELAN type into our local state. */
  
  switch (frame->lan_type) {
  case 1  : p_elan->lec_state.c2_lan_type = LAN_802_3;   break;
  case 2  : p_elan->lec_state.c2_lan_type = LAN_802_5;   break;
  default : return;
  }

  /* Copy the max frame size into our local state. */
  
  switch (frame->max_frame_size) {
  case 1  : p_elan->lec_state.c3_lan_mtu = MTU_1516;     break;
  case 2  : p_elan->lec_state.c3_lan_mtu = MTU_4544;     break;
  case 3  : p_elan->lec_state.c3_lan_mtu = MTU_9234;     break;
  case 4  : p_elan->lec_state.c3_lan_mtu = MTU_18190;    break;
  default : return;
  }

  /* Copy the ELAN name into our local state. */
  
  p_elan->lec_state.elan_name_size = frame->elan_name_size;
  memcpy(p_elan->lec_state.c5_elan_name, frame->elan_name,
	 MIN (frame->elan_name_size, 32));

  /* Provide the state machine with a received-join-response event. */
  
  sm_exec ((HANDLE) p_elan, E_RCV_JOIN_RSP);
}

/*++
* ===================
* = lec_ready_query =
* ===================
--*/
STATIC void 
lec_ready_query (LC_ELAN_CONTEXT *p_elan,
		 HANDLE           conn_handle)
{
  lc_ready_ind_xmt ((HANDLE) p_elan, conn_handle);
}

/*++
* =================
* = lec_ready_ind =
* =================
--*/
STATIC void 
lec_ready_ind (LC_ELAN_CONTEXT *p_elan,
	       HANDLE           conn_handle)
{
  /* No Action Necessary. */
}

/*++
* ===============
* = lec_reg_req =
* ===============
--*/
STATIC void 
lec_reg_req (LC_ELAN_CONTEXT *p_elan,
	     void            *p_packet,
	     UINT32           length)
{
  p_elan->illegal_frame_rcv_count += 1;
  EVENT (EM_EVENT, ("Registration Request received - Illegal.\n"));
  return;
}

/*++
* ===============
* = lec_reg_rsp =
* ===============
--*/
STATIC void 
lec_reg_rsp (LC_ELAN_CONTEXT *p_elan,
	     void            *p_packet,
	     UINT32           length)
{
  LE_REG_FRAME   *frame;

  frame = (LE_REG_FRAME *)p_packet;

  if (length < sizeof(LE_REG_FRAME))
    return;
  
  /* Check the completion status of the frame.  If it isn't success, handle
   * the error.
   */
  if (frame->hdr.status != ntoh16 (LE_STATUS_SUCCESS)) {
    EVENT (EM_NERR, ("Register Request Rejected by LES. Error = %s.\n",
		     disp_status_text (ntoh16 (frame->hdr.status))));
    return;
  }

  /* Mark MAC address as successfully registered.
   */
  
  if (!memcmp(p_elan->lec_state.c6_my_mac_addr.mac_addr,
	      frame->src_lan_dst.mac_addr, sizeof(ESI)) &&
      ntoh32(frame->hdr.tran_id) == p_elan->lec_state.c6_my_mac_addr.tran_id){
    p_elan->lec_state.c6_my_mac_addr.registered = TRUE;
    EVENT (EM_EVENT, ("MAC Address successfully registered.\n"));
  }
}

/*++
* =================
* = lec_unreg_req =
* =================
--*/
STATIC void 
lec_unreg_req (LC_ELAN_CONTEXT *p_elan,
	       void            *p_packet,
	       UINT32           length)
{
  p_elan->illegal_frame_rcv_count += 1;
  EVENT (EM_EVENT, ("Unregister Request received - Illegal.\n"));
  return;
}

/*++
* =================
* = lec_unreg_rsp =
* =================
--*/
STATIC void 
lec_unreg_rsp (LC_ELAN_CONTEXT *p_elan,
	       void            *p_packet,
	       UINT32           length)
{
  LE_REG_FRAME   *frame;
  
  frame = (LE_REG_FRAME*)p_packet;
  
  if (length < sizeof (LE_ARP_FRAME))
    return;
  
  /* Check the completion status of the frame.  If it isn't success, handle
   * the error.
   */
  if (frame->hdr.status != ntoh16 (LE_STATUS_SUCCESS)) {
    EVENT (EM_NERR, ("Unregister Request Rejected by LES. Error = %s.\n",
		     disp_status_text (ntoh16 (frame->hdr.status))));
    return;
  }
  
  /* Delete MAC address */
  
  if (!memcmp(p_elan->lec_state.c6_my_mac_addr.mac_addr,
	      frame->src_lan_dst.mac_addr, sizeof(ESI)) &&
      ntoh32(frame->hdr.tran_id)==p_elan->lec_state.c6_my_mac_addr.tran_id) {
    memset(&p_elan->lec_state.c6_my_mac_addr,0,sizeof(REG_DEST));
    EVENT (EM_EVENT, ("MAC Address successfully unregistered.\n"));
  }
}

/*++
* ===============
* = lec_arp_req =
* ===============
--*/
STATIC void 
lec_arp_req (LC_ELAN_CONTEXT *p_elan,
	     void            *p_packet,
	     UINT32           length)
{
  LE_ARP_FRAME   *frame;
  STATUS         status;
  UINT32         user_data;
  
  /* If the LEC control state machine is not in the operational state, simply
   * discard the receive packet.
   */
  if (p_elan->sm_state != S_OPERATIONAL)
    return;
  
  p_elan->arps_rcvd += 1;
  
  frame = (LE_ARP_FRAME*)p_packet;
  if (length < sizeof (LE_ARP_FRAME))
    return;
  
  switch (ntoh16 (frame->target_lan_dst.tag)) {
  case TAG_NOT_PRESENT:
    break;
  case TAG_MAC_ADDR:
    if (lc_dest_is_registered ((HANDLE) p_elan,
			       frame->target_lan_dst.mac_addr))
      {
	frame->hdr.op_code = hton16 (LE_ARP_RSP);
	frame->hdr.status  = hton16 (LE_STATUS_SUCCESS);
	frame->hdr.flags   = hton16 (0);        /* Clear Remote Flag */
	ATM_COPY (p_elan->lec_state.c1_my_atm_addr,
		  frame->target_atm_addr);
	user_data = USER_DATA_INTERNAL;
	status = cm_sap_xmt_vc (p_elan->ctrl_direct_conn_handle,
				(void *) frame,
				sizeof (LE_ARP_FRAME),
				user_data,
				NULL);
	return;
      }
    
    break;
  case TAG_ROUTE_DESC:
    break;
  }
}

/*++
* ===============
* = lec_arp_rsp =
* ===============
--*/
STATIC void 
lec_arp_rsp (LC_ELAN_CONTEXT *p_elan,
	     void            *p_packet,
	     UINT32           length)
{
  LE_ARP_FRAME  *frame;
  
  frame = (LE_ARP_FRAME *)p_packet;
  
  if (length < sizeof (LE_ARP_FRAME))
    return;
  
  if (frame->hdr.status == ntoh16 (LE_STATUS_SUCCESS)) {
    switch (ntoh16 (frame->target_lan_dst.tag)) {
    case TAG_NOT_PRESENT:
      break;
    case TAG_MAC_ADDR:
      
      if ((ESI_IS_BCAST (frame->target_lan_dst.mac_addr)) &&
	  (p_elan->sm_state == S_BUS_ARP_WAIT)) {
	ATM_COPY (frame->target_atm_addr, p_elan->bus_addr);
	sm_exec ((HANDLE) p_elan, E_RCV_BUS_ARP_RSP);
      } else {
	EVENT (EM_RDATA, ("Received LE-ARP response for: %02x-%02x-%02x-%02x-%02x-%02x\n",
			  frame->target_lan_dst.mac_addr[0],
			  frame->target_lan_dst.mac_addr[1],
			  frame->target_lan_dst.mac_addr[2],
			  frame->target_lan_dst.mac_addr[3],
			  frame->target_lan_dst.mac_addr[4],
			  frame->target_lan_dst.mac_addr[5]));
	if (memcmp(p_elan->lec_state.c6_my_mac_addr.mac_addr,
		   frame->target_lan_dst.mac_addr, 
		   sizeof(ESI))) {
	  la_arp_update (p_elan->la_elan_handle,
			 frame->target_lan_dst.mac_addr,
			 frame->target_atm_addr,
			 (ntoh16(frame->hdr.flags) &
			  LE_FLAG_REMOTE_ADDR) ? TRUE : FALSE);
	}
      }
      break;
    case TAG_ROUTE_DESC:
      break;
    }
  }  
  return;
}

/*++
* =================
* = lec_flush_req =
* =================
--*/
STATIC void 
lec_flush_req (LC_ELAN_CONTEXT *p_elan,
	       void            *p_packet,
	       UINT32           length)
{
  LE_FLUSH_FRAME *frame;
  UINT32          user_data;
  STATUS          status;

  frame = (LE_FLUSH_FRAME *)p_packet;

  if (length < sizeof (LE_FLUSH_FRAME))
    return;

  EVENT(EM_DEBUG,("Received flush request, my atmaddr:%s\n",
                 disp_atm_text(p_elan->lec_state.c1_my_atm_addr)));
  EVENT(EM_DEBUG,("                              for :%s\n",             
		  disp_atm_text(frame->target_atm_addr)));
  if (ATM_EQUAL (p_elan->lec_state.c1_my_atm_addr, frame->target_atm_addr)) {
    EVENT (EM_RDATA, ("Received Flush Request, Responding.\n"));
    frame->hdr.op_code = hton16 (LE_FLUSH_RSP);
    frame->hdr.status  = hton16 (LE_STATUS_SUCCESS);
    user_data = USER_DATA_INTERNAL;
    status = cm_sap_xmt_vc (p_elan->ctrl_direct_conn_handle,
			    (void *) frame,
			    sizeof (LE_FLUSH_FRAME),
			    user_data,
			    NULL);
    if (status != STATUS_K_SUCCESS) {
      p_elan->ctrl_xmt_failure_count += 1;
    } else {
      p_elan->ctrl_frames_sent += 1;
    }
  }  
  return;
}

/*++
* =================
* = lec_flush_rsp =
* =================
--*/
STATIC void 
lec_flush_rsp(LC_ELAN_CONTEXT *p_elan,
	      void            *p_packet,
	      UINT32           length)
{
  LE_FLUSH_FRAME *frame;

  frame = (LE_FLUSH_FRAME *)p_packet;
  EVENT (EM_RDATA, ("Received flush response.\n"));

  if (length < sizeof(LE_CTRL_HDR))
    return;

  if ((frame->hdr.status == ntoh16 (LE_STATUS_SUCCESS)) &&
      (ntoh16(frame->hdr.req_lec_id) == p_elan->lec_state.c14_lec_id)) {
    la_flush_complete(p_elan->la_elan_handle,
		      ntoh32 (frame->hdr.tran_id));
  }
  return;
}

/*++
* ================
* = lec_narp_req =
* ================
--*/
STATIC void 
lec_narp_req (LC_ELAN_CONTEXT *p_elan,
	      void            *p_packet,
	      UINT32           length)
{
}

/*++
* ===============
* = lec_top_req =
* ===============
--*/
STATIC void 
lec_top_req (LC_ELAN_CONTEXT *p_elan,
	     void            *p_packet,
	     UINT32           length)
{
  LE_TOPOLOGY_FRAME *frame;
  
  frame = (LE_TOPOLOGY_FRAME*)p_packet;

  if (length < sizeof (LE_TOPOLOGY_FRAME))
    return;

  la_topology_change_set (p_elan->la_elan_handle,
			  (ntoh16(frame->hdr.flags) &
			   LE_FLAG_TOPOLOGY_CHANGE) ? TRUE : FALSE);
  
  return;
}

/*++
* ==============
* = lc_sap_rcv =
* ==============
--*/
void lc_sap_rcv (HANDLE     conn_context,
                 HANDLE     conn_handle,
                 UINT32     length,
                 UINT32     user_data,
                 AAL_DATA  *p_aal_data,
                 void     **pp_packet)
{
  LE_READY_HDR      *hdr;
  LEC_CONN_CONTEXT  *p_conn;
  LC_ELAN_CONTEXT   *p_elan;
  /* Convert handles to structure pointers. */
  
   p_conn = (LEC_CONN_CONTEXT *) conn_context;
   p_elan = (LC_ELAN_CONTEXT  *) p_conn->lc_elan_handle;   
   hdr = (LE_READY_HDR *)*pp_packet;
   
   if (length < sizeof (LE_READY_HDR))
     return;

   /* Check the marker and protocol fields.  If they are inappropriate for
    * a LEC control frame, discard the frame.
    */
   if ((hdr->marker   != ntoh16 (LE_CTRL_MARKER)) ||
       (hdr->protocol != LE_CTRL_PROTOCOL))
     return;

   /* Check the version field, discard if it does not match. */

   if (hdr->version != LE_CTRL_VERSION)
     return;

   p_elan->ctrl_frames_rcvd += 1;

   /* Dispatch to the the appropriate handler for the op-code. */

   switch (ntoh16 (hdr->op_code)) {
   case LE_CONFIG_REQ : 
     lec_config_req  (p_elan, *pp_packet, length);   break;
   case LE_CONFIG_RSP :
     lec_config_rsp  (p_elan, *pp_packet, length);   break;
   case LE_JOIN_REQ :
     lec_join_req    (p_elan, *pp_packet, length);   break;
   case LE_JOIN_RSP :
     lec_join_rsp    (p_elan, *pp_packet, length);   break;
   case READY_QUERY :
     lec_ready_query (p_elan, conn_handle);          break;
   case READY_IND :
     lec_ready_ind   (p_elan, conn_handle);          break;
   case LE_REGISTER_REQ :
     lec_reg_req     (p_elan, *pp_packet, length);   break;
   case LE_REGISTER_RSP :
     lec_reg_rsp     (p_elan, *pp_packet, length);   break;
   case LE_UNREGISTER_REQ :
     lec_unreg_req   (p_elan, *pp_packet, length);   break;
   case LE_UNREGISTER_RSP :
     lec_unreg_rsp   (p_elan, *pp_packet, length);   break;
   case LE_ARP_REQ :
     lec_arp_req     (p_elan, *pp_packet, length);   break;
   case LE_ARP_RSP :
     lec_arp_rsp     (p_elan, *pp_packet, length);   break;
   case LE_FLUSH_REQ :
     lec_flush_req   (p_elan, *pp_packet, length);   break;
   case LE_FLUSH_RSP :
     lec_flush_rsp   (p_elan, *pp_packet, length);   break;
   case LE_NARP_REQ :
     lec_narp_req    (p_elan, *pp_packet, length);   break;
   case LE_TOPOLOGY_REQ :
     lec_top_req     (p_elan, *pp_packet, length);   break;
   default:
     break;
   }
   return;
}

void 
lc_addr_delete(HANDLE lc_elan_handle, unsigned char *atm_addr)
{
  LC_ELAN_CONTEXT *p_elan;

  p_elan = (LC_ELAN_CONTEXT *) lc_elan_handle;

  la_addr_delete(p_elan->la_elan_handle, atm_addr, FALSE);
}
