/**
 * @file i4bcmgr_plcism.c
 *
 * I4BCMgr-PlciSM - PLCI state machine.
 *
 * Copyright: 2000-2004 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: i4bcmgr_plcism.c,v 1.1.2.1 2006/02/28 21:35:29 thomas Exp $
 * Project  CAPI for BSD
 * Target   i4bcapimgr - The I4B driver for CAPI manager driven controllers
 * @date    01.01.2000
 * @author  "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/* system includes */
#include <sys/limits.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <net/if.h>
#include <capi20.h>
#include <capi_bsd.h>
#include <i4b_cause.h>
#include <i4b_ioctl.h>

/* import includes */
#include <i4b/include/i4b_l3l4.h>
#include <i4b/include/i4b_isdnq931.h>
#include <i4b/layer4/i4b_l4.h>

#define __I4BCMGR_PLCISM__

/* local includes */
#include <i4b/i4bcapimgr/i4bcmgr_var.h>
#include <i4b/i4bcapimgr/i4bcmgr_capiutil.h>
#include <i4b/i4bcapimgr/i4bcmgr_nccism.h>
#include <i4b/i4bcapimgr/i4bcmgr_plcism.h>





/* === public definitions ================================================ */





/* === private definitions =============================================== */





/* --- PLCI timeout values for several situations --- */

/* timeout waiting for Connect-Confirm after Connect-Request; if this timer is
 * signalled, the controller (driver) is out of order
 */
#define WAIT_FOR_CONNECT_CONF_TIMEOUT   (2 * hz) /* 2 seconds */

/* timeout when waiting for active connection after Connect-Confirm or
 * Connect-Response; should be long enough to let D-channel layer 3 timer T301
 * expire (3 minutes)
 */
#define WAIT_FOR_CONNECT_ACTIVE_TIMEOUT (4 * 60 * 60 * hz) /* 4 minutes */

/* timeout when waiting for Disconnect-Indication, should be a little higher
 * than D-channel layer 3 timer T305 (30 seconds)
 */
#define WAIT_FOR_DISCONNECT_IND_TIMEOUT (40 * hz) /* 40 seconds */

/* timeout for submitting a message to CAPI when the controller is currently
 * too busy to accept a new message
 */
#define CAPI_PUT_MESSAGE_TIMEOUT        (1 * hz) /* 1 second */



/* --- possible events for the CAPI PLCI state machine --- */

/* Important: These values are used to index the array for translation into
 *            strings. If the enum definition is modified, the mentioned array
 *            must be modified, too.
 */
 
typedef enum
{
   /* events for communication with i4b layer 4 */
   PLCISM_EVENT_I4B_CONN_REQ = 0,
   PLCISM_EVENT_I4B_CONN_RESP,
   PLCISM_EVENT_I4B_DISC_REQ,
   PLCISM_EVENT_I4B_ALERT_REQ,
   PLCISM_EVENT_TIMEOUT,
   /* now the events for incoming CAPI messages */
   PLCISM_EVENT_CAPI_CONN_CONF,
   PLCISM_EVENT_CAPI_CONN_IND,
   PLCISM_EVENT_CAPI_CONN_ACT_IND,
   PLCISM_EVENT_CAPI_DISC_CONF,
   PLCISM_EVENT_CAPI_DISC_IND,
   PLCISM_EVENT_CAPI_INFO_CONF,
   PLCISM_EVENT_CAPI_INFO_IND,
   PLCISM_EVENT_CAPI_ALERT_CONF,
   PLCISM_EVENT_CAPI_GET_B3_MSG,
   NUM_PLCISM_EVENTS
} PlciEventType_t;



/* --- translate CAPI message commands into events --- */

static PlciEventType_t g_aCapiCmdToEvent [] =
   {
      NUM_PLCISM_EVENTS,                /* no command 0 - confirmation */
      NUM_PLCISM_EVENTS,                /* no command 0 - indication */
      PLCISM_EVENT_CAPI_ALERT_CONF,     /* alert-conf */
      NUM_PLCISM_EVENTS,                /* alert-ind, does not exist */
      PLCISM_EVENT_CAPI_CONN_CONF,      /* connect-conf */
      PLCISM_EVENT_CAPI_CONN_IND,       /* connect-ind */
      NUM_PLCISM_EVENTS,                /* connect-active-conf, does not exist
                                         */
      PLCISM_EVENT_CAPI_CONN_ACT_IND,   /* connect-active-ind */
      PLCISM_EVENT_CAPI_DISC_CONF,      /* disconnect-conf */
      PLCISM_EVENT_CAPI_DISC_IND,       /* disconnect-ind */
      NUM_PLCISM_EVENTS,                /* Listen-Conf, not handled in sm */
      NUM_PLCISM_EVENTS,                /* Listen-Ind, not handled in sm */
      NUM_PLCISM_EVENTS,                /* no command 6 - confirmation */
      NUM_PLCISM_EVENTS,                /* no command 6 - indication */
      NUM_PLCISM_EVENTS,                /* no command 7 - confirmation */
      NUM_PLCISM_EVENTS,                /* no command 7 - indication */
      PLCISM_EVENT_CAPI_INFO_CONF,      /* info-conf */
      PLCISM_EVENT_CAPI_INFO_IND        /* info-ind */
   };



/* --- the result of the state machine action functions --- */

typedef enum
{
   SMRES_SUCCESS,
   SMRES_FAILURE,
   SMRES_NO_STATE_TRANSIT,
   SMRES_FATAL
} StateMachineResult_t;



/* --- the definition of an entry in the state table --- */

typedef struct
{
   I4bCapiPlciState_t initialState;
   StateMachineResult_t (*pfnAction)
      (I4bCapiConnectionData_t  *pConnData,
       PlciEventType_t           event,
       CAPIMsg_t                *pCapiMsg,
       const I4bCapiEventData_t *pAddInfo);
   I4bCapiPlciState_t successState;
   I4bCapiPlciState_t failureState;
} StateMachineAction_t;



/* --- translation table for PLCI states into strings --- */

/* Important: The array is indexed by the PLCI state enum values! */
static const char *g_apszStateNames [] =
   {
      "P-0",
      "P-0.1",
      "P-1",
      "P-2",
      "P-3",
      "P-4",
      "P-ACT",
      "P-5",
      "P-6"
   };



/* --- translation table for PLCI events into strings --- */

/* Important: The array is indexed by the PLCI event enum values! */
static const char *g_apszEventNames [] =
   {
      "I4B-Connect-Req",
      "I4B-Connect-Resp",
      "I4B-Disconnect-Req",
      "I4B-Alert-Req",
      "Timeout",
      "C-Connect-Conf",
      "C-Connect-Ind",
      "C-Connect-Active-Ind",
      "C-Disconnect-Conf",
      "C-Disconnect-Ind",
      "C-Info-Conf",
      "C-Info-Ind",
      "C-Alert-Conf",
      "C-Get-B3-Msg"
   };





/* === prototypes for private functions ================================== */





/* --- functions around the PLCI state machine --- */

/* main event handling function for the PLCI state machine */
static void HandleEvent
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* initialize the state variables for the PLCI layer */
static void InitStateVariables
   (I4bCapiConnectionData_t *pConnData);

/* the timer function */
static void PlciTimeout
   (I4bCapiConnectionData_t *pConnData);

/* create an PLCI state machine event from a CAPI message */
static PlciEventType_t GetEventFromCapiMsg
   (const CAPIMsg_t *pCapiMsg);

/* determine the connection data entry from a CAPI message */
static I4bCapiConnectionData_t *GetConnDataFromCapiMsg
   (const CAPIMsg_t *pCapiMsg);

/* perform a state transit with log output */
static void DoStateTransit
   (I4bCapiConnectionData_t *pConnData,
    I4bCapiPlciState_t       newState);



/* --- the state machine functions --- */

/* handle i4b connect-request */
static StateMachineResult_t SendConnectReq
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle acknowledge for connect-request */
static StateMachineResult_t HandleConnectConf
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle new incoming call */
static StateMachineResult_t HandleConnectInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* acknowledge new incoming call */
static StateMachineResult_t SendConnectResp
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* terminate current connection */
static StateMachineResult_t SendDisconnectReq
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle disconnect-req while still waiting for connect-conf */
static StateMachineResult_t HandleDisconnectReqInP0_1
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle acknowledge for disconnect-req */
static StateMachineResult_t HandleDisconnectConf
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle terminated connection */
static StateMachineResult_t HandleDisconnectInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle terminated connection while still waiting for connect-conf */
static StateMachineResult_t ReportDisconnectInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle established connection */
static StateMachineResult_t HandleConnectActiveInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* let a new incomnig call not expire too soon */
static StateMachineResult_t SendAlertReq
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* handle incoming D-channel information */
static StateMachineResult_t HandleInfoInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* forward a B3-message from CAPI to the NCCI layer */
static StateMachineResult_t HandleIncomingB3Msg
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* ignore an incoming event */
static StateMachineResult_t IgnoreEvent
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* timeout occurred waiting for connect-conf */
static StateMachineResult_t HandleConnectConfTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* timeout occurred waiting for active outgoing connection */
static StateMachineResult_t HandleActiveConnectTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* timeout occurred waiting for active incoming connection */
static StateMachineResult_t HandlePassiveConnectTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);

/* timeout occurred waiting for disconnect-ind after disconnect-req */
static StateMachineResult_t HandleActiveDisconnectTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo);



/* --- miscellaneous --- */

/* allocate a new connection and call descriptor */
static I4bCapiConnectionData_t *AllocateConnection
   (unsigned uCapiCtlrNum);

/* release a connection and call descriptor */
static void FreeConnection
   (I4bCapiConnectionData_t *pConnData);

/* enter the B-channel protocol configuration into a CAPI message */
static unsigned char *EnterBProtocol
   (unsigned char *pOrgPos,
    int            iBProt);

/* send a dummy response for an incoming CAPI message */
static void SendDummyResponse
   (const CAPIMsg_t *pCapiIndMsg);

/* send out a CAPI message with retries */
static unsigned PutCapiMessage
   (struct mbuf *pmbMsg);

/* translate PLCI state into string */
static const char *GetPlciStateName
   (I4bCapiPlciState_t state);

/* translate PLCI event into string */
static const char *GetPlciEventName
   (PlciEventType_t event);



/* --- the CAPI PLCI state table --- */

/* must go here after the state machine functions */

