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

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

/* system includes */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <capi20.h>
#include <capi_bsd.h>
#include <errno.h>
#include <assert.h>

/* import includes */

#define __CONTROL__

/* local includes */
#include "config.h"
#include "ctlrinfo.h"
#include "batchadmin.h"
#include "capism.h"
#include "capiutil.h"
#include "control.h"





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





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





#define SELF_CONNECT_FILE_NAME  "self_test"

#define TEST_PATTERN_1  "1-1-1-1-1-1-1-1-1-1\n"
#define TEST_PATTERN_2  "-2-2-2-2-2-2-2-2-2-\n"
#define TEST_PATTERN_3  "3-3-3-3-3-3-3-3-3-3\n"
#define TEST_PATTERN_4  "-4-4-4-4-4-4-4-4-4-\n"
#define TEST_PATTERN_5  "5-5-5-5-5-5-5-5-5-5\n"
#define TEST_PATTERN_6  "-6-6-6-6-6-6-6-6-6-\n"
#define TEST_PATTERN_7  "7-7-7-7-7-7-7-7-7-7\n"
#define TEST_PATTERN_8  "-8-8-8-8-8-8-8-8-8-\n"

#define ALL_TEST_PATTERNS \
   TEST_PATTERN_1         \
   TEST_PATTERN_2         \
   TEST_PATTERN_3         \
   TEST_PATTERN_4         \
   TEST_PATTERN_5         \
   TEST_PATTERN_6         \
   TEST_PATTERN_7         \
   TEST_PATTERN_8

#define NUM_TEST_PATTERN_SEQUENCES      100



/**
 * Prototype for a function to create new outgoing call objects.
 */
typedef void (*ConnectionFinishedFct_t)
   (CCapiStateMachine *pSm);



/**
 * Flag to leave the workloop when all connections are finished.
 *
 * This will be set by the signal handler.
 */
static volatile bool g_fTerminate = false;

/** The CAPI application id as soon as the registration is complete. */
static unsigned g_uApplID = 0;

/// The flag to use all controllers (or more exactly not only one).
static bool g_fUseAllControllers = false;



/* --- Data to maintain active connections and CAPI message routing --- */

/**
 * The array of pointers to running state machines.
 *
 * This array will be incremented on demand if the current length is not enough
 * to take all necessary state machine object addresses. The index into the
 * array is termed connection id. If the pointer at an index is NULL, there is
 * no state machine with the corresponding connection id.
 */
static std::vector<CCapiStateMachine *> g_aStateMachines;

/**
 * The mapping array for PLCIs to connection ids.
 *
 * The 2D-array is initialized before executing the work loop with all fields
 * set to UINT_MAX, an invalid connection id. As soon as a new connection is
 * started, i.e. a Connect-Confirm or a Connect-Indication is received, the
 * position [<ctlr.no.>] [<PLCI part>] is set to the connection id of the
 * corresponding state machine object. As soon as the state machine object has
 * finished its work (i.e. has left running state), the array position is reset
 * to UINT_MAX to declare an unused controller number / PLCI combination.
 */
static unsigned g_aauMapPlciToConnID [128] [256];

/// The current number of active incoming connections.
static size_t g_nNumIncomingConnections      = 0;

/// The maximum number of concurrently active incoming connections.
static size_t g_nMaxIncomingConnections      = 0;

/// The current number of active outgoing connections.
static size_t g_nNumOutgoingConnections      = 0;

/// The maximum number of concurrently active outgoing connections.
static size_t g_nMaxOutgoingConnections      = 0;

/// The total number of outgoing connections to perform.
static size_t g_nMaxTotalOutgoingConnections = 0;



/* --- Data to create file names to store data for incoming connections --- */

/**
 * The current destination file name pattern, if incoming connections are to be
 * handled.
 */
static std::string g_strDstFilePattern = "";

/**
 * The counter to create distinct file names for incoming data for incoming
 * connections.
 */
static unsigned g_uCurrIncFileNumber = 0;



// --- Statistic counters ---

/// The counter for the total number of outgoing connections.
static size_t g_nTotalOutgoingConnections = 0;

/// The counter for the total number of successful outgoing connections.
static size_t g_nSuccessfulOutgoingConnections = 0;

/// The counter for the total number of failed outgoing connections.
static size_t g_nFailedOutgoingConnections = 0;

/// The counter for the total number of incoming connections.
static size_t g_nTotalIncomingConnections = 0;

/// The counter for the total number of successful incoming connections.
static size_t g_nSuccessfulIncomingConnections = 0;

/// The counter for the total number of failed incoming connections.
static size_t g_nFailedIncomingConnections = 0;



/// The current object to deliver data for new call jobs.
static CBatchAdmin *g_pBatch = NULL;

/// The current function for the follow-up treatment of a connection.
static ConnectionFinishedFct_t g_pfnConnectionFinished = NULL;





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





/**
 * The loop for doing the work.
 */
static int DoWorkLoop (void);

/**
 * Sigterm and sigint signal handler.
 */
static void SignalHandler
   (int iSig);

/**
 * Determine the overall maximum of available B-channels.
 */
static size_t GetMaxNumBChannels (void);

/**
 * Create a file with a test pattern for a self connection.
 */
static bool CreateTestPatternFile
   (const char *pszFileName);

/**
 * Check the contents of a file for a self connection.
 */
static bool CheckTestPatternFile
   (const char *pszFileName);

/**
 * Create a new outgoing connection object and start connection.
 */
static void CreateNewOutgoingCall (void);

/**
 * Create a new incoming connection object.
 */
static CCapiStateMachine *CreateNewIncomingCallObject
   (unsigned uController);

/**
 * Perform cleanup and counting for finished connections.
 */
static void HandleFinishedConnection
   (CCapiStateMachine *pSm);

/**
 * Follow-up treatment for self connections.
 */
static void SelfConnectionFinished
   (CCapiStateMachine *pSm);

/**
 * Follow-up treatment for incoming connections.
 */
static void IncomingConnectionFinished
   (CCapiStateMachine *pSm);

/**
 * Follow-up treatment for outgoing connections.
 */
static void OutgoingConnectionFinished
   (CCapiStateMachine *pSm);

/**
 * Send a listen request message.
 */
static void SendListenRequest
   (unsigned uController,
    bool     fIncomingCalls);

/**
 * Send a dummy CAPI response message.
 */
static void SendDummyResponse
   (const CAPIMsg_t *pCapiIndMsg);





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





/**
 * List all controllers installed.
 */

