/**
 * @file kcapi_appl.c
 *
 * kcapi_appl - kernel CAPI manager for *BSD: CAPI application handling.
 *
 * Copyright: 2000-2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: kcapi_appl.c,v 1.1.2.1 2006/02/28 21:35:25 thomas Exp $
 * Project  CAPI for BSD
 * Target   kcapimgr - The CAPI manager for *BSD
 * @date    12.08.2002
 * @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/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sx.h>
#include <sys/socket.h> /* for net/if.h */
#include <net/if.h> /* struct ifqueue */
#include <capi20.h>
#include <capi_bsd.h>
#include <capi_bsdtrc.h>

/* import includes */

#define __KCAPI_APPL__

/* local includes */
#include <opt_kcapimgr.h>
#include <c4b/kcapimgr/kcapimgr_var.h>
#include <c4b/kcapimgr/kcapi_ctlr.h>
#include <c4b/kcapimgr/kcapi_trace.h>
#include <c4b/kcapimgr/kcapi_appl.h>





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





/* --- some global counters available to all modules --- */

/** The current number of registered applications. */
unsigned e_uNumApps = 0;





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





/* --- some constants --- */

/** The maximum number of unconfirmed incoming B3-data blocks for an appl. */
#define KCAPI_MAX_NUM_B3_DATA_BLOCKS    16

/**
 * The default length of the application message queue if the application does
 * not handle any B3-connection (e.g. a monitoring-only application).
 */
#define KCAPI_DEFAULT_APPL_QUEUE_LEN    64

/** The timeout in hz units when waiting for access to application data. */
#define KCAPI_APPL_ACCESS_TIMEOUT       (10 * hz)
/**
 * The wait message for the release operation to wait for all active calls
 * finished.
 */
#define KCAPI_APPL_WAIT_MSG_NO_CALLS         "wait for no calls"

/** The wait message to wait for a Disconnect-Indication while releasing. */
#define KCAPI_APPL_WAIT_MSG_DISC_IND         "wait for Disconnect-Ind"



/* --- data needed to care for a logical connection (NCCI) --- */

/**
 * Data structure maintained for every logical connection.
 *
 * An entry for a logical connection is allocated each time a
 * Connect-B3-Confirm or a Connect-B3-Indication is received. For a
 * Connect-B3-Request and these two messages it is checked, if another logical
 * connection is allowed for the application. When a Disconnect-B3-Indication
 * arrives, the member fGotDisconnectB3Ind of the corresponding entry is set to
 * let the CAPI manager filter the messages to the controller (after enqueueing
 * of Disconnect-B3-Indication no more B3-Messages but Disconnect-B3-Response
 * are valid, all other B3-messages are silently discarded).
 *
 * The logical connections are maintained as a per PLCI list, whose elements
 * are pre-allocated in a array of fixed size (from application registration).
 * At application registration all possible NCCIs are linked to form the free
 * list. For each Connect-B3-Confirm and Connect-B3-Indication a member of the
 * free list is dequeued and inserted into the NCCI list of the respective PLCI.
 * As there is normally only one NCCI per PLCI there is no need to sort the
 * NCCI list, too. If more than one logical connection is established per PLCI,
 * the number of NCCIs will be very short, a linear search will not yield any
 * (noteable) performance degrade.
 */
typedef struct CapiManNcciData
{
   struct CapiManNcciData *pNext;       /**<
                                         * The address of the next NCCI data
                                         * structure in a list.
                                         */
   u_int32_t               dwNcci;      /**< The NCCI for this connection. */
   size_t                  nNumIncDataBlocks;
                                        /**<
                                         * The current number of unresponded
                                         * incoming B3-data blocks for this
                                         * connection.
                                         */
   size_t                  nNumOutDataBlocks;
                                        /**<
                                         * The current number of unconfirmed
                                         * incoming B3-data blocks for this
                                         * connection.
                                         */
   int                     fGotDisconnectB3Ind;
                                        /**<
                                         * If a Disconnect-B3-Indication is
                                         * enqueued for the application, no more
                                         * Data-B3 messages and no more
                                         * Disconnect-B3-Request is allowed. So
                                         * this flag resembles this situation.
                                         * All messages not allowed in this
                                         * state are not sent to the
                                         * controller.
                                         */
} CapiManNcciData_t;



/* --- data needed to care for a physical connection (PLCI) --- */

/**
 * Data structure maintained for every physical connection.
 *
 * An entry for a physical connection is allocated each time a Connect-Confirm
 * or a Connect-Indication is received. When a Disconnect-Indication is
 * received, the flag fGotDisconnectInd is set to let the CAPI manager filter
 * the messages to the controller (after enqueueing of Disconnect-Indication
 * only Disconnect-Response may be sent, all other messages are silently
 * discarded).
 *
 * For each PLCI a list of NCCIs is maintained. As soon as a Connect-B3-Confirm
 * or a Connect-B3-Indication is received, a new NCCI struct is taken from the
 * list of free NCCIs for the current application. If there is no free NCCI, a
 * Connect-B3-Request will be rejected and for a Connect-B3-Indication a
 * Connect-B3-Response will be sent to the controller to reject the incoming
 * logical connection. The controller may not be able to suppress new
 * B3-connections, because if the application handles multiple controllers, the
 * single controller does not know the overall limit.
 *
 * The PLCIs are maintained as a sorted array. A new PLCI will be inserted and
 * some of the array elements may be moved to another address. The same applies
 * when the array needs to be enlarged (there is currently no limit on the
 * number of PLCIs per application). The array is sorted, so to find the PLCI
 * data structure for an incoming or outgoing message a binary search is used.
 */
typedef struct
{
   u_int32_t          dwPlci;           /**< The PLCI for this connection. */
   int                fGotDisconnectInd;/**<
                                         * If a Disconnect-Indication is queued
                                         * for the application, only a
                                         * Disconnect-Response may be sent. So
                                         * this flag resembles this situation.
                                         * All messages not allowed in this
                                         * state (preceedence over
                                         * fGotDisconnectB3Ind) are not sent to
                                         * the controller.
                                         */
   int                fWaitForDisconnect;
                                        /**<
                                         * If this field is 1, the application
                                         * is in its release phase and is only
                                         * waiting for the
                                         * Disconnect-Indication (with msleep()
                                         * on the address of the PLCI data
                                         * structure).
                                         */
   CapiManNcciData_t *pFirstNcciData;   /**<
                                         * The list of NCCIs for this PLCI. If
                                         * NULL there is currently no
                                         * B3-connection for this PLCI.
                                         */
} CapiManPlciData_t;



/* --- The array of data for registered CAPI applications --- */

/** Information maintained for every registered CAPI application. */
typedef struct
{
   int                fAppRegistered;
                                /**< Flag for validity of following fields. */
   unsigned           uApplID;  /**< CAPI manager assigned application id. */
   int                fIsReleasing;
                                /**<
                                 * - 0
                                 *    Normal operation, the application may
                                 *    receive new connections.
                                 * - 1
                                 *    Release called, no new connections and no
                                 *    CAPI application function calls allowed.
                                 */
   size_t             nNumActiveCalls;
                                 /**<
                                  * The current number of active calls for this
                                  * application id. On every CAPI call this
                                  * value will be incremented and decremented on
                                  * function exit. This is also true for an
                                  * active callback into the application to
                                  * signal a new incoming CAPI message.
                                  *
                                  * CAPI is by definition not thread safe within
                                  * an application. So users must be careful
                                  * when performing concurrent calls for the
                                  * same application. The CAPI manager accepts
                                  * multiple calls for the same application, but
                                  * at least the controller drivers will
                                  * normally serialize calls.
                                  *
                                  * The only exception for parallelity is when
                                  * kcapi_release() is called. This operation is
                                  * only allowed when no other call for the
                                  * respective application id is active. The
                                  * function kcapi_release() will wait until
                                  * there is no call pending and then block all
                                  * further calls. If the caller is the same
                                  * thread as the one currently doing a
                                  * callback, the release call will fail. It is
                                  * not allowed to call kcapi_release() during
                                  * a callback.
                                  */
   uid_t              effUserID;/**<
                                 * The effective user id of the owning process
                                 * (needed to check monitoring rights)
                                 */
   gid_t              effGroupID;
                                /**<
                                 * The effective group id of the owning process
                                 * (needed to check monitoring rights)
                                 */
   unsigned           uMaxLogicalConnections;
                                /**< Max. no. connections from registration. */
   unsigned           uMaxBDataBlocks;
                                /**<
                                 * Maximum no. incoming data blocks per
                                 * connection from registration.
                                 */
   unsigned           uMaxBDataLen;
                                /**< Max. data block length from registration. */
   size_t             nMaxNumPlciData;
                                /**< The array size of paPlciData. */
   size_t             nCurrNumPlciData;
                                /**<
                                 * The current number of entries used in
                                 * paPlciData, must be <= nMaxNumPlciData.
                                 */
   CapiManPlciData_t *paPlciData;
                                /**<
                                 * Array of information per physical connection.
                                 * Length is nMaxNumPlciData, the number of
                                 * currently used entries is nCurrNumPlciData.
                                 */
   CapiManNcciData_t *paNcciData;
                                /**<
                                 * The array of information per logical
                                 * connection. Length is uMaxLogicalConnections.
                                 */
   CapiManNcciData_t *pFirstFreeNcci;
                                /**<
                                 * The address of the first free NCCI data
                                 * structure in paNcciData. If NULL there is no
                                 * more B3-connection possible.
                                 */
   struct ifqueue     msgQueue; /**< 
                                 * The message queue to the application. The
                                 * field ifq_drops is incremented, if a message
                                 * was lost because of mbuf shortage or queue
                                 * overflow. When the next message is inserted,
                                 * a new mbuf with length zero will be added to
                                 * the queue and ifq_drops will be decrement. If
                                 * the queue is empty and the application wants
                                 * to get the next message, it will get no
                                 * message but CME_GET_QUEUE_OVERFLOW and
                                 * ifq_drops will be reset to null. The same
                                 * error will be signaled for an empty mbuf as
                                 * a message. This behaviour is chosen in order
                                 * to deliver the error in the corrent place in
                                 * the message stream.
                                 *
                                 * The internal mutex of this queue (since
                                 * REL-5.0) will not be initialized and used.
                                 * This is not necessary, because the whole
                                 * application array is protected by its own
                                 * mutex that also protects the message queue.
                                 */
   struct ifqueue     traceQueue;
                                /**<
                                 * The trace queue to the application. If the
                                 * application starts any tracers this queue
                                 * receives trace messages for these tracers.
                                 * The handling is the same as for the normal
                                 * message queue. The message queue has higher
                                 * priority, so a kcapi_get_message() first
                                 * empties the normal message queue and then
                                 * reads the trace queue.
                                 *
                                 * The internal mutex of this queue is also not
                                 * initialized or used.
                                 */
#if defined (CAPI_64BIT)
   void (*pfnCallback) (unsigned uApplID, u_int64_t dwParam);
                                /**<
                                 * - NULL
                                 *    No callback function registered.
                                 * - Else
                                 *    The address of a callback function to be
                                 *    called, if new messages are in the queue.
                                 */
   u_int64_t          dwParam;  /**<
                                 * The application specific parameter to be
                                 * passed to the callback function. Termed
                                 * "dwParam" even though it's a qword.
                                 */
#else /* CAPI_64BIT */
   void (*pfnCallback) (unsigned uApplID, u_int32_t dwParam);
                                /**<
                                 * - NULL
                                 *    No callback function registered.
                                 * - Else
                                 *    The address of a callback function to be
                                 *    called, if new messages are in the queue.
                                 */
   u_int32_t          dwParam;  /**<
                                 * The application specific parameter to be
                                 * passed to the callback function.
                                 */
#endif /* CAPI_64BIT */
   struct thread     *ptdCallback;
                                /**<
                                 * When the callback function is called, this
                                 * field will be set to the thread address of
                                 * the calling thread. It will be checked by
                                 * kcapi_release() to not allow a callback call
                                 * to perform a release operation. When no
                                 * callback is active, this field will be set to
                                 * NULL.
                                 */
   size_t             nNumTracers;
                                /**<
                                 * The current number of tracers started by the
                                 * application
                                 */
   unsigned           auTracerIDs [C4BTRC_MAX_TRACERS_PER_APPL];
                                /**<
                                 * The array of the ids for the active tracers.
                                 * Only the first nNumTracers entries are valid,
                                 * new ids are appended. For removed entries the
                                 * array is compacted.
                                 */
} CapiManAppInfo_t;

/** The application data array, protected by the following mutex. */
static CapiManAppInfo_t g_aAppInfo [CAPI_MAX_APPLICATIONS] = { { 0 } };

/**
 * The index of the last assigned application id.
 *
 * Every time a new application shall be registered a new entry in the
 * application array is searched for starting from this value plus one. When the
 * last array index is reached, it starts over at one (null is never used). This
 * way every new application will get a new id and old ids are only reused when
 * it is absolutely necessary.
 */
static unsigned g_uLastAssignedApplID = 0;

/**
 * The mutex for accessing application data.
 *
 * @note The mutex must be waitable to make some operations easier and cleaner.
 */
static struct sx g_sxAppData;





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





/* --- Sending a CAPI message --- */

/**
 * Send a Disconnect-Request for a connection.
 *
 * @param uApplID               I: The application id for the request.
 * @param dwPlci                I: The PLCI for which the Disconnect-Request is
 *                                 to be sent.
 *
 * @return CAPI result value, CAPI_OK on success.
 */
static unsigned kcapiappl_send_disconnect_req
   (unsigned  uApplID,
    u_int32_t dwPlci);

/**
 * Send out a CAPI message to a controller with message filtering.
 *
 * @pre This function is called with the application array and the application
 *      entry locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param uCtlr                 I: The controller to send the message to.
 * @param pmbMsg                I: The CAPI message to send. The mbuf is
 *                                 released within this function if the result
 *                                 is CAPI_OK.
 *
 * @retval CAPI_OK              The message was sent successfully, the caller is
 *                              no longer responsible for the mbuf.
 * @retval Else                 An error occurred, the caller remains
 *                              responsible for the mbuf.
 */
static unsigned kcapiappl_put_message
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         uCtlr,
    struct mbuf      *pmbMsg);



/* --- Receiving specific CAPI messages --- */

/**
 * Signal a Disconnect-Indication for a connection.
 *
 * @pre This function is called with the application data mutex locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param dwPlci                I: The PLCI for which to enqueue the
 *                                 Disconnect-Indication.
 * @param wReason               I: The disconnect reason to report.
 *
 * @return Nothing.
 */
static void kcapiappl_enqueue_disconnect_ind
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci,
    u_int16_t         wReason);

/**
 * Signal a received CAPI message for a connection (internal code).
 *
 * @pre This function is called with the application data mutex locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param pmbMsg                I: The CAPI message to enqueue. The mbuf will
 *                                 change its ownership to the application
 *                                 message queue.
 *
 * @return Nothing.
 */
static void kcapiappl_enqueue_capi_message_internal
   (CapiManAppInfo_t *pAppInfo,
    struct mbuf      *pmbMsg);



/* --- Message check and filtering --- */

/**
 * Check if an incoming message is delivering a new PLCI.
 *
 * @param pMsg                  I: The incoming message to examine.
 *
 * @retval 0                    The message does not introduce a new PLCI.
 * @retval 1                    The message signals a new PLCI.
 */
