/**
 * @file capilib.c
 *
 * capilib - The code for the shared library for user space CAPI applications.
 *
 * Copyright: 2000-2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: capilib.c,v 1.1.2.1 2006/02/28 21:35:32 thomas Exp $
 * $Project     CAPI for BSD $
 * $Target      libcapi20 - The (shared) library for user space CAPI support $
 * @date        12.08.2002
 * @author      "Thomas Wintergerst" <twinterg@gmx.net>
 * <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 <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <poll.h>
#include <errno.h>
#include <capi20.h>
#include <capi_bsd.h>
#include <capi_bsdtrc.h>

/* import includes */

#define __LIBCAPI__

/* local includes */





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





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





/* --- some helper definitions --- */

/**
 * The type of one data buffer for holding data blocks until acknowledged by
 * the application.
 */
typedef struct DataBuffer
{
   /* fields for chaining in the free and used lists */
   struct DataBuffer *pNext;
   struct DataBuffer *pPrev;
   
   /* the real content */
   unsigned  uDataHandle;       /**<
                                 * The data handle of the corresponding
                                 * Data-B3-Indication.
                                 */
   CAPIMsg_t capiMsg;           /**< Buffer for the CAPI message itself. */
   
   /* memory for a data block of length AppInfo_t::regData.uMaxBDataLen follows
    */
} DataBuffer_t;

/** Information needed for every open B3-connection. */
typedef struct
{
   int       fEntryUsed;        /**< Flag for validity of the rest. */
   u_int32_t dwNcci;            /**< NCCI for the B3-connection. */
   
   /* queue of unacknowledged incoming data blocks */
   DataBuffer_t *pHead;
   DataBuffer_t *pTail;
   size_t        nQueueLen;
} NcciData_t;



/* --- the information structure for a registered application --- */

typedef struct
{
   int                     fEntryUsed;
                                /**< Flag for validity of the rest. */
   int                     fdCapiDev;
                                /**<
                                 * The handle to CAPI_DEVICE_NAME for this
                                 * CAPI application.
                                 */
   unsigned                uApplID;
                                /**<
                                 * The resulting appl. id from
                                 * C4BIOC_CAPI_REGISTER.
                                 */
   capi_register_params_t  regData;
                                /**< The registration params for further use. */
   unsigned char          *pMsgBuf;
                                /**<
                                 * The address of the raw allocated memory for
                                 * the application.
                                 */
   size_t                  nMaxTotalMsgSize;
                                /**<
                                 * Max. length of a CAPI message with data block
                                 * (= sizeof (CAPIMsg_t) + regData.uMaxBDataLen).
                                 */
   size_t                  nTotalBufLen;
                                /**<
                                 * Total size of one of the buffers at pMsgBuf
                                 * (= sizeof (DataBuffer_t) +
                                 * regData.uMaxBDataLen).
                                 */
   size_t                  nTotalNumBuffers;
                                /**<
                                 * Total no. buffers at pMsgBuf (=
                                 * (regData.uMaxLogicalConnections *
                                 * regData.uMaxBDataBlocks) + 1).
                                 */
   DataBuffer_t           *pFreeList;
                                /**<
                                 * Chained list of free send buffers for the
                                 * application.
                                 */
   NcciData_t             *paNcciData;
                                /**<
                                 * Array of information per connection; length
                                 * is regData.uMaxLogicalConnections.
                                 */
} AppInfo_t;



/* --- the array of registered applications --- */

/* We handle each registered CAPI application (for our process) by an entry of
 * type AppInfo_t. For quick access the uApplID is used to index an array of
 * application structures (programs may register multiple applications). The
 * array is enlarged if needed. g_nMaxApplications is just a counter of
 * registered applications. If its value becomes zero after a capi20_release,
 * the complete app-info array is released.
 */

static AppInfo_t **g_papAppInfo = NULL;
static size_t      g_nAppInfoLen = 0;
static size_t      g_nNumApplications = 0;





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





/** Release an entry of the app-info array. */
static void FreeAppInfoEntry
   (unsigned uApplID);

/** Allocate a new B3-connection for the application. */
static NcciData_t *AllocB3Connection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci);

/* Find an allocated B3-connection. */
static NcciData_t *FindB3Connection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci);

/* Release a previously allocated B3-connection and all data buffers. */
static void ReleaseB3Connection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci);

/* Release all previously allocated B3-connections for a physical connection. */
static void ReleasePhysConnection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwPlci);

/* Store a new incoming data buffer for a NCCI. */
static int AllocDataBuffer
   (AppInfo_t    *pAppInfo,
    u_int32_t     dwNcci,
    DataBuffer_t *pBuf);

/* Release a stored incoming data buffer for a NCCI. */
static void ReleaseDataBuffer
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci,
    unsigned   uDataHandle);
   
   
   
   
   