int Ctrl_ListAllControllers
   (bool fVerboseOutput)
{
   CAPIProfileBuffer_t profile;
   unsigned            uNumCtlr;
   unsigned            uCtlrFound;
   int                 i;
   unsigned            uRes;
   
   // first get the number of installed controllers
   uRes = capi20_get_profile (0, &profile);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Error getting number of installed controllers: 0x"
                << std::hex << std::setw (4) << std::setfill ('0') << uRes
		<< std::dec
                << std::endl;
      return (2);
   }
   uNumCtlr = C_GET_WORD (profile.wCtlr);
   if (uNumCtlr == 0)
   {
      std::cout << e_oConfig.GetProgName ()
                << ": No controllers installed" << std::endl;
      return (0);
   }
   
   // now query all controllers until all found
   std::cout << std::endl;
   std::cout << uNumCtlr << " controller" << ((uNumCtlr == 1) ? "" : "s")
             << " installed:"
             << std::endl << std::endl;
   for (i = 1, uCtlrFound = 0; i <= 127 && uCtlrFound < uNumCtlr; i++)
   {
      // get controller data
      std::ostringstream oss;
      CControllerInfo    ctlrInfo;
      uRes = ctlrInfo.Init ((unsigned) i, oss);
      if (uRes == CRE_CAPI_NOT_INSTALLED)
      {
         // seems to be a hole in the array of CAPI controllers...
         continue;
      }
      uCtlrFound++;                     // count the controller found
      if (uRes != CAPI_OK)
      {
         std::cerr << e_oConfig.GetProgName () << ": " << oss.str ()
                   << std::endl;
         continue;
      }
      
      // print information
      if (fVerboseOutput)
      {
         ctlrInfo.PrintDetailed (std::cout);
      }
      else
      {
         ctlrInfo.Print (std::cout);
      }
   }
   
   return (0);
} // Ctrl_ListAllControllers





/**
 * Do one or a series of self connections.
 */

int Ctrl_SelfConnect
   (bool fRepeat)
{
   unsigned uRes;
   
   // create a batch object with exactly one call entry for the self connection
   std::string strSrcFileName = SELF_CONNECT_FILE_NAME;
   g_pBatch = new CBatchAdmin (1,
                               e_oConfig.GetController (),
                               e_oConfig.GetConnectionType (),
                               strSrcFileName,
                               e_oConfig.GetDstPhoneNumber (),
                               e_oConfig.GetSrcPhoneNumber ());
   
   // prepare the global data for use in the work function
   g_strDstFilePattern            = SELF_CONNECT_FILE_NAME;
   g_fUseAllControllers           = false;
   g_nMaxOutgoingConnections      = 1;
   g_nMaxIncomingConnections      = 1;
   g_nMaxTotalOutgoingConnections = (fRepeat ? 0 : 1);
   g_pfnConnectionFinished        = SelfConnectionFinished;
   
   // prepare the source file with the data to transmit
   if (! CreateTestPatternFile (strSrcFileName.c_str ()))
   {
      delete g_pBatch;
      g_pBatch = NULL;
      return (2);
   }
   
   // register at CAPI, only two connections will be established
   uRes = capi20_register (2, 7, MAX_BDATA_LEN, &g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setw (4) << std::setfill ('0') << uRes
		<< std::dec
                << " registering at CAPI"
                << std::endl;
      delete g_pBatch;
      g_pBatch = NULL;
      return (2);
   }
   
   std::cout << e_oConfig.GetProgName () << ": Starting self connection..."
             << std::endl;
   
   // now do the self connection
   (void) DoWorkLoop ();
   
   delete g_pBatch;
   g_pBatch = NULL;

   std::cout << e_oConfig.GetProgName () << ": Self connection finished"
             << std::endl;
   
   // the CAPI registration is not needed any more
   uRes = capi20_release (g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setw (4) << std::setfill ('0') << uRes
		<< std::dec
                << " releasing CAPI access"
                << std::endl;
   }
   
   // print out statistics for the self connection(s); the follow-up treatment
   // function for this task will maintain the relevant counters
   std::cout << std::endl;
   std::cout << "Self connection statistics:" << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Total number of connections:"
             << g_nTotalOutgoingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Successful connections:"
             << g_nSuccessfulIncomingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Failed connections:"
             << g_nFailedIncomingConnections << std::endl;
   std::cout << std::endl;
   
   return (0);
} // Ctrl_SelfConnect
   




/**
 * Handle all incoming calls.
 */

int Ctrl_HandleIncomingCalls (void)
{
   unsigned uRes;
   
   // prepare the global data for use in the work function
   g_strDstFilePattern       = e_oConfig.GetInputFileNamePattern ();
   g_fUseAllControllers      = e_oConfig.GetUseAllControllers ();
   g_nMaxOutgoingConnections = 0;
   g_nMaxIncomingConnections = e_oConfig.GetNumCalls ();
   g_pfnConnectionFinished   = IncomingConnectionFinished;
   
   // if the number of concurrent incoming connections shall not be limited,
   // the only limit is the number of available B-channels over all existing
   // controllers
   if (g_nMaxIncomingConnections == 0)
   {
      g_nMaxIncomingConnections = (unsigned) GetMaxNumBChannels ();
   }

   // register at CAPI
   uRes = capi20_register (g_nMaxIncomingConnections, 7, MAX_BDATA_LEN,
                           &g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " registering at CAPI"
                << std::endl;
      return (2);
   }
   
   std::cout << e_oConfig.GetProgName ()
             << ": Start handling incoming calls..."
             << std::endl;
   
   // now handle all incoming calls until <ctrl-c> pressed
   (void) DoWorkLoop ();
   
   std::cout << e_oConfig.GetProgName ()
             << ": Handling incoming calls finished"
             << std::endl;
   
   // the CAPI registration is not needed any more
   uRes = capi20_release (g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " releasing CAPI access"
                << std::endl;
   }
   
   // print out statistics for the incoming connections
   std::cout << std::endl;
   std::cout << "Incoming connections statistics:" << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Total number of connections:"
             << g_nTotalIncomingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Successful connections:"
             << g_nSuccessfulIncomingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Failed connections:" << g_nFailedIncomingConnections
             << std::endl;
   std::cout << std::endl;
   
   return (0);
} // Ctrl_HandleIncomingCalls
   




/**
 * Do one or a repeated series of identical outgoing calls.
 */