static int kcapiappl_check_for_new_plci
   (const CAPIMsg_t *pMsg);

/**
 * Check if a message will create a new allowed connection.
 *
 * @pre This function is called with the application array and the application
 *      entry locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param pmbMsg                I: The CAPI message to check.
 *
 * @retval CAPI_OK              The message is allowed. There is room for a new
 *                              PLCI or NCCI if the message will introduce or
 *                              acknowledge a new one.
 */
static unsigned kcapiappl_check_outgoing_message_for_new_connection
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pMsg);

/**
 * Check and count for terminated connection.
 *
 * @pre This function is called with the application array and the application
 *      entry locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param pMsg                  I: The CAPI message to check.
 *
 * @return Nothing
 */
static void kcapiappl_check_for_terminated_connection
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pMsg);

/**
 * Check if a message has to be silently discarded.
 *
 * @param pAppInfo              I: The application data for the context.
 * @param pMsg                  I: The CAPI message to check.
 *
 * @retval 0                    The message shall be sent.
 * @retval 1                    The message shall be discarded.
 */
static int kcapiappl_check_for_message_to_filter
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pMsg);



/* --- Evaluate CAPI manager specific messages --- */

/**
 * Handle a CAPI manager trace request message.
 *
 * @pre This function may only be called with a Manufacturer-Request or
 *      -Response where the manufacturer id is C4B_TRACE_MANU_ID.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pmbMsg                I: Address of the mbuf holding the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */
static unsigned kcapiappl_handle_trace_request
   (CapiManAppInfo_t *pAppInfo,
    struct mbuf      *pmbMsg);

/**
 * Handle a request message to start a new application tracer.
 *
 * @pre This function must be called with the application data array and the
 *      application entry locked.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pReqMsg               I: Address of the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */
static unsigned kcapiappl_handle_start_appl_tracer_request
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pReqMsg);

/**
 * Handle a request message to start a new controller tracer.
 *
 * @pre This function must be called with the application data array and the
 *      application entry locked.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pReqMsg               I: Address of the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */
static unsigned kcapiappl_handle_start_ctlr_tracer_request
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pReqMsg);

/**
 * Handle a request message to stop a tracer.
 *
 * @pre This function must be called with the application data array and the
 *      application entry locked.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pReqMsg               I: Address of the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */
static unsigned kcapiappl_handle_stop_tracer_request
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pReqMsg);



/* --- maintaining PLCI and NCCI lists --- */

/**
 * Insert a new PLCI into the array of current physical connections.
 */
static CapiManPlciData_t *kcapiappl_insert_new_plci
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci);

/**
 * Find PLCI data for an application.
 */
static CapiManPlciData_t *kcapiappl_find_plci
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci);

/**
 * Remove a PLCI data element from the array of current physical connections.
 */
static void kcapiappl_remove_plci
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci);

/**
 * Perform binary search on an array of PLCIs.
 *
 * @param paPlciData            I: The array of PLCI data elements to search
 *                                 within.
 * @param nLenPlciData          I: The length of the array (in no. elements).
 * @param dwPlci                I: The PLCI to search for.
 *
 * @return The array index where the PLCI is found or the index of the position
 *         to insert the (new) PLCI at.
 */
static int kcapiappl_plci_binsearch
   (CapiManPlciData_t *paPlciData,
    size_t             nLenPlciData,
    u_int32_t          dwPlci);

/**
 * Insert a new NCCI into the list of logical connections for a PLCI.
 */
static CapiManNcciData_t *kcapiappl_insert_new_ncci
   (CapiManAppInfo_t  *pAppInfo,
    CapiManPlciData_t *pPlciData,
    u_int32_t          dwNcci);

/**
 * Find NCCI data for an application.
 */
static CapiManNcciData_t *kcapiappl_find_ncci
   (CapiManAppInfo_t  *pAppInfo,
    CapiManPlciData_t *pPlciData,
    u_int32_t          dwNcci);

/**
 * Remove a NCCI data element from the list of logical connections for a PLCI.
 */
static void kcapiappl_remove_ncci
   (CapiManAppInfo_t  *pAppInfo,
    CapiManPlciData_t *pPlciData,
    u_int32_t          dwNcci);





/* === implementation of CAPI interface functions ======================== */





/**
 * CAPI application registration.
 *
 * @param uMaxLogicalConnections
 *                              I: Maximum number of active B3-connections.
 * @param uMaxBDataBlocks       I: Maximum number of unacknowledged incoming
 *                                 data blocks per connection.
 * @param uMaxBDataLen          I: Maximum data block length to use.
 * @param puApplID              O: The application id assigned by the CAPI
 *                                 manager if the call is successful.
 *
 * @retval CAPI_OK              Application registration was successful.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h.
 */

unsigned kcapi_register
   (unsigned  uMaxLogicalConnections,
    unsigned  uMaxBDataBlocks,
    unsigned  uMaxBDataLen,
    unsigned *puApplID)
{
   CapiManAppInfo_t  *pAppInfo;
   CapiManPlciData_t *paPlciData;
   CapiManNcciData_t *paNcciData;
   uid_t              effUserID;
   gid_t              effGroupID;
   unsigned           uNewApplID;
   size_t             n;
   int                i;
   unsigned           uRes;
   
   /* test for invalid parameters */
   if (! puApplID)
   {
      DBG (LOG_ERROR, "Invalid appl. id pointer %p", puApplID);
      uRes = CRE_INVALID_PARAM;
      kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                uMaxBDataLen, puApplID, uRes);
      return (uRes);
   }
   
   /* check the parameters for plausibility */
   if (uMaxLogicalConnections > 0xFFFF)
   {
      DBG (LOG_ERROR, "Invalid number of connections %u",
           uMaxLogicalConnections);
      uRes = CRE_INVALID_CONNECT_NUM;
      kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                uMaxBDataLen, puApplID, uRes);
      return (uRes);
   }
   if (uMaxBDataBlocks < 1)
   {
      DBG (LOG_ERROR, "Invalid number of data blocks %u",
           uMaxBDataBlocks);
      uRes = CRE_INVALID_PARAM;
      kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                uMaxBDataLen, puApplID, uRes);
      return (uRes);
   }
   if (uMaxBDataBlocks > KCAPI_MAX_NUM_B3_DATA_BLOCKS)
   {
      DBG (LOG_ERROR,
           "Maximum number of B3-data blocks (%u) exceeds maximum, will be limited to %u",
           uMaxBDataBlocks, (unsigned) KCAPI_MAX_NUM_B3_DATA_BLOCKS);
      uMaxBDataBlocks = KCAPI_MAX_NUM_B3_DATA_BLOCKS;
   }
   if (uMaxBDataLen < 128 || uMaxBDataLen > MCLBYTES)
   {
      DBG (LOG_ERROR, "Invalid data block length %u", uMaxBDataLen);
      uRes = CRE_INVALID_BLOCK_SIZE;
      kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                uMaxBDataLen, puApplID, uRes);
      return (uRes);
   }
   
   /* memory allocation for logical and physical connections is only done if the
    * application declares to handle at least one B3-connection (a monitoring
    * application does not need any B3-connection)
    */
   if (uMaxLogicalConnections > 0)
   {
      /* allocate memory to maintain the logical connections */
      MALLOC (paNcciData, CapiManNcciData_t *,
              uMaxLogicalConnections * sizeof (paNcciData [0]),
              M_KCAPIMGRBUF, M_WAITOK);
      if (! paNcciData)
      {
         printf ("kcapimgr: ERROR: Out of memory for %u logical connections\n",
                 uMaxLogicalConnections);
         uRes = CRE_OS_RESOURCE_ERROR;
         kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                   uMaxBDataLen, puApplID, uRes);
         return (uRes);
      }
      bzero (paNcciData, uMaxLogicalConnections * sizeof (paNcciData [0]));

      /* pre-allocate memory for the same number of physical connections as the
       * number of logical connections (may grow during operation)
       */
      MALLOC (paPlciData, CapiManPlciData_t *,
              uMaxLogicalConnections * sizeof (paPlciData [0]),
              M_KCAPIMGRBUF, M_WAITOK);
      if (! paPlciData)
      {
         printf ("kcapimgr: ERROR: Out of memory for %u physical connections\n",
                 uMaxLogicalConnections);
         FREE (paNcciData, M_KCAPIMGRBUF);
         uRes = CRE_OS_RESOURCE_ERROR;
         kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                   uMaxBDataLen, puApplID, uRes);
         return (uRes);
      }
      bzero (paPlciData, uMaxLogicalConnections * sizeof (paPlciData [0]));
   }
   else
   {
      paNcciData = NULL;
      paPlciData = NULL;
   }

   /* determine the current user credentials for later checking of rights for
    * some operations
    */
   PROC_LOCK (curproc);
   if (curproc != NULL &&
       curproc->p_ucred != NOCRED &&
       curproc->p_ucred != FSCRED)
   {
      effUserID = curproc->p_ucred->cr_uid;
      effGroupID = curproc->p_ucred->cr_gid;
   }
   else
   {
      /* no user credentials available, assume root:wheel */
      effUserID = 0;
      effGroupID = 0;
   }
   PROC_UNLOCK (curproc);

   /* allocating a new application data entry and initializing it for operation
    * requires exclusive access
    */
   sx_xlock (&g_sxAppData);

   /* allocate new application id, starting at 1 */
   for (n = ARRAY_COUNT (g_aAppInfo),
           uNewApplID = (g_uLastAssignedApplID + 1) % ARRAY_COUNT (g_aAppInfo);
        n > 0;
        --n, uNewApplID = (uNewApplID + 1) % ARRAY_COUNT (g_aAppInfo))
   {
      if (uNewApplID == 0)
      {
         continue;
      }
      if (! g_aAppInfo [uNewApplID].fAppRegistered)
      {
         break;
      }
   }
   if (n == 0)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Too many registered applications (%zu)",
           ARRAY_COUNT (g_aAppInfo) - 1);
      if (paPlciData)
      {
         FREE (paPlciData, M_KCAPIMGRBUF);
      }
      if (paNcciData)
      {
         FREE (paNcciData, M_KCAPIMGRBUF);
      }
      uRes = CRE_TOO_MANY_APPLICATIONS;
      kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                uMaxBDataLen, puApplID, uRes);
      return (uRes);
   }
   g_uLastAssignedApplID = uNewApplID;
   pAppInfo = &(g_aAppInfo [uNewApplID]);
   bzero (pAppInfo, sizeof (*pAppInfo));
   pAppInfo->uApplID = uNewApplID;
   pAppInfo->effUserID = effUserID;
   pAppInfo->effGroupID = effGroupID;
   pAppInfo->uMaxLogicalConnections = uMaxLogicalConnections;
   pAppInfo->uMaxBDataBlocks = uMaxBDataBlocks;
   pAppInfo->uMaxBDataLen = uMaxBDataLen;
   if (uMaxLogicalConnections > 0)
   {
      _IF_QINIT (&(pAppInfo->msgQueue),
                 uMaxBDataBlocks * uMaxLogicalConnections * 2);
   }
   else
   {
      _IF_QINIT (&(pAppInfo->msgQueue), KCAPI_DEFAULT_APPL_QUEUE_LEN);
   }
   _IF_QINIT (&(pAppInfo->traceQueue), KCAPI_DEFAULT_APPL_QUEUE_LEN);
   pAppInfo->paNcciData = paNcciData;
   pAppInfo->paPlciData = paPlciData;
   pAppInfo->nMaxNumPlciData = uMaxLogicalConnections;
   pAppInfo->nCurrNumPlciData = 0;
   pAppInfo->fAppRegistered = 1;

   /* create the list of free NCCIs for the application */
   if (uMaxLogicalConnections > 0)
   {
      pAppInfo->pFirstFreeNcci = &(pAppInfo->paNcciData [0]);
      for (i = 0; i < pAppInfo->uMaxLogicalConnections - 1; i++)
      {
         pAppInfo->paNcciData [i].pNext = &(pAppInfo->paNcciData [i + 1]);
      }
      pAppInfo->paNcciData [i].pNext = NULL;
   }
   else
   {
      pAppInfo->pFirstFreeNcci = NULL;
   }
   
   /* count the registered applications */
   atomic_add_int (&e_uNumApps, 1);
   
   /* prepare to return the assigned application id */
   *puApplID = pAppInfo->uApplID;

   /* The real registration at the controller(s) is perfomed "on-the-fly", when
    * the first message shall be sent to the controller. This saves resources,
    * if there are multiple applications using only one of many controllers
    * respectively. The only drawback is an irritating result value for a
    * CAPI_PUT_MESSAGE, if the registration at the controller fails.
    */

   /* if there are active tracers, they must update their application rights
    * tables
    */
   if (e_uNumTracers > 0)
   {
      kcapitrace_update_tracer_for_registered_appl
         (*puApplID, effUserID, effGroupID);
   }
   
   sx_xunlock (&g_sxAppData);
   
   DBG (LOG_TRACE,
        "Application registered successfully, id=%u, "
           "uMaxLogicalConnections=%u, uMaxBDataBlocks=%u, uMaxBDataLen=%u",
        *puApplID, uMaxLogicalConnections, uMaxBDataBlocks, uMaxBDataLen);

   uRes = CAPI_OK;
   if (e_uNumTracers > 0)
   {
      kcapitrace_appl_register (uMaxLogicalConnections, uMaxBDataBlocks,
                                uMaxBDataLen, puApplID, uRes);
   }
   return (uRes);
} /* kcapi_register */