/* === definition of public 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 capi20_register
   (unsigned  uMaxLogicalConnections,
    unsigned  uMaxBDataBlocks,
    unsigned  uMaxBDataLen,
    unsigned *puApplID)
{
   int                     fd;
   capi_register_params_t  registerParams;
   int                     iRes;
   unsigned                uRes;
   AppInfo_t              *pAppInfoEntry;
   int                     i;
   DataBuffer_t           *pBuf;
   
   /* test for available CAPI device and open it */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the registration */
   memset (&registerParams, 0, sizeof (registerParams));
   registerParams.uMaxLogicalConnections = uMaxLogicalConnections;
   registerParams.uMaxBDataBlocks        = uMaxBDataBlocks;
   registerParams.uMaxBDataLen           = uMaxBDataLen;
   iRes = ioctl (fd, C4BIOC_CAPI_REGISTER, (caddr_t) &registerParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   
   /* test for duplicated application id, a can't-happen-situation */
   if (registerParams.uApplID < g_nAppInfoLen &&
       g_papAppInfo [registerParams.uApplID] &&
       g_papAppInfo [registerParams.uApplID]->fEntryUsed)
   {
      /* release the current app-info structure, we will reuse this array index
       * later
       */
      FreeAppInfoEntry (registerParams.uApplID);
   }
   
   /* registration at the CAPI manager was successful, now we need some memory
    */

   /* if needed, enlarge the app-info array */
   if (registerParams.uApplID >= g_nAppInfoLen)
   {
      AppInfo_t **papTmp;
      
      papTmp = (AppInfo_t **)
                  realloc (g_papAppInfo,
                           (registerParams.uApplID + 1) *
                              sizeof (g_papAppInfo [0]));
      if (! papTmp)
      {
         /* out of memory, undo CAPI registration */
         (void) close (fd);
         return (CRE_OS_RESOURCE_ERROR);
      }
      g_papAppInfo = papTmp;
      for (i = (int) g_nAppInfoLen; (size_t) i <= registerParams.uApplID; i++)
      {
         g_papAppInfo [i] = NULL;
      }
      g_nAppInfoLen = registerParams.uApplID + 1;
   }
   /* registerParams.uApplID is now a valid index into the app-info array, but
    * there is normally no valid pointer at this position
    */
   
   /* allocate memory for the app-info structure */
   pAppInfoEntry = (AppInfo_t *) calloc (1, sizeof (*pAppInfoEntry));
   if (! pAppInfoEntry)
   {
      /* out of memory, undo CAPI registration */
      (void) close (fd);
      return (CRE_OS_RESOURCE_ERROR);
   }
   pAppInfoEntry->fEntryUsed = 1;
   pAppInfoEntry->fdCapiDev = fd;
   pAppInfoEntry->uApplID = registerParams.uApplID;
   pAppInfoEntry->regData = registerParams;
   pAppInfoEntry->nMaxTotalMsgSize = sizeof (CAPIMsg_t) +
                                     registerParams.uMaxBDataLen;
   pAppInfoEntry->nTotalBufLen = sizeof (DataBuffer_t) +
                                 registerParams.uMaxBDataLen;
   pAppInfoEntry->nTotalNumBuffers = (registerParams.uMaxLogicalConnections *
                                      registerParams.uMaxBDataBlocks) + 1;
   
   /* allocate memory for the incoming message and data block buffer */
   pAppInfoEntry->pMsgBuf =
      (unsigned char *) calloc (pAppInfoEntry->nTotalNumBuffers,
                                pAppInfoEntry->nTotalBufLen);
   if (! pAppInfoEntry->pMsgBuf)
   {
      /* out of memory, undo CAPI registration */
      (void) close (fd);
      free (pAppInfoEntry);
      return (CRE_OS_RESOURCE_ERROR);
   }
   /* create the free list */
   for (i = 0; (size_t) i < pAppInfoEntry->nTotalNumBuffers; i++)
   {
      /* addressing the current buffer indexed by i */
      pBuf = (DataBuffer_t *) &(pAppInfoEntry->pMsgBuf
                                   [i * pAppInfoEntry->nTotalBufLen]);
      
      /* chain this buffer with its predecessor, if exists */
      if (i > 0)
      {
         pBuf->pPrev = (DataBuffer_t *)
                          &(pAppInfoEntry->pMsgBuf
                               [(i - 1) * pAppInfoEntry->nTotalBufLen]);
      }
      else
      {
         pBuf->pPrev = NULL;
      }
      
      /* chain this buffer with its successor, if exists */
      if ((size_t) i < pAppInfoEntry->nTotalNumBuffers - 1)
      {
         pBuf->pNext = (DataBuffer_t *)
                          &(pAppInfoEntry->pMsgBuf
                               [(i + 1) * pAppInfoEntry->nTotalBufLen]);
      }
      else
      {
         pBuf->pNext = NULL;
      }
   }
   pAppInfoEntry->pFreeList = (DataBuffer_t *) (pAppInfoEntry->pMsgBuf);
   
   /* allocate memory to handle the requested B3-connections */
   if (registerParams.uMaxLogicalConnections > 0)
   {
      pAppInfoEntry->paNcciData =
         (NcciData_t *) calloc (registerParams.uMaxLogicalConnections,
                                sizeof (pAppInfoEntry->paNcciData [0]));
      if (! pAppInfoEntry->paNcciData)
      {
         /* out of memory, undo CAPI registration */
         (void) close (fd);
         free (pAppInfoEntry->pMsgBuf);
         free (pAppInfoEntry);
         return (CRE_OS_RESOURCE_ERROR);
      }
   }
   
   /* now we can assign the app-info structure into the array */
   g_papAppInfo [pAppInfoEntry->uApplID] = pAppInfoEntry;
   g_nNumApplications++;
   
   /* everything is correctly initialized, return the application id */
   if (puApplID)
   {
      *puApplID = pAppInfoEntry->uApplID;
   }
   return (CAPI_OK);
} /* capi20_register */





/**
 * Releasing a CAPI application.
 *
 * @note CAPI is by definition not reentrant. So we do not have to serialize
 *       the access to the application array, this is the responsibility of the
 *       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 capi20_release
   (unsigned uApplID)
{
   unsigned uRes;
   
   /* test for valid application id */
   if (uApplID >= g_nAppInfoLen || ! g_papAppInfo)
   {
      /* application not in array */
      return (CME_INVALID_APPLICATION_ID);
   }
   
   /* test for registered application */
   if (! g_papAppInfo [uApplID] || ! g_papAppInfo [uApplID]->fEntryUsed)
   {
      return (CME_INVALID_APPLICATION_ID);
   }
   
   /* application found, do the CAPI release */
   if (close (g_papAppInfo [uApplID]->fdCapiDev) == 0)
   {
      uRes = CAPI_OK;
   }
   else
   {
      /* error occurred, CAPI error value is in errno */
      uRes = (unsigned) errno;
   }
   
   /* finally release the memory used by the application */
   FreeAppInfoEntry (uApplID);
   
   /* if there are no registered applications any more, the app-info array
    * itself can be released
    */
   if (g_nNumApplications == 0)
   {
      g_nAppInfoLen = 0;
      free (g_papAppInfo);
      g_papAppInfo = NULL;
   }
   
   return (uRes);
} /* capi20_release */