int Ctrl_DoOutgoingCalls
   (bool fRepeat)
{
   unsigned uRes;
   
   // create a batch object with the desired number of equal call entries
   g_pBatch = new CBatchAdmin (e_oConfig.GetNumCalls (),
                               e_oConfig.GetController (),
                               e_oConfig.GetConnectionType (),
                               e_oConfig.GetOutputFileName (),
                               e_oConfig.GetDstPhoneNumber (),
                               e_oConfig.GetSrcPhoneNumber ());
   
   // prepare the global data for use in the work function
   g_fUseAllControllers           = false;
   g_nMaxOutgoingConnections      = e_oConfig.GetNumCalls ();
   g_nMaxIncomingConnections      = 0;
   g_nMaxTotalOutgoingConnections = fRepeat ? 0 : e_oConfig.GetNumCalls ();
   g_pfnConnectionFinished        = OutgoingConnectionFinished;
   
   // register at CAPI
   uRes = capi20_register (g_nMaxOutgoingConnections, 7, MAX_BDATA_LEN,
                           &g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " registering at CAPI"
                << std::endl;
      return (2);
   }
   
   std::cout << e_oConfig.GetProgName ()
             << ": Start doing " << e_oConfig.GetNumCalls ()
             << (fRepeat ? " repeated" : "") << " outgoing calls..."
             << std::endl;
   
   // now perform the outgoing calls
   (void) DoWorkLoop ();
   
   std::cout << e_oConfig.GetProgName ()
             << ": Outgoing calls finished"
             << std::endl;
   
   // the CAPI registration is not needed any more
   uRes = capi20_release (g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " releasing CAPI access"
                << std::endl;
   }
   
   // print out statistics for the outgoing connection(s)
   std::cout << std::endl;
   std::cout << "Outgoing connection(s) statistics:" << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Total number of connections:"
             << g_nTotalOutgoingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Successful connections:"
             << g_nSuccessfulOutgoingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Failed connections:"
             << g_nFailedOutgoingConnections << std::endl;
   std::cout << std::endl;
   
   return (0);
} // Ctrl_DoOutgoingCalls

    



/**
 * Do one or a repeated batch of outgoing calls.
 */

int Ctrl_DoBatchCalls
   (const std::string &strBatchFileName,
    bool               fRepeat)
{
   unsigned uRes;
   
   // create a batch object with the desired number of equal call entries
   g_pBatch = new CBatchAdmin (strBatchFileName);
   
   // prepare the global data for use in the work function
   g_fUseAllControllers           = true;
   g_nMaxOutgoingConnections      = g_pBatch->GetNumCalls ();
   g_nMaxIncomingConnections      = 0;
   g_nMaxTotalOutgoingConnections = fRepeat ? 0 : g_pBatch->GetNumCalls ();
   g_pfnConnectionFinished        = OutgoingConnectionFinished;
   
   // register at CAPI
   uRes = capi20_register (g_nMaxOutgoingConnections, 7, MAX_BDATA_LEN,
                           &g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " registering at CAPI"
                << std::endl;
      return (2);
   }
   
   std::cout << e_oConfig.GetProgName ()
             << ": Start doing " << g_pBatch->GetNumCalls ()
             << (fRepeat ? " repeated" : "") << " outgoing calls..."
             << std::endl;
   
   // now perform the outgoing calls
   (void) DoWorkLoop ();
   
   std::cout << e_oConfig.GetProgName ()
             << ": Outgoing calls finished"
             << std::endl;
   
   // the CAPI registration is not needed any more
   uRes = capi20_release (g_uApplID);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " releasing CAPI access"
                << std::endl;
   }
   
   // print out statistics for the outgoing connection(s)
   std::cout << std::endl;
   std::cout << "Outgoing connection(s) statistics:" << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Total number of connections:"
             << g_nTotalOutgoingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Successful connections:"
             << g_nSuccessfulOutgoingConnections << std::endl;
   std::cout << std::left << std::setw (32) << std::setfill (' ')
             << "   Failed connections:"
             << g_nFailedOutgoingConnections << std::endl;
   std::cout << std::endl;
   
   return (0);
} // Ctrl_DoBatchCalls





// === definition of class members =======================================





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





/**
 * The loop for doing the work.
 */