static StateMachineAction_t g_aStateTable [NUM_PLCI_STATES]
                                          [NUM_PLCISM_EVENTS] =
   {
      /* state P-0 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P0, SendConnectReq,
            PLCI_STATE_P0_1, PLCI_STATE_P0
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P0, NULL,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P0, ReportDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P0, NULL,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event timeout */
         {
            PLCI_STATE_P0, IgnoreEvent,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P0, IgnoreEvent,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P2, HandleConnectInd,
            PLCI_STATE_P4, PLCI_STATE_P5
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_P0, NULL,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P0, IgnoreEvent,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P0, IgnoreEvent,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P0, NULL,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P0, IgnoreEvent,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P0, IgnoreEvent,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P0, NULL,
            PLCI_STATE_P0, PLCI_STATE_P0
         }
      },
      /* state P-0.1 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P0_1, HandleDisconnectReqInP0_1,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event timeout */
         {
            PLCI_STATE_P0_1, HandleConnectConfTimeout,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P1, HandleConnectConf,
            PLCI_STATE_P1, PLCI_STATE_P0
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P0_1, IgnoreEvent,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P0_1, NULL,
            PLCI_STATE_P0_1, PLCI_STATE_P0_1
         }
      },
      /* state P-1 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P1, SendDisconnectReq,
            PLCI_STATE_P5, PLCI_STATE_P1
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event timeout */
         {
            PLCI_STATE_P1, HandleActiveConnectTimeout,
            PLCI_STATE_P5, PLCI_STATE_P1
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_PACT, HandleConnectActiveInd,
            PLCI_STATE_PACT, PLCI_STATE_P5
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P1, IgnoreEvent,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P1, HandleInfoInd,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P1, IgnoreEvent,
            PLCI_STATE_P1, PLCI_STATE_P1
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P1, NULL,
            PLCI_STATE_P1, PLCI_STATE_P1
         }
      },
      /* state P-2 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P2, SendConnectResp,
            PLCI_STATE_P4, PLCI_STATE_P5
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P2, SendDisconnectReq,
            PLCI_STATE_P5, PLCI_STATE_P2
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P2, SendAlertReq,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event timeout */
         /* Note: If I4B supported overlapped receiving (extension digits,
          *       direct dial-in), a timeout would be necessary here. In a
          *       normal situation ISDN has a timeout of 15 seconds between
          *       digits. But if something goes wrong, this timer is needed.
          *       Currently we just go strait from P-2 to P-4 without waiting
          *       for further digits.
          */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P2, IgnoreEvent,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P2, HandleInfoInd,
            PLCI_STATE_P4, PLCI_STATE_P5
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P2, IgnoreEvent,
            PLCI_STATE_P2, PLCI_STATE_P2
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P2, NULL,
            PLCI_STATE_P2, PLCI_STATE_P2
         }
      },
      /* state P-3 (handset support not implemented) */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P3, SendDisconnectReq,
            PLCI_STATE_P5, PLCI_STATE_P3
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event timeout */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P3, IgnoreEvent,
            PLCI_STATE_P3, PLCI_STATE_P3
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P3, NULL,
            PLCI_STATE_P3, PLCI_STATE_P3
         }
      },
      /* state P-4 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P4, SendDisconnectReq,
            PLCI_STATE_P5, PLCI_STATE_P4
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event timeout */
         {
            PLCI_STATE_P4, HandlePassiveConnectTimeout,
            PLCI_STATE_P5, PLCI_STATE_P4
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_PACT, HandleConnectActiveInd,
            PLCI_STATE_PACT, PLCI_STATE_P5
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P4, IgnoreEvent,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P4, HandleInfoInd,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P4, IgnoreEvent,
            PLCI_STATE_P4, PLCI_STATE_P4
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P4, NULL,
            PLCI_STATE_P4, PLCI_STATE_P4
         }
      },
      /* state P-ACT */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_PACT, SendDisconnectReq,
            PLCI_STATE_P5, PLCI_STATE_PACT
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event timeout */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_PACT, NULL,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_PACT, IgnoreEvent,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_PACT, HandleInfoInd,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_PACT, IgnoreEvent,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_PACT, HandleIncomingB3Msg,
            PLCI_STATE_PACT, PLCI_STATE_PACT
         }
      },
      /* state P-5 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P5, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P5, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P5, SendDisconnectReq,
            PLCI_STATE_P5, PLCI_STATE_P0
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P5, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event timeout */
         {
            PLCI_STATE_P5, HandleActiveDisconnectTimeout,
            PLCI_STATE_P5, PLCI_STATE_P0
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P5, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P5, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_P5, NULL,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P5, HandleDisconnectConf,
            PLCI_STATE_P5, PLCI_STATE_P0
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P5, IgnoreEvent,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P5, HandleInfoInd,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P5, IgnoreEvent,
            PLCI_STATE_P5, PLCI_STATE_P5
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P5, IgnoreEvent,
            PLCI_STATE_P5, PLCI_STATE_P5
         }
      },
      /* state P-6 */
      {
         /* event i4b connect-request */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event i4b connect-response */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event i4b disconnect-request */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event i4b alert-request */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event timeout */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI connect-confirm */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI connect-indication */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI connect-active-indication */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI disconnect-confirm */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI disconnect-indication */
         {
            PLCI_STATE_P6, HandleDisconnectInd,
            PLCI_STATE_P0, PLCI_STATE_P0
         },
         /* event CAPI info-confirm */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI info-indication */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event CAPI alert-confirm */
         {
            PLCI_STATE_P6, IgnoreEvent,
            PLCI_STATE_P6, PLCI_STATE_P6
         },
         /* event incoming CAPI b3-message */
         {
            PLCI_STATE_P6, NULL,
            PLCI_STATE_P6, PLCI_STATE_P6
         }
      }
   };
   
   
   


/* === definition of public functions ==================================== */





/*
        start a new outgoing connection
        -------------------------------
*/

void I4bCmgr_PlciConnectReq
   (I4bCapiConnectionData_t *pConnData)
{
   /* initialize the PLCI state machine */
   InitStateVariables (pConnData);
   
   DBG (LOG_INFO,
        "cd %u: start new outgoing connection on i4b controller %d, CAPI "
           "controller %u, B-channel %u",
        pConnData->pI4bCd->cdid, pConnData->pCtlrData->iI4bCtlrNum,
        pConnData->pCtlrData->uCapiCtlrNum, pConnData->uBChn);

   /* send the event to the state machine function */
   HandleEvent (pConnData, PLCISM_EVENT_I4B_CONN_REQ, NULL, NULL);
   
} /* I4bCmgr_PlciConnectReq */





/*
        acknowledge a new incoming connection
        -------------------------------------
*/

void I4bCmgr_PlciConnectResp
   (I4bCapiConnectionData_t  *pConnData,
    const I4bCapiEventData_t *pEventData)
{
   /* send the event to the state machine function */
   HandleEvent (pConnData, PLCISM_EVENT_I4B_CONN_RESP, NULL, pEventData);
   
} /* I4bCmgr_PlciConnectResp */





/*
        terminate a connection
        ----------------------
*/

void I4bCmgr_PlciDisconnectReq
   (I4bCapiConnectionData_t  *pConnData,
    const I4bCapiEventData_t *pEventData)
{
   /* send the event to the state machine function */
   HandleEvent (pConnData, PLCISM_EVENT_I4B_DISC_REQ, NULL, pEventData);
   
} /* I4bCmgr_PlciDisconnectReq */





/*
        do not let a new incoming connection expire
        -------------------------------------------
*/

void I4bCmgr_PlciAlertReq
   (I4bCapiConnectionData_t *pConnData)
{
   /* send the event to the state machine function */
   HandleEvent (pConnData, PLCISM_EVENT_I4B_ALERT_REQ, NULL, NULL);
   
} /* I4bCmgr_PlciAlertReq */





/*
        send a B3-level CAPI message
        ----------------------------
        This event cannot go through the state machine, because the NCCI layer
        needs the CAPI result of sending the message. This is not supported by
        the state machine.
        So this function just checks for state P-ACT and sends out the message.
        If the state is not P-ACT, an error will be returned. But the state of
        the PLCI state machine is not touched.
        
        The mbuf specified as a parameter must be released by the caller if the
        result is not CAPI_OK. If the result is CAPI_OK, the mbuf is released
        by the CAPI manager.
*/

unsigned I4bCmgr_PlciPutB3Msg
   (I4bCapiConnectionData_t *pConnData,
    struct mbuf             *pmbMsg)
{
   if (pConnData->plciState != PLCI_STATE_PACT)
   {
      return (CCE_MSG_NOT_ALLOWED_YET);
   }
   return (PutCapiMessage (pmbMsg));
} /* I4bCmgr_PlciPutB3Msg */





/*
        handle timeout for the PLCI layer
        ---------------------------------
*/

void I4bCmgr_PlciTimeout
   (I4bCapiConnectionData_t *pConnData)
{
   /* send the event to the state machine function */
   HandleEvent (pConnData, PLCISM_EVENT_TIMEOUT, NULL, NULL);
   
} /* I4bCmgr_PlciTimeout */





/*
        handle an incoming CAPI message
        -------------------------------
*/

void I4bCmgr_PlciReceiveCapiMessage
   (CAPIMsg_t *pCapiMsg)
{
   PlciEventType_t          event;
   I4bCapiConnectionData_t *pConnData;
   int                      s;

   /* create an event from the CAPI message */
   event = GetEventFromCapiMsg (pCapiMsg);

   s = SPLI4B ();

   /* if the message is a Connect-Ind, we now must allocate a new
    * connection data entry and call descriptor
    */
   if (event == PLCISM_EVENT_CAPI_CONN_IND)
   {
      pConnData = AllocateConnection
                     (CAPI_GET_CID (pCapiMsg) & CAPI_CIDMASK_CTLR);

      /* initialize the PLCI layer variables */
      InitStateVariables (pConnData);
   }
   /* if the message signals a valid event: get connection data entry
    * from dwCid in CAPI message header
    */
   else if ((size_t) event < (size_t) NUM_PLCISM_EVENTS)
   {
      pConnData = GetConnDataFromCapiMsg (pCapiMsg);
   }
   /* else: got unsupported message */
   else
   {
      DBG (LOG_ERROR,
           "Got unsupported CAPI message 0x%04X, dwCid 0x%08X, wNum 0x%04X",
           (unsigned) CAPI_GET_CMD (pCapiMsg),
           (unsigned) CAPI_GET_CID (pCapiMsg),
           (unsigned) CAPI_GET_MSGNUM (pCapiMsg));
      pConnData = NULL;
   }
   
   splx (s);
   
   /* if the message addresses a valid connection data entry: handle it */
   if (pConnData)
   {
      /* forward the CAPI event to the state machine function */
      HandleEvent (pConnData, event, pCapiMsg, NULL);
      /* Important: The connection data entry may not be allocated
       *            any more after handling a CAPI message!
       */
   }
   else
   {
      /* error message already printed */
      SendDummyResponse (pCapiMsg);
   }

} /* I4bCmgr_PlciReceiveCapiMessage */





/* === definition of private functions =================================== */