/**
 * Send a CAPI message.
 *
 * @param uApplID               I: Application id from registration.
 * @param pMsg                  I: The CAPI message to send.
 *
 * @retval CAPI_OK              The message was accepted by CAPI.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_put_message
   (unsigned   uApplID,
    CAPIMsg_t *pMsg)
{
   AppInfo_t    *pAppInfo;
   struct iovec  ioVec [2];
   int           iNumVecUsed;
   unsigned      uCmd;
   unsigned      uRes;
   
   /* test for valid application id */
   if (uApplID >= g_nAppInfoLen || ! g_papAppInfo)
   {
      /* application not in array */
      return (CME_INVALID_APPLICATION_ID);
   }
   
   /* test for registered application */
   if (! g_papAppInfo [uApplID] || ! g_papAppInfo [uApplID]->fEntryUsed)
   {
      return (CME_INVALID_APPLICATION_ID);
   }

   /* simpler addressing of the app-info entry */
   pAppInfo = g_papAppInfo [uApplID];
   
   /* application found, now perform the CAPI_PUT_MESSAGE operation
    * if the message is a Data-B3-Request, we must use two io vectors, else one
    */
   uCmd = CAPI_GET_CMD (pMsg);
   if (uCmd == CAPI_REQUEST (C_DATA_B3))
   {
      ioVec [0].iov_base = (char *) pMsg;
      ioVec [0].iov_len = (size_t) CAPI_GET_LEN (pMsg);
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
      if (ioVec [0].iov_len <
             sizeof (pMsg->head) + sizeof (pMsg->info.data_b3_64_req))
      {
         return (CCE_ILLEGAL_MSG_PARAMETER);
      }
      ioVec [1].iov_base =
         (char *) C_GET_QWORD (pMsg->info.data_b3_64_req.qwData64);
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
      if (ioVec [0].iov_len <
             sizeof (pMsg->head) + sizeof (pMsg->info.data_b3_32_req))
      {
         return (CCE_ILLEGAL_MSG_PARAMETER);
      }
      ioVec [1].iov_base =
         (char *) C_GET_DWORD (pMsg->info.data_b3_32_req.dwData);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
      ioVec [1].iov_len = (size_t) C_GET_WORD (pMsg->info.data_b3_req.wLen);
      if (ioVec [1].iov_len == 0 || ioVec [1].iov_base == NULL)
      {
         /* Data-B3-Request without a data block, "correct" the CAPI message to
          * not contain any hint of a data block
          */
         iNumVecUsed = 1;
         C_PUT_DWORD (pMsg->info.data_b3_req.dwData, 0);
         C_PUT_WORD (pMsg->info.data_b3_req.wLen, 0);
         if (ioVec [0].iov_len >=
                sizeof (pMsg->head) + sizeof (pMsg->info.data_b3_64_req))
         {
            C_PUT_QWORD (pMsg->info.data_b3_req.qwData64, 0);
         }
      }
      else
      {
         iNumVecUsed = 2;
      }
   }
   else
   {
      ioVec [0].iov_base = (char *) pMsg;
      ioVec [0].iov_len = (size_t) CAPI_GET_LEN (pMsg);
      iNumVecUsed = 1;
   }
   if (writev (pAppInfo->fdCapiDev, ioVec, iNumVecUsed) >= 0)
   {
      uRes = CAPI_OK;
   }
   else
   {
      uRes = (unsigned) errno;
   }

   /* if the message was a Data-B3-Response, we must release the corresponding
    * data block in our used list for the NCCI, regardless of success of the
    * put message operation
    */
   if (uCmd == CAPI_RESPONSE (C_DATA_B3))
   {
      ReleaseDataBuffer (pAppInfo, CAPI_GET_CID (pMsg),
                         C_GET_WORD (pMsg->info.data_b3_resp.wHandle));
   }

   return (uRes);
} /* capi20_put_message */