/**
 * Releasing a CAPI application.
 *
 * @param uApplID               I: Application id from registration.
 *
 * @retval CAPI_OK              Application release was successful.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned kcapi_release
   (unsigned uApplID)
{
   CapiManAppInfo_t  *pAppInfo;
   CapiManPlciData_t *pPlciData;
   struct mbuf       *pmb;
   unsigned long      ulTimeout;
   u_int32_t          dwPlci;
   unsigned           uTracerID;
   int                i;
   unsigned           uRes;
   
   /* First we need exclusive access to the application data and block any
    * further calls by the application and by the callback function. Then we
    * must wait until all active calls including a callback are finished. If the
    * release call is made by the thread performing the callback, the release
    * call must be rejected. The application entry will not be blocked for other
    * calls in this case.
    */
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Appl. id %u not registered", uApplID);
      uRes = CME_INVALID_APPLICATION_ID;
      kcapitrace_appl_release (uApplID, uRes);
      return (uRes);
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* check for the callback thread trying to perform the release operation */
   if (pAppInfo->ptdCallback != NULL &&
       pAppInfo->ptdCallback == curthread)
   {
      sx_xunlock (&g_sxAppData);
      printf ("kcapimgr: ERROR: Appl. id %u: Callback thread attempts kcapi_release()\n",
              uApplID);
      uRes = CRE_BUSY;
      kcapitrace_appl_release (uApplID, uRes);
      return (uRes);
   }
   
   /* check if the application is already releasing */
   if (pAppInfo->fIsReleasing)
   {
      sx_xunlock (&g_sxAppData);
      printf ("kcapimgr: WARNING: Multiple parallel calls to kcapi_release() for appl. id %u\n",
              uApplID);
      uRes = CME_INVALID_APPLICATION_ID;
      kcapitrace_appl_release (uApplID, uRes);
      return (uRes);
   }

   /* block all other calls regarding this application id */
   pAppInfo->fIsReleasing = 1;
   pAppInfo->pfnCallback  = NULL;
   
   /* now wait for completion of all currently active calls, including a
    * callback
    */
   ulTimeout = 0;
   while (pAppInfo->nNumActiveCalls > 0 &&
          ulTimeout < KCAPI_APPL_ACCESS_TIMEOUT)
   {
      sx_xunlock (&g_sxAppData);
      (void) tsleep (pAppInfo, PZERO, KCAPI_APPL_WAIT_MSG_NO_CALLS, hz / 10);
      ulTimeout += hz / 10;
      sx_xlock (&g_sxAppData);
   }
   if (pAppInfo->nNumActiveCalls > 0)
   {
      printf ("kcapimgr: ERROR: Appl. id %u: Still %zu calls active after %lus\n",
              uApplID, pAppInfo->nNumActiveCalls, ulTimeout / hz);
      sx_xunlock (&g_sxAppData);
      uRes = CRE_BUSY;
      kcapitrace_appl_release (uApplID, uRes);
      return (uRes);
   }
   /* now the release operation is allowed and there are no other calls active
    * for this application id
    */
   
   /* If there are still active connections, send a Disconnect-Request and wait
    * for some short time to let the controller really perform the disconnect.
    * The Disconnect-Indication will only be enqueued for the application but
    * the callback function cannot be called. But for every
    * Disconnect-Indication a flag will be set in the data entry for the PLCI,
    * so in the following loop we may safely wait for the Disconnect-Indication
    * to arrive.
    */
   /* Note: A PLCI is only removed from the list if the corresponding
    *       Disconnect-Response is sent by the application. This is not possible
    *       in this context, so the array of PLCIs itself will not be modified
    *       during this loop (new incoming connections are blocked). It is safe
    *       to assume the same address of the data for each PLCI during the
    *       loop.
    */
   for (i = 0; (size_t) i < pAppInfo->nCurrNumPlciData; i++)
   {
      pPlciData = &(pAppInfo->paPlciData [i]);
      if (! pPlciData->fGotDisconnectInd)
      {
         DBG (LOG_INFO,
              "Appl. id %u: PLCI 0x%04X still active, will be disconnected",
              uApplID, (unsigned) (pPlciData->dwPlci));

         pPlciData->fWaitForDisconnect = 1;
         dwPlci = pPlciData->dwPlci;

         /* Note: We may unlock the mutex because the application entry is
          *       marked to be in release. So no additional call to
          *       kcapi_release() will be executed and we may release the
          *       lock and obtain it again without check for still registered
          *       application. The release is necessary to let the
          *       Disconnect-Indication be enqueued for this application.
          */
         sx_xunlock (&g_sxAppData);
         uRes = kcapiappl_send_disconnect_req (uApplID, dwPlci);
         if (uRes == CAPI_OK)
         {
            /* Note: Wakeup() will be called by the routine enqueueing the
             *       Disconnect-Indication, if the flag fWaitForDisconnect is
             *       set for the PLCI.
             */
            (void) tsleep (pPlciData, PZERO, KCAPI_APPL_WAIT_MSG_DISC_IND,
                           5 * hz);
         }
         sx_xlock (&g_sxAppData);

         /* For security reasons the PLCI data pointer is recreated */
         if (! pAppInfo->paPlciData ||
             (size_t) i >= pAppInfo->nCurrNumPlciData)
         {
            break;
         }
         pPlciData = &(pAppInfo->paPlciData [i]);
         pPlciData->fWaitForDisconnect = 0;

         if (! pPlciData->fGotDisconnectInd)
         {
            printf ("kcapimgr: WARNING: Appl. id %u: Unable to disconnect PLCI 0x%04X while releasing",
                    pAppInfo->uApplID, (unsigned) (pPlciData->dwPlci));
         }
      }
   }
   
   /* if the application has any active tracers still active: stop them */
   if (pAppInfo->nNumTracers > 0)
   {
      for (i = 0; (size_t) i < pAppInfo->nNumTracers; i++)
      {
         uTracerID = pAppInfo->auTracerIDs [i];
         (void) kcapitrace_remove_tracer (uTracerID);
      }
      pAppInfo->nNumTracers = 0;
   }
   
   /* now perform the release for all controllers, the application is
    * registered at
    */
   sx_xunlock (&g_sxAppData);
   uRes = kcapictlr_release_app (uApplID);
   sx_xlock (&g_sxAppData);
   
   /* release any memory used by the app-info entry */
   FREE (pAppInfo->paPlciData, M_KCAPIMGRBUF);
   pAppInfo->paPlciData = NULL;
   FREE (pAppInfo->paNcciData, M_KCAPIMGRBUF);
   pAppInfo->paNcciData = NULL;
   
   /* clear the application queue */
   do
   {
      _IF_DEQUEUE (&(pAppInfo->msgQueue), pmb);
      kcapi_free_mbuf (pmb);
   } while (pmb);
   
   /* clear the trace queue */
   do
   {
      _IF_DEQUEUE (&(pAppInfo->traceQueue), pmb);
      kcapi_free_mbuf (pmb);
   } while (pmb);
   
   /* count the release of the application */
   pAppInfo->fAppRegistered = 0;
   if (e_uNumApps > 0)
   {
      atomic_subtract_int (&e_uNumApps, 1);
   }

   /* if there are active tracers, they must update their application rights
    * tables
    */
   if (e_uNumTracers > 0)
   {
      kcapitrace_update_tracer_for_released_appl (uApplID);
   }
   
   sx_xunlock (&g_sxAppData);
   
   DBG (LOG_TRACE, "Appl. id %u released, result=0x%04X",
        uApplID, uRes);

   if (e_uNumTracers > 0)
   {
      kcapitrace_appl_release (uApplID, uRes);
   }

   return (uRes);
} /* kcapi_release */





/**
 * Send a CAPI message.
 *
 * @param uApplID               I: Application id from registration.
 * @param pmbMsg                I: The mbuf for the CAPI message to send. For
 *                                 Data-B3-Requests the data block must be
 *                                 stored in a second mbuf. The address of this
 *                                 mbuf must be stored in pmbMsg->m_next. If the
 *                                 function call returns CAPI_OK, the ownership
 *                                 of the mbuf goes to the CAPI manager. The
 *                                 calling application must not access any data
 *                                 in the mbuf(s) thereafter. The CAPI manager
 *                                 will release the mbuf(s) when it is (they
 *                                 are) not needed any more.
 *
 * @retval CAPI_OK              The message was accepted by CAPI.
 * @retval CME_PUT_QUELE_FULL   The send queue of the addressed controller is
 *                              filled up, try again later.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned kcapi_put_message
   (unsigned     uApplID,
    struct mbuf *pmbMsg)
{
   CapiManAppInfo_t  *pAppInfo;
   unsigned           uCtlr;
   unsigned           uRes;
   int                fIsDataB3Msg = 0;
   
   /* check for valid message */
   if (! pmbMsg || pmbMsg->m_len < sizeof (CAPIMsgHead_t))
   {
      DBG (LOG_INFO, "pmbMsg==NULL or message header less than %zu bytes",
           sizeof (CAPIMsgHead_t));
      uRes = CME_ILLEGAL_COMMAND;
      kcapitrace_appl_put_message (uApplID, pmbMsg, uRes, 0);
      return (uRes);
   }
   if (uApplID != CAPI_GET_APPL (mtod (pmbMsg, CAPIMsg_t *)))
   {
      DBG (LOG_INFO,
           "Application id %u as the function argument and within the message %u are different for message 0x%04X",
           uApplID, (unsigned) CAPI_GET_APPL (mtod (pmbMsg, CAPIMsg_t *)),
           (unsigned) CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *)));
      uRes = CCE_ILLEGAL_MSG_PARAMETER;
      kcapitrace_appl_put_message (uApplID, pmbMsg, uRes, 0);
      return (uRes);
   }
   
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Invalid appl. id %u for message 0x%04X",
           uApplID, (unsigned) CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *)));
      uRes = CME_INVALID_APPLICATION_ID;
      kcapitrace_appl_put_message (uApplID, pmbMsg, uRes, 0);
      return (uRes);
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* check for blocked application because of pending release operation */
   if (pAppInfo->fIsReleasing)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Appl. id %u: Is already releasing", uApplID);
      uRes = CME_BUSY;
      kcapitrace_appl_put_message (uApplID, pmbMsg, uRes, 0);
      return (uRes);
   }
   
   /* register this call to block a release operation until this function call
    * is finished
    */
   pAppInfo->nNumActiveCalls++;
   sx_xunlock (&g_sxAppData);
   
   /* check if the message is a Manufacturer-Request for tracing */
   if ((CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *)) & CAPI_CMDMASK_COMMAND) ==
          C_MANUFACTURER &&
       C_GET_DWORD
          (mtod (pmbMsg, CAPIMsg_t *)->info.manufacturer_req.dwManuID) ==
          C4B_TRACE_MANU_ID)
   {
      uRes = kcapiappl_handle_trace_request (pAppInfo, pmbMsg);

      sx_xlock (&g_sxAppData);
      pAppInfo->nNumActiveCalls--;
      sx_xunlock (&g_sxAppData);
      
      return (uRes);
   }
   
   /* determine destination controller */
   uCtlr = CAPI_GET_CID (mtod (pmbMsg, CAPIMsg_t *));
   uCtlr &= CAPI_CIDMASK_CTLR;

   /* The CAPI message itself must be traced before the put message operation.
    * After the call to kcapiappl_put_message() the mbuf is not valid any more.
    */
   if (e_uNumTracers > 0)
   {
      fIsDataB3Msg = ((CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *)) &
                      CAPI_CMDMASK_COMMAND) == C_DATA_B3);
      kcapitrace_trace_appl_msg (uApplID, pmbMsg, fIsDataB3Msg);
   }
   
   /* perform the put message operation to the destination controller */
   uRes = kcapiappl_put_message (pAppInfo, uCtlr, pmbMsg);
   
   sx_xlock (&g_sxAppData);
   pAppInfo->nNumActiveCalls--;
   sx_xunlock (&g_sxAppData);

   /* The function call itself may be traced now. The trace function call will
    * not access the data the message buffer address points to.
    */
   if (e_uNumTracers > 0)
   {
      kcapitrace_appl_put_message (uApplID, pmbMsg, uRes, fIsDataB3Msg);
   }

   return (uRes);
} /* kcapi_put_message */





/**
 * Receive a CAPI message.
 *
 * @param uApplID               I: Application id from registration.
 * @param ppmbMsg               O: The address of an mbuf containing a CAPI
 *                                 message if the function result is CAPI_OK.
 *                                 If the message is a Data-B3-Indication the
 *                                 data block is stored in a second mbuf whose
 *                                 address is stored into (*ppmbMsg)->m_next.
 *                                 The calling application is responsible for
 *                                 releasing the mbuf(s) when they are no longer
 *                                 needed. After calling this function
 *                                 successfully the CAPI manager will not access
 *                                 any data in the mbuf(s) returned.
 *
 * @retval CAPI_OK              A CAPI message was available, the address of its
 *                              surrounding mbuf is returned in ppmbMsg.
 * @retval CME_GET_QUEUE_OVERFLOW
 *                              A CAPI message or data block was lost, maybe
 *                              because of low system resources.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned kcapi_get_message
   (unsigned      uApplID,
    struct mbuf **ppmbMsg)
{
   CapiManAppInfo_t *pAppInfo;
   unsigned          uRes;
   
   /* check for valid parameters */
   if (! ppmbMsg)
   {
      DBG (LOG_ERROR, "Appl. id %u: Invalid message pointer address",
           uApplID);
      uRes = CME_INVALID_PARAM;
      kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
      return (uRes);
   }
   
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Invalid appl. id %u", uApplID);
      uRes = CME_INVALID_APPLICATION_ID;
      kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
      return (uRes);
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* check for blocked application because of pending release operation */
   if (pAppInfo->fIsReleasing)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Appl. id %u: Is already releasing", uApplID);
      uRes = CME_BUSY;
      kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
      return (uRes);
   }
   
   /* get next message from application queue */
   _IF_DEQUEUE (&(pAppInfo->msgQueue), *ppmbMsg);
   
   /* if no message was obtained: message queue empty */
   if (! *ppmbMsg)
   {
      /* special case: number of message drops not zero */
      if (pAppInfo->msgQueue.ifq_drops > 0)
      {
         pAppInfo->msgQueue.ifq_drops = 0;

         sx_xunlock (&g_sxAppData);

         DBG (LOG_INFO, "Appl. id %u: Signal for dropped message", uApplID);
         
         uRes = CME_GET_QUEUE_OVERFLOW;
         kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
         return (uRes);
      }

      /* if the normal message queue is empty and no normal message was lost:
       * check the trace queue
       */
      if (pAppInfo->traceQueue.ifq_drops > 0)
      {
         pAppInfo->traceQueue.ifq_drops = 0;

         sx_xunlock (&g_sxAppData);

         DBG (LOG_INFO, "Appl. id %u: Signal for dropped trace message",
              uApplID);
         
         return (CME_GET_QUEUE_OVERFLOW);
      }
      _IF_DEQUEUE (&(pAppInfo->traceQueue), *ppmbMsg);
      if (*ppmbMsg)
      {
         sx_xunlock (&g_sxAppData);

         DBG (LOG_DEBUG, "Appl.id %u: Got trace message", uApplID);

         return (CAPI_OK);
      }
      
      sx_xunlock (&g_sxAppData);

      DBG (LOG_DEBUG, "Appl.id %u: Message queue is empty", uApplID);
      
      uRes = CME_GET_QUEUE_EMPTY;
      if (e_uNumTracers > 0)
      {
         kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
      }
      return (uRes);
   }

   sx_xunlock (&g_sxAppData);
   
   /* if empty message was obtained: a previous message was lost */
   if ((*ppmbMsg)->m_len == 0)
   {
      kcapi_free_mbuf (*ppmbMsg);
      *ppmbMsg = NULL;
      DBG (LOG_INFO, "Appl. id %u: Empty message retrieved", uApplID);
      uRes = CME_GET_QUEUE_OVERFLOW;
      kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
      return (uRes);
   }

   /* else: got valid message (hope so) */
   DBG (LOG_DEBUG, "Appl. id %u: Got valid message, cmd=0x%04X",
        uApplID, (unsigned) CAPI_GET_CMD (mtod (*ppmbMsg, CAPIMsg_t *)));
   uRes = CAPI_OK;
   if (e_uNumTracers > 0)
   {
      int fIsDataB3Msg;
      
      fIsDataB3Msg = ((CAPI_GET_CMD (mtod (*ppmbMsg, CAPIMsg_t *)) &
                      CAPI_CMDMASK_COMMAND) == C_DATA_B3);
      kcapitrace_trace_appl_msg (uApplID, *ppmbMsg, fIsDataB3Msg);
      kcapitrace_appl_get_message (uApplID, ppmbMsg, uRes);
   }
   return (uRes);
} /* kcapi_get_message */





#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)