static int DoWorkLoop (void)
{
   bool               fCreateNewOutgoingCall;
   CAPIMsg_t         *pCapiMsg;
   CCapiStateMachine *poSm;
   struct timeval     timeoutVal;
   unsigned           uConnID;
   unsigned           uCtlr;
   unsigned           uPlci;
   int                i;
   int                j;
   unsigned           uRes;
   
   // initialize work data
   g_fTerminate = false;
   
   // initialize the mapping between PLCIs and the connection IDs
   for (i = 0; (size_t) i < ARRAY_COUNT (g_aauMapPlciToConnID); i++)
   {
      for (j = 0; (size_t) j < ARRAY_COUNT (g_aauMapPlciToConnID [i]); j++)
      {
         // just store an invalid connection id
         g_aauMapPlciToConnID [i] [j] = UINT_MAX;
      }
   }
   
   // set the sigterm and sigint handler to let us do an orderly release for
   // open connections
   (void) signal (SIGTERM, SignalHandler);
   (void) signal (SIGINT, SignalHandler);
   
   // Send out a Listen-Request. If incoming calls shall be handled, we ask for
   // incoming connections. Else we only ask for network events. This must be
   // done either for the one and only controller to use or for all available
   // controllers.
   if (g_fUseAllControllers)
   {
      CAPIProfileBuffer_t profile;
      size_t              n;

      uRes = capi20_get_profile (0, &profile);
      if (uRes != CAPI_OK)
      {
         std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                   << std::hex << std::setfill ('0') << std::setw (4) << uRes
		   << std::dec
                   << " getting the number of available controllers"
                   << std::endl;
         return (1);
      }
      n = profile.wCtlr;

      for (i = 1; i <= 127 && n > 0; ++i)
      {
         uRes = capi20_get_profile ((unsigned) i, &profile);
         if (uRes != CAPI_OK)
         {
            continue;
         }
         SendListenRequest ((unsigned) i,
                            (g_nMaxIncomingConnections > 0)
                               ? true : false);
         --n;
      }
      if (n > 0)
      {
         std::cerr << e_oConfig.GetProgName ()
                   << ": Unable to find all installed controllers, "
                   << n << " left"
                   << std::endl;
      }
   }
   else
   {
      SendListenRequest (e_oConfig.GetController (),
                         (g_nMaxIncomingConnections > 0)
                            ? true : false);
   }
   
   // if doing a self connection or handling outgoing calls we now must start
   // the (first) connection
   if (g_nNumOutgoingConnections < g_nMaxOutgoingConnections)
   {
      CreateNewOutgoingCall ();
   }
   
   std::cout << e_oConfig.GetProgName ()
             << ": Entering loop for handling connections, max. outgoing "
             << g_nMaxOutgoingConnections << " max. incoming "
             << g_nMaxIncomingConnections
             << std::endl;
   
   // loop until break signalled or other reason for termination
   fCreateNewOutgoingCall = false;
   while (! g_fTerminate ||
          g_nNumIncomingConnections > 0 ||
          g_nNumOutgoingConnections > 0)
   {
      // wait for next available CAPI message
      // Note: The timeout is necessary because of the sigterm handler. This
      //       function may terminate connections and release state machine
      //       objects, so the number of connections may get zero. We must
      //       check this in regular intervals.
      timeoutVal.tv_sec = 5;
      timeoutVal.tv_usec = 0;
      uRes = capi20_wait_for_message (g_uApplID, &timeoutVal);
      if (uRes != CAPI_OK)
      {
         continue;
      }
      uRes = capi20_get_message (g_uApplID, &pCapiMsg);
      if (uRes == CME_GET_QUEUE_EMPTY)
      {
         continue;
      }
      else if (uRes != CAPI_OK)
      {
         std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                   << std::hex << std::setfill ('0') << std::setw (4) << uRes
		   << std::dec << std::setfill (' ')
                   << " getting CAPI messages"
                   << std::endl;
         continue;
      }
      
      // special handling of the message before passing it to the corresponding
      // CAPI state machine object
      if (pCapiMsg->head.wCmd == CAPI_CONFIRM (C_LISTEN))
      {
         if (pCapiMsg->info.listen_conf.wInfo == CAPI_OK)
         {
            std::cout << e_oConfig.GetProgName ()
                      << ": Listening for incoming calls or events confirmed by controller "
                      << CAPI_GET_CTLR_FROM_CID (CAPI_GET_CID (pCapiMsg))
                      << std::endl;
         }
         else
         {
            std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                      << std::hex << std::setfill ('0') << std::setw (4)
                      << pCapiMsg->info.listen_conf.wInfo << std::dec
                      << " in Listen-Conf by controller "
                      << CAPI_GET_CTLR_FROM_CID (CAPI_GET_CID (pCapiMsg))
                      << std::endl;
         }
         continue;
      }
      else if (pCapiMsg->head.wCmd == CAPI_INDICAT (C_CONNECT))
      {
         // got connect-ind, allocate new state machine object if number of
         // incoming connections not exhausted
         if (g_nNumIncomingConnections < g_nMaxIncomingConnections)
         {
            poSm = CreateNewIncomingCallObject
                      (CAPI_GET_CTLR_FROM_CID (CAPI_GET_CID (pCapiMsg)));
            if (! poSm)
            {
               // unable to create object, reject the call
               SendDummyResponse (pCapiMsg);
               continue;
            }
         }
         else
         {
            // reject the call, no (more) incoming calls allowed
            std::cerr << e_oConfig.GetProgName ()
                      << ": Got new incoming call but no more incoming connections allowed, "
                      << g_nMaxIncomingConnections << " already running"
                      << std::endl;
            SendDummyResponse (pCapiMsg);
            continue;
         }
         // poSm now points to the new state machine

         // set mapping between new PLCI and the connection ID
         uConnID = poSm->GetConnectionID ();
         uCtlr = CAPI_GET_CTLR_FROM_CID (pCapiMsg->head.dwCid);
         uPlci = CAPI_GET_PLCI_FROM_CID (pCapiMsg->head.dwCid);
         if (g_aauMapPlciToConnID [uCtlr] [uPlci] != UINT_MAX)
         {
            std::cerr << e_oConfig.GetProgName ()
                      << ": Controller and PLCI (" << uCtlr << ", 0x"
                      << std::hex << std::setw (4) << std::setfill ('0')
                      << uPlci << std::dec
                      << ") are already assigned to connection id "
                      << g_aauMapPlciToConnID [uCtlr] [uPlci]
                      << " when trying to assign new connection id " << uConnID
                      << " for Connect-Indication";
            SendDummyResponse (pCapiMsg);
            continue;
         }
         g_aauMapPlciToConnID [uCtlr] [uPlci] = uConnID;
      }
      else if (pCapiMsg->head.wCmd == CAPI_CONFIRM (C_CONNECT))
      {
         // got connect-conf, find the corresponding state machine object by
         // searching for an object with a Connect-Request out and the same
         // last request message number
         for (uConnID = 0; uConnID < g_aStateMachines.size (); ++uConnID)
         {
            if (g_aStateMachines [uConnID] != NULL &&
                g_aStateMachines [uConnID]->IsConnectRequestPending () &&
                g_aStateMachines [uConnID]->GetCurrentRequestMessageNumber () ==
                   pCapiMsg->head.wNum)
            {
               break;
            }
         }
         if (uConnID >= g_aStateMachines.size () ||
             g_aStateMachines [uConnID] == NULL)
         {
            // there is no connection object with that connection id, ignore
            // the message
            std::cerr << e_oConfig.GetProgName ()
                      << ": Unable to find connection object for Connect-Conf (message number "
                      << (unsigned) (pCapiMsg->head.wNum) << ")"
                      << std::endl;
            continue;
         }
         poSm = g_aStateMachines [uConnID];
         
         // set mapping between new PLCI and the connection ID
         uCtlr = CAPI_GET_CTLR_FROM_CID (pCapiMsg->head.dwCid);
         uPlci = CAPI_GET_PLCI_FROM_CID (pCapiMsg->head.dwCid);
         if (g_aauMapPlciToConnID [uCtlr] [uPlci] != UINT_MAX)
         {
            std::cerr << e_oConfig.GetProgName ()
                      << ": Controller and PLCI (" << uCtlr << ", 0x"
                      << std::hex << std::setw (4) << std::setfill ('0')
                      << uPlci << std::dec
                      << ") are already assigned to connection id "
                      << g_aauMapPlciToConnID [uCtlr] [uPlci]
                      << " when trying to assign new connection id " << uConnID
                      << " for Connect-Confirm";
         }
         g_aauMapPlciToConnID [CAPI_GET_CTLR_FROM_CID (pCapiMsg->head.dwCid)]
                              [CAPI_GET_PLCI_FROM_CID (pCapiMsg->head.dwCid)] =
            poSm->GetConnectionID ();
      }
      else
      {
         // got general message, find the corresponding state machine object
         // by the PLCI
         uCtlr = CAPI_GET_CTLR_FROM_CID (pCapiMsg->head.dwCid);
         uPlci = CAPI_GET_PLCI_FROM_CID (pCapiMsg->head.dwCid);
         if (uCtlr >= ARRAY_COUNT (g_aauMapPlciToConnID) ||
             uPlci >= ARRAY_COUNT (g_aauMapPlciToConnID [uCtlr]) ||
             g_aauMapPlciToConnID [uCtlr] [uPlci] >= g_aStateMachines.size () ||
             g_aStateMachines [g_aauMapPlciToConnID [uCtlr] [uPlci]] == NULL)
         {
            // there is no connection object with that connection id, ignore
            // the message
            std::cerr << e_oConfig.GetProgName ()
                      << ": Unable to find connection object for CAPI message "
                      << CapiUt_GetCapiMsgCmdName (pCapiMsg->head.wCmd) << ' '
                      << CapiUt_GetCapiMsgSubCmdName (pCapiMsg->head.wCmd)
                      << " (PLCI 0x"
                      << std::hex << std::setfill ('0') << std::setw (8)
                      << pCapiMsg->head.dwCid << std::dec
                      << ", stored connection id "
                      << g_aauMapPlciToConnID [uCtlr] [uPlci] << ")"
                      << std::endl;
            SendDummyResponse (pCapiMsg);
            continue;
         }
         uConnID = g_aauMapPlciToConnID [uCtlr] [uPlci];
         poSm = g_aStateMachines [uConnID];
      }
      
      // if state machine object found, pass it the message
      if (poSm)
      {
         poSm->HandleCapiMessage (pCapiMsg);

         // if the connection object is now idle, destroy it and maintain the
         // number of open connections
         if (poSm->GetConnectionState () != CCapiStateMachine::CS_RUNNING)
         {
            HandleFinishedConnection (poSm);
            fCreateNewOutgoingCall = true;
         }
      }
      // else send dummy response and ignore it
      else
      {
         SendDummyResponse (pCapiMsg);
         continue;
      }
      
      // check if a new connect-req should be sent (and do it in this case);
      // this is only performed after receiving a connect-conf or a
      // disconnect-ind (i.e. after a terminated connection)
      if (pCapiMsg->head.wCmd == CAPI_CONFIRM (C_CONNECT) ||
          pCapiMsg->head.wCmd == CAPI_INDICAT (C_DISCONNECT))
      {
         fCreateNewOutgoingCall = true;
      }
      if (fCreateNewOutgoingCall)
      {
         CreateNewOutgoingCall ();
         fCreateNewOutgoingCall = false;
      }
      
      // if we only do a limited number of outgoing calls (self connection,
      // too), check if all connection objects are IDLE (and then let us
      // terminate)
      if (g_nMaxTotalOutgoingConnections != 0 &&
          g_nTotalOutgoingConnections >= g_nMaxTotalOutgoingConnections)
      {
         if (g_nNumIncomingConnections == 0 && g_nNumOutgoingConnections == 0)
         {
            g_fTerminate = true;
         }
      }
   }
   
   std::cout << e_oConfig.GetProgName () << ": Work loop finished"
             << std::endl;
   
   // cleanup dynamic data
   for (i = 0; (size_t) i < g_aStateMachines.size (); i++)
   {
      delete g_aStateMachines [i];
      g_aStateMachines [i] = NULL;
   }
   g_aStateMachines.clear ();
   
   return (0);
} // DoWorkLoop