/**
 * Receive a CAPI message.
 *
 * @param uApplID               I: Application id from registration.
 * @param ppMsg                 O: Address of the CAPI message if the result is
 *                                 CAPI_OK.
 *
 * @retval CAPI_OK              A message was available and its address is
 *                              successfully returned in ppMsg. Note that this
 *                              message buffer will only be valid until the next
 *                              capi20_get_message() call.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_get_message
   (unsigned    uApplID,
    CAPIMsg_t **ppMsg)
{
   AppInfo_t      *pAppInfo;
   DataBuffer_t   *pBuf;
   CAPITraceMsg_t *pTrcMsg;
   unsigned        uCmd;
   u_int32_t       dwCid;
   int             iRes;
   
   /* test for valid application id */
   if (uApplID >= g_nAppInfoLen || ! g_papAppInfo)
   {
      /* application not in array */
      return (CME_INVALID_APPLICATION_ID);
   }
   
   /* test for registered application */
   if (! g_papAppInfo [uApplID] || ! g_papAppInfo [uApplID]->fEntryUsed)
   {
      return (CME_INVALID_APPLICATION_ID);
   }
   
   /* application found */
   pAppInfo = g_papAppInfo [uApplID];

   /* to read a message we need a data buffer */
   if (! pAppInfo->pFreeList)
   {
      return (CME_BUSY);
   }
   pBuf = pAppInfo->pFreeList;

   /* try to read a message with possible data block */
   iRes = read (pAppInfo->fdCapiDev,
                &(pBuf->capiMsg), pAppInfo->nMaxTotalMsgSize);
   if (iRes < 0)
   {
      // error occurred, CAPI error value is in errno
      return ((unsigned) errno);
   }
   if (iRes == 0)
   {
      return (CME_GET_QUEUE_EMPTY);
   }

   /* special handling of Connect-B3-Active-Indication */
   uCmd = CAPI_GET_CMD (&(pBuf->capiMsg));
   dwCid = CAPI_GET_CID (&(pBuf->capiMsg));
   if (uCmd == CAPI_INDICAT (C_CONNECT_B3_ACTIVE))
   {
      /* there is a new B3-connection, search and mark NCCI entry as used */
      (void) AllocB3Connection (pAppInfo, dwCid);
   }
   /* special handling of Data-B3-Indications */
   else if (uCmd == CAPI_INDICAT (C_DATA_B3))
   {
      /* extract the block handle for later usage */
      pBuf->uDataHandle =
         (unsigned) C_GET_WORD (pBuf->capiMsg.info.data_b3_ind.wHandle);

      /* the data block address must be corrected in the message, the length is
       * still valid
       */
      /* Note: The message is expected to allways include the 64bit data block
       *       address.
       */
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
      C_PUT_DWORD (pBuf->capiMsg.info.data_b3_ind.dwData, 0);
      C_PUT_QWORD (pBuf->capiMsg.info.data_b3_ind.qwData64,
                   (u_int64_t) ((unsigned char *) &(pBuf->capiMsg) +
                                CAPI_GET_LEN (&(pBuf->capiMsg))));
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
      C_PUT_DWORD (pBuf->capiMsg.info.data_b3_ind.dwData,
                   (u_int32_t) ((unsigned char *) &(pBuf->capiMsg) +
                                CAPI_GET_LEN (&(pBuf->capiMsg))));
      C_PUT_QWORD (pBuf->capiMsg.info.data_b3_ind.qwData64, 0);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */

      /* extract buffer from the free list, for the buffer must be held until
       * the corresponding Data-B3-Response is sent (for other CAPI messages
       * the message buffer may be overwritten on the next capi20_get_message)
       */
      pAppInfo->pFreeList = pBuf->pNext;
      if (pAppInfo->pFreeList)
      {
         pAppInfo->pFreeList->pPrev = NULL;
      }
         
      /* add data buffer to the used list of the B3-connection */
      if (! AllocDataBuffer (pAppInfo, dwCid, pBuf))
      {
         /* out of buffers for the connection, one (this) data block was (is
          * beeing) discarded
          */

         /* the data buffer must be reinserted into the free list */
         pBuf->pNext = pAppInfo->pFreeList;
         pBuf->pPrev = NULL;
         if (pAppInfo->pFreeList)
         {
            pAppInfo->pFreeList->pPrev = pBuf;
         }
         pAppInfo->pFreeList = pBuf;

         return (CME_GET_QUEUE_OVERFLOW);
      }
   }
   /* special handling of Disconnect-B3-Indication */
   else if (uCmd == CAPI_INDICAT (C_DISCONNECT_B3))
   {
      ReleaseB3Connection (pAppInfo, dwCid);
   }
   /* special handling of Disconnect-Indication */
   else if (uCmd == CAPI_INDICAT (C_DISCONNECT))
   {
      ReleasePhysConnection (pAppInfo, dwCid);
   }
   /* special handling of Manufacturer-Indications for trace messages */
   else if (uCmd == CAPI_INDICAT (C_MANUFACTURER) &&
            C_GET_DWORD (pBuf->capiMsg.info.manufacturer_ind.dwManuID) ==
               C4B_TRACE_MANU_ID)
   {
      /* if there is a data block in the trace message the address to this data
       * must be filled into the message
       */
      pTrcMsg = (CAPITraceMsg_t *)
                   ((u_int8_t *) &(pBuf->capiMsg.info.manufacturer_ind) +
                    sizeof (pBuf->capiMsg.info.manufacturer_ind));
      if (C_GET_WORD (pTrcMsg->head.wMsgType) == C4BTRC_MSGTYPE_APPL_MSG)
      {
         if (C_GET_DWORD (pTrcMsg->info.appl_msg.dwDataLength != 0))
         {
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
            u_int8_t *p = (u_int8_t *) &(pBuf->capiMsg) +
                          CAPI_GET_LEN (&(pBuf->capiMsg));

            C_PUT_DWORD (pTrcMsg->info.appl_msg.dwDataPointerLow,
                         (u_int32_t) (((u_int64_t) p) & 0x00000000FFFFFFFFUL));
            C_PUT_DWORD (pTrcMsg->info.appl_msg.dwDataPointerHigh,
                         (u_int32_t) (((u_int64_t) p) >> 32));
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
            C_PUT_DWORD (pTrcMsg->info.appl_msg.dwDataPointerLow,
                         (u_int32_t) ((u_int8_t *) &(pBuf->capiMsg) +
                                      CAPI_GET_LEN (&(pBuf->capiMsg))));
            C_PUT_DWORD (pTrcMsg->info.appl_msg.dwDataPointerHigh, 0);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
         }
      }
      else if (C_GET_WORD (pTrcMsg->head.wMsgType) == C4BTRC_MSGTYPE_DRVR_MSG)
      {
         if (C_GET_DWORD (pTrcMsg->info.drvr_msg.dwDataLength != 0))
         {
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
            u_int8_t *p = (u_int8_t *) &(pBuf->capiMsg) +
                          CAPI_GET_LEN (&(pBuf->capiMsg));

            C_PUT_DWORD (pTrcMsg->info.drvr_msg.dwDataPointerLow,
                         (u_int32_t) (((u_int64_t) p) & 0x00000000FFFFFFFFUL));
            C_PUT_DWORD (pTrcMsg->info.drvr_msg.dwDataPointerHigh,
                         (u_int32_t) (((u_int64_t) p) >> 32));
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
            C_PUT_DWORD (pTrcMsg->info.drvr_msg.dwDataPointerLow,
                         (u_int32_t) ((u_int8_t *) &(pBuf->capiMsg) +
                                      CAPI_GET_LEN (&(pBuf->capiMsg))));
            C_PUT_DWORD (pTrcMsg->info.drvr_msg.dwDataPointerHigh, 0);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
         }
      }
      
      /* Note: The message buffer including the data block remains on the free
       *       list. So the next capi20_get_message() call overwrites the trace
       *       message _and_ the data block. There is no buffer handling like
       *       for Data-B3-Indications.
       */
   }

   /* if this line is reached, everything is o.k. and we have a message */
   if (ppMsg)
   {
      *ppMsg = &(pBuf->capiMsg);
   }
   return (CAPI_OK);
} /* capi20_get_message */