/**
 * Register callback function for incoming messages.
 *
 * @param uApplID               I: Application id from registration.
 * @param pfnCallback           I: The function to be called if new CAPI
 *                                 messages arrive for the application. If this
 *                                 argument is NULL, a formerly registered
 *                                 callback function is unregistered.
 * @param qwParam               I: The additional parameter to be passed as the
 *                                 second parameter of the callback function.
 *
 * @retval CAPI_OK              The callback function was successfully
 *                              registered.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned kcapi_set_signal
   (unsigned  uApplID,
    void (*pfnCallback) (unsigned uApplID, u_int64_t qwParam),
    u_int64_t qwParam)
{
   CapiManAppInfo_t *pAppInfo;
   unsigned          uRes;
   
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || (size_t) uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Invalid appl. id %u", uApplID);
      uRes = CME_INVALID_APPLICATION_ID;
      kcapitrace_appl_set_signal (uApplID, pfnCallback, qwParam, uRes);
      return (uRes);
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* check for blocked application because of pending release operation */
   if (pAppInfo->fIsReleasing)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Appl. id %u: Is already releasing", uApplID);
      uRes = CME_BUSY;
      kcapitrace_appl_set_signal (uApplID, pfnCallback, qwParam, uRes);
      return (uRes);
   }
   
   pAppInfo->dwParam     = qwParam;
   pAppInfo->pfnCallback = pfnCallback;
   
   sx_xunlock (&g_sxAppData);

   DBG (LOG_TRACE,
        "Appl. id %u: Signal function %p, parameter 0x%08llX registered",
        uApplID, pfnCallback, (unsigned long long) qwParam);

   uRes = CAPI_OK;
   if (e_uNumTracers > 0)
   {
      kcapitrace_appl_set_signal (uApplID, pfnCallback, qwParam, uRes);
   }
   return (uRes);
} /* kcapi_set_signal */

#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */

/**
 * Register callback function for incoming messages.
 *
 * @param uApplID               I: Application id from registration.
 * @param pfnCallback           I: The function to be called if new CAPI
 *                                 messages arrive for the application. If this
 *                                 argument is NULL, a formerly registered
 *                                 callback function is unregistered.
 * @param dwParam               I: The additional parameter to be passed as the
 *                                 second parameter of the callback function.
 *
 * @retval CAPI_OK              The callback function was successfully
 *                              registered.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned kcapi_set_signal
   (unsigned  uApplID,
    void (*pfnCallback) (unsigned uApplID, u_int32_t dwParam),
    u_int32_t dwParam)
{
   CapiManAppInfo_t *pAppInfo;
   unsigned          uRes;
   
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || (size_t) uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Invalid appl. id %u", uApplID);
      uRes = CME_INVALID_APPLICATION_ID;
      kcapitrace_appl_set_signal (uApplID, pfnCallback, dwParam, uRes);
      return (uRes);
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* check for blocked application because of pending release operation */
   if (pAppInfo->fIsReleasing)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Appl. id %u: Is already releasing", uApplID);
      uRes = CME_BUSY;
      kcapitrace_appl_set_signal (uApplID, pfnCallback, dwParam, uRes);
      return (uRes);
   }
   
   pAppInfo->dwParam     = dwParam;
   pAppInfo->pfnCallback = pfnCallback;
   
   sx_xunlock (&g_sxAppData);

   DBG (LOG_TRACE,
        "Appl. id %u: Signal function %p, parameter 0x%08lX registered",
        uApplID, pfnCallback, (unsigned long) dwParam);

   uRes = CAPI_OK;
   if (e_uNumTracers > 0)
   {
      kcapitrace_appl_set_signal (uApplID, pfnCallback, dwParam, uRes);
   }
   return (uRes);
} /* kcapi_set_signal */

#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */





/* === implementation of public functions ================================ */





/**
 * Initialize the application handling module.
 */

void kcapiappl_init (void)
{
   /* initialize the application data array */
   bzero (g_aAppInfo, sizeof (g_aAppInfo));
   g_uLastAssignedApplID = 0;
   e_uNumApps = 0;
   
   /* initialize the mutex for application data access */
   sx_init (&g_sxAppData, "kcapimgr-appl-data");
   
} /* kcapiappl_init */





/**
 * Close the application handling module.
 */

void kcapiappl_close (void)
{
   int i;
   int f;
   
   /* be sure all applications are released */
   for (i = 0; (size_t) i < ARRAY_COUNT (g_aAppInfo); i++)
   {
      sx_xlock (&g_sxAppData);
      f = g_aAppInfo [i].fAppRegistered;
      sx_xunlock (&g_sxAppData);
      if (f)
      {
         DBG (LOG_INFO, "Appl. id %u still registered, perform release",
              (unsigned) i);
         (void) kcapi_release ((unsigned) i);
      }
   }
   
   sx_destroy (&g_sxAppData);
   
} /* kcapiappl_close */





/**
 * Signal a received CAPI message for a connection.
 *
 * This function is called by the controller handling module to forward a CAPI
 * message from a controller. So this function must first obtain access to the
 * application data. Then the call may be forwarded to the internal function to
 * enqueue the message for the application.
 *
 * @param uApplID               I: The application id to forward the message to.
 * @param pmbMsg                I: The mbuf containing the CAPI message.
 *
 * @return Nothing.
 */

void kcapiappl_enqueue_capi_message
   (unsigned     uApplID,
    struct mbuf *pmbMsg)
{
   CapiManAppInfo_t *pAppInfo;
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
   void (*pfnCallback) (unsigned, u_int64_t);
   u_int64_t         dwParam;
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   void (*pfnCallback) (unsigned, u_int32_t);
   u_int32_t         dwParam;
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Invalid appl. id %u", uApplID);
      return;
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* forward the call to the internal handling function */
   kcapiappl_enqueue_capi_message_internal (pAppInfo, pmbMsg);
   
   /* perform callback if registered and not in kcapi_release() */
   if (pAppInfo->pfnCallback && ! pAppInfo->fIsReleasing)
   {
      pfnCallback = pAppInfo->pfnCallback;
      dwParam     = pAppInfo->dwParam;

      /* Now release the application entry lock to let the application fetch
       * messages from its queue or send new ones during the callback. The
       * application array lock remains active, so the application may not
       * perform a release during the callback. This would lead to a kernel
       * panic. Note that the application is not allowed to perform a release
       * during the callback.
       */
      pAppInfo->nNumActiveCalls++;
      pAppInfo->ptdCallback = curthread;
      sx_xunlock (&g_sxAppData);

      /* tell application about the new message by calling callback function */
      DBG (LOG_DEBUG, "Appl. id %u: Call signal function, parameter 0x%08llX",
           uApplID, (unsigned long long) dwParam);
      pfnCallback (uApplID, dwParam);
      DBG (LOG_DEBUG, "Appl. id %u: Signal function called", uApplID);
      
      sx_xlock (&g_sxAppData);
      pAppInfo->ptdCallback = NULL;
      pAppInfo->nNumActiveCalls--;
   }
   
   sx_xunlock (&g_sxAppData);
   
} /* kcapiappl_enqueue_capi_message */





/**
 * Signal a trace message to an application.
 *
 * This function is called by the tracing module to forward a CAPI message from
 * with tracing content. So this function must first obtain access to the
 * application data array. Then the trace message is enqueued into the trace
 * queue for the addressed application. Finally the callback function is called
 * to tell the application there is a new message to retrieve.
 *
 * @param uApplID               I: The application id to forward the message to.
 * @param pmbMsg                I: The mbuf containing the trace message.
 *
 * @return Nothing.
 */

void kcapiappl_enqueue_trace_message
   (unsigned     uApplID,
    struct mbuf *pmbMsg)
{
   CapiManAppInfo_t *pAppInfo;
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
   void (*pfnCallback) (unsigned, u_int64_t);
   u_int64_t         dwParam;
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   void (*pfnCallback) (unsigned, u_int32_t);
   u_int32_t         dwParam;
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   
   sx_xlock (&g_sxAppData);
   
   /* check for registered application */
   if (uApplID <= 0 || uApplID >= ARRAY_COUNT (g_aAppInfo) ||
       ! g_aAppInfo [uApplID].fAppRegistered)
   {
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR, "Invalid appl. id %u", uApplID);
      return;
   }
   pAppInfo = &(g_aAppInfo [uApplID]);
   
   /* check for full queue */
   if (_IF_QFULL (&(pAppInfo->traceQueue)))
   {
      /* we must drop the current message */
      pAppInfo->traceQueue.ifq_drops++;
      
      sx_xunlock (&g_sxAppData);

      kcapi_free_mbuf (pmbMsg);
      
      DBG (LOG_ERROR, "Appl. id %u: Trace queue is full, increment drops",
           uApplID);

      return;
   }
   
   /* enqueue trace message */
   _IF_ENQUEUE (&(pAppInfo->traceQueue), pmbMsg);
   DBG (LOG_DEBUG, "Appl. id %u: Trace message enqueued", pAppInfo->uApplID);
   
   
   /* perform callback if registered */
   if (pAppInfo->pfnCallback && ! pAppInfo->fIsReleasing)
   {
      pfnCallback = pAppInfo->pfnCallback;
      dwParam     = pAppInfo->dwParam;

      /* Now release the application entry lock to let the application fetch
       * messages from its queue or send new ones during the callback. The
       * application array lock remains active, so the application may not
       * perform a release during the callback. This would lead to a kernel
       * panic. Note that the application is not allowed to perform a release
       * during the callback.
       */
      pAppInfo->nNumActiveCalls++;
      pAppInfo->ptdCallback = curthread;
      sx_xunlock (&g_sxAppData);

      /* tell application about the new message by calling callback function */
      DBG (LOG_DEBUG, "Appl. id %u: Call signal function, parameter 0x%08llX",
           uApplID, (unsigned long long) dwParam);
      pfnCallback (uApplID, dwParam);
      DBG (LOG_DEBUG, "Appl. id %u: Signal function called", uApplID);
      
      sx_xlock (&g_sxAppData);
      pAppInfo->ptdCallback = NULL;
      pAppInfo->nNumActiveCalls--;
   }

   sx_xunlock (&g_sxAppData);
   
} /* kcapiappl_enqueue_trace_message */





/**
 * Signal disabling of a controller.
 *
 * When a controller is disabled (by its driver), all connections of each
 * application over this controller are aborted. So a Disconnect-Indication must
 * be forwarded for each open physical connection.
 *
 * The Disconnect-Response as a reaction will fail with error, because the
 * application is not registered with the respective controller any more. But
 * there is no need to handle this in a special manner.
 *
 * @param uCtlr                 I: The unique controller number that is
 *                                 disabled.
 *
 * @return Nothing.
 */

void kcapiappl_notify_ctlr_disable
   (unsigned uCtlr)
{
   CapiManAppInfo_t  *pAppInfo;
   CapiManPlciData_t *pPlciData;
   int                i;
   int                j;
   int                fMsgEnqueued;
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
   void (*pfnCallback) (unsigned, u_int64_t);
   u_int64_t         dwParam;
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   void (*pfnCallback) (unsigned, u_int32_t);
   u_int32_t         dwParam;
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */

   sx_xlock (&g_sxAppData);
   
   /* walk over the application array */
   for (i = 0; (size_t) i < ARRAY_COUNT (g_aAppInfo); ++i)
   {
      /* check for registered application at current array position */
      pAppInfo = &(g_aAppInfo [i]);
      if (! pAppInfo->fAppRegistered)
      {
         continue;
      }
      
      /* walk through all open connections and check for the controller
       * specified
       */
      fMsgEnqueued = 0;
      for (j = 0; j < pAppInfo->nCurrNumPlciData; j++)
      {
         pPlciData = &(pAppInfo->paPlciData [j]);
         if ((pPlciData->dwPlci & CAPI_CIDMASK_CTLR) != uCtlr)
         {
            continue;
         }
         printf ("kcapimgr: WARNING: Disabling controller %u with open connection for appl. id %u (PLCI=0x%08X)\n",
                 uCtlr, pAppInfo->uApplID, (unsigned) pPlciData->dwPlci);
         kcapiappl_enqueue_disconnect_ind
            (pAppInfo, pPlciData->dwPlci, CDISC_PROTOCOL_LAYER_1);
         fMsgEnqueued = 1;
      }
      
      /* perform callback if registered and messages were enqueued */
      if (fMsgEnqueued && pAppInfo->pfnCallback && ! pAppInfo->fIsReleasing)
      {
         pfnCallback = pAppInfo->pfnCallback;
         dwParam     = pAppInfo->dwParam;

         /* Now release the application entry lock to let the application fetch
          * messages from its queue or send new ones during the callback. The
          * application array lock remains active, so the application may not
          * perform a release during the callback. This would lead to a kernel
          * panic. Note that the application is not allowed to perform a release
          * during the callback.
          */
         pAppInfo->nNumActiveCalls++;
         pAppInfo->ptdCallback = curthread;
         sx_xunlock (&g_sxAppData);

         /* tell application about the new message by calling callback function
          */
         DBG (LOG_DEBUG, "Appl. id %u: Call signal function, parameter 0x%08llX",
              (unsigned) i, (unsigned long long) dwParam);
         pfnCallback ((unsigned) i, dwParam);
         DBG (LOG_DEBUG, "Appl. id %u: Signal function called", (unsigned) i);
      
         sx_xlock (&g_sxAppData);
         pAppInfo->ptdCallback = NULL;
         pAppInfo->nNumActiveCalls--;
      }
   }

   sx_xunlock (&g_sxAppData);

} /* kcapiappl_notify_ctlr_disable */





/* === implementation of private functions =============================== */





/**
 * Send a Disconnect-Request for a connection.
 *
 * @param uApplID               I: The application id for the request.
 * @param dwPlci                I: The PLCI for which the Disconnect-Request is
 *                                 to be sent.
 *
 * @return CAPI result value, CAPI_OK on success.
 */
static unsigned kcapiappl_send_disconnect_req
   (unsigned  uApplID,
    u_int32_t dwPlci)
{
   struct mbuf *pmbMsg;
   CAPIMsg_t   *pMsg;
   unsigned     uCtlr;
   unsigned     uRes;
   
   /* get new mbuf for the message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t) + 1);
   if (! pmbMsg)
   {
      printf ("kcapimgr: ERROR: Out of mbufs to send Disconnect-Request for appl. id %u, PLCI=0x%08X\n",
              uApplID, (unsigned) dwPlci);
      return (CME_OS_RESOURCE_ERROR);
   }
   
   /* construct message */
   pMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   C_PUT_WORD (pMsg->head.wApp, uApplID);
   C_PUT_WORD (pMsg->head.wCmd, CAPI_REQUEST (C_DISCONNECT));
   C_PUT_WORD (pMsg->head.wNum, 0);
   C_PUT_DWORD (pMsg->head.dwCid, dwPlci);
   C_PUT_BYTE (pMsg->info.any.b [0], 0);
   C_PUT_WORD (pMsg->head.wLen, sizeof (pMsg->head) + 1);
   
   /* send message to controller (no filtering needed, so call controller
    * directly)
    */
   /* Note: There is no need to check for need to register the application
    *       on-the-fly. As there is a connection to disconnect, the application
    *       _must_ be registered.
    */
   uCtlr = (dwPlci & CAPI_CIDMASK_CTLR);
   uRes = kcapictlr_put_message (uApplID, uCtlr, pmbMsg);
   if (uRes != CAPI_OK)
   {
      printf ("kcapimgr: WARNING: Error 0x%04X sending Disconnect-Request to controller %u for appl. id %u, PLCI=0x%08X\n",
              uRes, uCtlr, uApplID, (unsigned) dwPlci);
      kcapi_free_mbuf (pmbMsg);
   }
   
   return (uRes);
} /* kcapiappl_send_disconnect_req */




    
/**
 * Send out a CAPI message to a controller with message filtering.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param uCtlr                 I: The controller to send the message to.
 * @param pmbMsg                I: The CAPI message to send. The mbuf is
 *                                 released within this function if the result
 *                                 is CAPI_OK.
 *
 * @retval CAPI_OK              The message was sent successfully, the caller is
 *                              no longer responsible for the mbuf.
 * @retval Else                 An error occurred, the caller remains
 *                              responsible for the mbuf.
 */