/**
 * Sigterm and sigint signal handler.
 */

static void SignalHandler
   (int iSig)
{
   CCapiStateMachine *poSm;
   int                i;
   
   std::cout << e_oConfig.GetProgName () << ": Got "
             << ((iSig == SIGTERM)
                    ? "termination"
                    : ((iSig == SIGINT)
                          ? "interrupt"
                          : "unknown"))
             << " signal, will abort all connections..."
             << std::endl;

   // tell the work loop to terminate when all connections are finished
   g_fTerminate = true;

   // in case of sigterm all connections must be terminated
   for (i = 0; (size_t) i < g_aStateMachines.size (); i++)
   {
      if (g_aStateMachines [i] != NULL)
      {
         poSm = g_aStateMachines [i];
         if (poSm->GetConnectionState () != CCapiStateMachine::CS_RUNNING)
         {
            HandleFinishedConnection (poSm);
            continue;
         }
         (void) poSm->TerminateConnection ();
         if (poSm->GetConnectionState () != CCapiStateMachine::CS_RUNNING)
         {
            // connection alread terminated
            HandleFinishedConnection (poSm);
         }
      }
   }
   
   if (g_nNumIncomingConnections == 0 && g_nNumOutgoingConnections == 0)
   {
      std::cout << e_oConfig.GetProgName ()
                << ": All connections finished, will terminate"
                << std::endl;
   }
   else
   {
      std::cout << e_oConfig.GetProgName ()
                << ": Waiting for finished connections (still "
                << g_nNumIncomingConnections << " incoming and "
                << g_nNumOutgoingConnections << " outgoing active)..."
                << std::endl;
   }
   
} // SignalHandler





/**
 * Determine the overall maximum of available B-channels.
 */

static size_t GetMaxNumBChannels (void)
{
   CAPIProfileBuffer_t profile;
   size_t              nCtlr;
   size_t              nBChannels;
   int                 i;
   unsigned            uRes;

   // determine the number of installed controllers
   uRes = capi20_get_profile (0, &profile);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " getting the number of available controllers"
                << std::endl;
      return (1);
   }
   nCtlr = profile.wCtlr;

   // walk through the list of controllers and sum up the number of B-channels
   for (i = 1, nBChannels = 0; i <= 127 && nCtlr > 0; ++i)
   {
      uRes = capi20_get_profile ((unsigned) i, &profile);
      if (uRes != CAPI_OK)
      {
         continue;
      }
      nBChannels += (size_t) (profile.wNumBChannels);
      --nCtlr;
   }
   if (nCtlr > 0)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Unable to find all installed controllers, "
                << nCtlr << " left"
                << std::endl;
   }
   if (nBChannels == 0)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": No B-channels available over all installed controllers"
                << std::endl;
   }
   
   return (nBChannels);
} // GetMaxNumBChannels





/*
        create a file with a test pattern for a self connection
        -------------------------------------------------------
*/