/**
 * Wait for an incoming CAPI message with timeout.
 *
 * @param uApplID               I: Application id from registration.
 * @param pTimeout              I: If NULL the call only returns if
 *                                 capi20_release() is called from another
 *                                 thread for the same application id. Else the
 *                                 call will return after the time specified has
 *                                 elapsed.
 *
 * @retval CAPI_OK              There is a new message to fetch via
 *                              capi20_get_message().
 * @retval CME_GET_QUEUE_EMPTY  A timeout was specified when calling this
 *                              function and the time has passed without a CAPI
 *                              message arriving.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_wait_for_message
   (unsigned        uApplID,
    struct timeval *pTimeout)
{
   struct pollfd  pollfd;
   struct timeval tvEnd;
   struct timeval tvCurr;
   struct timeval tvTmp;
   int            timeout;
   int            iRes;
   
   /* determine the file descriptor for the application id */
   pollfd.fd = capi20_fileno (uApplID);
   if (pollfd.fd < 0)
   {
      return (CME_INVALID_APPLICATION_ID);
   }
   pollfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR |
                   POLLHUP | POLLNVAL;
   pollfd.revents = 0;

   /* determine the end time value; needed if the poll operation is interrupted
    * by a signal and it must be restarted with a reduced timeout
    */
   timerclear (&tvEnd);
   (void) gettimeofday (&tvCurr, NULL);
   if (pTimeout != NULL)
   {
      timeradd (&tvCurr, pTimeout, &tvEnd);
   }
   else
   {
      tvEnd = tvCurr;
   }
   
   /* loop to (re-)start the poll operation if interrupted by a signal */
   do
   {
      /* compute the timeout in ms */
      if (pTimeout != NULL)
      {
         timerclear (&tvCurr);
         (void) gettimeofday (&tvCurr, NULL);
         if (timercmp (&tvEnd, &tvCurr, > ))
         {
            timersub (&tvEnd, &tvCurr, &tvTmp);
            timeout = tvTmp.tv_sec * 1000 + tvTmp.tv_usec / 1000;
         }
         else
         {
            timeout = 0;
         }
      }
      else
      {
         timeout = INFTIM;
      }

      /* do a poll on the file descriptor */
      iRes = poll (&pollfd, 1, timeout);

   } while (iRes < 0 && errno == EINTR);
   
   /* evaluate the result of the poll call */
   if (iRes == 0)
   {
      /* timeout occurred, no CAPI message available */
      return (CME_GET_QUEUE_EMPTY);
   }
   if (iRes < 0)
   {
      /* some errno values may not resemble any CAPI result values */
      if (errno == EINVAL || errno == EFAULT)
      {
         /* Note: This is normally an internal error in this library. */
         return (CME_INVALID_APPLICATION_ID);
      }
      /* Note: EINTR should not make its way here. */
      
      /* error occurred, CAPI error is in errno */
      return ((unsigned) errno);
   }

   /* if we reach this point there is a CAPI message available */
   return (CAPI_OK);
} /* capi20_wait_for_message */





/**
 * Ask for the manufacturer of a controller.
 *
 * @note The call is valid even if no registration took place. So the CAPI
 *       device must explicitly be opened and later closed in this function.
 *
 * @param uCtlr                 I: Controller number in question. If 0 is
 *                                 specified the manufacturer string of the CAPI
 *                                 manufacturer itself is returned in pszBuffer.
 * @param pszBuffer             O: Null terminated string for the manufacturer
 *                                 name. The buffer must be at least 64 bytes
 *                                 large.
 *
 * @retval CAPI_OK              The manufacturer string was successfully filled
 *                              into pszBuffer.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_get_manufacturer
   (unsigned       uCtlr,
    unsigned char *pszBuffer)
{
   int                            fd;
   capi_get_manufacturer_params_t getManufacturerParams;
   int                            iRes;
   unsigned                       uRes;
   
   /* test for available CAPI device, open it temporarily */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the requested action */
   memset (&getManufacturerParams, 0, sizeof (getManufacturerParams));
   getManufacturerParams.uCtlr = uCtlr;
   iRes = ioctl (fd, C4BIOC_CAPI_GET_MANUFACTURER, &getManufacturerParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   (void) close (fd);
   
   /* return the delivered manufacturer string */
   /* Prereq.: the ioctl buffer has the correct size of 64 bytes */
   if (pszBuffer)
   {
      memcpy (pszBuffer, getManufacturerParams.szBuffer,
              sizeof (getManufacturerParams.szBuffer));
   }
   return (CAPI_OK);
} /* capi20_get_manufacturer */





/**
 * Get the version of the CAPI implementation.
 *
 * @note All output arguments may be NULL if the corresponding version data is
 *       not needed by the caller.
 *
 * @note The call is valid even if no registration took place. So the CAPI
 *       device must explicitly be opened and later closed in this function.
 *
 * @param uCtlr                 I: Controller number in question. If 0 is
 *                                 specified the version numbers for the CAPI
 *                                 manager itself are returned. As there is no
 *                                 distinction between firmware and driver
 *                                 software, the manufacturer and the BSD
 *                                 version numbers will be the same.
 * @param puCAPIMajor           O: Major CAPI version (2).
 * @param puCAPIMinor           O: Minor CAPI version (0).
 * @param puManufacturerMajor   O: Major version of the controller firmware. If
 *                                 the board is a passive one and thus runs no
 *                                 firmware, this value will be the same as the
 *                                 BSD version.
 * @param puManufacturerMinor   O: Minor version of the controller firmware. If
 *                                 the board is a passive one and thus runs no
 *                                 firmware, this value will be the same as the
 *                                 BSD version.
 * @param puBSDMajor            O: Major version of the controller device driver.
 * @param puBSDMinor            O: Minor version of the controller device driver.
 *
 * @retval CAPI_OK              The version information was successfully filled
 *                              into the argument addresses that are not NULL.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_get_version
   (unsigned  uCtlr,
    unsigned *puCAPIMajor,
    unsigned *puCAPIMinor,
    unsigned *puManufacturerMajor,
    unsigned *puManufacturerMinor,
    unsigned *puBSDMajor,
    unsigned *puBSDMinor)
{
   int                       fd;
   capi_get_version_params_t getVersionParams;
   int                       iRes;
   unsigned                  uRes;
   
   /* test for available CAPI device, open it temporarily */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the requested action */
   memset (&getVersionParams, 0, sizeof (getVersionParams));
   getVersionParams.uCtlr = uCtlr;
   iRes = ioctl (fd, C4BIOC_CAPI_GET_VERSION, &getVersionParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   (void) close (fd);
   
   /* return the delivered version information */
   if (puCAPIMajor)
   {
      *puCAPIMajor = getVersionParams.uCAPIMajor;
   }
   if (puCAPIMinor)
   {
      *puCAPIMinor = getVersionParams.uCAPIMinor;
   }
   if (puManufacturerMajor)
   {
      *puManufacturerMajor = getVersionParams.uManufacturerMajor;
   }
   if (puManufacturerMinor)
   {
      *puManufacturerMinor = getVersionParams.uManufacturerMinor;
   }
   if (puBSDMajor)
   {
      *puBSDMajor = getVersionParams.uBSDMajor;
   }
   if (puBSDMinor)
   {
      *puBSDMinor = getVersionParams.uBSDMinor;
   }
   return (CAPI_OK);
} /* capi20_get_version */