static unsigned kcapiappl_put_message
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         uCtlr,
    struct mbuf      *pmbMsg)
{
   CapiManPlciData_t *pPlciData;
   CapiManNcciData_t *pNcciData;
   u_int16_t          wCmd;
   u_int32_t          dwCid;
   unsigned           uRes;
   
   sx_xlock (&g_sxAppData);
   
   /* for Connect-B3-Request or positive -Response check if a new connection is
    * allowed
    */
   uRes = kcapiappl_check_outgoing_message_for_new_connection
             (pAppInfo, mtod (pmbMsg, CAPIMsg_t *));
   if (uRes != CAPI_OK)
   {
      sx_xunlock (&g_sxAppData);
      return (uRes);
   }
   
   /* check for needed filtering of the message */
   if (kcapiappl_check_for_message_to_filter
          (pAppInfo, mtod (pmbMsg, CAPIMsg_t *)))
   {
      sx_xunlock (&g_sxAppData);
      kcapi_free_mbuf (pmbMsg);
      return (CAPI_OK);
   }
   
   /* check for terminated physical or logical connection */
   /* Note: As the connection will be removed during this call, this function
    *       must be called after all other checks working on PLCI or NCCI data
    *       are done.
    */
   kcapiappl_check_for_terminated_connection
      (pAppInfo, mtod (pmbMsg, CAPIMsg_t *));

   /* for send and receive window counting we need to know if the message is a
    * Data-B3-Response
    */
   wCmd = CAPI_GET_CMD (mtod (pmbMsg, CAPIMsg_t *));
   dwCid = CAPI_GET_CID (mtod (pmbMsg, CAPIMsg_t *));
   
   /* if the message is a Data-B3-Request check if an additional data block is
    * allowed and count it (on failure counting will be undone later)
    */
   if (wCmd == CAPI_REQUEST (C_DATA_B3))
   {
      pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
      if (pPlciData)
      {
         pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
         if (pNcciData)
         {
            if (pNcciData->nNumOutDataBlocks < CAPIMAN_OUT_WINDOW_SIZE)
            {
               pNcciData->nNumOutDataBlocks++;
            }
            else
            {
               sx_xunlock (&g_sxAppData);
               DBG (LOG_TRACE,
                    "Appl. id %u: Got Data-B3-Request with no more Data-B3 block allowed for NCCI 0x%08X",
                    pAppInfo->uApplID, (unsigned) dwCid);
               uRes = CME_PUT_QUEUE_FULL;
               return (uRes);
            }
         }
         else
         {
            DBG (LOG_ERROR,
                 "Appl. id %u: Got Data-B3-Request for unknown NCCI 0x%08X",
                 pAppInfo->uApplID, (unsigned) dwCid);
         }
      }
      else
      {
         DBG (LOG_ERROR,
              "Appl. id %u: Got Data-B3-Request for unknown PLCI of NCCI 0x%08X",
              pAppInfo->uApplID, (unsigned) dwCid);
      }
   }

   sx_xunlock (&g_sxAppData);
   
   /* pass the message to the controller */
   uRes = kcapictlr_put_message (pAppInfo->uApplID, uCtlr, pmbMsg);
   if (uRes != CME_INVALID_APPLICATION_ID)
   {
      /* success or general error, ready... */
      
      /* ...but count an acknowledged incoming Data-B3 block... */
      if (uRes == CAPI_OK && wCmd == CAPI_RESPONSE (C_DATA_B3))
      {
         sx_xlock (&g_sxAppData);
         
         pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
         if (pPlciData)
         {
            pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
            if (pNcciData)
            {
               if (pNcciData->nNumIncDataBlocks > 0)
               {
                  pNcciData->nNumIncDataBlocks--;
               }
               else
               {
                  /* this may happen if a Reset-B3-Indication was received but
                   * the application still responds to an earlier received data
                   * block
                   */
                  DBG (LOG_TRACE,
                       "Appl. id %u: Got Data-B3-Response with no Data-B3 block pending for NCCI 0x%08X",
                       pAppInfo->uApplID, (unsigned) dwCid);
               }
            }
            else
            {
               DBG (LOG_ERROR,
                    "Appl. id %u: Got Data-B3-Response for unknown NCCI 0x%08X",
                    pAppInfo->uApplID, (unsigned) dwCid);
            }
         }
         else
         {
            DBG (LOG_ERROR,
                 "Appl. id %u: Got Data-B3-Response for unknown PLCI of NCCI 0x%08X",
                 pAppInfo->uApplID, (unsigned) dwCid);
         }
         
         sx_xunlock (&g_sxAppData);
      }
      /* ...or undo counting of an outgoing Data-B3 block if it was not
       * accepted (O.K. or information values may be returned)
       */
      else if ((uRes & 0xFF00) != 0 && wCmd == CAPI_REQUEST (C_DATA_B3))
      {
         sx_xlock (&g_sxAppData);
         
         pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
         if (pPlciData)
         {
            pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
            if (pNcciData)
            {
               if (pNcciData->nNumOutDataBlocks > 0)
               {
                  pNcciData->nNumOutDataBlocks--;
               }
            }
         }
         
         sx_xunlock (&g_sxAppData);
      }
      return (uRes);
   }

   /* Note: If the PutMessage operation fails with CME_INVALID_APPLICATION_ID,
    *       this cannot be a Data-B3-Response, at least no one to be counted.
    *       The first message to a controller after the Register operation may
    *       only be a Connect-Request, a Listen-Request or some other
    *       connectionless message. A Data-B3-Response would imply a formerly
    *       received Data-B3-Indication, so the application _must_ already be
    *       registered. And so there is no need to check for a
    *       Data-B3-Indication after the following PutMessage operation.
    */
   
   /* application is not registered at the controller yet, perform registration
    * on-the-fly
    */
   uRes = kcapictlr_register_app (pAppInfo->uApplID,
                                  uCtlr,
                                  pAppInfo->uMaxLogicalConnections,
                                  pAppInfo->uMaxBDataBlocks,
                                  pAppInfo->uMaxBDataLen);
   if (uRes != CAPI_OK)
   {
      /* application registration failed, unable to send message */
      return (uRes);
   }
   
   /* now finally re-send the message */
   return (kcapictlr_put_message (pAppInfo->uApplID, uCtlr, pmbMsg));
} /* kcapiappl_put_message */





/**
 * Signal a Disconnect-Indication for a connection.
 *
 * @pre This function is called with the application data mutex locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param dwPlci                I: The PLCI for which to enqueue the
 *                                 Disconnect-Indication.
 * @param wReason               I: The disconnect reason to report.
 *
 * @return Nothing.
 */

static void kcapiappl_enqueue_disconnect_ind
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci,
    u_int16_t         wReason)
{
   struct mbuf *pmbMsg;
   CAPIMsg_t   *pMsg;
   
   /* get new mbuf for the message */
   pmbMsg = kcapi_get_mbuf (sizeof (CAPIMsgHead_t) +
                            sizeof (CAPIDisconnectInd_t));
   if (! pmbMsg)
   {
      printf ("kcapimgr: ERROR: Out of mbufs to signal Disconnect-Indication for appl. id %u, PLCI=0x%08X, reason=0x%04X\n",
              pAppInfo->uApplID, (unsigned) dwPlci, (unsigned) wReason);
      return;
   }
   
   /* construct message */
   pMsg = (CAPIMsg_t *) (pmbMsg->m_data);
   C_PUT_WORD (pMsg->head.wApp, pAppInfo->uApplID);
   C_PUT_WORD (pMsg->head.wCmd, CAPI_INDICAT (C_DISCONNECT));
   C_PUT_WORD (pMsg->head.wNum, 0);
   C_PUT_DWORD (pMsg->head.dwCid, dwPlci);
   C_PUT_WORD (pMsg->info.disconnect_ind.wReason, wReason);
   C_PUT_WORD (pMsg->head.wLen,
               sizeof (pMsg->head) + sizeof (pMsg->info.disconnect_ind));
   
   /* signal message to application, without a callback */
   kcapiappl_enqueue_capi_message_internal (pAppInfo, pmbMsg);

   /* Note: The callback is left to the caller. */
   
} /* kcapiappl_enqueue_disconnect_ind */





/**
 * Signal a received CAPI message for a connection (internal code).
 *
 * @pre This function is called with the application data mutex locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param pmbMsg                I: The CAPI message to enqueue. The mbuf will
 *                                 change its ownership to the application
 *                                 message queue.
 *
 * @return Nothing.
 */

static void kcapiappl_enqueue_capi_message_internal
   (CapiManAppInfo_t *pAppInfo,
    struct mbuf      *pmbMsg)
{
   unsigned           uCmd;
   u_int32_t          dwCid;
   CapiManPlciData_t *pPlciData;
   CapiManNcciData_t *pNcciData;
   
   /* check for no mbuf */
   if (! pmbMsg)
   {
      pAppInfo->msgQueue.ifq_drops++;
      
      DBG (LOG_ERROR, "Appl. id %u: Got no mbuf, increment drops",
           pAppInfo->uApplID);

      return;
   }
   
   /* Note: For incoming connections we accept a new connection even if there
    *       is no more space. That should have been handled earlier by the
    *       driver. If the connection cannot be administrated by the CAPI
    *       manager it is not so important. The only drawback is that message
    *       filtering is not possible. Same applies for outgoing connections.
    */

   /* message command and connection identifier are needed further on */
   uCmd = CAPI_GET_CMD ((CAPIMsg_t *) (pmbMsg->m_data));
   dwCid = CAPI_GET_CID ((CAPIMsg_t *) (pmbMsg->m_data));

   /* a Connect-Indication, a positive Connect-Confirm, a
    * Facility-Resume-Confirm or a Facility-Handset-Indication  requires
    * insertion of a new PLCI data structure (new physical connection)
    */
   if (kcapiappl_check_for_new_plci (mtod (pmbMsg, CAPIMsg_t *)))
   {
      /* insert new PLCI into the PLCI data array for the application */
      if (! _IF_QFULL (&(pAppInfo->msgQueue)))
      {
         pPlciData = kcapiappl_insert_new_plci (pAppInfo, dwCid);
         if (! pPlciData)
         {
            printf ("kcapimgr: ERROR: Appl. id %u: Out of memory inserting new PLCI 0x%04X, message 0x%04X is lost\n",
                    pAppInfo->uApplID, (unsigned) dwCid, uCmd);
            /* empty message signals dropped message */
            kcapi_free_mbuf (pmbMsg);
            pmbMsg = kcapi_get_mbuf (0);
         }
         else
         {
            DBG (LOG_TRACE, "Appl. id %u: PLCI 0x%04X registered",
                 pAppInfo->uApplID, (unsigned) (pPlciData->dwPlci));
         }
      }
   }
   else if (uCmd == CAPI_CONFIRM (C_CONNECT) &&
	    C_GET_WORD (mtod (pmbMsg, CAPIMsg_t *)
			   ->info.connect_conf.wInfo) != CAPI_OK)
   {
      /* a Connect-Confirm with a negative result does not lead to a new
       * connection and there is no PLCI data entry, so just pass the message
       * as is
       */
      ;
   }
   /* no new connection and no negative Connect-Confirm, so there must be an
    * existing PLCI data entry for this application, if the connection
    * identifier is not only a controller message independent of any physical
    * connection
    */
   else if ((dwCid & CAPI_CIDMASK_PLCI) != 0)
   {
      /* find the PLCI data structure for the current message */
      pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
      if (! pPlciData)
      {
         printf ("kcapimgr: WARNING: Appl. id %u: Incoming CAPI message 0x%04X with unknown PLCI 0x%04X\n",
                 pAppInfo->uApplID, uCmd, (unsigned) dwCid);
         /* deliver the message even the PLCI is unknown to us */
      }
      else
      {
         /* if the message is a Disconnect-Indication, the PLCI data must be
          * marked to only let a Disconnect-Response pass to the controller
          */
         if (uCmd == CAPI_INDICAT (C_DISCONNECT))
         {
            pPlciData->fGotDisconnectInd = 1;
            
            /* if there is a releasing application waiting for a
             * Disconnect-Indication, tell it the message has arrived
             */
            if (pPlciData->fWaitForDisconnect)
            {
               pPlciData->fWaitForDisconnect = 0;
               wakeup (pPlciData);
            }
         }
         /* if the message is a positive Connect-B3-Confirm there is a new
          * logical connection to be inserted into the applications lists
          */
         /* Note: New logical connections signalled by Connect-B3-Indication are
          *       only added to the NCCI list when the application accepts them.
          *       So the application has a chance to reject a connection itself,
          *       if the number of B3-connections is exceeded.
          */
         else if (uCmd == CAPI_CONFIRM (C_CONNECT_B3) &&
                  C_GET_WORD (mtod (pmbMsg, CAPIMsg_t *)
                                 ->info.connect_b3_conf.wInfo) == CAPI_OK)
         {
            /* insert the new NCCI */
            if (! _IF_QFULL (&(pAppInfo->msgQueue)))
            {
               (void) kcapiappl_insert_new_ncci (pAppInfo, pPlciData, dwCid);
            }
         }
         /* if the message is a Disconnect-B3-Indication the NCCI data must be
          * marked to only let a Disconnect-B3-Response or PLCI-only messages
          * pass to the controller
          */
         else if (uCmd == CAPI_INDICAT (C_DISCONNECT_B3))
         {
            pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
            if (pNcciData)
            {
               pNcciData->fGotDisconnectB3Ind = 1;
            }
         }
         /* if the message is a Data-B3-Indication, the number of outstanding
          * Data-B3-Confirmations must be incremented in the NCCI data, maybe
          * the message must be dropped because of too many unacknowledged
          * data blocks
          */
         else if (uCmd == CAPI_INDICAT (C_DATA_B3))
         {
            pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
            if (pNcciData)
            {
               if (pNcciData->nNumIncDataBlocks < pAppInfo->uMaxBDataBlocks)
               {
                  pNcciData->nNumIncDataBlocks++;
               }
               else
               {
                  /* Too many unacknowledged Data-B3 blocks; to not let the
                   * system run out of mbufs (probably large clusters), because
                   * of a malicious application, we drop the CAPI message and
                   * the data block.
                   */
                  DBG (LOG_ERROR,
                       "Appl. id %u: Got unacknowledged Data-B3-Indication no. %zu for NCCI 0x%08X, message will be dropped",
                       pAppInfo->uApplID,
                       pNcciData->nNumIncDataBlocks + 1, (unsigned) dwCid);
                  /* empty message signals dropped message */
                  kcapi_free_mbuf (pmbMsg);
                  pmbMsg = NULL;
               }
            }
         }
         else if (uCmd == CAPI_CONFIRM (C_DATA_B3))
         {
            pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
            if (pNcciData)
            {
               if (pNcciData->nNumOutDataBlocks > 0)
               {
                  pNcciData->nNumOutDataBlocks--;
               }
               else
               {
                  /* More Data-B3-Confirms than -Requests? */
                  DBG (LOG_ERROR,
                       "Appl. id %u: Got Data-B3-Confirm with no -Request pending for NCCI 0x%08X",
                       pAppInfo->uApplID, (unsigned) dwCid);
                  /* just send the message to the application for error handling
                   */
               }
            }
         }
         else if (uCmd == CAPI_INDICAT (C_RESET_B3_20))
         {
            pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
            if (pNcciData)
            {
               DBG (LOG_TRACE,
                    "Appl. id %u: Got Reset-B3-Indication for NCCI 0x%08X, reset number of incoming (currently %zu) and outgoing (currently %zu) data blocks",
                    pAppInfo->uApplID, (unsigned) dwCid,
                    pNcciData->nNumIncDataBlocks, pNcciData->nNumOutDataBlocks);
               pNcciData->nNumIncDataBlocks = 0;
               pNcciData->nNumOutDataBlocks = 0;
            }
         }
      }
   }
   