/*
        main event handling function for the PLCI state machine
        -------------------------------------------------------
*/

static void HandleEvent
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   StateMachineResult_t  res;
   StateMachineAction_t *pAction;
   
   /* test for valid parameters */
   if (! pConnData)
   {
      DBG (LOG_ERROR, "Invalid connection data pointer 0x%p", pConnData);
      return;
   }
   if (! pConnData->pI4bCd)
   {
      DBG (LOG_ERROR, "Controller %u, B-channel %u: no call descriptor linked",
           pConnData->pCtlrData->uCapiCtlrNum, pConnData->uBChn);
      return;
   }
   if ((size_t) (pConnData->plciState) >= NUM_PLCI_STATES)
   {
      DBG (LOG_ERROR, "Cd %u: invalid connection state %s",
           pConnData->pI4bCd->cdid, GetPlciStateName (pConnData->plciState));
      return;
   }
   if ((size_t) event >= NUM_PLCISM_EVENTS)
   {
      DBG (LOG_ERROR, "Cd %u: Got invalid event %s",
           pConnData->uCdId, GetPlciEventName (event));
      return;
   }
   
   /* determine action data for current state and event type */
   pAction = &(g_aStateTable [(int) (pConnData->plciState)] [(int) event]);
   
   /* if there is no action function, the event is not valid in current state
    * --> abort connection if not in state P-0
    */
   if (! pAction->pfnAction)
   {
      I4bCapiEventData_t eventData;

      DBG (LOG_ERROR, "Cd %u: State %s: Got unexpected event %s",
           pConnData->pI4bCd->cdid, GetPlciStateName (pConnData->plciState),
           GetPlciEventName (event));

      SET_CAUSE_TV (eventData.discReq.iCause, CAUSET_I4B, CAUSE_I4B_OOO);
      if (pConnData->plciState == PLCI_STATE_P0 ||
          pConnData->plciState == PLCI_STATE_P0_1)
      {
         (void) ReportDisconnectInd (pConnData, PLCISM_EVENT_I4B_DISC_REQ,
                                     NULL, &eventData);
      }
      else
      {
         (void) SendDisconnectReq (pConnData, PLCISM_EVENT_I4B_DISC_REQ,
                                   NULL, &eventData);
      }
      return;
   }
   
   /* first do initial state transit */
   DoStateTransit (pConnData, pAction->initialState);
   
   /* perform action for the event */
   res = (pAction->pfnAction) (pConnData, event, pCapiMsg, pAddInfo);
   
   /* do state transit according to action result */
   switch (res)
   {
      case SMRES_SUCCESS:
         if (pConnData->pI4bCd &&
             pConnData->plciState == pAction->initialState)
         {
            DoStateTransit (pConnData, pAction->successState);
         }
         break;
         
      case SMRES_FAILURE:
         if (pConnData->pI4bCd &&
             pConnData->plciState == pAction->initialState)
         {
            DoStateTransit (pConnData, pAction->failureState);
         }
         break;
         
      case SMRES_FATAL:
         if (pConnData->pI4bCd)
         {
            I4bCapiEventData_t eventData;

            if (pConnData->plciState == PLCI_STATE_P0 ||
                pConnData->plciState == PLCI_STATE_P0_1)
            {
               SET_CAUSE_TV (eventData.discReq.iCause,
                             CAUSET_I4B, CAUSE_I4B_OOO);
               (void) ReportDisconnectInd (pConnData,
                                           PLCISM_EVENT_I4B_DISC_REQ,
                                           NULL, &eventData);
            }
            else if (pConnData->plciState == PLCI_STATE_P2)
            {
               eventData.connResp.iResponse = SETUP_RESP_DNTCRE;
               SET_CAUSE_TV (eventData.connResp.iCause,
                             CAUSET_I4B, CAUSE_I4B_OOO);
               (void) SendConnectResp (pConnData,
                                       PLCISM_EVENT_I4B_CONN_RESP,
                                       NULL, &eventData);
            }
            else
            {
               SET_CAUSE_TV (eventData.discReq.iCause,
                             CAUSET_I4B, CAUSE_I4B_OOO);
               (void) SendDisconnectReq (pConnData,
                                         PLCISM_EVENT_I4B_DISC_REQ,
                                         NULL, &eventData);
            }
         }
         break;
         
      default:
      case SMRES_NO_STATE_TRANSIT:
         break;
   }
   
   DBG (LOG_DEBUG, "Cd %u: State %s: Event %s handled",
        (pConnData->pI4bCd) ? pConnData->pI4bCd->cdid : 0,
        GetPlciStateName (pConnData->plciState), GetPlciEventName (event));

} /* HandleEvent */





/*
        initialize the state variables for the PLCI layer
        -------------------------------------------------
*/

static void InitStateVariables
   (I4bCapiConnectionData_t *pConnData)
{
   pConnData->plciState = PLCI_STATE_P0;
   pConnData->dwPlci    = 0;
   if (! pConnData->fPlciCalloutInitOk)
   {
#ifdef __FreeBSD__
      callout_handle_init (&(pConnData->calloutPlci));
#else /* __FreeBSD__ */
      callout_init (&(pConnData->calloutPlci));
#endif /* __FreeBSD__ */
      pConnData->fPlciCalloutInitOk = 1;
   }
   pConnData->fPlciTimerRunning = 0;
   pConnData->uLastIndMsgNum = 0;
   pConnData->nNumDiscReq = 0;
   
} /* InitStateVariables */





/*
        the timer function
        ------------------
*/

static void PlciTimeout
   (I4bCapiConnectionData_t *pConnData)
{
   I4bCEventQueueEntry_t *pMsg;
   int                    s;
   
   s = SPLI4B ();

   /* check for (still) running timer */
   if (! pConnData->fPlciTimerRunning)
   {
      splx (s);
      DBG (LOG_DEBUG,
           "Cd %u: State %s: Timeout occurred while no timer running",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));
      return;
   }
   
   /* remember timer not running anymore */
   pConnData->fPlciTimerRunning = 0;
   
   /* send the timeout event to the state machine */
   pMsg = I4bCmgr_GetFreeEventQueueEntry ();
   if (! pMsg)
   {
      splx (s);
      printf ("i4bcapimgr: Cd %u: ERROR: Out of memory for PLCI timeout",
              pConnData->uCdId);
      return;
   }
   pMsg->uCapiCtlrNum = pConnData->pCtlrData->uCapiCtlrNum;
   pMsg->uBChn        = pConnData->uBChn;
   pMsg->uCdId        = pConnData->uCdId;
   pMsg->event        = I4BCMGR_EVENT_PLCI_TIMEOUT;

   TAILQ_INSERT_TAIL (&e_eventQueue, pMsg, links.tqe);
#if defined (__FreeBSD_version) && __FreeBSD_version >= 500000
   sema_post (&e_semMsgAvail);
#else /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   wakeup (&e_eventQueue);
#endif /* defined (__FreeBSD_version) && __FreeBSD_version >= 500000 */
   
   DBG (LOG_DEBUG, "Cd %u: PLCI timeout enqueued", pConnData->uCdId);
   
   splx (s);
   
} /* PlciTimeout */





/*
        create an PLCI state machine event from a CAPI message
        ------------------------------------------------------
        Note: The CAPI message is assumed to be an incoming message, i.e. a
              indication or a confirmation.
*/

static PlciEventType_t GetEventFromCapiMsg
   (const CAPIMsg_t *pCapiMsg)
{
   unsigned uCmd;
   unsigned uRawCmd;
   unsigned uIdx;
   
   uCmd = C_GET_WORD (pCapiMsg->head.wCmd);
   uRawCmd = (uCmd & CAPI_CMDMASK_COMMAND);
   if (uRawCmd < ARRAY_COUNT (g_aCapiCmdToEvent) / 2)
   {
      /* this is a PLCI message, translate it into events for confirmations and
       * indications
       */
      uIdx = uRawCmd * 2 +
             (((uCmd & CAPI_CMDMASK_SUBCMD) == C_CONF)
              ? 0 : 1);
      return (g_aCapiCmdToEvent [uIdx]);
   }
   
   /* every other message is a B3-level message (Select-B3-Protocol is not
    * supported)
    */
   return (PLCISM_EVENT_CAPI_GET_B3_MSG);
} /* GetEventFromCapiMsg */





/*
        determine the connection data entry from a CAPI message
        -------------------------------------------------------
*/

static I4bCapiConnectionData_t *GetConnDataFromCapiMsg
   (const CAPIMsg_t *pCapiMsg)
{
   I4bCapiCtlrData_t *pCtlrData;
   unsigned           uCmd;
   u_int32_t          dwCid;
   u_int32_t          dwPlci;
   unsigned           uCtlr;
   unsigned           uBChn;
   
   /* extract some needed values from the message */
   uCmd = CAPI_GET_CMD (pCapiMsg);
   dwCid = CAPI_GET_CID (pCapiMsg);

   /* get the CAPI controller no. from the message */
   uCtlr = (dwCid & CAPI_CIDMASK_CTLR);
   if (uCtlr >= ARRAY_COUNT (e_aI4bCapiCtlrData))
   {
      panic ("i4bcapimgr: PANIC: Invalid controller no. %u in CAPI message 0x%04X\n",
             uCtlr, CAPI_GET_CMD (pCapiMsg));
      return (NULL);
   }
   pCtlrData = &(e_aI4bCapiCtlrData [uCtlr]);
   if (! pCtlrData->fEntryUsed ||
       pCtlrData->iI4bCtlrNum < 0 ||
       ! pCtlrData->paConnData)
   {
      printf ("i4bcapimgr: ERROR: Got CAPI message 0x%04X for unusable controller, dwCid 0x%08x, wNum 0x%04X\n",
              uCmd, (unsigned) dwCid, CAPI_GET_MSGNUM (pCapiMsg));
      return (NULL);
   }
   
   /* if the message is a confirmation, the B-channel is simply identified by
    * the message number (really needed for Connect-Conf, as the PLCI is only
    * defined by this message)
    */
   if ((uCmd & CAPI_CMDMASK_SUBCMD) == C_CONF)
   {
      uBChn = CAPI_GET_MSGNUM (pCapiMsg);
   }
   /* else we must check all possible B-channel connections for the
    * controller for the PLCI
    */
   else
   {
      dwPlci = (dwCid & ~CAPI_CIDMASK_NCCI);
      for (uBChn = 0; uBChn < pCtlrData->nNumBChannels; uBChn++)
      {
         if (pCtlrData->paConnData [uBChn].pI4bCd &&
             pCtlrData->paConnData [uBChn].dwPlci == dwPlci)
         {
            break;
         }
      }
   }
   if (uBChn >= pCtlrData->nNumBChannels)
   {
      printf ("i4bcapimgr: ERROR: Got CAPI message 0x%04X for unknown connection, dwCid 0x%08X, dwNum 0x%04X\n",
              uCmd, (unsigned) dwCid, (unsigned) CAPI_GET_MSGNUM (pCapiMsg));
      return (NULL);
   }
   
   /* return  the connection data entry found */
   return (&(pCtlrData->paConnData [uBChn]));
} /* GetConnDataFromCapiMsg */