/**
 * Get the serial number of a controller.
 *
 * @note The call is valid even if no registration took place. So the CAPI
 *       device must explicitly be opened and later closed in this function.
 *
 * @param uCtlr                 I: Controller number in question. If 0 is
 *                                 specified, the serial number of the CAPI
 *                                 manager itself is requested. As it does not
 *                                 support serial numbers, the result is an
 *                                 empty string.
 * @param pszBuffer             O: Null terminated string for the serial number
 *                                 of the controller. If the controller does not
 *                                 support serial numbers, this will result in
 *                                 an empty string. The buffer for the string
 *                                 must be at least 8 bytes large.
 *
 * @retval CAPI_OK              The serial number was successfully copied into
 *                              the buffer (even if it is empty).
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_get_serial_number
   (unsigned       uCtlr,
    unsigned char *pszBuffer)
{
   int                             fd;
   capi_get_serial_number_params_t getSerNoParams;
   int                             iRes;
   unsigned                        uRes;
   
   /* test for available CAPI device, open it temporarily */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the requested action */
   memset (&getSerNoParams, 0, sizeof (getSerNoParams));
   getSerNoParams.uCtlr = uCtlr;
   iRes = ioctl (fd, C4BIOC_CAPI_GET_SERIAL_NUMBER, &getSerNoParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   (void) close (fd);
   
   /* return the delivered serial number */
   /* Prereq.: the ioctl buffer has the correct size of 8 bytes */
   if (pszBuffer)
   {
      memcpy (pszBuffer, getSerNoParams.szBuffer,
              sizeof (getSerNoParams.szBuffer));
   }
   return (CAPI_OK);
} /* capi20_get_serial_number */





/**
 * Get the profile of a controller or the number of controllers.
 *
 * @note The call is valid even if no registration took place. So the CAPI
 *       device must explicitly be opened and later closed in this function.
 *
 * @param uCtlr                 I: Controller number in question. If 0 is
 *                                 specified, only the total number of installed
 *                                 controllers is returned in the profile
 *                                 buffer.
 * @param pProfile              O: This buffer receives the profile data if the
 *                                 result is CAPI_OK.
 *
 * @retval CAPI_OK              The profile buffer was successfully filled (even
 *                              if only with the number of installed
 *                              controllers).
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_get_profile
   (unsigned             uCtlr,
    CAPIProfileBuffer_t *pProfile)
{
   int                       fd;
   capi_get_profile_params_t getProfileParams;
   int                       iRes;
   unsigned                  uRes;
   
   /* test for available CAPI device, open it temporarily */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the requested action */
   memset (&getProfileParams, 0, sizeof (getProfileParams));
   getProfileParams.uCtlr = uCtlr;
   iRes = ioctl (fd, C4BIOC_CAPI_GET_PROFILE, &getProfileParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   (void) close (fd);
   
   /* return the delivered profile */
   if (pProfile)
   {
      *pProfile = getProfileParams.profile;
   }
   return (CAPI_OK);
} /* capi20_get_profile */





/**
 * Get additional info about the driver of a controller.
 *
 * @note The call is valid even if no registration took place. So the CAPI
 *       device must explicitly be opened and later closed in this function.
 *
 * @param uCtlr                 I: Controller number in question. Note that here
 *                                 controller number 0 is not allowed.
 * @param pDrvInfo              O: This buffer receives the driver data if the
 *                                 result is CAPI_OK.
 *
 * @retval CAPI_OK              The driver data buffer was successfully filled.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_get_ctlr_driver_info
   (unsigned              uCtlr,
    CAPICtlrDriverInfo_t *pDrvInfo)
{
   int                                fd;
   capi_get_ctlr_driver_info_params_t getDrvInfoParams;
   int                                iRes;
   unsigned                           uRes;
   
   /* test for available CAPI device, open it temporarily */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the requested action */
   memset (&getDrvInfoParams, 0, sizeof (getDrvInfoParams));
   getDrvInfoParams.uCtlr = uCtlr;
   iRes = ioctl (fd, C4BIOC_CAPI_GET_CTLR_DRIVER_INFO, &getDrvInfoParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   (void) close (fd);
   
   /* return the delivered profile */
   if (pDrvInfo)
   {
      *pDrvInfo = getDrvInfoParams.drvInfo;
   }
   return (CAPI_OK);
} /* capi20_get_ctlr_driver_info */
    




/**
 * Reset and/or download the controller.
 *
 * This call is controller specific, its support is optional. But for active
 * ISDN adapters it is the only way to download the software and settings onto
 * the adapter. How many data blocks must be specified and what their contents
 * must look like is driver specific. You have to look at the specification for
 * the driver of the controller in question.
 *
 * If the number of data blocks to send down to the controller is specified as
 * null, the desired operation is only to reset the controller to disabled
 * state. No download is performed. After such a call the board is not usable
 * any more until another call to this function will download the firmware
 * again. This behaviour may be used to physically disable active boards so they
 * will in no way handle any ISDN connections any more.
 *
 * @note This call is only allowed if the calling processes user id is 0 or the
 *       user is member of the group with id 0.
 *
 * @param uCtlr                 I: Controller number to reset.
 * @param nNumDataBlocks        I: The number of data blocks to be sent to the
 *                                 controller. It is the length of the array at
 *                                 paDataBlocks.
 * @param paDataBlocks          I: The array of data blocks to be sent to the
 *                                 controller.
 *
 * @retval CAPI_OK              The reset operation was successful.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_reset_ctlr
   (unsigned             uCtlr,
    size_t               nNumDataBlocks,
    CAPICtlrDataBlock_t *paDataBlocks)
{
   int                      fd;
   capi_reset_ctlr_params_t resetCtlrParams;
   int                      iRes;
   unsigned                 uRes;
   
   /* test for available CAPI device, open it temporarily */
   fd = open (CAPI_DEVICE_NAME, O_RDWR | O_NONBLOCK);
   if (fd < 0)
   {
      return (CRE_CAPI_NOT_INSTALLED);
   }
   
   /* now perform the requested action */
   memset (&resetCtlrParams, 0, sizeof (resetCtlrParams));
   resetCtlrParams.uCtlr          = uCtlr;
   resetCtlrParams.nNumDataBlocks = nNumDataBlocks;
   resetCtlrParams.paDataBlocks   = paDataBlocks;
   iRes = ioctl (fd, C4BIOC_CAPI_RESET_CTLR, &resetCtlrParams);
   if (iRes < 0)
   {
      /* CAPI error from CAPI manager is in errno */
      uRes = (unsigned) errno;
      (void) close (fd);
      return (uRes);
   }
   (void) close (fd);
   
   return (CAPI_OK);
} /* capi20_reset_ctlr */




    
/**
 * Test for installed CAPI and at least one controller.
 *
 * @param None.
 *
 * @retval CAPI_OK              There is at least one ISDN controller installed.
 * @retval CRE_CAPI_NOT_INSTALLED
 *                              The CAPI subsystem (C4B) is basically loaded,
 *                              but no ISDN controller is registered, i.e. not
 *                              controller driver is loaded.
 * @retval Else                 An error occurred, the result is one of the CAPI
 *                              result values defined in capi_result.h
 */