   /* enqueue CAPI message if the queue is not filled up */
   if (pmbMsg != NULL && _IF_QFULL (&(pAppInfo->msgQueue)))
   {
      /* we must drop the current message */
      pAppInfo->msgQueue.ifq_drops++;
      
      kcapi_free_mbuf (pmbMsg);
      
      DBG (LOG_ERROR,
           "Appl. id %u: Message queue is full, increment drops and drop message 0x%04X",
           pAppInfo->uApplID, uCmd);

      return;
   }
   if (pmbMsg != NULL)
   {
      _IF_ENQUEUE (&(pAppInfo->msgQueue), pmbMsg);
      DBG (LOG_DEBUG, "Appl. id %u: Message 0x%04X enqueued",
           pAppInfo->uApplID, uCmd);
   }
   else
   {
      pAppInfo->msgQueue.ifq_drops++;
      DBG (LOG_ERROR, "Appl. id %u: Message 0x%04X dropped",
           pAppInfo->uApplID, uCmd);
   }
   
} /* kcapiappl_enqueue_capi_message_internal */





/**
 * Check if an incoming message is delivering a new PLCI.
 *
 * @param pMsg                  I: The incoming message to examine.
 *
 * @retval 0                    The message does not introduce a new PLCI.
 * @retval 1                    The message signals a new PLCI.
 */

static int kcapiappl_check_for_new_plci
   (const CAPIMsg_t *pMsg)
{
   unsigned uCmd;
   unsigned uLen;
   
   /* if the message does not contain a valid PLCI, it will not deliver a new
    * one
    */
   if ((CAPI_GET_CID (pMsg) & CAPI_CIDMASK_PLCI) == 0)
   {
      return (0);
   }
   
   /* extract the message command */
   uCmd = CAPI_GET_CMD (pMsg);
   
   /* a Connect-Indication or a positive Connect-Confirm signals a new physical
    * connection
    */
   if (uCmd == CAPI_INDICAT (C_CONNECT) ||
       (uCmd == CAPI_CONFIRM (C_CONNECT) &&
        C_GET_WORD (pMsg->info.connect_conf.wInfo) == CAPI_OK))
   {
      return (1);
   }
   
   /* a positive Facility-Resume-Confirm delivers a new PLCI for a resumed
    * physical connection
    */
   uLen = (unsigned) C_GET_WORD (pMsg->head.wLen);
   if (uCmd == CAPI_CONFIRM (C_FACILITY) &&
       uLen > sizeof (pMsg->head) + sizeof (pMsg->info.facility_conf) &&
       C_GET_WORD (pMsg->info.facility_conf.wInfo) == CAPI_OK &&
       C_GET_WORD (pMsg->info.facility_conf.wSelector) ==
          CAPI_FACILITY_SELECTOR_SUPPL_SERVICES)
   {
      const CAPIFacilitySupplConf_t     *pFacConf;
      const CAPISupplParamGeneralConf_t *pConfParam;
      
      /* this is a positive Facility-Confirm for supplementary services with an
       * additional Facility-Confirm-Parameter; now check for function resume
       * and the function specific info value
       */
      pFacConf = (const CAPIFacilitySupplConf_t *)
                    ((const u_int8_t *) &(pMsg->info.facility_conf.wSelector) +
                     sizeof (pMsg->info.facility_conf.wSelector));
      if (C_GET_BYTE (pFacConf->bLength) >= sizeof (*pFacConf) &&
          C_GET_WORD (pFacConf->wFunction) == C_SUPPL_FUNC_RESUME)
      {
         /* this is a Facility-Resume-Confirm with an additional parameter
          * struct; now check the function specific info value in the parameter
          * struct
          */
         pConfParam = (const CAPISupplParamGeneralConf_t *)
                         ((const u_int8_t *) &(pFacConf->wFunction) +
                          sizeof (pFacConf->wFunction));
         if (C_GET_BYTE (pConfParam->bLength) >= sizeof (pConfParam->wInfo) &&
             C_GET_WORD (pConfParam->wInfo) == CAPI_OK)
         {
            /* now finally this is _really_ a positive Facility-Resume-Confirm
             * introducing a new PLCI for a resumed connection
             */
            return (1);
         }
      }
   }
   
   /* a Facility-Handset-Indication delivers a new PLCI for a connection to the
    * handset going off-hook
    */
   if (uCmd == CAPI_INDICAT (C_FACILITY) &&
       uLen > sizeof (pMsg->head) + sizeof (pMsg->info.facility_ind) &&
       C_GET_WORD (pMsg->info.facility_ind.wSelector) ==
          CAPI_FACILITY_SELECTOR_HANDSET)
   {
      const u_int8_t *pDigits;
      unsigned        uLenDigits;
      int             fOffHook;
      int             i;
      
      /* this is a Facility-Handset-Indication; now check if the handset really
       * went off-hook
       */
      pDigits = (const u_int8_t *) &(pMsg->info.facility_ind.wSelector) +
                sizeof (pMsg->info.facility_ind.wSelector);
      uLenDigits = *(pDigits++);
      /* search for the digit indicating handset going off-hook; only the last
       * on-hook / off-hook signal counts
       */
      fOffHook = 0;
      for (i = 0; (unsigned) i < uLenDigits; i++)
      {
         if (pDigits [i] == '+')
         {
            fOffHook = 1;
         }
         else if (pDigits [i] == '-')
         {
            fOffHook = 0;
         }
      }
      if (fOffHook)
      {
         return (1);
      }
   }
   
   /* if we reach this point, the message does contain a new PLCI */
   return (0);
} /* kcapiappl_check_for_new_plci */





/**
 * Check if a message will create a new allowed connection.
 *
 * @pre This function is called with the application array and the application
 *      entry locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param pmbMsg                I: The CAPI message to check.
 *
 * @retval CAPI_OK              The message is allowed. There is room for a new
 *                              PLCI or NCCI if the message will introduce or
 *                              acknowledge a new one.
 */

static unsigned kcapiappl_check_outgoing_message_for_new_connection
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pMsg)
{
   CapiManPlciData_t *pPlciData;
   u_int32_t          dwCid;
   u_int16_t          wCmd;
   
   /* get message command */
   dwCid = CAPI_GET_CID (pMsg);
   wCmd  = CAPI_GET_CMD (pMsg);
   
   /* check for Connect-B3-Request */
   if (wCmd == CAPI_REQUEST (C_CONNECT_B3))
   {
      /* a new logical connection should be made --> check for available entry
       * in the logical connections array; an entry will be allocated on
       * Connect-B3-Confirm
       */
      if (pAppInfo->pFirstFreeNcci == NULL)
      {
         DBG (LOG_INFO,
              "Appl. id %u: Connect-B3-Request would exceed maximum no. logical connections %u",
              pAppInfo->uApplID, pAppInfo->uMaxLogicalConnections);
         return (CCE_NO_NCCI_AVAILABLE);
      }
   }
   else if (wCmd == CAPI_RESPONSE (C_CONNECT_B3) &&
            C_GET_WORD (pMsg->info.connect_b3_resp.wReject) == CAPI_CALL_ACCEPT)
   {
      /* a new incoming logical connection should be accepted --> check for
       * available entry in the logical connections array and allocate an
       * appropriate entry
       */
      if (pAppInfo->pFirstFreeNcci == NULL)
      {
         DBG (LOG_INFO,
              "Appl. id %u: Connect-B3-Response would exceed maximum no. logical connections %u",
              pAppInfo->uApplID, pAppInfo->uMaxLogicalConnections);
         return (CCE_NO_NCCI_AVAILABLE);
      }
      
      /* find the PLCI for this NCCI */
      pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
      if (! pPlciData)
      {
         /* unknown PLCI? --> just forward the message to the controller without
          * counting anything
          */
         DBG (LOG_ERROR,
              "Appl. id %u: Connect-B3-Response with NCCI 0x%08X contains unknown PLCI",
              pAppInfo->uApplID, (unsigned) dwCid);
         return (CAPI_OK);
      }
      
      /* insert the new NCCI into the applications data structures */
      (void) kcapiappl_insert_new_ncci (pAppInfo, pPlciData, dwCid);
   }
   
   /* every other message will not be checked and is allowed */
   return (CAPI_OK);
} /* kcapiappl_check_outgoing_message_for_new_connection */





/**
 * Check and count for terminated connection.
 *
 * @pre This function is called with the application array and the application
 *      entry locked.
 *
 * @param pAppInfo              I: Information about the application context.
 * @param pMsg                  I: The CAPI message to check.
 *
 * @return Nothing
 */

static void kcapiappl_check_for_terminated_connection
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pMsg)
{
   CapiManPlciData_t *pPlciData;
   u_int32_t          dwCid;
   u_int16_t          wCmd;
   
   dwCid = CAPI_GET_CID (pMsg);
   wCmd = CAPI_GET_CMD (pMsg);
   
   /* check for terminated B3-connection */
   if (wCmd == CAPI_RESPONSE (C_DISCONNECT_B3))
   {
      pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
      if (pPlciData)
      {
         kcapiappl_remove_ncci (pAppInfo, pPlciData, dwCid);
      }
   }
   /* check for terminated physical connection */
   else if (wCmd == CAPI_RESPONSE (C_DISCONNECT))
   {
      /* remove the PLCI specified and any of the NCCIs managed by it */
      kcapiappl_remove_plci (pAppInfo, dwCid);
   }
   
} /* kcapiappl_check_for_terminated_connection */





/**
 * Check if a message has to be silently discarded.
 *
 * @param pAppInfo              I: The application data for the context.
 * @param pMsg                  I: The CAPI message to check.
 *
 * @retval 0                    The message shall be sent.
 * @retval 1                    The message shall be discarded.
 */

static int kcapiappl_check_for_message_to_filter
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pMsg)
{
   CapiManPlciData_t *pPlciData;
   CapiManNcciData_t *pNcciData;
   u_int16_t          wCmd;
   u_int32_t          dwCid;
   
   /* If a Connect-Request is allowed (i.e. this function is reached), it will
    * be sent, for there is no corresponding allocated connection entry to be
    * managed. The same applies to messages without a PLCI part, i.e. to
    * messages only addressed to the controller itself without any connection
    * context.
    */
   wCmd = CAPI_GET_CMD (pMsg);   
   dwCid = CAPI_GET_CID (pMsg);
   if ((dwCid & CAPI_CIDMASK_PLCI) == 0 ||
       wCmd == CAPI_REQUEST (C_CONNECT))
   {
      return (0);
   }
   
   /* first check the physical connection for the message */
   pPlciData = kcapiappl_find_plci (pAppInfo, dwCid);
   if (! pPlciData)
   {
      /* unregistered PLCI? --> just let the message be sent */
      DBG (LOG_INFO,
           "Appl. id %u: Got CAPI message 0x%04X with unknown PLCI in CID 0x%08X",
           pAppInfo->uApplID, (unsigned) wCmd, (unsigned) dwCid);
      return (0);
   }
   if (pPlciData->fGotDisconnectInd)
   {
      /* Only Disconnect-Response is valid for this connection; all other
       * messages should be ignored as the Disconnect-Indication is already
       * queued for this connection, except for a Disconnect-B3-Response (needed
       * for logical connection cleanup in the controller driver)
       */
      if (wCmd != CAPI_RESPONSE (C_DISCONNECT) &&
          wCmd != CAPI_RESPONSE (C_DISCONNECT_B3))
      {
         DBG (LOG_DEBUG,
              "Appl. id %u, PLCI 0x%04X: Only Disconnect-Response allowed, ignore message 0x%04X",
              pAppInfo->uApplID, (unsigned) pPlciData->dwPlci, (unsigned) wCmd);
         return (1);
      }
   }
   
   /* now check the logical connection, if the CID contains a NCCI part */
   if ((dwCid & CAPI_CIDMASK_NCCI) != 0)
   {
      pNcciData = kcapiappl_find_ncci (pAppInfo, pPlciData, dwCid);
      if (pNcciData &&
          pNcciData->fGotDisconnectB3Ind)
      {
         /* Disconnect-B3-Indication is already queued for the application;
          * Data-B3 messages and Disconnect-B3-Requests must be silently ignored
          */
         if (wCmd != CAPI_RESPONSE (C_DISCONNECT_B3))
         {
            DBG (LOG_DEBUG,
                 "Appl. id %u, NCCI 0x%08X: Only Disconnect-B3-Response allowed, ignore message 0x%04X",
                 pAppInfo->uApplID, (unsigned) pNcciData->dwNcci,
                 (unsigned) wCmd);
            return (1);
         }
      }
   }
   
   return (0);
} /* kcapiappl_check_for_message_to_filter */





/**
 * Handle a CAPI manager trace request message.
 *
 * @pre This function may only be called with a Manufacturer-Request or
 *      -Response where the manufacturer id is C4B_TRACE_MANU_ID.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pmbMsg                I: Address of the mbuf holding the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */

static unsigned kcapiappl_handle_trace_request
   (CapiManAppInfo_t *pAppInfo,
    struct mbuf      *pmbMsg)
{
   CAPITraceReq_t *pTrcReq;
   CAPIMsg_t      *pCapiMsg;
   unsigned        uRes;
   
   /* easier addressing of the CAPI message itself */
   pCapiMsg = mtod (pmbMsg, CAPIMsg_t *);
   
   /* a Manufacturer-Response is simply ignored */
   if (CAPI_GET_CMD (pCapiMsg) == CAPI_RESPONSE (C_MANUFACTURER))
   {
      DBG (LOG_DEBUG, "Appl. id %u: Ignore trace response message",
           pAppInfo->uApplID);
      kcapi_free_mbuf (pmbMsg);
      return (CAPI_OK);
   }
   /* now the message must be a Manufacturer-Request for CAPI tracing */
   
   /* address the trace request information structure */
   pTrcReq = (CAPITraceReq_t *)
                ((u_int8_t *) &(pCapiMsg->info.manufacturer_req.dwManuID) +
                 sizeof (pCapiMsg->info.manufacturer_req.dwManuID));
   