/*
        perform a state transit with log output
        ---------------------------------------
*/

static void DoStateTransit
   (I4bCapiConnectionData_t *pConnData,
    I4bCapiPlciState_t       newState)
{
   if (newState == pConnData->plciState)
   {
      return;
   }
   DBG (LOG_TRACE, "Cd %u: State %s: Moved to state %s",
        pConnData->uCdId,
        GetPlciStateName (pConnData->plciState),
        GetPlciStateName (newState));
   pConnData->plciState = newState;
   
} /* DoStateTransit */





/*
        handle i4b connect-request
        --------------------------
*/

static StateMachineResult_t SendConnectReq
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   struct mbuf   *pmbMsg;
   CAPIMsg_t     *pReqCapiMsg;
   unsigned       uCipValue;
   unsigned char *p;
   unsigned       uRes;
   
   /* get an mbuf for the CAPI message; as the real length is unknown yet, use
    * the maximum length of a CAPI message
    */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsg_t));
   if (! pmbMsg)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Out of mbufs for sending Connect-Req",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));

      SET_CAUSE_TV (pConnData->pI4bCd->cause_in,
                    CAUSET_I4B, CAUSE_I4B_TMPFAIL);
      FreeConnection (pConnData);
      return (SMRES_FAILURE);
   }
   
   /* fill CAPI message Connect-Req */
   pReqCapiMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   C_PUT_WORD (pReqCapiMsg->head.wApp, e_uCapiApplID);
   C_PUT_WORD (pReqCapiMsg->head.wCmd, CAPI_REQUEST (C_CONNECT));
   C_PUT_WORD (pReqCapiMsg->head.wNum, pConnData->uBChn);
   C_PUT_DWORD (pReqCapiMsg->head.dwCid, pConnData->pCtlrData->uCapiCtlrNum);
   uCipValue = (pConnData->pI4bCd->bprot == BPROT_RHDLC)
                  ? CAPI_CIP_UNRESTRICTED_DATA
                  : CAPI_CIP_3100Hz_AUDIO;
   C_PUT_WORD (pReqCapiMsg->info.connect_req.wCip, uCipValue);
   p = (unsigned char *) &(pReqCapiMsg->info.connect_req.wCip) +
       sizeof (pReqCapiMsg->info.connect_req.wCip);
   p = CapiUt_EnterCalledPartyNumber (p, pConnData->pI4bCd->dst_telno);
   p = CapiUt_EnterCallingPartyNumber (p, pConnData->pI4bCd->src_telno);
   p = CapiUt_EnterEmptyStruct (p);     /* no called party subaddress */
   p = CapiUt_EnterEmptyStruct (p);     /* no calling party subaddress */
   p = EnterBProtocol (p, pConnData->pI4bCd->bprot);
   p = CapiUt_EnterEmptyStruct (p);     /* no BC */
   p = CapiUt_EnterEmptyStruct (p);     /* no LLC */
   p = CapiUt_EnterEmptyStruct (p);     /* no HLC */
   p = CapiUt_EnterEmptyStruct (p);     /* no AdditionalInfo */
   
   /* compute the message length */
   pmbMsg->m_len = (int) (p - (unsigned char *) pReqCapiMsg);
   C_PUT_WORD (pReqCapiMsg->head.wLen, pmbMsg->m_len);
   
   /* send the message */
   uRes = PutCapiMessage (pmbMsg);
   if (uRes != CAPI_OK)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Error 0x%04X sending Connect-Req",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);

      kcapi_free_mbuf (pmbMsg);
      SET_CAUSE_TV (pConnData->pI4bCd->cause_in,
                    CAUSET_I4B, CAUSE_I4B_REJECT);
      FreeConnection (pConnData);
      return (SMRES_FAILURE);
   }
   
   /* start timer for receiving Connect-Conf */
   pConnData->fPlciTimerRunning = 1;
   START_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData,
                WAIT_FOR_CONNECT_CONF_TIMEOUT);
   
   /* suppress warning for unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   return (SMRES_SUCCESS);
} /* SendConnectReq */





/*
        handle acknowledge for connect-request
        --------------------------------------
*/

static StateMachineResult_t HandleConnectConf
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   unsigned uInfo;
   
   /* stop running timer */
   if (pConnData->fPlciTimerRunning)
   {
      pConnData->fPlciTimerRunning = 0;
      STOP_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData);
   }
   
   /* set the PLCI value in the connection data */
   pConnData->dwPlci = CAPI_GET_CID (pCapiMsg);
   
   /* evalutate result value of confirmation */
   uInfo = C_GET_WORD (pCapiMsg->info.connect_conf.wInfo);
   if (uInfo != CAPI_OK)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Error 0x%04X in Connect-Conf",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState), uInfo);

      SET_CAUSE_TV (pConnData->pI4bCd->cause_in,
                    CAUSET_I4B, CAUSE_I4B_REJECT);
      FreeConnection (pConnData);
      return (SMRES_FAILURE);
   }
   
   /* start timer to wait for Connect-Active-Ind */
   pConnData->fPlciTimerRunning = 1;
   START_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData,
                WAIT_FOR_CONNECT_ACTIVE_TIMEOUT);

   /* suppress warning for unused parameters */
   (void) pAddInfo;

   return (SMRES_SUCCESS);
} /* HandleConnectConf */





/*
        handle new incoming call
        ------------------------
*/

static StateMachineResult_t HandleConnectInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   const unsigned char *p;

   /* set the PLCI value in the connection data */
   pConnData->dwPlci = CAPI_GET_CID (pCapiMsg);
   pConnData->uLastIndMsgNum = CAPI_GET_MSGNUM (pCapiMsg);
   pConnData->nNumDiscReq = 0;
   
   /* extract the message data */
   switch (C_GET_WORD (pCapiMsg->info.connect_ind.wCip))
   {
      case CAPI_CIP_SPEECH:
      case CAPI_CIP_3100Hz_AUDIO:
      case CAPI_CIP_TELEPHONY:
         pConnData->pI4bCd->bprot = BPROT_NONE;
         break;
      
      default:
         DBG (LOG_DEBUG,
              "Cd %u: Got unknown cip value %u, fallback to data connection",
              pConnData->pI4bCd->cdid,
              (unsigned) C_GET_WORD (pCapiMsg->info.connect_ind.wCip));
         /*fallthrough*/
      case CAPI_CIP_UNRESTRICTED_DATA:
         pConnData->pI4bCd->bprot = BPROT_RHDLC;
         break;
   }
   p = (const unsigned char *) &(pCapiMsg->info.connect_ind.wCip) +
       sizeof (pCapiMsg->info.connect_ind.wCip);
   p = CapiUt_ExtractCalledPartyNumber
          (p,
           pConnData->pI4bCd->dst_telno,
           sizeof (pConnData->pI4bCd->dst_telno));
   p = CapiUt_ExtractCallingPartyNumber
          (p,
           pConnData->pI4bCd->src_telno,
           sizeof (pConnData->pI4bCd->src_telno),
           &(pConnData->pI4bCd->scr_ind),
           &(pConnData->pI4bCd->prs_ind));
   /* the rest of the message is not relevant to i4b */
   
   /* initialize further members of the call descriptor */
   pConnData->pI4bCd->cause_in = 0;
   pConnData->pI4bCd->cause_out = 0;
   pConnData->pI4bCd->display [0] = '\0';
   pConnData->pI4bCd->datetime [0] = '\0';

   /* forward the new incoming connection to the NCCI layer */
   I4bCmgr_NcciReceiveConnectInd (pConnData);
   
   /* Note: The response to this indication is sent when handling
    * N_CONNECT_RESPONSE
    */

   return (SMRES_NO_STATE_TRANSIT);
} /* HandleConnectInd */





/*
        acknowledge new incoming call
        -----------------------------
*/

static StateMachineResult_t SendConnectResp
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   struct mbuf   *pmbMsg;
   CAPIMsg_t     *pRespMsg;
   unsigned       uReject;
   unsigned char *p;
   unsigned       uRes;
   
   /* mark B-channel as allocated if needed */
   /* XXX: It seems that the isdnd wants to reset the B-channel busy state
    *      itself. To avoid severe error messages from isdnd we set the
    *      B-channel to busy in every case.
    */
   /*if (pAddInfo->connResp.iResponse == SETUP_RESP_ACCEPT)*/
   {
      if (pConnData->uBChn <=
             ARRAY_COUNT (ctrl_desc [pConnData->pI4bCd->controller].bch_state))
      {
         ctrl_desc [pConnData->pI4bCd->controller].bch_state
            [pConnData->uBChn] = BCH_ST_USED;
      }
   }
   
   /* get an mbuf for the CAPI message; as the real length is unknown yet, use
    * the maximum length of a CAPI message
    */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsg_t));
   if (! pmbMsg)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Out of mbufs for sending Connect-Resp",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));
      return (SMRES_FATAL);
   }
   
   /* fill CAPI message Connect-Resp */
   pRespMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   C_PUT_WORD (pRespMsg->head.wApp, e_uCapiApplID);
   C_PUT_WORD (pRespMsg->head.wCmd, CAPI_RESPONSE (C_CONNECT));
   C_PUT_WORD (pRespMsg->head.wNum, pConnData->uLastIndMsgNum);
   C_PUT_DWORD (pRespMsg->head.dwCid, pConnData->dwPlci);
   pConnData->pI4bCd->response = pAddInfo->connResp.iResponse;
   switch (pAddInfo->connResp.iResponse)
   {
      case SETUP_RESP_ACCEPT:
         uReject = CAPI_CALL_ACCEPT;
         break;
         
      case SETUP_RESP_REJECT:
         if (GET_CAUSE_TYPE (pAddInfo->connResp.iCause) == CAUSET_Q850)
         {
            uReject = CDISC_NETWORK_CAUSE_MASK +
                      GET_CAUSE_VAL (pAddInfo->connResp.iCause);
         }
         else
         {
            uReject = CAPI_CALL_REJECT_NORMAL_CLEARING;
         }
         break;

      default:
      case SETUP_RESP_DNTCRE:
         uReject = CAPI_CALL_IGNORE;
         break;
   }
   C_PUT_WORD (pRespMsg->info.connect_resp.wReject, uReject);
   p = (unsigned char *) &(pRespMsg->info.connect_resp.wReject) +
       sizeof (pRespMsg->info.connect_resp.wReject);
   p = EnterBProtocol (p, pConnData->pI4bCd->bprot);
   p = CapiUt_EnterEmptyStruct (p);     /* no connected party number */
   p = CapiUt_EnterEmptyStruct (p);     /* no connected party subaddress */
   p = CapiUt_EnterEmptyStruct (p);     /* no LLC */
   p = CapiUt_EnterEmptyStruct (p);     /* no AdditionalInfo */
   
   /* compute the message length */
   pmbMsg->m_len = (size_t) (p - (unsigned char *) pRespMsg);
   C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
   
   /* send the message */
   uRes = PutCapiMessage (pmbMsg);
   if (uRes != CAPI_OK)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Error 0x%04X sending Connect-Resp",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);

      kcapi_free_mbuf (pmbMsg);
      return (SMRES_FATAL);
   }
   
   /* if the call is not accepted: wait for Disconnect-Ind */
   if (uReject != CAPI_CALL_ACCEPT)
   {
      /* enter the cause value into the call descriptor */
      if (uReject >= CDISC_NETWORK_CAUSE_MASK)
      {
         pConnData->pI4bCd->cause_out = uReject - CDISC_NETWORK_CAUSE_MASK;
      }
      else
      {
         pConnData->pI4bCd->cause_out = CAUSE_Q850_NCCLR;
      }
      
      DBG (LOG_DEBUG, "Cd %u: Call %s, wait for Disconnect-Ind",
           pConnData->uCdId,
           (uReject == CAPI_CALL_IGNORE) ? "ignored" : "rejected");

      /* start timer to wait for Disconnect-Ind */
      pConnData->fPlciTimerRunning = 1;
      START_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData,
                   WAIT_FOR_DISCONNECT_IND_TIMEOUT);
      return (SMRES_FAILURE);
   }
   
   DBG (LOG_DEBUG, "Cd %u: Call accepted, wait for Connect-Active-Ind",
        pConnData->uCdId);

   /* start timer to wait for Connect-Active-Ind */
   pConnData->fPlciTimerRunning = 1;
   START_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData,
                WAIT_FOR_CONNECT_ACTIVE_TIMEOUT);
   
   /* suppress warning for unused parameters */
   (void) pCapiMsg;
   
   return (SMRES_SUCCESS);
} /* SendConnectResp */