unsigned capi20_is_installed (void)
{
   CAPIProfileBuffer_t profile;
   unsigned            uRes;
   
   /* We just order the number of installed controllers. This delivers us the
    * information, if CAPI is installed.
    */
   memset (&profile, 0, sizeof (profile));
   uRes = capi20_get_profile (0, &profile);
   if (uRes == CAPI_OK)
   {
      if (C_GET_WORD (profile.wCtlr) > 0)
      {
         /* call was successful and there is at least one controller installed
          */
         return (CAPI_OK);
      }
      else
      {
         /* call was successful but no controller installed */
         return (CRE_CAPI_NOT_INSTALLED);
      }
   }
   /* error returned, just forward it */
   return (uRes);
} /* capi20_is_installed */





/**
 * Get the file descriptor for an application id.
 *
 * @note It is recommended to use capi20_wait_for_message() instead of poll.
 *
 * @param uApplID               The application id from registration.
 *
 * @retval -1                   uApplID is not a valid application id for the
 *                              calling process.
 * @retval Else                 The open file descriptor to /dev/capi20 for the
 *                              application id specified is returned.
 */

int capi20_fileno
   (unsigned uApplID)
{
   /* test for valid application id */
   if (uApplID >= g_nAppInfoLen || ! g_papAppInfo)
   {
      /* application not in array */
      return (-1);
   }
   
   /* test for registered application */
   if (! g_papAppInfo [uApplID] || ! g_papAppInfo [uApplID]->fEntryUsed)
   {
      return (-1);
   }
   
   /* application found, return file descriptor */
   return (g_papAppInfo [uApplID]->fdCapiDev);
} /* capi20_fileno */





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





/**
 * Release an entry of the app-info array.
 */

static void FreeAppInfoEntry
   (unsigned uApplID)
{
   /* test for valid application id */
   if (uApplID >= g_nAppInfoLen || ! g_papAppInfo)
   {
      return;
   }
   
   /* test for valid array entry */
   if (g_papAppInfo [uApplID])
   {
      /* array entry used --> free all memory and decrement application count */
      AppInfo_t *p = g_papAppInfo [uApplID];
      g_papAppInfo [uApplID] = NULL;
      if (g_nNumApplications > 0 && p->fEntryUsed)
      {
         g_nNumApplications--;
      }
      if (p->paNcciData)
      {
         free (p->paNcciData);
      }
      if (p->pMsgBuf)
      {
         free (p->pMsgBuf);
      }
      free (p);
   }
   
} /* FreeAppInfoEntry */





/**
 * Allocate a new B3-connection for the application.
 */

static NcciData_t *AllocB3Connection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci)
{
   int         iConnIdx;
   NcciData_t *pNcciData;
   
   /* if there is no NCCI data array, we can get no NCCI data structure */
   if (pAppInfo->regData.uMaxLogicalConnections <= 0 ||
       ! pAppInfo->paNcciData)
   {
      return (NULL);
   }
   
   /* try direct addressing by controller and NCCI first */
   iConnIdx = ((dwNcci & CAPI_CIDMASK_CTLR) - 1) *
              (((dwNcci & CAPI_CIDMASK_NCCI) >> CAPI_CIDSHIFT_NCCI) - 1);
   if ((size_t) iConnIdx >= pAppInfo->regData.uMaxLogicalConnections ||
       (pAppInfo->paNcciData [iConnIdx].fEntryUsed &&
        pAppInfo->paNcciData [iConnIdx].dwNcci != dwNcci))
   {
      /* directly addressed entry already used by another connection, search
       * from end of array
       */
      for (iConnIdx = pAppInfo->regData.uMaxLogicalConnections - 1;
           iConnIdx >= 0;
           iConnIdx--)
      {
         pNcciData = &(pAppInfo->paNcciData [iConnIdx]);
         if (! pNcciData->fEntryUsed)
         {
            break;
         }
      }
      if (iConnIdx < 0)
      {
         /* no free entry available, no more B3 connections possible */
         return (NULL);
      }
   }

   /* iConnIdx is now the index into the NCCI array for the B3-connection found
    */
   pNcciData = &(pAppInfo->paNcciData [iConnIdx]);
   pNcciData->fEntryUsed = 1;
   pNcciData->dwNcci = dwNcci;
   
   return (pNcciData);
} /* AllocB3Connection */





/**
 * Find an allocated B3-connection.
 */

static NcciData_t *FindB3Connection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci)
{
   int         iConnIdx;
   NcciData_t *pNcciData;
   
   /* if there is no NCCI data array, we can get no NCCI data structure */
   if (pAppInfo->regData.uMaxLogicalConnections <= 0 ||
       ! pAppInfo->paNcciData)
   {
      return (NULL);
   }
   
   /* find the NCCI in the array of B3-connections,  try direct addressing by
    * controller and NCCI first
    */
   iConnIdx = ((dwNcci & CAPI_CIDMASK_CTLR) - 1) *
              (((dwNcci & CAPI_CIDMASK_NCCI) >> CAPI_CIDSHIFT_NCCI) - 1);
   if ((size_t) iConnIdx >= pAppInfo->regData.uMaxLogicalConnections ||
       ! pAppInfo->paNcciData [iConnIdx].fEntryUsed ||
       pAppInfo->paNcciData [iConnIdx].dwNcci != dwNcci)
   {
      /* B3-connection not found by direct addressing, search from end of array
       */
      for (iConnIdx = pAppInfo->regData.uMaxLogicalConnections - 1;
           iConnIdx >= 0;
           iConnIdx--)
      {
         pNcciData = &(pAppInfo->paNcciData [iConnIdx]);
         if (pNcciData->fEntryUsed && pNcciData->dwNcci == dwNcci)
         {
            break;
         }
      }
      if (iConnIdx < 0)
      {
         /* B3-connection not found */
         return (NULL);
      }
   }
   /* iConnIdx is now the index into the NCCI array for the B3-connection found
    */
   return (&(pAppInfo->paNcciData [iConnIdx]));
} /* FindB3Connection */




    
/**
 * Release a previously allocated B3-connection and all data buffers.
 */