   /* distinguish between the possible requests */
   switch (C_GET_WORD (pTrcReq->wRequest))
   {
      case C4BTRC_REQ_START_APPL_TRACER:
         uRes = kcapiappl_handle_start_appl_tracer_request (pAppInfo, pCapiMsg);
         break;
         
      case C4BTRC_REQ_START_CTLR_TRACER:
         uRes = kcapiappl_handle_start_ctlr_tracer_request (pAppInfo, pCapiMsg);
         break;
         
      case C4BTRC_REQ_STOP_TRACER:
         uRes = kcapiappl_handle_stop_tracer_request (pAppInfo, pCapiMsg);
         break;
      
      default:
         DBG (LOG_ERROR, "Appl. id %u: Got invalid trace request %u",
              pAppInfo->uApplID, (unsigned) C_GET_WORD (pTrcReq->wRequest));
         uRes = CCE_ILLEGAL_MSG_PARAMETER;
         break;
   }
   
   /* the original message mbuf is not needed any more */
   kcapi_free_mbuf (pmbMsg);
   
   return (uRes);
} /* kcapiappl_handle_trace_request */





/**
 * Handle a request message to start a new application tracer.
 *
 * @pre This function must be called with the application data array and the
 *      application entry locked.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pReqMsg               I: Address of the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */

static unsigned kcapiappl_handle_start_appl_tracer_request
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pReqMsg)
{
   struct mbuf           *pmbConf;
   CAPIMsg_t             *pConfMsg;
   const CAPITraceReq_t  *pTrcReq;
   CAPITraceConf_t       *pTrcConf;
   unsigned long          ulTraceMask;
   unsigned long          ulDataBlockLength;
   int                    fIsRoot;
   int                    afMayTraceAppl [CAPI_MAX_APPLICATIONS];
   int                    i;
   unsigned               uTracerID;
   unsigned               uRes;

   /* determine the tracer parameters */
   pTrcReq = (const CAPITraceReq_t *)
                ((const u_int8_t *) &(pReqMsg->info.manufacturer_req) +
                 sizeof (pReqMsg->info.manufacturer_req));
   if (CAPI_GET_LEN (pReqMsg) <
          sizeof (pReqMsg->head) +
          sizeof (pReqMsg->info.manufacturer_req) +
          sizeof (pTrcReq->wRequest) +
          sizeof (pTrcReq->info.start_appl_tracer))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Start appl. tracer request: Invalid message length %u, %zu expected",
           pAppInfo->uApplID,
           (unsigned) CAPI_GET_LEN (pReqMsg),
           sizeof (pReqMsg->head) +
              sizeof (pReqMsg->info.manufacturer_req) +
              sizeof (pTrcReq->wRequest) +
              sizeof (pTrcReq->info.start_appl_tracer));
      return (CCE_ILLEGAL_MSG_PARAMETER);
   }
   if (C_GET_WORD (pTrcReq->info.start_appl_tracer.wLength) !=
          sizeof (pTrcReq->info.start_appl_tracer))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Start appl. tracer request: Invalid length %u of parameter, %zu expected",
           pAppInfo->uApplID,
           (unsigned) C_GET_WORD (pTrcReq->info.start_appl_tracer.wLength),
           sizeof (pTrcReq->info.start_appl_tracer));
      return (CCE_ILLEGAL_MSG_PARAMETER);
   }
   ulTraceMask = C_GET_DWORD (pTrcReq->info.start_appl_tracer.dwTraceMask);
   ulDataBlockLength = C_GET_DWORD
                          (pTrcReq->info.start_appl_tracer.dwDataBlockLength);

   /* determine the other applications this application may receive trace
    * messages for
    */
   bzero (afMayTraceAppl, sizeof (afMayTraceAppl));

   sx_xlock (&g_sxAppData);

   fIsRoot = (suser (curthread) == 0);
   for (i = 0; (size_t) i < ARRAY_COUNT (g_aAppInfo); ++i)
   {
      if (! g_aAppInfo [i].fAppRegistered)
      {
         continue;
      }
      if (fIsRoot ||
          (curthread->td_ucred &&
           (curthread->td_ucred->cr_uid == g_aAppInfo [i].effUserID ||
            groupmember (g_aAppInfo [i].effGroupID, curthread->td_ucred) != 0)))
      {
         afMayTraceAppl [i] = 1;
      }
   }

   /* check if the maximum number of tracers per application is exhausted */
   if (pAppInfo->nNumTracers >= ARRAY_COUNT (pAppInfo->auTracerIDs))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Start appl. tracer request: Max. no. tracers (%zu) exhausted",
           pAppInfo->uApplID, pAppInfo->nNumTracers);
      sx_xunlock (&g_sxAppData);
      return (CRE_OUT_OF_TRACERS_PER_APPL);
   }
   
   /* start the tracer */
   uRes = kcapitrace_add_tracer (pAppInfo->uApplID,
                                 ulTraceMask,
                                 ulDataBlockLength,
                                 1,
                                 afMayTraceAppl,
                                 0,
                                 &uTracerID);

   /* insert the new tracer into the tracer array of the application */
   pAppInfo->auTracerIDs [pAppInfo->nNumTracers] = uTracerID;
   pAppInfo->nNumTracers++;

   /* create a confirmation message for this operation */
   pmbConf = kcapictlr_get_trace_mbuf
                            (sizeof (pConfMsg->head) +
                             sizeof (pConfMsg->info.manufacturer_conf) +
                             sizeof (CAPITraceConf_t));
   if (pmbConf)
   {
      DBG (LOG_TRACE,
           "Appl. id %u: Application tracer started with id %u, currently %zu tracers",
           pAppInfo->uApplID, uTracerID, pAppInfo->nNumTracers);
      
      sx_xunlock (&g_sxAppData);
      
      pConfMsg = mtod (pmbConf, CAPIMsg_t *);
      pConfMsg->head = pReqMsg->head;
      C_PUT_WORD (pConfMsg->head.wCmd, CAPI_CONFIRM (C_MANUFACTURER));
      C_PUT_DWORD (pConfMsg->info.manufacturer_conf.dwManuID,
                   C4B_TRACE_MANU_ID);
      pTrcConf = (CAPITraceConf_t *)
                    ((u_int8_t *) &(pConfMsg->info.manufacturer_conf) +
                     sizeof (pConfMsg->info.manufacturer_conf));
      C_PUT_WORD (pTrcConf->wRequest, C_GET_WORD (pTrcReq->wRequest));
      C_PUT_WORD (pTrcConf->info.start_tracer.wLength,
                  sizeof (pTrcConf->info.start_tracer));
      C_PUT_WORD (pTrcConf->info.start_tracer.wInfo, uRes);
      if (uRes == CAPI_OK)
      {
         C_PUT_DWORD (pTrcConf->info.start_tracer.dwTracerID, uTracerID);
      }
      else
      {
         C_PUT_DWORD (pTrcConf->info.start_tracer.dwTracerID, 0);
      }
      C_PUT_WORD (pConfMsg->head.wLen,
                  sizeof (pConfMsg->head) +
                     sizeof (pConfMsg->info.manufacturer_conf) +
                     sizeof (cWORD) +
                     sizeof (CAPITraceStartTracerConf_t));
      kcapictlr_enqueue_trace_message (pAppInfo->uApplID, pmbConf);

      /* the result of the request is CAPI_OK regardless of the operation
       * result
       */
      uRes = CAPI_OK;
   }
   else
   {
      /* unable to allocate a new mbuf --> out of resources */
      if (uRes == CAPI_OK)
      {
         (void) kcapitrace_remove_tracer (uTracerID);
         pAppInfo->nNumTracers--;
         uRes = CME_OS_RESOURCE_ERROR;
      }
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR,
           "Appl. id %u: Unable to allocate mbuf for Manufacturer-Confirm",
           pAppInfo->uApplID);
   }
   
   return (uRes);
} /* kcapiappl_handle_start_appl_tracer_request */





/**
 * Handle a request message to start a new controller tracer.
 *
 * @pre This function must be called with the application data array and the
 *      application entry locked.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pReqMsg               I: Address of the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */

static unsigned kcapiappl_handle_start_ctlr_tracer_request
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pReqMsg)
{
   struct mbuf           *pmbConf;
   CAPIMsg_t             *pConfMsg;
   const CAPITraceReq_t  *pTrcReq;
   CAPITraceConf_t       *pTrcConf;
   unsigned long          ulTraceMask;
   unsigned long          ulDataBlockLength;
   unsigned               uCtlrNum;
   int                    afMayTraceAppl [CAPI_MAX_APPLICATIONS];
   unsigned               uTracerID;
   unsigned               uRes;

   /* determine the tracer parameters */
   pTrcReq = (const CAPITraceReq_t *)
                ((const u_int8_t *) &(pReqMsg->info.manufacturer_req) +
                 sizeof (pReqMsg->info.manufacturer_req));
   if (CAPI_GET_LEN (pReqMsg) <
          sizeof (pReqMsg->head) +
          sizeof (pReqMsg->info.manufacturer_req) +
          sizeof (pTrcReq->wRequest) +
          sizeof (pTrcReq->info.start_ctlr_tracer))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Start ctlr. tracer request: Invalid message length %u, %zu expected",
           pAppInfo->uApplID,
           (unsigned) CAPI_GET_LEN (pReqMsg),
           sizeof (pReqMsg->head) +
              sizeof (pReqMsg->info.manufacturer_req) +
              sizeof (pTrcReq->wRequest) +
              sizeof (pTrcReq->info.start_ctlr_tracer));
      return (CCE_ILLEGAL_MSG_PARAMETER);
   }
   if (C_GET_WORD (pTrcReq->info.start_ctlr_tracer.wLength) !=
          sizeof (pTrcReq->info.start_ctlr_tracer))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Start ctlr. tracer request: Invalid length %u of parameter, %zu expected",
           pAppInfo->uApplID,
           (unsigned) C_GET_WORD (pTrcReq->info.start_ctlr_tracer.wLength),
           sizeof (pTrcReq->info.start_ctlr_tracer));
      return (CCE_ILLEGAL_MSG_PARAMETER);
   }
   ulTraceMask = C_GET_DWORD (pTrcReq->info.start_ctlr_tracer.dwTraceMask);
   ulDataBlockLength = C_GET_DWORD
                          (pTrcReq->info.start_ctlr_tracer.dwDataBlockLength);
   uCtlrNum = C_GET_DWORD (pTrcReq->info.start_ctlr_tracer.dwCtlrNum);

   sx_xlock (&g_sxAppData);
   
   /* check if the maximum number of tracers per application is exhausted */
   if (pAppInfo->nNumTracers >= ARRAY_COUNT (pAppInfo->auTracerIDs))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Start ctlr. tracer request: Max. no. tracers (%zu) exhausted",
           pAppInfo->uApplID, pAppInfo->nNumTracers);
      sx_xunlock (&g_sxAppData);
      return (CRE_OUT_OF_TRACERS_PER_APPL);
   }
   
   /* start the tracer */
   bzero (afMayTraceAppl, sizeof (afMayTraceAppl));
   uRes = kcapitrace_add_tracer (pAppInfo->uApplID,
                                 ulTraceMask,
                                 ulDataBlockLength,
                                 0,
                                 afMayTraceAppl,
                                 uCtlrNum,
                                 &uTracerID);

   /* insert the new tracer into the tracer array of the application */
   pAppInfo->auTracerIDs [pAppInfo->nNumTracers] = uTracerID;
   pAppInfo->nNumTracers++;

   /* create a confirmation message for this operation */
   pmbConf = kcapictlr_get_trace_mbuf
                            (sizeof (pConfMsg->head) +
                             sizeof (pConfMsg->info.manufacturer_conf) +
                             sizeof (CAPITraceConf_t));
   if (pmbConf)
   {
      DBG (LOG_TRACE,
           "Appl. id %u: Controller tracer started with id %u, currently %zu tracers",
           pAppInfo->uApplID, uTracerID, pAppInfo->nNumTracers);

      sx_xunlock (&g_sxAppData);
      
      pConfMsg = mtod (pmbConf, CAPIMsg_t *);
      pConfMsg->head = pReqMsg->head;
      C_PUT_WORD (pConfMsg->head.wCmd, CAPI_CONFIRM (C_MANUFACTURER));
      C_PUT_DWORD (pConfMsg->info.manufacturer_conf.dwManuID,
                   C4B_TRACE_MANU_ID);
      pTrcConf = (CAPITraceConf_t *)
                    ((u_int8_t *) &(pConfMsg->info.manufacturer_conf) +
                     sizeof (pConfMsg->info.manufacturer_conf));
      C_PUT_WORD (pTrcConf->wRequest, C_GET_WORD (pTrcReq->wRequest));
      C_PUT_WORD (pTrcConf->info.start_tracer.wLength,
                  sizeof (pTrcConf->info.start_tracer));
      C_PUT_WORD (pTrcConf->info.start_tracer.wInfo, uRes);
      if (uRes == CAPI_OK)
      {
         C_PUT_DWORD (pTrcConf->info.start_tracer.dwTracerID, uTracerID);
      }
      else
      {
         C_PUT_DWORD (pTrcConf->info.start_tracer.dwTracerID, 0);
      }
      C_PUT_WORD (pConfMsg->head.wLen,
                  sizeof (pConfMsg->head) +
                     sizeof (pConfMsg->info.manufacturer_conf) +
                     sizeof (cWORD) +
                     sizeof (CAPITraceStartTracerConf_t));
      kcapictlr_enqueue_trace_message (pAppInfo->uApplID, pmbConf);

      /* the result of the request is CAPI_OK regardless of the operation
       * result
       */
      uRes = CAPI_OK;
   }
   else
   {
      /* unable to allocate a new mbuf --> out of resources */
      if (uRes == CAPI_OK)
      {
         (void) kcapitrace_remove_tracer (uTracerID);
         pAppInfo->nNumTracers--;
         uRes = CME_OS_RESOURCE_ERROR;
      }
      sx_xunlock (&g_sxAppData);
      DBG (LOG_ERROR,
           "Appl. id %u: Unable to allocate mbuf for Manufacturer-Confirm",
           pAppInfo->uApplID);
   }
   
   return (uRes);
} /* kcapiappl_handle_start_ctlr_tracer_request */





/**
 * Handle a request message to stop a tracer.
 *
 * @pre This function must be called with the application data array and the
 *      application entry locked.
 *
 * @param pAppInfo              I: Info. structure about the application sending
 *                                 the request.
 * @param pReqMsg               I: Address of the CAPI message.
 *
 * @return CAPI_OK              The request was processed successfully.
 * @return Else                 An error occurred processing the request.
 */