/*
        terminate current connection
        ----------------------------
*/

static StateMachineResult_t SendDisconnectReq
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   struct mbuf   *pmbMsg;
   CAPIMsg_t     *pReqMsg;
   unsigned       uRes;
   
   /* stop possibly running timer */
   if (pConnData->fPlciTimerRunning)
   {
      pConnData->fPlciTimerRunning = 0;
      STOP_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData);
   }
   
   /* copy the cause parameter to the call descriptor */
   pConnData->pI4bCd->cause_out = pAddInfo->discReq.iCause;
   
   /* if we are already in state P-0, just declare the connection as finished */
   if (pConnData->plciState == PLCI_STATE_P0)
   {
      FreeConnection (pConnData);
      return (SMRES_NO_STATE_TRANSIT);
   }
   
   /* check if this is the third Disconnect-Req to be sent */
   if (pConnData->nNumDiscReq >= 2)
   {
      /* another Disconnect-Req will not do it better, declare the connection
       * as finished
       */
      printf ("i4bcapimgr: ERROR: Cd %u: State %s: Unable to disconnect\n",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState));
      DoStateTransit (pConnData, PLCI_STATE_P0);
      FreeConnection (pConnData);
      return (SMRES_NO_STATE_TRANSIT);
   }
   pConnData->nNumDiscReq++;
   
   /* Start the timer to wait for Disconnect-Ind now. So we also get a timeout
    * if the message could not be sent this time.
    */
   pConnData->fPlciTimerRunning = 1;
   START_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData,
                WAIT_FOR_DISCONNECT_IND_TIMEOUT);

   /* get an mbuf for the CAPI message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t) + 1);
   if (! pmbMsg)
   {
      DBG (LOG_ERROR,
           "Cd %u: State %s: Out of mbufs for sending Disconnect-Ind",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));
      return (SMRES_FAILURE);
   }
   
   /* fill CAPI message Disconnect-Req */
   pReqMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   C_PUT_WORD (pReqMsg->head.wApp, e_uCapiApplID);
   C_PUT_WORD (pReqMsg->head.wCmd, CAPI_REQUEST (C_DISCONNECT));
   C_PUT_WORD (pReqMsg->head.wNum, pConnData->uBChn);
   C_PUT_DWORD (pReqMsg->head.dwCid, pConnData->dwPlci);
   C_PUT_BYTE (pReqMsg->info.any.b [0], 0);
                                        /* no AdditionalInfo */

   /* compute the message length */
   pmbMsg->m_len = sizeof (pReqMsg->head) + 1;
   C_PUT_WORD (pReqMsg->head.wLen, pmbMsg->m_len);
   
   /* send the message */
   uRes = PutCapiMessage (pmbMsg);
   if (uRes != CAPI_OK)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Error 0x%04X sending Disconnect-Req",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);

      kcapi_free_mbuf (pmbMsg);
      return (SMRES_FAILURE);
   }
   
   return (SMRES_SUCCESS);
} /* SendDisconnectReq */





/*
        handle disconnect-req while still waiting for connect-conf
        ----------------------------------------------------------
*/

static StateMachineResult_t HandleDisconnectReqInP0_1
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   /* stop possibly running timer */
   if (pConnData->fPlciTimerRunning)
   {
      pConnData->fPlciTimerRunning = 0;
      STOP_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData);
   }
   
   /* copy the cause parameter to the call descriptor */
   pConnData->pI4bCd->cause_out = pAddInfo->discReq.iCause;
   
   /* In this state there is still no PLCI, so it is not possible to abort the
    * connection. We just can go to state P-0 and let the SendDummyResponse
    * function handle the Connect-Conf (i.e. send a Disconnect-Req).
    */
   DoStateTransit (pConnData, PLCI_STATE_P0);
   FreeConnection (pConnData);
   
   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   return (SMRES_NO_STATE_TRANSIT);
} /* HandleDisconnectReqInP0_1 */





/*
        handle acknowledge for disconnect-req
        -------------------------------------
*/

static StateMachineResult_t HandleDisconnectConf
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   /* check the info value of the message */
   if (C_GET_WORD (pCapiMsg->info.disconnect_conf.wInfo) != CAPI_OK)
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Error 0x%04X in Disconnect-Conf",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState),
           (unsigned) C_GET_WORD (pCapiMsg->info.disconnect_conf.wInfo));

      /* let us do another Disconnect-Req */
      return (SMRES_FATAL);
   }
   
   /* just wait further for Disconnect-Ind */
   return (SMRES_SUCCESS);
} /* HandleDisconnectConf */





/*
        handle terminated connection
        ----------------------------
*/

static StateMachineResult_t HandleDisconnectInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   struct mbuf *pmbMsg;
   CAPIMsg_t   *pRespMsg;
   unsigned     uReason;
   unsigned     uRes;
   
   /* stop possibly running timer */
   if (pConnData->fPlciTimerRunning)
   {
      pConnData->fPlciTimerRunning = 0;
      STOP_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData);
   }
   
   /* copy the cause value to the call descriptor */
   uReason = (unsigned) C_GET_WORD (pCapiMsg->info.disconnect_ind.wReason);
   if ((uReason & 0xFF00) == CDISC_NETWORK_CAUSE_MASK)
   {
      pConnData->pI4bCd->cause_in = (uReason & 0x007F);
   }
   
   DBG (LOG_TRACE, "Cd %u: State %s: Got Disconnect-Ind, reason 0x%04X",
        pConnData->uCdId, GetPlciStateName (pConnData->plciState), uReason);

   /* send response message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t));
   if (pmbMsg)
   {
      pRespMsg = (CAPIMsg_t *) (pmbMsg->m_data);
      pRespMsg->head = pCapiMsg->head;
      pRespMsg->head.wCmd = CAPI_RESPONSE (C_DISCONNECT);
      pmbMsg->m_len = sizeof (pRespMsg->head);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
      
      uRes = PutCapiMessage (pmbMsg);
      if (uRes != CAPI_OK)
      {
         kcapi_free_mbuf (pmbMsg);

         DBG (LOG_ERROR,
              "Cd %u: State %s: Error 0x%04X responding to CAPI message Disconnect-Ind",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);
      }
   }
   else
   {
      DBG (LOG_ERROR, "Cd %u: %s: Out of mbufs for response to CAPI message Disconnect-Ind",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState));
   }
   
   /* release the connection and call descriptor */
   FreeConnection (pConnData);
   
   return (SMRES_SUCCESS);
} /* HandleDisconnectInd */





/*
        handle terminated connection while still waiting for connect-conf
        -----------------------------------------------------------------
*/

static StateMachineResult_t ReportDisconnectInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   DoStateTransit (pConnData, PLCI_STATE_P0);
   FreeConnection (pConnData);
   
   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   return (SMRES_NO_STATE_TRANSIT);
} /* ReportDisconnectInd */





/*
        handle established connection
        -----------------------------
*/

static StateMachineResult_t HandleConnectActiveInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   struct mbuf *pmbMsg;
   CAPIMsg_t   *pRespMsg;
   unsigned     uRes;
   
   /* stop possibly running timer */
   if (pConnData->fPlciTimerRunning)
   {
      pConnData->fPlciTimerRunning = 0;
      STOP_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData);
   }
   
   DBG (LOG_TRACE,
        "Cd %u: State %s: Got Connect-Active-Ind, connection established",
        pConnData->uCdId, GetPlciStateName (pConnData->plciState));

   /* send response message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t));
   if (pmbMsg)
   {
      pRespMsg = (CAPIMsg_t *) (pmbMsg->m_data);
      pRespMsg->head = pCapiMsg->head;
      pRespMsg->head.wCmd = CAPI_RESPONSE (C_CONNECT_ACTIVE);
      pmbMsg->m_len = sizeof (pRespMsg->head);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
      
      uRes = PutCapiMessage (pmbMsg);
      if (uRes != CAPI_OK)
      {
         kcapi_free_mbuf (pmbMsg);
         DBG (LOG_ERROR,
              "Cd %u: State %s: Error 0x%04X responding to CAPI message Connect-Active-Ind",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);
      }
   }
   else
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Out of mbufs for Connect-Active-Resp",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));
   }
   
   /* forward the signal for the active connection to the NCCI layer */
   I4bCmgr_NcciReceiveConnectionUp (pConnData);
   
   return (SMRES_SUCCESS);
} /* HandleConnectActiveInd */





/*
        let a new incomnig call not expire too soon
        -------------------------------------------
*/