static void ReleaseB3Connection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci)
{
   NcciData_t   *pNcciData;
   DataBuffer_t *pBuf;
   DataBuffer_t *pTmp;

   /* find the B3-connection in the array of the application */
   pNcciData = FindB3Connection (pAppInfo, dwNcci);
   if (! pNcciData)
   {
      /* connection not found, do nothing */
      return;
   }
   
   /* shift all data buffers from the queue to the free list */
   for (pBuf = pNcciData->pHead; pBuf; )
   {
      /* first get address of next buffer */
      pTmp = pBuf;
      pBuf = pTmp->pNext;

      /* now put buffer into the free list */
      pTmp->pPrev = NULL;
      pTmp->pNext = pAppInfo->pFreeList;
      if (pAppInfo->pFreeList)
      {
         pAppInfo->pFreeList->pPrev = pTmp;
      }
      pAppInfo->pFreeList = pTmp;
   }

   /* connection entry is now free */
   pNcciData->fEntryUsed = 0;
   pNcciData->dwNcci = 0;
   pNcciData->pHead = NULL;
   pNcciData->pTail = NULL;
   pNcciData->nQueueLen = 0;
   
} /* ReleaseB3Connection */





/**
 * Release all previously allocated B3-connections for a phys. connection.
 */

static void ReleasePhysConnection
   (AppInfo_t *pAppInfo,
    u_int32_t  dwPlci)
{
   int         i;
   NcciData_t *pNcciData;
   
   /* Traverse the B3-connection array */
   for (i = 0; (size_t) i < pAppInfo->regData.uMaxLogicalConnections; i++)
   {
      /* easier addressing of the NCCI data */
      pNcciData = &(pAppInfo->paNcciData [i]);
      
      /* check if entry used */
      if (! pNcciData->fEntryUsed)
      {
         continue;
      }
      
      /* check for equal controller and PLCI */
      if ((dwPlci & ~CAPI_CIDMASK_NCCI) !=
             (pNcciData->dwNcci & ~CAPI_CIDMASK_NCCI))
      {
         continue;
      }

      /* release the still allocated entry (will normally not find one) */
      ReleaseB3Connection (pAppInfo, pNcciData->dwNcci);
   }
   
} /* ReleasePhysConnection */





/**
 * Store a new incoming data buffer for a NCCI.
 */

static int AllocDataBuffer
   (AppInfo_t    *pAppInfo,
    u_int32_t     dwNcci,
    DataBuffer_t *pBuf)
{
   NcciData_t *pNcciData;

   /* find the B3-connection in the array of the application */
   pNcciData = FindB3Connection (pAppInfo, dwNcci);
   if (pNcciData)
   {
      /* check for max. queue length */
      if (pNcciData->nQueueLen >= pAppInfo->regData.uMaxBDataBlocks)
      {
         CAPIMsg_t capiMsg;
         
         /* cannot forward the data block to the application, just acknowledge
          * the message to the CAPI manager and return error
          */
         capiMsg.head = pBuf->capiMsg.head;
         C_PUT_WORD (capiMsg.head.wCmd, CAPI_RESPONSE (C_DATA_B3));
         C_PUT_WORD (capiMsg.info.data_b3_resp.wHandle,
                     (u_int16_t) (pBuf->uDataHandle));
         C_PUT_WORD (capiMsg.head.wLen,
                     sizeof (capiMsg.head) +
                        sizeof (capiMsg.info.data_b3_resp));
         (void) write (pAppInfo->fdCapiDev, &capiMsg, CAPI_GET_LEN (&capiMsg));
         
         return (0);
      }
         
      /* add data buffer to the used queue (at tail) */
      pBuf->pNext = NULL;
      pBuf->pPrev = pNcciData->pTail;
      pNcciData->pTail = pBuf;
      if (pNcciData->pHead == NULL)
      {
         pNcciData->pHead = pBuf;
      }
      pNcciData->nQueueLen++;

      return (1);
   }
   /* else: NCCI is not known, do nothing but return error */
   return (0);
} /* AllocDataBuffer */





/**
 * Release a stored incoming data buffer for a NCCI.
 */

static void ReleaseDataBuffer
   (AppInfo_t *pAppInfo,
    u_int32_t  dwNcci,
    unsigned   uDataHandle)
{
   NcciData_t   *pNcciData;
   DataBuffer_t *pBuf;

   /* find the B3-connection in the array of the application */
   pNcciData = FindB3Connection (pAppInfo, dwNcci);
   if (! pNcciData)
   {
      /* NCCI is not known, do nothing */
      return;
   }

   /* find the data block in the buffer queue (should be at the head) */
   for (pBuf = pNcciData->pHead; pBuf; pBuf = pBuf->pNext)
   {
      if (pBuf->uDataHandle == uDataHandle)
      {
         /* data buffer found, unchain from used list */
         if (pBuf->pNext)
         {
            pBuf->pNext->pPrev = pBuf->pPrev;
         }
         if (pBuf->pPrev)
         {
            pBuf->pPrev->pNext = pBuf->pNext;
         }
         if (pBuf == pNcciData->pHead)
         {
            /* buffer taken from head of list */
            pNcciData->pHead = pBuf->pNext;
         }
         if (pBuf == pNcciData->pTail)
         {
            /* buffer taken from end of list (should normally be the last list
             * element)
             */
            pNcciData->pTail = pBuf->pPrev;
         }
         if (pNcciData->nQueueLen > 0)
         {
            pNcciData->nQueueLen--;
         }
         
         /* put the buffer into the free list for reuse */
         pBuf->pPrev = NULL;
         pBuf->pNext = pAppInfo->pFreeList;
         if (pAppInfo->pFreeList)
         {
            pAppInfo->pFreeList->pPrev = pBuf;
         }
         pAppInfo->pFreeList = pBuf;
         
         /* ready */
         return;
      }
   }

} /* ReleaseDataBuffer */