static unsigned kcapiappl_handle_stop_tracer_request
   (CapiManAppInfo_t *pAppInfo,
    const CAPIMsg_t  *pReqMsg)
{
   struct mbuf          *pmbConf;
   CAPIMsg_t            *pConfMsg;
   const CAPITraceReq_t *pTrcReq;
   CAPITraceConf_t      *pTrcConf;
   unsigned              uTracerID;
   int                   i;
   unsigned              uRes;

   /* determine the tracer to stop */
   pTrcReq = (const CAPITraceReq_t *)
                ((const u_int8_t *) &(pReqMsg->info.manufacturer_req) +
                 sizeof (pReqMsg->info.manufacturer_req));
   if (CAPI_GET_LEN (pReqMsg) <
          sizeof (pReqMsg->head) +
          sizeof (pReqMsg->info.manufacturer_req) +
          sizeof (pTrcReq->wRequest) +
          sizeof (pTrcReq->info.stop_tracer))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Stop tracer request: Invalid message length %u, %zu expected",
           pAppInfo->uApplID,
           (unsigned) CAPI_GET_LEN (pReqMsg),
           sizeof (pReqMsg->head) +
              sizeof (pReqMsg->info.manufacturer_req) +
              sizeof (pTrcReq->wRequest) +
              sizeof (pTrcReq->info.stop_tracer));
      return (CCE_ILLEGAL_MSG_PARAMETER);
   }
   if (pTrcReq->info.stop_tracer.wLength != sizeof (pTrcReq->info.stop_tracer))
   {
      DBG (LOG_ERROR,
           "Appl. id %u: Stop tracer request: Invalid length %u of parameter, %zu expected",
           pAppInfo->uApplID,
           (unsigned) C_GET_WORD (pTrcReq->info.stop_tracer.wLength),
           sizeof (pTrcReq->info.stop_tracer));
      return (CCE_ILLEGAL_MSG_PARAMETER);
   }
   uTracerID = (unsigned) C_GET_DWORD (pTrcReq->info.stop_tracer.dwTracerID);

   sx_xlock (&g_sxAppData);
   
   /* stop the tracer */
   uRes = kcapitrace_remove_tracer (uTracerID);
   if (uRes == CAPI_OK)
   {
      /* remove the tracer from the tracer list of the application */
      for (i = 0; (size_t) i < pAppInfo->nNumTracers; i++)
      {
         if (pAppInfo->auTracerIDs [i] == uTracerID)
         {
            break;
         }
      }
      if ((size_t) i < pAppInfo->nNumTracers)
      {
         pAppInfo->nNumTracers--;
         if ((size_t) i < pAppInfo->nNumTracers)
         {
            pAppInfo->auTracerIDs [i] =
               pAppInfo->auTracerIDs [pAppInfo->nNumTracers];
         }
      }

      DBG (LOG_TRACE,
           "Appl. id %u: Tracer %u stopped, currently %zu tracers",
           pAppInfo->uApplID, uTracerID, pAppInfo->nNumTracers);
   }
   else
   {
      DBG (LOG_ERROR, "Appl. id %u: Error stopping tracer %u: 0x%04X",
           pAppInfo->uApplID, uTracerID, uRes);
   }
   
   sx_xunlock (&g_sxAppData);
   
   /* create a confirmation message for this operation */
   pmbConf = kcapictlr_get_trace_mbuf
                            (sizeof (pConfMsg->head) +
                             sizeof (pConfMsg->info.manufacturer_conf) +
                             sizeof (CAPITraceConf_t));
   if (pmbConf)
   {
      pConfMsg = mtod (pmbConf, CAPIMsg_t *);
      pConfMsg->head = pReqMsg->head;
      C_PUT_WORD (pConfMsg->head.wCmd, CAPI_CONFIRM (C_MANUFACTURER));
      C_PUT_DWORD (pConfMsg->info.manufacturer_conf.dwManuID,
                   C4B_TRACE_MANU_ID);
      pTrcConf = (CAPITraceConf_t *)
                    ((u_int8_t *) &(pConfMsg->info.manufacturer_conf) +
                     sizeof (pConfMsg->info.manufacturer_conf));
      C_PUT_WORD (pTrcConf->wRequest, C_GET_WORD (pTrcReq->wRequest));
      C_PUT_WORD (pTrcConf->info.stop_tracer.wLength,
                  sizeof (pTrcConf->info.stop_tracer));
      C_PUT_WORD (pTrcConf->info.stop_tracer.wInfo, uRes);
      C_PUT_DWORD (pTrcConf->info.stop_tracer.dwTracerID,
                   (u_int32_t) uTracerID);
      C_PUT_WORD (pConfMsg->head.wLen,
                  sizeof (pConfMsg->head) +
                     sizeof (pConfMsg->info.manufacturer_conf) +
                     sizeof (cWORD) +
                     sizeof (CAPITraceStopTracerConf_t));
      kcapictlr_enqueue_trace_message (pAppInfo->uApplID, pmbConf);

      /* the result of the request is CAPI_OK regardless of the operation
       * result
       */
      uRes = CAPI_OK;
   }
   else
   {
      /* unable to allocate a new mbuf --> out of resources */
      if (uRes == CAPI_OK)
      {
         uRes = CME_OS_RESOURCE_ERROR;
      }
      DBG (LOG_ERROR,
           "Appl. id %u: Unable to allocate mbuf for Manufacturer-Confirm",
           pAppInfo->uApplID);
   }
   
   return (uRes);
} /* kcapiappl_handle_stop_tracer_request */





/**
 * Insert a new PLCI into the array of current physical connections.
 */

static CapiManPlciData_t *kcapiappl_insert_new_plci
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci)
{
   CapiManPlciData_t *paTmp;
   CapiManPlciData_t *pPlciData;
   int                i;
   
   dwPlci &= (CAPI_CIDMASK_PLCI | CAPI_CIDMASK_CTLR);
   
   /* if there is no free entry in the PLCI array, it must be enlarged */
   if (pAppInfo->nCurrNumPlciData >= pAppInfo->nMaxNumPlciData)
   {
      MALLOC (paTmp, CapiManPlciData_t *,
              (pAppInfo->nMaxNumPlciData + 2) * sizeof (paTmp [0]),
              M_KCAPIMGRBUF, M_WAITOK);
      if (! paTmp)
      {
         DBG (LOG_ERROR,
              "Appl. id %u: Out of memory for %zu PLCI data entries",
              pAppInfo->uApplID, pAppInfo->nMaxNumPlciData + 2);
         return (NULL);
      }
      bcopy (pAppInfo->paPlciData, paTmp,
             pAppInfo->nMaxNumPlciData * sizeof (paTmp [0]));
      FREE (pAppInfo->paPlciData, M_KCAPIMGRBUF);
      pAppInfo->paPlciData = paTmp;
      pAppInfo->nMaxNumPlciData += 2;
   }
   /* now there is enough room to insert at least one new PLCI */
   
   /* find the position to insert the new PLCI at */
   paTmp = pAppInfo->paPlciData;
   i = kcapiappl_plci_binsearch (paTmp,
                                 pAppInfo->nCurrNumPlciData,
                                 dwPlci);
   if ((size_t) i >= pAppInfo->nCurrNumPlciData)
   {
      /* append to the end of the array */
      i = pAppInfo->nCurrNumPlciData;
   }
   else
   {
      /* maybe the PLCI is already registered? */
      if (paTmp [i].dwPlci == dwPlci)
      {
         /* PLCI already registered!!! --> ready */
         DBG (LOG_ERROR,
              "Appl. id %u: Attempt to re-register PLCI 0x%04X",
              pAppInfo->uApplID, (unsigned) dwPlci);
         return (&(paTmp [i]));
      }
      else
      {
         /* real insertion, move upper elements one entry further */
         bcopy (&(paTmp [i]), &(paTmp [i + 1]),
                (pAppInfo->nCurrNumPlciData - i) * sizeof (paTmp [0]));
      }
   }
   pAppInfo->nCurrNumPlciData++;
   pPlciData = &(paTmp [i]);
   
   /* now finally initialize the new entry */
   bzero (pPlciData, sizeof (*pPlciData));
   pPlciData->dwPlci = dwPlci;
   
   /* return the address of the new PLCI data structure */
   return (pPlciData);
} /* kcapiappl_insert_new_plci */





/**
 * Find PLCI data for an application.
 */

static CapiManPlciData_t *kcapiappl_find_plci
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci)
{
   CapiManPlciData_t *pPlciData;
   int                i;
   
   dwPlci &= (CAPI_CIDMASK_PLCI | CAPI_CIDMASK_CTLR);
   
   i = kcapiappl_plci_binsearch (pAppInfo->paPlciData,
                                 pAppInfo->nCurrNumPlciData,
                                 dwPlci);
   if ((size_t) i >= pAppInfo->nCurrNumPlciData)
   {
      return (NULL);
   }
   pPlciData = &(pAppInfo->paPlciData [i]);
   if (pPlciData->dwPlci != dwPlci)
   {
      return (NULL);
   }
   return (pPlciData);
} /* kcapiappl_find_plci */





/**
 * Remove a PLCI data element from the array of current phys. connections.
 */

static void kcapiappl_remove_plci
   (CapiManAppInfo_t *pAppInfo,
    u_int32_t         dwPlci)
{
   CapiManPlciData_t *pPlciData;
   int                i;
   
   dwPlci &= (CAPI_CIDMASK_PLCI | CAPI_CIDMASK_CTLR);
   
   i = kcapiappl_plci_binsearch (pAppInfo->paPlciData,
                                 pAppInfo->nCurrNumPlciData,
                                 dwPlci);
   if ((size_t) i >= pAppInfo->nCurrNumPlciData)
   {
      return;
   }
   pPlciData = &(pAppInfo->paPlciData [i]);
   if (pPlciData->dwPlci != dwPlci)
   {
      return;
   }
   /* pPlci now points to the element to remove */
   
   /* remove any B3-connections managed by this PLCI */
   while (pPlciData->pFirstNcciData != NULL)
   {
      kcapiappl_remove_ncci
         (pAppInfo, pPlciData, pPlciData->pFirstNcciData->dwNcci);
   }
   
   /* check for removing the last array element */
   if (i < pAppInfo->nCurrNumPlciData - 1)
   {
      /* remove some element before the last array entry */
      bcopy (pPlciData + 1, pPlciData,
             (pAppInfo->nCurrNumPlciData - i) * sizeof (*pPlciData));
   }
   pAppInfo->nCurrNumPlciData--;
   
} /* kcapiappl_remove_plci */





/**
 * Perform binary search on an array of PLCIs.
 *
 * @param paPlciData            I: The array of PLCI data elements to search
 *                                 within.
 * @param nLenPlciData          I: The length of the array (in no. elements).
 * @param dwPlci                I: The PLCI to search for.
 *
 * @return The array index where the PLCI is found or the index of the position
 *         to insert the (new) PLCI at.
 */

static int kcapiappl_plci_binsearch
   (CapiManPlciData_t *paPlciData,
    size_t             nLenPlciData,
    u_int32_t          dwPlci)
{
   CapiManPlciData_t *pTmp;
   int                iTop;
   int                iBottom;
   int                iMiddle;
   int                iInsert;
   
   /* check for an empty array */
   if (nLenPlciData == 0 || paPlciData == NULL)
   {
      return (nLenPlciData);
   }
   
   /* so there is at least one element in the array, check if PLCI is less than
    * the first element
    */
   if (dwPlci <= paPlciData [0].dwPlci)
   {
      return (0);
   }
   
   /* so there is at least one element in the array and the element is not at
    * the bottom of the array (i.e. the following algorithm cannot end up with
    * negative indices)
    */
   iTop = (int) (nLenPlciData - 1);
   iBottom = 0;
   iMiddle = 0;
   iInsert = 0;
   while (iBottom <= iTop)
   {
      iMiddle = (iBottom + iTop) / 2;
      pTmp = &(paPlciData [iMiddle]);
      
      if (pTmp->dwPlci == dwPlci)
      {
         return (iMiddle);
      }
      
      if (dwPlci < pTmp->dwPlci)
      {
         iTop = iMiddle - 1;
         
         /* If the assignment to iTop leads to loop termination, the insertion
          * point would be the current bottom. The middle index must be equal to
          * the bottom, but the search PLCI is smaller. The index just before
          * the bottom is even smaller and the search PLCI is higher. So the
          * new PLCI would have to be inserted just before iBottom.
          */
         iInsert = iBottom;
      }
      else
      {
         iBottom = iMiddle + 1;
         
         /* If the assignment to iBottom leads to loop termination, the
          * insertion point would be behind the current top. The middle must be
          * equal to the top, the the search PLCI is higher. The index just
          * above the top is even higher and the search PLCI is smaller. So the
          * new PLCI would have to be inserted just above iTop.
          */
         iInsert = iTop + 1;
      }
   }
   
   /* PLCI not found, iInsert points to the location for possible insertion */
   return (iInsert);
} /* kcapiappl_plci_binsearch */





/**
 * Insert a new NCCI into the list of logical connections for a PLCI.
 */

static CapiManNcciData_t *kcapiappl_insert_new_ncci
   (CapiManAppInfo_t  *pAppInfo,
    CapiManPlciData_t *pPlciData,
    u_int32_t          dwNcci)
{
   CapiManNcciData_t *pNcciData;
   
   /* get a free NCCI entry */
   pNcciData = pAppInfo->pFirstFreeNcci;
   if (! pNcciData)
   {
      return (NULL);
   }
   pAppInfo->pFirstFreeNcci = pNcciData->pNext;
   
   /* insert the new data structure at the front of the NCCI list of the PLCI */
   pNcciData->pNext = pPlciData->pFirstNcciData;
   pPlciData->pFirstNcciData = pNcciData;
   
   /* initialize the NCCI data entry */
   pNcciData->dwNcci = dwNcci;
   pNcciData->nNumIncDataBlocks = 0;
   pNcciData->nNumOutDataBlocks = 0;
   pNcciData->fGotDisconnectB3Ind = 0;
   
   return (pNcciData);
} /* kcapiappl_insert_new_ncci */





/**
 * Find NCCI data for an application.
 */

static CapiManNcciData_t *kcapiappl_find_ncci
   (CapiManAppInfo_t  *pAppInfo,
    CapiManPlciData_t *pPlciData,
    u_int32_t          dwNcci)
{
   CapiManNcciData_t *pNcciData;
   
   for (pNcciData = pPlciData->pFirstNcciData;
        pNcciData != NULL;
        pNcciData = pNcciData->pNext)
   {
      if (pNcciData->dwNcci == dwNcci)
      {
         return (pNcciData);
      }
   }
   
   /* suppress warnings */
   (void) pAppInfo;
   
   return (NULL);
} /* kcapiappl_find_ncci */





/**
 * Remove a NCCI data element from the list of log. conn.s for a PLCI.
 */

static void kcapiappl_remove_ncci
   (CapiManAppInfo_t  *pAppInfo,
    CapiManPlciData_t *pPlciData,
    u_int32_t          dwNcci)
{
   CapiManNcciData_t *p;
   CapiManNcciData_t *q;
   
   for (p = pPlciData->pFirstNcciData, q = NULL;
        p != NULL;
        q = p, p = p->pNext)
   {
      if (p->dwNcci == dwNcci)
      {
         break;
      }
   }
   if (p == NULL)
   {
      /* NCCI not found */
      return;
   }
   
   if (q == NULL)
   {
      /* remove first element of NCCI list */
      pPlciData->pFirstNcciData = p->pNext;
   }
   else
   {
      /* remove some element behind the first, q is set to its predecessor */
      q->pNext = p->pNext;
   }
   
   /* re-insert the NCCI data element into the free list of the application */
   bzero (p, sizeof (*p));
   p->pNext = pAppInfo->pFirstFreeNcci;
   pAppInfo->pFirstFreeNcci = p;
   
} /* kcapiappl_remove_ncci */