static StateMachineResult_t SendAlertReq
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   struct mbuf *pmbMsg;
   CAPIMsg_t   *pReqMsg;
   unsigned     uRes;
   
   /* send response message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t) + 1);
   if (pmbMsg)
   {
      pReqMsg = (CAPIMsg_t *) (pmbMsg->m_data);
      C_PUT_WORD (pReqMsg->head.wApp, e_uCapiApplID);
      C_PUT_WORD (pReqMsg->head.wCmd, CAPI_REQUEST (C_ALERT));
      C_PUT_WORD (pReqMsg->head.wNum, pConnData->uBChn);
      C_PUT_WORD (pReqMsg->head.dwCid, pConnData->dwPlci);
      C_PUT_BYTE (pReqMsg->info.any.b [0], 0);
      pmbMsg->m_len = sizeof (pReqMsg->head) + 1;
      C_PUT_WORD (pReqMsg->head.wLen, pmbMsg->m_len);
      
      uRes = PutCapiMessage (pmbMsg);
      if (uRes == CAPI_OK)
      {
         DBG (LOG_DEBUG, "Cd %u: State %s: Alert-Req sent",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState));
      }
      else
      {
         kcapi_free_mbuf (pmbMsg);

         DBG (LOG_ERROR,
              "Cd %u: State %s: Error 0x%04X sending CAPI message Alert-Req",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);
      }
   }
   else
   {
      DBG (LOG_ERROR, "Cd %u: State %s: Out of mbufs for sending Alert-Req",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));
   }
   
   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   return (SMRES_SUCCESS);
} /* SendAlertReq */





/*
        handle incoming D-channel information
        -------------------------------------
*/

static StateMachineResult_t HandleInfoInd
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   unsigned              uInfoNum;
   const unsigned char  *pInfoElt;
   int                   fDoAlertInd = 0;
   int                   fDoProceedingInd = 0;
   int                   fDoChargingInd = 0;
   struct mbuf          *pmbMsg;
   CAPIMsg_t            *pRespMsg;
   unsigned              uRes;
   StateMachineResult_t  smRes;
   
   /* address the parts of the message */
   uInfoNum = C_GET_WORD (pCapiMsg->info.info_ind.wInfoNum);
   pInfoElt = (const unsigned char *) &(pCapiMsg->info.info_ind.wInfoNum) +
              sizeof (pCapiMsg->info.info_ind.wInfoNum);

   /* distinguish between the different types of information */
   if ((uInfoNum & 0x8000) != 0)
   {
      unsigned uMsgType;
      
      /* network event, low byte of uInfoNum contains ETSI message type */
      
      /* check for valid message types */
      uMsgType = (uInfoNum & 0xFF);
      switch (uMsgType)
      {
         case ALERT:
            DBG (LOG_DEBUG,
                 "Cd %u: State %s: Got D-channel message ALERT",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState));
            fDoAlertInd = 1;
            break;
            
         case CALL_PROCEEDING:
            DBG (LOG_DEBUG,
                 "Cd %u: State %s: Got D-channel message CALL PROCEEDING",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState));
            fDoProceedingInd = 1;
            break;
            
         case SETUP_ACKNOWLEDGE:
            /* handle this like proceeding (see note in i4b_l3fsm, function
             * F_01L)
             */
            DBG (LOG_DEBUG,
                 "Cd %u: State %s: Got D-channel message SETUP ACKNOWLEDGE",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState));
            fDoProceedingInd = 1;
            break;
            
         default:
            /* other message types are not handled by i4b */
            DBG (LOG_DEBUG,
                 "Cd %u: State %s: Got unhandled D-channel message type 0x%02X",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                 uMsgType);
            break;
      }
   }
   else if ((uInfoNum & 0xC000) == 0x0000)
   {
      unsigned uIeId;
      
      /* D-channel information element signalled; the low byte represents the
       * information element type, the information element is coded at pInfoElt
       */
      uIeId = (uInfoNum & 0x00FF);
      switch (uIeId)
      {
         case IEI_CAUSE:
            if (pInfoElt [0] < 2)
            {
               DBG (LOG_TRACE,
                    "Cd %u: State %s: Info-Ind: Got cause element without cause byte",
                    pConnData->uCdId, GetPlciStateName (pConnData->plciState));
            }
            else if ((pInfoElt [1] & 0x80) != 0)
            {
               pConnData->pI4bCd->cause_in = (pInfoElt [2] & 0x7F);
               DBG (LOG_TRACE,
                    "Cd %u: State %s: Info-Ind: Got cause value 0x%02X",
                    pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                    pConnData->pI4bCd->cause_in);
            }
            else if (pInfoElt [0] >= 3 && (pInfoElt [1] & 0x80) == 0)
            {
               pConnData->pI4bCd->cause_in = (pInfoElt [3] & 0x7F);
               DBG (LOG_TRACE,
                    "Cd %u: State %s: Info-Ind: Got cause value 0x%02X",
                    pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                    pConnData->pI4bCd->cause_in);
            }
            else
            {
               DBG (LOG_TRACE,
                    "Cd %u: State %s: Info-Ind: Got invalid cause element, length %u, 1st byte 0x%02X, 2nd byte 0x%02X",
                    pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                    (unsigned) (pInfoElt [0]), (unsigned) (pInfoElt [1]),
                    (unsigned) (pInfoElt [2]));
            }
            break;
            
         case IEI_DISPLAY:
            {
               size_t nLen;
               
               nLen = (size_t) (pInfoElt [0]);
               if (nLen >= sizeof (pConnData->pI4bCd->display))
               {
                  nLen = sizeof (pConnData->pI4bCd->display) - 1;
               }
               memcpy (pConnData->pI4bCd->display, &(pInfoElt [1]), nLen);
               pConnData->pI4bCd->display [nLen] = '\0';
            }
            DBG (LOG_DEBUG, "Cd %u: State %s: Info-Ind: Got DISPLAY \"%s\"",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                 pConnData->pI4bCd->display);
            break;
            
         case IEI_DATETIME:
            {
               size_t               nLenSrc;
               size_t               nLenDst;
               size_t               nLen;
               const unsigned char *pSrc;
               unsigned char       *pDst;
               
               nLenSrc = pInfoElt [0];
               nLenDst = sizeof (pConnData->pI4bCd->datetime);
               for (pSrc = &(pInfoElt [1]), pDst = pConnData->pI4bCd->datetime;
                    nLenSrc > 0 && nLenDst > 0;
                    nLenSrc--, pSrc++)
               {
                  nLen = snprintf (pDst, nLenDst, "%02d", (int) *pSrc);
                  nLenDst -= nLen;
                  pDst += nLen;
               }
            }
            DBG (LOG_DEBUG, "Cd %u: State %s: Info-Ind: Got DATETIME \"%s\"",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                 pConnData->pI4bCd->datetime);
            break;
         
         case IEI_CALLEDPN:
            /* The called party number information element is only used for
             * overlapped receiving (extension digits, direct dial-in). As I4B
             * does currently not support it, this IE is not handled here.
             */
         default:
            /* other information elements are not handled by i4b */
            DBG (LOG_DEBUG,
                 "Cd %u: State %s: Info-Ind: Got unhandled info element 0x%02X",
                 pConnData->uCdId, GetPlciStateName (pConnData->plciState),
                 uIeId);
            break;
      }
   }
   else if ((uInfoNum & 0xC000) == 0x4000)
   {
      unsigned uChargeType;
      size_t   nLen;
      
      /* supplementary information, currently only charges are defined */
      /* Note: We do not get information about the type (AOCD or AOCE). But
       *       we can guess what type is currently used. If we get a charging
       *       info element during an active connection (NCCI state N-ACT),
       *       AOCD is used, else it is AOCE. But AOCE is only set if the
       *       units_type member is set to CHARGE_INVALID (initialization
       *       value). So a formerly determined type AOCD will not mutate to
       *       AOCE.
       */
      if (pConnData->pI4bCd->units_type == CHARGE_INVALID)
      {
         if (pConnData->ncciState == NCCI_STATE_NACT)
         {
            pConnData->pI4bCd->units_type = CHARGE_AOCD;
         }
         else
         {
            pConnData->pI4bCd->units_type = CHARGE_AOCE;
         }
      }
      uChargeType = (uInfoNum & 0x00FF);
      if (uChargeType == 0)
      {
         /* charging units signalled */
         nLen = (size_t) *(pInfoElt++);
         if (nLen > 0)
         {
            pConnData->pI4bCd->units = (int) (unsigned) (*(pInfoElt++));
         }
         if (nLen > 1)
         {
            pConnData->pI4bCd->units += (int) ((unsigned) (*(pInfoElt++)) << 8);
         }
         if (nLen > 2)
         {
            pConnData->pI4bCd->units += (int) ((unsigned) (*(pInfoElt++)) << 16);
         }
         if (nLen > 3)
         {
            pConnData->pI4bCd->units += (int) ((unsigned) (*(pInfoElt++)) << 24);
         }
         fDoChargingInd = 1;
         DBG (LOG_DEBUG,
              "Cd %u: State %s: Info-Ind: %d charging units signaled",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState),
              pConnData->pI4bCd->units);
      }
      else if (uChargeType == 1)
      {
         /* charging currency units */
         /* Note: I4B does not support handling of currency units. But it does
          *       use the AOCD for shorthold calculation. So we just signal
          *       the currency units as AOCD (if applicable). Luckily currency
          *       units are very rarely used (at least in Germany :-) )
          */
         nLen = (size_t) *(pInfoElt++);
         if (nLen > 0)
         {
            pConnData->pI4bCd->units = (int) (unsigned) (*(pInfoElt++));
         }
         if (nLen > 1)
         {
            pConnData->pI4bCd->units += (int) ((unsigned) (*(pInfoElt++)) << 8);
         }
         if (nLen > 2)
         {
            pConnData->pI4bCd->units += (int) ((unsigned) (*(pInfoElt++)) << 16);
         }
         if (nLen > 3)
         {
            pConnData->pI4bCd->units += (int) ((unsigned) (*(pInfoElt++)) << 24);
         }
         fDoChargingInd = 1;
         DBG (LOG_DEBUG,
              "Cd %u: State %s: Info-Ind: Got charging currency units of %d, not supported",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState),
              pConnData->pI4bCd->units);
      }
      else
      {
         /* unknown supplementary information */
         DBG (LOG_DEBUG,
              "Cd %u: State %s: Info-Ind: Got unknown supplementary information 0x%02X",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState),
              uChargeType);
      }
   }
   else
   {
      /* unknown Info-Ind */
      DBG (LOG_DEBUG,
           "Cd %u: State %s: Info-Ind: Got unknown info number 0x%04X",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState),
           uInfoNum);
   }
   
   
   /* send the Info-Resp */
   smRes = SMRES_NO_STATE_TRANSIT;      /* a state transit will only be
                                         * performed when supporting overlapped
                                         * receiving (extension digits, direct
                                         * dial-in) and enough digits arrived
                                         * to decide to accept or reject/ignore
                                         * the call
                                         */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t));
   if (pmbMsg)
   {
      pRespMsg = (CAPIMsg_t *) (pmbMsg->m_data);
      pRespMsg->head = pCapiMsg->head;
      C_PUT_WORD (pRespMsg->head.wCmd, CAPI_RESPONSE (C_INFO_20));
      pmbMsg->m_len = sizeof (pRespMsg->head);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
      
      uRes = PutCapiMessage (pmbMsg);
      if (uRes != CAPI_OK)
      {
         DBG (LOG_ERROR,
              "Cd %u: State %s: Error 0x%04X responding to message Info-Ind\n",
              pConnData->uCdId, GetPlciStateName (pConnData->plciState), uRes);
         kcapi_free_mbuf (pmbMsg);
         smRes = SMRES_FATAL;
      }
   }
   else
   {
      DBG (LOG_ERROR,
           "Cd %u: State %s: Out of mbufs responding to message Info-Ind\n",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState));
      smRes = SMRES_FATAL;
   }
   
   /* if found forward event to i4b layer 4 (bypass NCCI layer) */
   if (fDoAlertInd)
   {
      i4b_l4_alert_ind (pConnData->pI4bCd);
   }
   if (fDoProceedingInd)
   {
      i4b_l4_proceeding_ind (pConnData->pI4bCd);
   }
   if (fDoChargingInd)
   {
      i4b_l4_charging_ind (pConnData->pI4bCd);
   }
   
   /* suppress warning about unused parameters */
   (void) pAddInfo;
   
   return (smRes);
} /* HandleInfoInd */