static bool CreateTestPatternFile
   (const char *pszFileName)
{
   int fd;
   int i;
   int iRes;
   
   fd = open (pszFileName, O_WRONLY | O_CREAT | O_TRUNC,
              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
   if (fd == -1)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Error creating test pattern source file \""
                << pszFileName << "\": "
                << strerror (errno) << " (" << errno << ")"
                << std::endl;
      return (false);
   }
   
   for (i = NUM_TEST_PATTERN_SEQUENCES; i > 0; --i)
   {
      iRes = write (fd, ALL_TEST_PATTERNS, sizeof (ALL_TEST_PATTERNS) - 1);
      if (iRes != sizeof (ALL_TEST_PATTERNS) - 1)
      {
         std::cerr << e_oConfig.GetProgName ()
                   << ": Error writing test patterns to file \""
                   << pszFileName << "\": "
                   << strerror (errno) << " (" << errno << ")"
                   << std::endl;
         (void) close (fd);
         return (false);
      }
      iRes = write (fd, "\n", 1);
      if (iRes != 1)
      {
         std::cerr << e_oConfig.GetProgName ()
                   << ": Error writing test patterns to file \""
                   << pszFileName << "\": "
                   << strerror (errno) << " (" << errno << ")"
                   << std::endl;
         (void) close (fd);
         return (false);
      }
   }
   
   if (close (fd) != 0)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Error closing test pattern file \""
                << pszFileName << "\": "
                << strerror (errno) << " (" << errno << ")"
                << std::endl;
      return (false);
   }
   
   return (true);
} // CreateTestPatternFile





/*
        check the contents of a file for a self connection
        --------------------------------------------------
*/

static bool CheckTestPatternFile
   (const char *pszFileName)
{
   int  fd;
   int  i;
   int  iRes;
   char szBuf [sizeof (ALL_TEST_PATTERNS) + 1];
   
   fd = open (pszFileName, O_RDONLY);
   if (fd == -1)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Error opening test pattern destination file \""
                << pszFileName << "\": "
                << strerror (errno) << " (" << errno << ")"
                << std::endl;
      return (false);
   }
   
   for (i = NUM_TEST_PATTERN_SEQUENCES; i > 0; --i)
   {
      iRes = read (fd, szBuf, sizeof (szBuf) - 1);
      if (iRes != sizeof (szBuf) - 1)
      {
         std::cerr << e_oConfig.GetProgName ()
                   << ": Error reading test patterns from file \""
                   << pszFileName << "\" (read only " << (unsigned) iRes
                   << " of " << sizeof (szBuf) - 1 << " bytes): "
                   << strerror (errno) << " (" << errno << ")"
                   << std::endl;
         (void) close (fd);
         return (false);
      }
      if (szBuf [sizeof (szBuf) - 2] != '\n')
      {
         std::cerr << e_oConfig.GetProgName ()
                   << ": Self connection failed, test pattern not followed by newline"
                   << std::endl;
         (void) close (fd);
         return (false);
      }
      szBuf [sizeof (szBuf) - 2] = '\0';
      if (strcmp (szBuf, ALL_TEST_PATTERNS) != 0)
      {
         std::cerr << e_oConfig.GetProgName ()
                   << ": Self connection failed, test pattern not received correctly"
                   << std::endl;
         (void) close (fd);
         return (false);
      }
   }
   
   if (close (fd) != 0)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Error closing test pattern file \"" << pszFileName
                << "\": " << strerror (errno) << " (" << errno << ")"
                << std::endl;
      return (false);
   }
   
   return (true);
} // CheckTestPatternFile





/*
        create a new outgoing connection object and start connection
        ------------------------------------------------------------
*/

static void CreateNewOutgoingCall (void)
{
   unsigned                             uController;
   CCapiStateMachine::ConnectionType_t  connType;
   std::string                          strSrcFileName;
   std::string                          strDstPhoneNumber;
   std::string                          strSrcPhoneNumber;
   CCapiStateMachine                   *poSm;
   int                                  i;
   
   // check if a new connection is allowed
   if (g_fTerminate ||
       g_nNumOutgoingConnections >= g_nMaxOutgoingConnections ||
       (g_nMaxTotalOutgoingConnections != 0 &&
        g_nTotalOutgoingConnections >= g_nMaxTotalOutgoingConnections))
   {
      return;
   }
   
   // find unused entry in the array of connection object pointers
   for (i = 0; (size_t) i < g_aStateMachines.size (); i++)
   {
      if (g_aStateMachines [i] == NULL)
      {
         break;
      }
   }
   if ((size_t) i >= g_aStateMachines.size ())
   {
      // no free entry, enlarge the array
      poSm = NULL;
      g_aStateMachines.resize (i + 1, poSm);
   }
   // i is now the index to a free array entry

   // get the call data for the job with id i
   assert (g_pBatch);
   g_pBatch->GetJobData ((unsigned) i,
                         uController,
                         connType,
                         strSrcFileName,
                         strDstPhoneNumber,
                         strSrcPhoneNumber);
   
   // create a new connection object
   poSm = new CCapiStateMachine ((unsigned) i,
                                 g_uApplID,
                                 uController,
                                 strDstPhoneNumber,
                                 strSrcPhoneNumber,
                                 connType,
                                 false,
                                 CCapiStateMachine::IDD_IGNORE,
                                 std::string (),
                                 strSrcFileName);
   if (! poSm)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Out of memory for new outgoing connection object"
                << std::endl;
      return;

   }
   
   // set additional options for the connection
   poSm->SetVerboseOutput (e_oConfig.GetVerboseOutput ());
   poSm->SetSendUUData (e_oConfig.GetSendUUData ());
   
   // store the new object into the array entry
   g_aStateMachines [i] = poSm;
   
   std::cout << e_oConfig.GetProgName ()
             << ": New outgoing call object created with connection id "
             << poSm->GetConnectionID () << ", start call"
             << std::endl;
           
   // start the connection (do a connect-req)
   (void) poSm->DoConnectRequest ();
   
   // maintain the current number of connections
   // Note: Because the handler function for fininshed connections will
   //       decrement the connection counter, we must increment it even if the
   //       Connect-Request was not successful.
   g_nTotalOutgoingConnections++;
   g_nNumOutgoingConnections++;
   if (poSm->GetConnectionState () != CCapiStateMachine::CS_RUNNING)
   {
      HandleFinishedConnection (poSm);
   }
   
} // CreateNewOutGoingCall





/*
        create a new incoming connection object
        ---------------------------------------
*/