/*
        forward a B3-message from CAPI to the NCCI layer
        ------------------------------------------------
*/

static StateMachineResult_t HandleIncomingB3Msg
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   I4bCmgr_NcciReceiveCapiMessage (pConnData, pCapiMsg);
   
   /* suppress warning about unused parameters */
   (void) pAddInfo;
   
   return (SMRES_NO_STATE_TRANSIT);
} /* HandleIncomingB3Msg */





/*
        ignore an incoming event
        ------------------------
*/

static StateMachineResult_t IgnoreEvent
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   /* if the event is a CAPI message, we must send a dummy response message if
    * it is an indication
    */
   if (event >= PLCISM_EVENT_CAPI_CONN_IND)
   {
      DBG (LOG_DEBUG, "Cd %u: State %s: Ignoring CAPI message 0x%04X",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState),
           (unsigned) CAPI_GET_CMD (pCapiMsg));

      SendDummyResponse (pCapiMsg);
   }
   else
   {
      DBG (LOG_DEBUG, "Cd %u: State %s: Ignoring event %s",
           pConnData->uCdId, GetPlciStateName (pConnData->plciState),
           GetPlciEventName (event));
   }
   
   /* suppress warning about unused parameters */
   (void) pAddInfo;
   
   return (SMRES_SUCCESS);
} /* IgnoreEvent */





/*
        timeout occurred waiting for connect-conf
        -----------------------------------------
        If the controller does not respond to a Connect-Req with a
        Connect-Conf, the controller is probably out of order. The outgoing
        connection cannot be made. But we also cannot abort it, because there
        is still no PLCI available (should be deliverd by Connect-Conf). So we
        just declare the connection as terminated and release it.
*/

static StateMachineResult_t HandleConnectConfTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   DBG (LOG_ERROR,
        "Cd %u: State %s: Timeout waiting for Connect-Conf, CAPI controller %u possibly not working",
        pConnData->uCdId, GetPlciStateName (pConnData->plciState),
        pConnData->pCtlrData->uCapiCtlrNum);

   /* set cause to "layer 1 error"; seems to match the situation best */
   SET_CAUSE_TV (pConnData->pI4bCd->cause_in, CAUSET_I4B, CAUSE_I4B_L1ERROR);
   
   /* release the current connection */
   FreeConnection (pConnData);
   
   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   return (SMRES_SUCCESS);
} /* HandleConnectConfTimeout */





/*
        timeout occurred waiting for active outgoing connection
        -------------------------------------------------------
*/

static StateMachineResult_t HandleActiveConnectTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   I4bCapiEventData_t eventData;
   
   DBG (LOG_ERROR,
        "Cd %u: State %s: Timeout waiting for established outgoing connection (Connect-Active-Ind)",
        pConnData->uCdId, GetPlciStateName (pConnData->plciState));

   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   /* abort the connection */
   SET_CAUSE_TV (eventData.discReq.iCause, CAUSET_I4B, CAUSE_I4B_TMPFAIL);
   return (SendDisconnectReq (pConnData, PLCISM_EVENT_I4B_DISC_REQ,
                              NULL, &eventData));
} /* HandleActiveConnectTimeout */





/*
        timeout occurred waiting for active incoming connection
        -------------------------------------------------------
*/

static StateMachineResult_t HandlePassiveConnectTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   I4bCapiEventData_t eventData;
   
   DBG (LOG_ERROR,
        "Cd %u: State %s: Timeout waiting for established incoming connection (Connect-Active-Ind)",
        pConnData->uCdId, GetPlciStateName (pConnData->plciState));

   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   /* abort the connection */
   SET_CAUSE_TV (eventData.discReq.iCause, CAUSET_I4B, CAUSE_I4B_TMPFAIL);
   return (SendDisconnectReq (pConnData, PLCISM_EVENT_I4B_DISC_REQ,
                              NULL, &eventData));
} /* HandlePassiveConnectTimeout */





/*
        timeout occurred waiting for disconnect-ind after disconnect-req
        ----------------------------------------------------------------
        If a timeout occurred waiting for a Disconnect-Ind, a Disconnect-Req
        (or an equivalent message) was formerly sent, but had no success. We
        try this several times and then we give up. Giving up is implemented in
        function SendDisconnectReq. So in this function we just issue another
        Disconnect-Req.
*/

static StateMachineResult_t HandleActiveDisconnectTimeout
   (I4bCapiConnectionData_t  *pConnData,
    PlciEventType_t           event,
    CAPIMsg_t                *pCapiMsg,
    const I4bCapiEventData_t *pAddInfo)
{
   I4bCapiEventData_t eventData;
   
   DBG (LOG_ERROR,
        "Cd %u: State %s: Timeout waiting for terminated connection (Disconnect-Ind)",
        pConnData->uCdId, GetPlciStateName (pConnData->plciState));

   /* suppress warning about unused parameters */
   (void) pCapiMsg;
   (void) pAddInfo;
   
   /* abort the connection */
   eventData.discReq.iCause = pConnData->pI4bCd->cause_out;
   return (SendDisconnectReq (pConnData, PLCISM_EVENT_I4B_DISC_REQ,
                              NULL, &eventData));
} /* HandleActiveDisconnectTimeout */





/*
        allocate a new connection and call descriptor
        ---------------------------------------------
*/

static I4bCapiConnectionData_t *AllocateConnection
   (unsigned uCapiCtlrNum)
{
   I4bCapiCtlrData_t       *pCtlrData;
   I4bCapiConnectionData_t *pConnData;
   call_desc_t             *pCd;
   int                      i;

   /* find free B-channel for the controller */
   if (uCapiCtlrNum >= ARRAY_COUNT (e_aI4bCapiCtlrData) ||
       ! e_aI4bCapiCtlrData [uCapiCtlrNum].fEntryUsed ||
       e_aI4bCapiCtlrData [uCapiCtlrNum].iI4bCtlrNum < 0 ||
       ! e_aI4bCapiCtlrData [uCapiCtlrNum].paConnData)
   {
      printf ("i4bcapimgr: ERROR: Got Connect-Ind for unusable CAPI controller %u\n",
              uCapiCtlrNum);
      return (NULL);
   }
   pCtlrData = &(e_aI4bCapiCtlrData [uCapiCtlrNum]);
   pConnData = NULL;
   for (i = 0; (size_t) i < pCtlrData->nNumBChannels; i++)
   {
      pConnData = &(pCtlrData->paConnData [i]);
      if (! pConnData->pI4bCd)
      {
         break;
      }
   }
   if (! pConnData || (size_t) i >= pCtlrData->nNumBChannels)
   {
      DBG (LOG_INFO,
           "Got Connect-Ind from CAPI controller %u while all B-channels in use\n",
           uCapiCtlrNum);
      return (NULL);
   }
   
   /* allocate a new call descriptor */
   pCd = reserve_cd ();
   if (! pCd)
   {
      DBG (LOG_INFO,
           "Got Connect-Ind from CAPI controller %u while all i4b call descriptors in use\n",
           uCapiCtlrNum);
      return (NULL);
   }
   
   /* initialize connection data and call descriptor */
   pCd->controller      = pCtlrData->iI4bCtlrNum;
   pCd->cr              = 0;
   pCd->crflag          = CRF_DEST;
   pCd->channelid       = pConnData->uBChn;
   pCd->channelexcl     = 0;
   pCd->ilt             = NULL;
   pCd->dlt             = NULL;
   pCd->units_type      = CHARGE_INVALID;
   pConnData->pI4bCd    = pCd;
   pConnData->uCdId     = pCd->cdid;
   if (pConnData->uBChn <=
          ARRAY_COUNT (ctrl_desc [pCd->controller].bch_state))
   {
      ctrl_desc [pCd->controller].bch_state [pConnData->uBChn] = BCH_ST_RSVD;
   }

   DBG (LOG_INFO,
        "Cd %u: Allocated for new incoming connection, i4b controller %d, CAPI controller %u, B-channel %u",
        pCd->cdid, pCd->controller, pCtlrData->uCapiCtlrNum, pConnData->uBChn);

   /* return the connection data entry allocated */
   return (pConnData);
} /* AllocateConnection */





/*
        release a connection and call descriptor
        ----------------------------------------
*/