static CCapiStateMachine *CreateNewIncomingCallObject
   (unsigned uController)
{
   CCapiStateMachine *poSm;
   int                i;
   
   // check if a new connection is allowed
   if (g_fTerminate)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Unable to handle another connection, currently terminating"
                << std::endl;
      return (NULL);
   }
   if (g_nNumIncomingConnections >= g_nMaxIncomingConnections)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Unable to handle another connection, "
                << g_nNumIncomingConnections << " already running"
                << std::endl;
      return (NULL);
   }
   
   // find unused entry in the array of connection object pointers
   for (i = 0; (size_t) i < g_aStateMachines.size (); i++)
   {
      if (g_aStateMachines [i] == NULL)
      {
         break;
      }
   }
   if ((size_t) i >= g_aStateMachines.size ())
   {
      // no free entry, enlarge the array
      poSm = NULL;
      g_aStateMachines.resize (i + 1, poSm);
   }
   // i is now the index to a free array entry
      
   // create a new connection object
   CCapiStateMachine::ConnectionType_t connType =
      e_oConfig.GetConnectionType ();
   std::ostringstream ossFileName;
   if (g_strDstFilePattern.length () != 0)
   {
      g_uCurrIncFileNumber = (g_uCurrIncFileNumber + 1) % 1000;
      ossFileName << g_strDstFilePattern << '.' << g_uCurrIncFileNumber;
   }
   poSm = new CCapiStateMachine ((unsigned) i,
                                 g_uApplID,
                                 uController,
                                 e_oConfig.GetDstPhoneNumber (),
                                 e_oConfig.GetSrcPhoneNumber (),
                                 connType,
                                 true,
                                 (g_strDstFilePattern.length () > 0)
                                    ? CCapiStateMachine::IDD_STORE_TO_FILE
                                    : (e_oConfig.GetEchoData ()
                                          ? CCapiStateMachine::IDD_ECHO
                                          : CCapiStateMachine::IDD_IGNORE),
                                 ossFileName.str (),
                                 std::string ());
   if (! poSm)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Out of memory for new incoming connection object"
                << std::endl;
      return (NULL);
   }
   
   // set additional options for the connection
   poSm->SetVerboseOutput (e_oConfig.GetVerboseOutput ());
   poSm->SetSendUUData (e_oConfig.GetSendUUData ());
   
   // store the new object into the array entry
   g_aStateMachines [i] = poSm;
   
   // maintain the current number of connections
   g_nTotalIncomingConnections++;
   g_nNumIncomingConnections++;

   std::cout << e_oConfig.GetProgName ()
             << ": New incoming call object created, connection id "
             << poSm->GetConnectionID ()
             << std::endl;
   
   return (poSm);
} // CreateNewIncomingCallObject





/**
 * Perform cleanup and counting for finished connections.
 */

static void HandleFinishedConnection
   (CCapiStateMachine *pSm)
{
   unsigned uConnID;
   unsigned uCtlr;
   unsigned uPlci;
   
   // check for valid parameters
   assert (pSm != NULL);
   
   // get some data for to be accessed multiple times
   uConnID = pSm->GetConnectionID ();
   uCtlr = pSm->GetController ();
   uPlci = CAPI_GET_PLCI_FROM_CID (pSm->GetPlci ());

   // after the connection is completed first execute the finished
   // handler; it may set the connection object state from O.K. to
   // failed
   if (g_pfnConnectionFinished != NULL)
   {
      g_pfnConnectionFinished (pSm);
   }

   // undo the PLCI-to-connection id mapping
   if (uCtlr >= ARRAY_COUNT (g_aauMapPlciToConnID) ||
       uPlci >= ARRAY_COUNT (g_aauMapPlciToConnID [uCtlr]) ||
       g_aauMapPlciToConnID [uCtlr] [uPlci] != uConnID)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": PLCI 0x"
                << std::hex << std::setw (4) << std::setfill ('0')
                << pSm->GetPlci () << std::dec
                << " is not assigned to connection id " << uConnID
                << " at the end of the connection";
   }
   else
   {
      g_aauMapPlciToConnID [uCtlr] [uPlci] = UINT_MAX;
   }

   // update statistic counters
   if (pSm->GetDirectionIn ())
   {
      g_nNumIncomingConnections--;
      if (pSm->GetFailed ())
      {
         ++g_nFailedIncomingConnections;
      }
      else
      {
         ++g_nSuccessfulIncomingConnections;
      }
   }
   else
   {
      g_nNumOutgoingConnections--;
      if (pSm->GetFailed ())
      {
         ++g_nFailedOutgoingConnections;
      }
      else
      {
         ++g_nSuccessfulOutgoingConnections;
      }
   }

   // now release the connection object
   delete pSm;
   g_aStateMachines [uConnID] = NULL;

} /* HandleFinishedConnection */





/**
 * Follow-up treatment for self connections.
 */

static void SelfConnectionFinished
   (CCapiStateMachine *pSm)
{
   // check for valid parameters
   assert (pSm != NULL);
   
   // we only check the incoming connection to have received the correct data
   if (! pSm->GetDirectionIn ())
   {
      return;
   }
   
   // check if the destination file exists
   if (access (pSm->GetDstFileName ().c_str (), R_OK) != 0)
   {
      std::cerr << e_oConfig.GetProgName ()
                << ": Self connection failed, destination file \""
                << pSm->GetDstFileName () << "\" does not exist: "
                << strerror (errno) << " (" << errno << ")"
                << std::endl;
      pSm->SetFailed ();
      return;
   }
   
   // compare the two files
   if (! CheckTestPatternFile (pSm->GetDstFileName ().c_str ()))
   {
      pSm->SetFailed ();
      return;
   }
   
   std::cout << e_oConfig.GetProgName ()
             << ": Self connection successful" << std::endl;
   
} // SelfConnectionFinished





/**
 * Follow-up treatment for incoming connections.
 */

static void IncomingConnectionFinished
   (CCapiStateMachine *pSm)
{
   // currently nothing to do
   
} // IncomingConnectionFinished





/**
 * Follow-up treatment for outgoing connections.
 */

static void OutgoingConnectionFinished
   (CCapiStateMachine *pSm)
{
   // currently nothing to do
   
} // OutgoingConnectionFinished





/*
        send a listen request message
        -----------------------------
*/

static void SendListenRequest
   (unsigned uController,
    bool     fIncomingCalls)
{
   CAPIMsg_t      capiMsg;
   unsigned char *p;
   unsigned       uRes;
   
   std::cout << e_oConfig.GetProgName ()
             << ": Sending listen request for "
             << (fIncomingCalls
                    ? "incoming calls"
                    : "network events")
             << std::endl;
   
   capiMsg.head.wApp  = g_uApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_LISTEN);
   capiMsg.head.wNum  = 0x7FFF;
   capiMsg.head.dwCid = uController;
   capiMsg.info.listen_req.dwInfoMask = CAPI_INFO_MASK_CAUSE |
                                        CAPI_INFO_MASK_DATE_TIME |
                                        CAPI_INFO_MASK_DISPLAY |
                                        CAPI_INFO_MASK_USER_USER |
                                        CAPI_INFO_MASK_CALL_PROGRESSION |
                                        CAPI_INFO_MASK_FACILITY |
                                        CAPI_INFO_MASK_CHARGING |
                                        CAPI_INFO_MASK_CALLED_PARTY_NUMBER |
                                        CAPI_INFO_MASK_CHANNEL_ID |
                                        CAPI_INFO_MASK_REDIRECTION_INFO |
                                        CAPI_INFO_MASK_SENDING_COMPLETE;
   if (fIncomingCalls)
   {
      capiMsg.info.listen_req.dwCipMask = CAPI_CIP_MASK_ANY |
                                          CAPI_CIP_MASK_SPEECH |
                                          CAPI_CIP_MASK_UNRESTRICTED_DATA |
                                          CAPI_CIP_MASK_3100Hz_AUDIO |
                                          CAPI_CIP_MASK_7kHz_AUDIO |
                                          CAPI_CIP_MASK_UNRESTR_DATA_TONES |
                                          CAPI_CIP_MASK_TELEPHONY |
                                          CAPI_CIP_MASK_FAX_G2_G3 |
                                          CAPI_CIP_MASK_7kHz_TELEPHONY;
      capiMsg.info.listen_req.dwCipMask2 = CAPI_CIP_MASK2_UNLISTEN;
   }
   else
   {
      capiMsg.info.listen_req.dwCipMask = CAPI_CIP_MASK_UNLISTEN;
      capiMsg.info.listen_req.dwCipMask2 = CAPI_CIP_MASK2_UNLISTEN;
   }
   p = (unsigned char *) &(capiMsg.info.listen_req.dwCipMask2) +
       sizeof (capiMsg.info.listen_req.dwCipMask2);
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   capiMsg.head.wLen = (u_int16_t) (p - (unsigned char *) &capiMsg);
   
   uRes = capi20_put_message (g_uApplID, &capiMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Listen-Req"
                << std::endl;
   }
   
} // SendListenRequest





/*
        send a dummy CAPI response message
        ----------------------------------
*/

static void SendDummyResponse
   (const CAPIMsg_t *pCapiIndMsg)
{
   CAPIMsg_t      capiRespMsg;
   unsigned char *p;
   unsigned       uRes;
   
   // check for real indication
   if ((pCapiIndMsg->head.wCmd & CAPI_CMDMASK_SUBCMD) != C_IND)
   {
      return;
   }
   
   // some messages must be handled specially
   if (pCapiIndMsg->head.wCmd == CAPI_INDICAT (C_CONNECT))
   {
      // a Connect-Indication must be answered with a Connect-Response to let
      // the controller ignore the call
      capiRespMsg.head = pCapiIndMsg->head;
      capiRespMsg.head.wCmd = CAPI_RESPONSE (C_CONNECT);
      capiRespMsg.info.connect_resp.wReject = CAPI_CALL_IGNORE;
      p = (unsigned char *) &(capiRespMsg.info.connect_resp.wReject) +
          sizeof (capiRespMsg.info.connect_resp.wReject);
      p = CapiUt_EnterEmptyStruct (p);  // default BProtocol
      p = CapiUt_EnterEmptyStruct (p);  // no connected party number
      p = CapiUt_EnterEmptyStruct (p);  // no connected party subaddress
      p = CapiUt_EnterEmptyStruct (p);  // no LLC
      p = CapiUt_EnterEmptyStruct (p);  // no AddtionalInfo
      capiRespMsg.head.wLen = (u_int16_t) (p - (unsigned char *) &capiRespMsg);
   }
   else if (pCapiIndMsg->head.wCmd == CAPI_INDICAT (C_FACILITY))
   {
      // for a Facility-Indication we at least send a response with the same
      // selector
      capiRespMsg.head = pCapiIndMsg->head;
      capiRespMsg.head.wCmd = CAPI_RESPONSE (C_FACILITY);
      capiRespMsg.info.facility_resp.wSelector =
         pCapiIndMsg->info.facility_ind.wSelector;
      p = (unsigned char *) &(capiRespMsg.info.facility_resp.wSelector) +
          sizeof (capiRespMsg.info.facility_resp.wSelector);
      p = CapiUt_EnterEmptyStruct (p);  // no facility parameters
      capiRespMsg.head.wLen = (u_int16_t) (p - (unsigned char *) &capiRespMsg);
   }
   else if (pCapiIndMsg->head.wCmd == CAPI_INDICAT (C_DATA_B3))
   {
      // for a Data-B3-Indication we must send a Data-B3-Response with the
      // correct data block handle to not cause an out-of-memory situation in
      // the controller
      capiRespMsg.head = pCapiIndMsg->head;
      capiRespMsg.head.wCmd = CAPI_RESPONSE (C_DATA_B3);
      capiRespMsg.info.data_b3_resp.wHandle =
         pCapiIndMsg->info.data_b3_ind.wHandle;
      capiRespMsg.head.wLen = sizeof (capiRespMsg.head) +
                              sizeof (capiRespMsg.info.data_b3_resp);
   }
   else
   {
      // for a dummy response to a general indication only the message header
      // is needed
      capiRespMsg.head = pCapiIndMsg->head;
      capiRespMsg.head.wCmd =
         CAPI_RESPONSE (pCapiIndMsg->head.wCmd & CAPI_CMDMASK_COMMAND);
      capiRespMsg.head.wLen = sizeof (capiRespMsg.head);
   }
   
   // send out the CAPI message created earlier
   uRes = capi20_put_message (g_uApplID, &capiRespMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << e_oConfig.GetProgName () << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending dummy response "
                << CapiUt_GetCapiMsgCmdName (CAPI_GET_CMD (&capiRespMsg)) << ' '
                << CapiUt_GetCapiMsgSubCmdName (CAPI_GET_CMD (&capiRespMsg))
                << std::endl;
   }
   
} // SendDummyResponse