static void FreeConnection
   (I4bCapiConnectionData_t *pConnData)
{
   u_int                  uCdId        = pConnData->uCdId;
   u_int                  uCapiCtlrNum = pConnData->pCtlrData->uCapiCtlrNum;
   I4bCEventQueueEntry_t *pMsg;
   int                    s;

   if (pConnData->fPlciTimerRunning)
   {
      STOP_TIMER (pConnData->calloutPlci, PlciTimeout, pConnData);
      pConnData->fPlciTimerRunning = 0;
   }
   I4bCmgr_NcciReceiveConnectionDown (pConnData);

   /* There may still be messages in the event queue for this connection, i.e.
    * a bch-config-down message. This message is sent by i4b layer 4 (or upper)
    * after receiving a disconnect-indication. To not let this message for a
    * non-existing connection produce error messages, we search the event queue
    * for such messages before releasing the call descriptor and the connection
    * data. Each such entry will be flagged with a special event, that will be
    * ignored by the message handler. The removeal of the queue entry should be
    * done exclusively in the message handler.
    */
   s = SPLI4B ();
   TAILQ_FOREACH (pMsg, &e_eventQueue, links.tqe)
   {
      if ((pMsg->uCapiCtlrNum == uCapiCtlrNum &&
           pMsg->uBChn == pConnData->uBChn) ||
          ((pMsg->uCapiCtlrNum == UINT_MAX ||
            pMsg->uBChn == UINT_MAX) &&
           pMsg->uCdId == uCdId))
      {
         DBG (LOG_DEBUG, "Cd %u: Invalidate event %s, connection already down",
              uCdId, I4bCmgr_GetI4bCEventName (pMsg->event));
         pMsg->event = NUM_I4BCMGR_EVENTS;
      }
   }
   splx (s);
    
   /* now finally release the call descriptor and the connection data entry */
   freecd_by_cd (pConnData->pI4bCd);
   pConnData->pI4bCd = NULL;
   pConnData->plciState = PLCI_STATE_P0;
   
   DBG (LOG_INFO, "Cd %u: Released", uCdId);

   /* if there is someone waiting for this connection to be finished, wake him
    * up
    */
   if (pConnData->fDisconnectSleeping)
   {
      wakeup (pConnData);
   }
   
} /* FreeConnection */





/*
        enter the B-channel protocol configuration into a CAPI message
        --------------------------------------------------------------
*/

static unsigned char *EnterBProtocol
   (unsigned char *pOrgPos,
    int            iBProt)
{
   CAPIBProtocol_t *pBProt;
   unsigned char   *p;
   
   /* enter the B-channel protocol values */
   pBProt = (CAPIBProtocol_t *) pOrgPos;
   if (iBProt == BPROT_RHDLC)
   {
      C_PUT_WORD (pBProt->wB1protocol, CAPI_B1_HDLC_64);
      C_PUT_WORD (pBProt->wB2protocol, CAPI_B2_TRANSPARENT);
      C_PUT_WORD (pBProt->wB3protocol, CAPI_B3_TRANSPARENT);
   }
   else
   {
      C_PUT_WORD (pBProt->wB1protocol, CAPI_B1_TRANSPARENT_64);
      C_PUT_WORD (pBProt->wB2protocol, CAPI_B2_TRANSPARENT);
      C_PUT_WORD (pBProt->wB3protocol, CAPI_B3_TRANSPARENT);
   }
   
   /* both supported protocol variants do not define any configuration data */
   p = (unsigned char *) pBProt + sizeof (*pBProt);
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   
   /* calculate length of the complete CAPI structure */
   C_PUT_BYTE (pBProt->bLength, (u_int8_t) (p - (unsigned char *) pBProt - 1));
   
   /* return pointer behind the last byte written */
   return (p);
} /* EnterBProtocol */





/*
        send a dummy response for an incoming CAPI message
        --------------------------------------------------
*/

static void SendDummyResponse
   (const CAPIMsg_t *pCapiIndMsg)
{
   struct mbuf   *pmbMsg;
   CAPIMsg_t     *pRespMsg;
   unsigned char *p;
   unsigned       uCmd;
   unsigned       uRes;
   
   /* check if the message is really an indication or a Connect-Conf */
   uCmd = CAPI_GET_CMD (pCapiIndMsg);
   if ((uCmd & CAPI_CMDMASK_SUBCMD) != C_IND &&
       uCmd != CAPI_CONFIRM (C_CONNECT))
   {
      return;
   }
   
   /* get an mbuf for the CAPI message; as the real length is unknown yet, use
    * the maximum length of a CAPI message
    */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsg_t));
   if (! pmbMsg)
   {
      DBG (LOG_ERROR,
           "Out of mbufs sending dummy response for CAPI message 0x%04X, dwCid 0x%08X, wNum 0x%04X",
           uCmd, (unsigned) CAPI_GET_CID (pCapiIndMsg),
           (unsigned) CAPI_GET_MSGNUM (pCapiIndMsg));
      return;
   }
   pRespMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   
   /* some messages must be handled specially */
   if (uCmd == CAPI_INDICAT (C_CONNECT))
   {
      /* fill CAPI message Connect-Resp */
      pRespMsg->head = pCapiIndMsg->head;
      C_PUT_WORD (pRespMsg->head.wCmd, CAPI_RESPONSE (C_CONNECT));
      C_PUT_WORD (pRespMsg->info.connect_resp.wReject, CAPI_CALL_IGNORE);
      p = (unsigned char *) &(pRespMsg->info.connect_resp.wReject) +
          sizeof (pRespMsg->info.connect_resp.wReject);
      p = CapiUt_EnterEmptyStruct (p);  /* no BProtocol needed */
      p = CapiUt_EnterEmptyStruct (p);  /* no connected party number */
      p = CapiUt_EnterEmptyStruct (p);  /* no connected party subaddress */
      p = CapiUt_EnterEmptyStruct (p);  /* no LLC */
      p = CapiUt_EnterEmptyStruct (p);  /* no AdditionalInfo */
   
      /* compute the message length */
      pmbMsg->m_len = (int) (p - (unsigned char *) pRespMsg);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
   }
   else if (uCmd == CAPI_CONFIRM (C_CONNECT))
   {
      C_PUT_WORD (pRespMsg->head.wApp, e_uCapiApplID);
      C_PUT_WORD (pRespMsg->head.wCmd, CAPI_REQUEST (C_DISCONNECT));
      C_PUT_WORD (pRespMsg->head.wNum, 0x7FFF);
      C_PUT_DWORD (pRespMsg->head.dwCid, CAPI_GET_CID (pCapiIndMsg));
      C_PUT_BYTE (pRespMsg->info.any.b [0], 0);
      pmbMsg->m_len = sizeof (pRespMsg->head) + 1;
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
   }
   else if (uCmd == CAPI_INDICAT (C_FACILITY))
   {
      pRespMsg->head = pCapiIndMsg->head;
      C_PUT_WORD (pRespMsg->head.wCmd, CAPI_RESPONSE (C_FACILITY));
      C_PUT_WORD (pRespMsg->info.facility_resp.wSelector,
                  C_GET_WORD (pCapiIndMsg->info.facility_ind.wSelector));
      p = (unsigned char *) &(pRespMsg->info.facility_resp.wSelector) +
          sizeof (pRespMsg->info.facility_resp.wSelector);
      p = CapiUt_EnterEmptyStruct (p);  /* no facility parameters */
      pmbMsg->m_len = (int) (p - (unsigned char *) pRespMsg);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
   }
   else if (uCmd == CAPI_INDICAT (C_DATA_B3))
   {
      pRespMsg->head = pCapiIndMsg->head;
      C_PUT_WORD (pRespMsg->head.wCmd, CAPI_RESPONSE (C_DATA_B3));
      C_PUT_WORD (pRespMsg->info.data_b3_resp.wHandle,
                  C_GET_WORD (pCapiIndMsg->info.data_b3_ind.wHandle));
      pmbMsg->m_len = sizeof (pRespMsg->head) +
                      sizeof (pRespMsg->info.data_b3_resp);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
   }
   else
   {
      /* for a dummy response only the message header is needed */
      pRespMsg->head = pCapiIndMsg->head;
      C_PUT_WORD (pRespMsg->head.wCmd,
                  CAPI_RESPONSE (uCmd & CAPI_CMDMASK_COMMAND));
      pmbMsg->m_len = sizeof (pRespMsg->head);
      C_PUT_WORD (pRespMsg->head.wLen, pmbMsg->m_len);
   }
   uRes = PutCapiMessage (pmbMsg);
   if (uRes != CAPI_OK)
   {
      if (CAPI_GET_CMD (pRespMsg) == CAPI_REQUEST (C_DISCONNECT))
      {
         DBG (LOG_ERROR,
              "Error 0x%04X sending Disconnect-Req for message 0x%04X, dwCid 0x%08X, wNum 0x%04X\n",
              uRes, uCmd, (unsigned) CAPI_GET_CID (pCapiIndMsg),
              (unsigned) CAPI_GET_MSGNUM (pCapiIndMsg));
      }
      else
      {
         DBG (LOG_ERROR,
              "Error 0x%04X responding to message 0x%04X, dwCid 0x%08X, wNum 0x%04X",
              uRes, uCmd, (unsigned) CAPI_GET_CID (pCapiIndMsg),
              (unsigned) CAPI_GET_MSGNUM (pCapiIndMsg));
      }
      kcapi_free_mbuf (pmbMsg);
   }
   
} /* SendDummyResponse */





/*
        send out a CAPI message with retries
        ------------------------------------
*/

static unsigned PutCapiMessage
   (struct mbuf *pmbMsg)
{
   unsigned long ulTimeout;
   unsigned long ulDelta;
   unsigned      uRes;
   
   /* for Data-B3-Req the retries must not be performed here, but in the NCCI
    * layer instead (or above)
    */
   if (CAPI_GET_CMD ((CAPIMsg_t *) (pmbMsg->m_data)) ==
          CAPI_REQUEST (C_DATA_B3))
   {
      ulTimeout = 0;
   }
   else
   {
      ulTimeout = CAPI_PUT_MESSAGE_TIMEOUT;
   }
   ulDelta = hz / 10;
   
   do
   {
      uRes = kcapi_put_message (e_uCapiApplID, pmbMsg);
      if ((uRes == CME_PUT_QUEUE_FULL || uRes == CME_BUSY) &&
          ulTimeout > 0)
      {
         /* wait for 100 ms */
         DELAY (ulDelta);
         if (ulTimeout > ulDelta)
         {
            ulTimeout -= ulDelta;
         }
         else
         {
            ulTimeout = 0;
         }
      }
   } while ((uRes == CME_PUT_QUEUE_FULL || uRes == CME_BUSY) &&
            ulTimeout > 0);

   return (uRes);
} /* PutCapiMessage */





/*
        translate PLCI state into string
        --------------------------------
*/

static const char *GetPlciStateName
   (I4bCapiPlciState_t state)
{
   static char szBuf [24];
   
   if ((size_t) state >= ARRAY_COUNT (g_apszStateNames))
   {
      snprintf (szBuf, sizeof (szBuf), "<<Unknown: %d>>", (int) state);
      return (szBuf);
   }
   return (g_apszStateNames [(int) state]);
} /* GetPlciStateName */





/*
        translate PLCI event into string
        --------------------------------
*/

static const char *GetPlciEventName
   (PlciEventType_t event)
{
   static char szBuf [24];
   
   if ((size_t) event >= ARRAY_COUNT (g_apszEventNames))
   {
      snprintf (szBuf, sizeof (szBuf), "<<Unknown: %d>>", (int) event);
      return (szBuf);
   }
   return (g_apszEventNames [(int) event]);
} /* GetPlciEventName */
