 /**
 * @file capism.cc
 *
 * CapiStateMachine - Class implementation for a (very) simple CAPI state
 * machine.
 *
 * Copyright: 2000-2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: capism.cc,v 1.1.2.1 2006/02/28 21:35:38 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 <time.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <capi20.h>
#include <capi_bsd.h>
#include <errno.h>
#include <assert.h>

/* import includes */

#define __CAPISM__

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





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





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





// --- some constants ---

static const char *UUDATA_CONN_TYPE_PREFIX = "CT=";
static const char *UUDATA_DATA_LEN_PREFIX  = "DT=";
static const char *UUDATA_CHECKSUM_PREFIX  = "CS=";



/// Translation table from connection state values to strings.
static const char *g_apszConnStateNames [] =
   {
      "Idle",           // CS_IDLE
      "Error-Idle",     // CS_ERROR_IDLE
      "Running"         // CS_RUNNING
   };

/// Translation table from incoming data destination values to strings.
static const char *g_apszIncDataDestNames [] =
   {
      "Ignore-Data",            // IDD_IGNORE
      "Echo-Data",              // IDD_ECHO
      "Store-Data-to-File"      // IDD_STORE_TO_FILE
   };

/// Translation table from connection type values to strings.
static const char *g_apszConnTypeNames [] =
   {
      "Auto",           // CT_AUTO
      "Voice",          // CT_VOICE
      "Data/HDLC",      // CT_DATA_HDLC
      "Data/X.75",      // CT_DATA_X75
      "Fax-g3"          // CT_FAXG3
   };

/// Translation table from PLCI state values to strings.
static const char *g_apszPlciStateNames [] =
   {
      "P-0",    // PLCI_STATE_P0
      "P-0.1",  // PLCI_STATE_P0_1
      "P-1",    // PLCI_STATE_P1
      "P-2",    // PLCI_STATE_P2
      "P-3",    // PLCI_STATE_P3
      "P-4",    // PLCI_STATE_P4
      "P-ACT",  // PLCI_STATE_PACT
      "P-5",    // PLCI_STATE_P5
      "P-6"     // PLCI_STATE_P6
   };

/// Translation table from NCCI state values to strings.
static const char *g_apszNcciStateNames [] =
   {
      "N-0",    // NCCI_STATE_N0
      "N-0.1",  // NCCI_STATE_N0_1
      "N-1",    // NCCI_STATE_N1
      "N-2",    // NCCI_STATE_N2
      "N-ACT",  // NCCI_STATE_NACT
      "N-3",    // NCCI_STATE_N3
      "N-4",    // NCCI_STATE_N4
      "N-5"     // NCCI_STATE_N5
   };





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





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





/**
 * Operator to print out a connection state value as a string.
 */

std::ostream & operator<<
   (std::ostream                         &os,
    CCapiStateMachine::ConnectionState_t  connState)
{
   if ((size_t) connState >= ARRAY_COUNT (g_apszConnStateNames) ||
       g_apszConnStateNames [(size_t) connState] == NULL)
   {
      os << "<<Unknown " << (int) connState << ">>";
   }
   os << g_apszConnStateNames [(size_t) connState];
   return (os);
} // operator<< (CCapiStateMachine::ConnectionState_t)





/**
 * Operator to print out the destination of incoming data as a string.
 */

std::ostream & operator<<
   (std::ostream                     &os,
    CCapiStateMachine::IncDataDest_t  incDataDst)
{
   if ((size_t) incDataDst >= ARRAY_COUNT (g_apszIncDataDestNames) ||
       g_apszIncDataDestNames [(size_t) incDataDst] == NULL)
   {
      os << "<<Unknown " << (int) incDataDst << ">>";
   }
   os << g_apszIncDataDestNames [(size_t) incDataDst];
   return (os);
} // operator<< (CCapiStateMachine::IncDataDest_t)





/**
 * Operator to print out a connection type value as a string.
 */

std::ostream & operator<<
   (std::ostream                        &os,
    CCapiStateMachine::ConnectionType_t  connType)
{
   if ((size_t) connType >= ARRAY_COUNT (g_apszConnTypeNames) ||
       g_apszConnTypeNames [(size_t) connType] == NULL)
   {
      os << "<<Unknown " << (int) connType << ">>";
   }
   os << g_apszConnTypeNames [(size_t) connType];
   return (os);
} // operator<< (CCapiStateMachine::ConnectionType_t)





/**
 * Operator to print out a PLCI state value as a string.
 */

std::ostream & operator<<
   (std::ostream                   &os,
    CCapiStateMachine::PlciState_t  plciState)
{
   if ((size_t) plciState >= ARRAY_COUNT (g_apszPlciStateNames) ||
       g_apszPlciStateNames [(size_t) plciState] == NULL)
   {
      os << "<<Unknown " << (int) plciState << ">>";
   }
   os << g_apszPlciStateNames [(size_t) plciState];
   return (os);
} // operator<< (CCapiStateMachine::PlciState_t)





/**
 * Operator to print out a NCCI state value as a string.
 */
std::ostream & operator<<
   (std::ostream                   &os,
    CCapiStateMachine::NcciState_t  ncciState)
{
   if ((size_t) ncciState >= ARRAY_COUNT (g_apszNcciStateNames) ||
       g_apszNcciStateNames [(size_t) ncciState] == NULL)
   {
      os << "<<Unknown " << (int) ncciState << ">>";
   }
   os << g_apszNcciStateNames [(size_t) ncciState];
   return (os);
} // operator<< (CCapiStateMachine::NcciState_t)





// === implementation of class members ===================================





// --- class for a simple CAPI state machine -----------------------------

/// The global continuous message numbering counter.
u_int16_t CCapiStateMachine::m_uhGlobalMessageCounter = 0;





/*
        constructor
        -----------
*/

CCapiStateMachine::CCapiStateMachine
   (unsigned           uConnID,
    unsigned           uCapiApplID,
    unsigned           uController,
    const std::string &strCalledNumber,
    const std::string &strCallingNumber,
    ConnectionType_t   connType,
    bool               fDirectionIn,
    IncDataDest_t      incDataDest,
    const std::string &strDstFileName,
    const std::string &strSrcFileName)
{
   // take all construction parameters
   m_uConnID          = uConnID;
   m_uCapiApplID      = uCapiApplID;
   m_uController      = uController;
   m_fDirectionIn     = fDirectionIn;
   m_strCalledNumber  = strCalledNumber;
   m_strCallingNumber = strCallingNumber;
   m_connType         = connType;
   m_incDataDest      = incDataDest;
   m_strDstFileName   = strDstFileName;
   m_strSrcFileName   = strSrcFileName;
   
   // initialize the state value for the object to idle, may be overwritten if
   // an error occurs
   m_connState  = CS_IDLE;
   m_fWasActive = false;
   m_fConnOk    = true;
   
   // initialize remaining object members
   m_fVerboseOutput = false;
   m_fdDstFile = -1;
   m_fdSrcFile = -1;
   m_fSrcFaxG3Sff = false;
   m_uSrcFaxG3Resolution = CAPI_FAXG3_RESOLUTION_FINE;
   switch (connType)
   {
      case CT_AUTO:
         // auto may only be used for incoming calls
         if (! m_fDirectionIn)
         {
            std::cerr << "Connection " << m_uConnID
                      << ": Connection type \"" << connType
                      << "\" may only be used for incoming calls"
                      << std::endl;
            m_connState = CS_ERROR_IDLE;
            return;
         }
         // initialize protocols with default values, maybe overwritten when
         // evaluating connect-ind
         m_uB1Prot = CAPI_B1_HDLC_64;
         m_uB2Prot = CAPI_B2_ISO7776_X75_SLP;
         m_uB3Prot = CAPI_B3_TRANSPARENT;
         break;
         
      case CT_VOICE:
         m_uB1Prot = CAPI_B1_TRANSPARENT_64;
         m_uB2Prot = CAPI_B2_TRANSPARENT;
         m_uB3Prot = CAPI_B3_TRANSPARENT;
         break;
         
      case CT_DATA_HDLC:
         m_uB1Prot = CAPI_B1_HDLC_64;
         m_uB2Prot = CAPI_B2_TRANSPARENT;
         m_uB3Prot = CAPI_B3_TRANSPARENT;
         break;
         
      case CT_DATA_X75:
         m_uB1Prot = CAPI_B1_HDLC_64;
         m_uB2Prot = CAPI_B2_ISO7776_X75_SLP;
         m_uB3Prot = CAPI_B3_TRANSPARENT;
         break;
         
      case CT_FAXG3:
         m_uB1Prot = CAPI_B1_T30_MODEM;
         m_uB2Prot = CAPI_B2_T30_FAX_G3;
         m_uB3Prot = CAPI_B3_T30_FAX_G3;// maybe we should switch to ..._EXT?
         break;
         
      default:
         std::cerr << "Connection " << m_uConnID
                   << ": Unknown connection type " << (int) connType
                   << std::endl;
         m_connState = CS_ERROR_IDLE;
         return;
   }
   m_plciState             = PLCI_STATE_P0;
   m_dwPlci                = 0;
   m_fActiveDisconnect     = (m_fDirectionIn) ? false : true;
   m_ncciState             = NCCI_STATE_N0;
   m_dwNcci                = 0;
   m_fActiveDisconnectB3   = (m_fDirectionIn) ? false : true;
   memset (m_aaucDataBuf, 0, sizeof (m_aaucDataBuf));
   m_iCurrBlockIdx         = 0;
   m_uCurrDataHandle       = 0;
   m_uConfirmedDataHandle  = 0;
   m_uCurrReqMsgNum        = 0;
   m_uConnIndMsgNum        = 0;
   m_fSendUUData           = false;
   m_nNumBytesIncoming     = 0;
   m_uhChecksumIncoming    = 0;
   m_nNumBytesOutgoing     = 0;
   m_uhChecksumOutgoing    = 0;
   m_nNumBytesFromPartner  = 0;
   m_uhChecksumFromPartner = 0;
   
   // now do some parameter checking
   
   // for outgoing connections a source file must be specified, so open it
   if (! m_fDirectionIn)
   {
      m_fdSrcFile = open (m_strSrcFileName.c_str (), O_RDONLY);
      if (m_fdSrcFile == -1)
      {
         std::cerr << "Connection " << m_uConnID
                   << ": Error opening source file \"" << m_strSrcFileName
                   << "\" for outgoing connection"
                   << std::endl;
         m_connState = CS_ERROR_IDLE;
         return;
      }
      
      // if the connection type is fax-g3, we must check if the document is of
      // format SFF or ASCII (everything but SFF is supposed to be ASCII)
      unsigned char aucBuf [1024];
      int           iRes;
      iRes = read (m_fdSrcFile, aucBuf, sizeof (aucBuf));
      if (iRes >= 4 &&
          aucBuf [0] == 0x53 &&
          aucBuf [1] == 0x66 &&
          aucBuf [2] == 0x66 &&
          aucBuf [3] == 0x66)
      {
         m_fSrcFaxG3Sff = 1;
         
         // for SFF files we must get the resolution from the first page header,
         // the offset for the first page header is at offset 10 in the file
         if (*((unsigned short *) &(aucBuf [10])) <= sizeof (aucBuf) - 4)
         {
            if (aucBuf [*((unsigned short *) &(aucBuf [10]))] == 0)
            {
               m_uSrcFaxG3Resolution = CAPI_FAXG3_RESOLUTION_STANDARD;
            }
         }
      }
      lseek (m_fdSrcFile, 0, SEEK_SET);
   }
   
} // CCapiStateMachine::CCapiStateMachine
          




/*
        destructor
        ----------
*/

CCapiStateMachine::~CCapiStateMachine (void)
{
   if (m_fdSrcFile != -1)
   {
      close (m_fdSrcFile);
      m_fdSrcFile = -1;
   }
   if (m_fdDstFile != -1)
   {
      close (m_fdDstFile);
      m_fdDstFile = -1;
   }
   
} // CCapiStateMachine::~CCapiStateMachine
      




/**
 * Set the flag for verbose output.
 */

void CCapiStateMachine::SetVerboseOutput
   (bool f)
{
   m_fVerboseOutput = f;
   
} // CCapiStateMachine::SetVerboseOutput





/**
 * Set the flag to send out user-user data.
 */

void CCapiStateMachine::SetSendUUData
   (bool f)
{
   m_fSendUUData = f;
   
} // CCapiStateMachine::SetSendUUData





/**
 * Get the connection type value for this connection.
 */

CCapiStateMachine::ConnectionType_t CCapiStateMachine::GetConnectionType (void)
{
   return (m_connType);
} // CCapiStateMachine::GetConnectionType





/**
 * Get the controller this state machine works on.
 */

unsigned CCapiStateMachine::GetController (void)
{
   return (m_uController);
} // CCapiStateMachine::GetController





/**
 * Get the currently active PLCI.
 *
 * If there is currently no PLCI active (e.g. right before the
 * Connect-Indication is delivered for an incoming call), the return
 * value will be null. After the connection is completed (i.e. after
 * receiving Disconnect-Indication), the formerly assigned PLCI will still
 * be returned by this function, the PLCI value will not be overwritten
 * until the object is destroyed.
 */

u_int32_t CCapiStateMachine::GetPlci (void)
{
   return (m_dwPlci);
} // CCapiStateMachine::GetPlci





/**
 * Get the current destination file name.
 */

const std::string &CCapiStateMachine::GetDstFileName (void)
{
   return (m_strDstFileName);
} // CCapiStateMachine::GetDstFileName





/*
        handler for incoming CAPI messages
        ----------------------------------
*/

void CCapiStateMachine::HandleCapiMessage
   (const CAPIMsg_t *pCapiMsg)
{
   unsigned uCmd;
   
   // CAPI messages are only handled in running state. The only exception is a
   // connect-ind in state idle.
   uCmd = (unsigned) (pCapiMsg->head.wCmd);
   if (m_connState != CS_RUNNING &&
       (m_connState != CS_IDLE || uCmd != CAPI_INDICAT (C_CONNECT)))
   {
      std::cerr << "Connection " << m_uConnID
                << ": Unable to handle message "
                << CapiUt_GetCapiMsgCmdName (uCmd) << ' '
                << CapiUt_GetCapiMsgSubCmdName (uCmd)
                << " in state \"" << m_connState << "\""
                << std::endl;
      m_connState = CS_ERROR_IDLE;
      return;
   }
   
   // handle message according to message command
   switch (uCmd)
   {
      case CAPI_CONFIRM (C_ALERT):
         HandleAlertConf (pCapiMsg);
         break;

      case CAPI_INDICAT (C_CONNECT):
         HandleConnectInd (pCapiMsg);
         break;
         
      case CAPI_CONFIRM (C_CONNECT):
         HandleConnectConf (pCapiMsg);
         break;
         
      case CAPI_INDICAT (C_CONNECT_ACTIVE):
         HandleConnectActInd (pCapiMsg);
         break;
         
      case CAPI_INDICAT (C_DISCONNECT):
         HandleDisconnectInd (pCapiMsg);
         break;
         
      case CAPI_CONFIRM (C_DISCONNECT):
         HandleDisconnectConf (pCapiMsg);
         break;
         
      case CAPI_INDICAT (C_INFO_20):
         HandleInfoInd (pCapiMsg);
         break;
         
      case CAPI_CONFIRM (C_INFO_20):
	 HandleInfoConf (pCapiMsg);
	 break;

      case CAPI_INDICAT (C_CONNECT_B3):
         HandleConnectB3Ind (pCapiMsg);
         break;
         
      case CAPI_CONFIRM (C_CONNECT_B3):
         HandleConnectB3Conf (pCapiMsg);
         break;
         
      case CAPI_INDICAT (C_CONNECT_B3_ACTIVE):
         HandleConnectB3ActInd (pCapiMsg);
         break;
         
      case CAPI_INDICAT (C_DISCONNECT_B3):
         HandleDisconnectB3Ind (pCapiMsg);
         break;
         
      case CAPI_CONFIRM (C_DISCONNECT_B3):
         HandleDisconnectB3Conf (pCapiMsg);
         break;
         
      case CAPI_INDICAT (C_DATA_B3):
         HandleDataB3Ind (pCapiMsg);
         break;
         
      case CAPI_CONFIRM (C_DATA_B3):
         HandleDataB3Conf (pCapiMsg);
         break;
      
      default:
         // other CAPI messages should not be received; abort connection if we
         // get one of those
         std::cerr << "Connection " << m_uConnID
                   << ": Got unexpected CAPI message "
                   << CapiUt_GetCapiMsgCmdName (uCmd) << ' '
                   << CapiUt_GetCapiMsgSubCmdName (uCmd)
                   << std::endl;
         m_fConnOk = false;
         SendDisconnectReq ();
         break;
   }
   
} // CCapiStateMachine::HandleCapiMessage





/*
        start an outgoing connection
        ----------------------------
*/

unsigned CCapiStateMachine::DoConnectRequest (void)
{
   // this call is only allowed in idle connection state and idle PLCI state
   if (m_connState != CS_IDLE || m_plciState != PLCI_STATE_P0)
   {
      std::cerr << "Connection " << m_uConnID
                << ": Connect-Req not allowed in connection state \""
                << m_connState << "\" or PLCI state " << m_plciState
                << ", terminate connection"
                << std::endl;
      if (m_plciState != PLCI_STATE_P0)
      {
         (void) SendDisconnectReq ();
         return (CCE_MSG_NOT_ALLOWED_YET);
      }
      m_connState = CS_ERROR_IDLE;
      return (CCE_MSG_NOT_ALLOWED_YET);
   }
   
   // now just forward the public call to our internal function
   std::cout << "Connection " << m_uConnID
             << ": start new outgoing call to \"" << m_strCalledNumber
             << "\" from \"" << m_strCallingNumber << "\", connection type \""
             << m_connType << "\""
             << std::endl;
   m_connState = CS_RUNNING;
   return (SendConnectReq ());
} // CCapiStateMachine::DoConnectRequest





/*
        terminate the connection
        ------------------------
*/

unsigned CCapiStateMachine::TerminateConnection (void)
{
   // just forward the public call to our internal function
   return (SendDisconnectReq ());
} // CCapiStateMachine::TerminateConnection





/**
 * Query if the object is just waiting for a Connect-Confirm message.
 */

bool CCapiStateMachine::IsConnectRequestPending (void)
{
   return (m_plciState == PLCI_STATE_P0_1);
} // CCapiStateMachine::IsConnectRequestPending





/**
 * Get the message number for the last request message sent.
 */

unsigned CCapiStateMachine::GetCurrentRequestMessageNumber (void)
{
   return (m_uCurrReqMsgNum);
} // CCapiStateMachine::GetCurrentRequestMessageNumber





/**
 * Declare a connection object as failed.
 */

void CCapiStateMachine::SetFailed (void)
{
   m_fConnOk = false;
   
} // CCapiStateMachine::SetFailed





/**
 * Query the connection object for failure.
 */

bool CCapiStateMachine::GetFailed (void)
{
   return (! m_fConnOk || m_connState == CS_ERROR_IDLE || ! m_fWasActive);
} // CCapiStateMachine::GetFailed





/**
 * Handle acknowledge of an alerting message for a pending
 * Connect-Indication.
 */

void CCapiStateMachine::HandleAlertConf
   (const CAPIMsg_t *pCapiMsg)
{
   // Alert-Confirm is only valid in state P-2, but ignored in states P-5 and
   // P-0
   if (m_connState != CS_RUNNING ||
       m_plciState == PLCI_STATE_P0 || m_plciState == PLCI_STATE_P5)
   {
      return;
   }
   if (m_plciState != PLCI_STATE_P2)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // accept the connection with specified or detected protocols
   (void) SendConnectResp (m_uConnIndMsgNum, CAPI_CALL_ACCEPT);

} // CCapiStateMachine::HandleAlertConf





/*
        handle new incoming connection
        ------------------------------
*/

void CCapiStateMachine::HandleConnectInd
   (const CAPIMsg_t *pCapiMsg)
{
   unsigned char *p;
   
   // connect-ind is only valid in connection state idle and PLCI state P0
   if (m_connState != CS_IDLE ||
       m_plciState != PLCI_STATE_P0)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // connect-ind is only valid if the object is created for incoming calls
   if (! m_fDirectionIn)
   {
      std::cerr << "Connection " << m_uConnID
                << ": Connection is not created to handle incoming calls"
                << std::endl;
      (void) SendDummyResponse (pCapiMsg);
      if (m_plciState != PLCI_STATE_P0)
      {
         m_fConnOk = false;
         (void) SendDisconnectReq ();
         return;
      }
      m_connState = CS_ERROR_IDLE;
      return;
   }
   
   // so connect-ind is valid, evaluate CAPI message
   m_connState = CS_RUNNING;
   m_plciState = PLCI_STATE_P2;
   m_dwPlci = pCapiMsg->head.dwCid;
   m_ncciState = NCCI_STATE_N0;
   m_dwNcci = 0;
   m_iCurrBlockIdx = 0;
   m_uCurrDataHandle = 0;
   m_uConfirmedDataHandle = 0;
   m_nNumBytesIncoming    = 0;
   m_uhChecksumIncoming   = 0;
   // if the CAPI message is long enough to include an additional info structure
   // (even an empty one), we try to extract user-user data from it
   if (pCapiMsg->head.wLen >
          sizeof (pCapiMsg->head) + sizeof (pCapiMsg->info.connect_ind))
   {
      (void) EvalUUDataFromAddInfo ((u_int8_t *) &(pCapiMsg->info.connect_ind) +
                                    sizeof (pCapiMsg->info.connect_ind));
   }
   if (m_connType == CT_AUTO)
   {
      switch (pCapiMsg->info.connect_ind.wCip)
      {
         case CAPI_CIP_SPEECH:
         case CAPI_CIP_3100Hz_AUDIO:
         case CAPI_CIP_TELEPHONY:
            m_connType = CT_VOICE;
            m_uB1Prot  = CAPI_B1_TRANSPARENT_64;
            m_uB2Prot  = CAPI_B2_TRANSPARENT;
            m_uB3Prot  = CAPI_B3_TRANSPARENT;
            std::cout << "Connection " << m_uConnID
                      << ": Voice connection automatically detected from CIP value "
                      << (int) (pCapiMsg->info.connect_ind.wCip)
                      << std::endl;
            break;

         case CAPI_CIP_UNRESTRICTED_DATA:
            m_connType = CT_DATA_X75;
            m_uB1Prot  = CAPI_B1_HDLC_64;
            m_uB2Prot  = CAPI_B2_ISO7776_X75_SLP;
            m_uB3Prot  = CAPI_B3_TRANSPARENT;
            std::cout << "Connection " << m_uConnID
                      << ": X.75 data connection automatically detected from CIP value "
                      << (int) (pCapiMsg->info.connect_ind.wCip)
                      << std::endl;
            break;

         case CAPI_CIP_FAX_G2_G3:
            m_connType = CT_FAXG3;
            m_uB1Prot  = CAPI_B1_T30_MODEM;
            m_uB2Prot  = CAPI_B2_T30_FAX_G3;
            m_uB3Prot  = CAPI_B3_T30_FAX_G3;
                                        // maybe we should switch to ..._EXT?
            std::cout << "Connection " << m_uConnID
                      << ": Fax-g3 connection automatically detected from CIP value "
                      << (int) (pCapiMsg->info.connect_ind.wCip)
                      << std::endl;
            break;
         
         default:
            // no directly supported CIP value, use default protocols
            m_connType = CT_DATA_X75;
            m_uB1Prot  = CAPI_B1_HDLC_64;
            m_uB2Prot  = CAPI_B2_ISO7776_X75_SLP;
            m_uB3Prot  = CAPI_B3_TRANSPARENT;
            std::cout << "Connection " << m_uConnID
                      << ": Unknown CIP value "
                      << (int) (pCapiMsg->info.connect_ind.wCip)
                      << ", use default protocols (X.75 data)"
                      << std::endl;
            break;
      }
   }
   p = (unsigned char *) &(pCapiMsg->info.connect_ind.wCip) +
       sizeof (pCapiMsg->info.connect_ind.wCip);
   p = CapiUt_ExtractCalledPartyNumber (p, m_strCalledNumber);
   p = CapiUt_ExtractCallingPartyNumber (p, m_strCallingNumber);
   // the rest of the message is ignored, not relevant
   
   std::cout << "Connection " << m_uConnID
             << ": Got new incoming call from \"" << m_strCallingNumber
             << "\" to \"" << m_strCalledNumber << "\", connection type \""
             << m_connType << "\", data destination " << m_incDataDest;
   if (m_incDataDest == IDD_STORE_TO_FILE)
   {
      std::cout << " (" << m_strDstFileName << ")";
   }
   std::cout << std::endl;

   // in order to be able to answer the Connect-Indication correctly we must
   // store the message number, that must be used in the response message
   m_uConnIndMsgNum = (unsigned) CAPI_GET_MSGNUM (pCapiMsg);
   
   // We first send out an Alert-Request. The Connect-Indication is answered
   // later when receiving the Alert-Confirm. This is done to get any
   // Info-Indication for the SETUP D-channel message, that may contain
   // user-user data for the connection type. These messages will be handled
   // before the Alert-Confirm message and thus will lead to correct protocol
   // selection.
   (void) SendAlertReq ();
   
} // CCapiStateMachine::HandleConnectInd





/*
        handle acknowledge for new outgoing connection
        ----------------------------------------------
*/

void CCapiStateMachine::HandleConnectConf
   (const CAPIMsg_t *pCapiMsg)
{
   // connect-conf is only valid in state P-0.1
   if (m_plciState != PLCI_STATE_P0_1)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // store the new PLCI, even if the confirmation denotes failure
   m_dwPlci = pCapiMsg->head.dwCid;

   // check for accepted connect-req
   if (pCapiMsg->info.connect_conf.wInfo != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID
                << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.connect_conf.wInfo << std::dec
                << " in Connect-Conf, call terminated"
                << std::endl;
      m_plciState = PLCI_STATE_P0;
      m_connState = CS_ERROR_IDLE;
      return;
   }
   
   // now wait for connect-active-ind
   m_plciState = PLCI_STATE_P1;
   
} // CCapiStateMachine::HandleConnectConf





/*
        handle signalling of activated PLCI connection
        ----------------------------------------------
*/

void CCapiStateMachine::HandleConnectActInd
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t capiMsg;
   unsigned  uRes;
   
   // connect-active-ind is only allowed in states P1 or P4
   if (m_plciState != PLCI_STATE_P1 &&
       m_plciState != PLCI_STATE_P4)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // perform state transit and send response
   m_plciState = PLCI_STATE_PACT;
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_RESPONSE (C_CONNECT_ACTIVE);
   capiMsg.head.wNum  = CAPI_GET_MSGNUM (pCapiMsg);
   capiMsg.head.dwCid = CAPI_GET_CID (pCapiMsg);
   capiMsg.head.wLen = sizeof (capiMsg.head);
   uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID
                << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Connect-Active-Resp, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return;
   }
   
   // for an outgoing connection we must send a connect-b3-req
   if (! m_fDirectionIn)
   {
      // create the next request message number
      m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;

      capiMsg.head.wCmd = CAPI_REQUEST (C_CONNECT_B3);
      capiMsg.head.wNum = m_uCurrReqMsgNum;
      capiMsg.info.any.b [0] = 0;       // no NCPI needed here, even for fax-g3
      capiMsg.head.wLen = sizeof (capiMsg.head) +
                          sizeof (capiMsg.info.any.b [0]);
      uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
      if (uRes == CAPI_OK)
      {
         // wait for connect-b3-active-ind
         m_ncciState = NCCI_STATE_N0_1;
      }
      else
      {
         std::cerr << "Connection " << m_uConnID
                   << ": Error 0x"
                   << std::hex << std::setfill ('0') << std::setw (4) << uRes
		   << std::dec
                   << " sending Connect-B3-Req, terminate connection"
                   << std::endl;
         m_fConnOk = false;
         (void) SendDisconnectReq ();
         return;
      }
   }
   // for incoming connections we must wait for the connect-b3-ind
   
} // CCapiStateMachine::HandleConnectActInd





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

void CCapiStateMachine::HandleDisconnectInd
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t capiRespMsg;
   unsigned  uRes;
   
   m_plciState = PLCI_STATE_P6;
   m_ncciState = NCCI_STATE_N0;
   
   capiRespMsg.head = pCapiMsg->head;
   capiRespMsg.head.wCmd = CAPI_RESPONSE (C_DISCONNECT);
   capiRespMsg.head.wLen = sizeof (capiRespMsg.head);
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (uRes == CAPI_OK)
   {
      u_int16_t wReason = (pCapiMsg->info.disconnect_ind.wReason & ~0x0080);
      
      switch (wReason)
      {
         case CAPI_OK:
         case 0x3410:
         case 0x341F:
         case 0x3400:
            std::cout << "Connection " << m_uConnID
                      << ": Connection completed with cause 0x"
                      << std::hex << std::setfill ('0') << std::setw (4)
                      << pCapiMsg->info.disconnect_ind.wReason << std::dec
                      << std::endl;
            m_connState = CS_IDLE;
            break;

         default:
            std::cerr << "Connection " << m_uConnID
                      << ": Connection completed with cause 0x"
                      << std::hex << std::setfill ('0') << std::setw (4)
                      << pCapiMsg->info.disconnect_ind.wReason << std::dec
                      << std::endl;
            m_connState = CS_ERROR_IDLE;
            break;
      }
   }
   else
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Disconnect-Resp"
                << std::endl;
      m_connState = CS_ERROR_IDLE;
   }
   m_plciState = PLCI_STATE_P0;
   
   // We now check for the integrity of the data received. This is only done if
   // no error occurred. And if no checksum information was received by the
   // partner, this check can also not be performed.
   if ((m_fConnOk && m_connState == CS_IDLE) &&
       m_nNumBytesFromPartner > 0)
   {
      if (m_nNumBytesFromPartner == m_nNumBytesIncoming &&
	  m_uhChecksumFromPartner == m_uhChecksumIncoming)
      {
	 std::cout << "Connection " << m_uConnID
		   << ": Total number of bytes and checksum are correct"
		   << std::endl;
      }
      if (m_nNumBytesFromPartner != m_nNumBytesIncoming)
      {
         std::cerr << "Connection " << m_uConnID
                   << ": Total number of bytes received "
                   << m_nNumBytesIncoming
                   << " is different from number of bytes declared by partner "
                   << m_nNumBytesFromPartner
                   << std::endl;
         m_fConnOk = false;
      }
      if (m_uhChecksumFromPartner != m_uhChecksumIncoming)
      {
         std::cerr << "Connection " << m_uConnID
                   << ": Checksum of bytes received "
                   << m_uhChecksumIncoming
                   << " is different from checksum provided by partner "
                   << m_uhChecksumFromPartner
                   << std::endl;
         m_fConnOk = false;
      }
      
#if 0 // this does not work because the partner has no time to send back the last block

      // If this was an outgoing connection, we received incoming data and
      // checksum information, then we can assume that the partner simply sent
      // our outgoing data back as an echo. So the outgoing number of bytes must
      // be equal to the number of incoming bytes, same for the checksum.
      if (! m_fDirectionIn)
      {
         if (m_nNumBytesOutgoing != m_nNumBytesIncoming)
         {
            std::cerr << "Connection " << m_uConnID
                      << ": Total number of bytes received "
                      << m_nNumBytesIncoming
                      << " is different from number of bytes sent to the partner "
                      << m_nNumBytesOutgoing
                      << std::endl;
            m_fConnOk = false;
         }
         if (m_uhChecksumOutgoing != m_uhChecksumIncoming)
         {
            std::cerr << "Connection " << m_uConnID
                      << ": Checksum of bytes received "
                      << m_uhChecksumIncoming
                      << " is different from checksum of data sent to the partner "
                      << m_uhChecksumOutgoing
                      << std::endl;
            m_fConnOk = false;
         }
      }
      
#endif // 0

   }
   
} // CCapiStateMachine::HandleDisconnectInd





/*
        handle acknowledge for terminating the current connection
        ---------------------------------------------------------
*/

void CCapiStateMachine::HandleDisconnectConf
   (const CAPIMsg_t *pCapiMsg)
{
   // disconnect-conf is only valid in state P5
   if (m_plciState != PLCI_STATE_P5)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // check the info value of the message
   if (pCapiMsg->info.disconnect_conf.wInfo != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.disconnect_conf.wInfo
		<< std::dec
                << " in Disconnect-Conf, termination impossible, declare connection as aborted"
                << std::endl;
      m_connState = CS_ERROR_IDLE;
      return;
   }
   
   // wait for disconnect-ind
   
} // CCapiStateMachine::HandleDisconnectConf





/*
        handle several network indications
        ----------------------------------
*/

void CCapiStateMachine::HandleInfoInd
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t capiRespMsg;
   unsigned  uRes;
   
   // info indications are just responded and ignored
   capiRespMsg.head = pCapiMsg->head;
   capiRespMsg.head.wCmd = CAPI_RESPONSE (C_INFO_20);
   capiRespMsg.head.wLen = sizeof (capiRespMsg.head);
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Info-Resp, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
   }
   
   // if the message indicates an user-user data information element, try to
   // evaluate it for checksum data
   if ((pCapiMsg->info.info_ind.wInfoNum & 0xC000) == 0x0000 &&
       (pCapiMsg->info.info_ind.wInfoNum & 0x00FF) == 0x7E)
   {
      (void) EvalUUDataElement ((u_int8_t *) &(pCapiMsg->info.info_ind) +
                                sizeof (pCapiMsg->info.info_ind));
   }
   
   // XXX: decode some (more) messages

} // CCapiStateMachine::HandleInfoInd





/**
 * Handle a confirmation for an Info-Request.
 */

void CCapiStateMachine::HandleInfoConf
   (const CAPIMsg_t *pCapiMsg)
{
   u_int16_t wInfo;

   wInfo = pCapiMsg->info.info_conf.wInfo;
   if (wInfo != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
		<< std::hex << std::setw (4) << std::setfill ('0') << wInfo
		<< std::dec
		<< " in Info-Confirm"
		<< std::endl;
   }

} // CCapiStateMachine::HandleInfoConf





/*
        handle indicated start of B3 connection establishment
        -----------------------------------------------------
*/

void CCapiStateMachine::HandleConnectB3Ind
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t      capiRespMsg;
   unsigned char *p;
   unsigned       uRes;
   
   // connect-b3-ind is only valid in PLCI state P-ACT and NCCI state N-0
   if (m_plciState != PLCI_STATE_PACT ||
       m_ncciState != NCCI_STATE_N0)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // only store the NCCI, the other message data is not relevant
   m_dwNcci = pCapiMsg->head.dwCid;
   m_ncciState = NCCI_STATE_N1;
   
   // do response
   capiRespMsg.head = pCapiMsg->head;
   capiRespMsg.head.wCmd = CAPI_RESPONSE (C_CONNECT_B3);
   capiRespMsg.info.connect_b3_resp.wReject = CAPI_CALL_ACCEPT;
   p = (unsigned char *) &(capiRespMsg.info.connect_b3_resp.wReject) +
       sizeof (capiRespMsg.info.connect_b3_resp.wReject);
   p = CapiUt_EnterEmptyStruct (p);
   capiRespMsg.head.wLen = (u_int16_t) (p - (unsigned char *) &capiRespMsg);
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (uRes == CAPI_OK)
   {
      // wait for connect-b3-active-ind
      m_ncciState = NCCI_STATE_N2;
   }
   else
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Connect-B3-Resp, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
   }
   
} // CCapiStateMachine::HandleConnectB3Ind





/*
        handle acknowledge for start of outgoing B3 connection
        ------------------------------------------------------
*/

void CCapiStateMachine::HandleConnectB3Conf
   (const CAPIMsg_t *pCapiMsg)
{
   // connect-b3-conf is only valid in PLCI state P-ACT and NCCI state N-0.1
   if (m_plciState != PLCI_STATE_PACT ||
       m_ncciState != NCCI_STATE_N0_1)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // check the info parameter of the message
   if (pCapiMsg->info.connect_b3_conf.wInfo != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.connect_b3_conf.wInfo
		<< std::dec
                << " in Connect-B3-Conf, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return;
   }
   
   // wait for connect-b3-active-ind
   m_dwNcci    = pCapiMsg->head.dwCid;
   m_ncciState = NCCI_STATE_N2;
   
} // CCapiStateMachine::HandleConnectB3Conf





/*
        handle activated B3 connection
        ------------------------------
*/

void CCapiStateMachine::HandleConnectB3ActInd
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t capiRespMsg;
   unsigned  uRes;
   
   // connect-b3-active-ind is only allowed in PLCI state P-ACT and NCCI state
   // N-2
   if (m_plciState != PLCI_STATE_PACT ||
       m_ncciState != NCCI_STATE_N2)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   m_fWasActive = true;
   std::cout << "Connection " << m_uConnID
             << ": Connection completely established"
             << std::endl;
   
   // perform state transit and send response
   m_ncciState = NCCI_STATE_NACT;
   capiRespMsg.head = pCapiMsg->head;
   capiRespMsg.head.wCmd  = CAPI_RESPONSE (C_CONNECT_B3_ACTIVE);
   capiRespMsg.head.dwCid = pCapiMsg->head.dwCid;
   capiRespMsg.head.wLen = sizeof (capiRespMsg.head);
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Connect-B3-Active-Resp, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return;
   }
   
   // for an outgoing connection we now must send the first two data blocks
   // (send window of two unconfirmed blocks)
   if (! m_fDirectionIn)
   {
      uRes = SendNextDataBlock ();
      // if the first block was sent successfully and there is still data to
      // send: send second data block to establish a data window of two blocks
      if (uRes == CAPI_OK && m_fdSrcFile != -1)
      {
         SendNextDataBlock ();
      }
   }
   // for incoming connections we just wait for incoming data
   
} // CCapiStateMachine::HandleConnectB3ActInd





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

void CCapiStateMachine::HandleDisconnectB3Ind
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t capiRespMsg;
   unsigned  uRes;
   
   // disconnect-b3-ind is only valid in PLCI state P-ACT and in NCCI states
   // other than N-0 and N-0.1
   if (m_plciState != PLCI_STATE_PACT ||
       m_ncciState == NCCI_STATE_N0 ||
       m_ncciState == NCCI_STATE_N0_1)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // check the B3-reason for the termination
   if (pCapiMsg->info.disconnect_b3_ind.wReason == CAPI_OK)
   {
      std::cout << "Connection " << m_uConnID
                << ": B3-connection terminated successfully"
                << std::endl;
   }
   else if (pCapiMsg->info.disconnect_b3_ind.wReason ==
               CDISC_B3_PROTOCOL_LAYER_1 &&
            m_uB2Prot == CAPI_B2_TRANSPARENT)
   {
      // For all connections with transparent layer 2 (e.g. voice or pure HDLC)
      // some ISDN adapters (namely the ones from AVM) deliver a reason value
      // of 0x3301 (signalling protocol removed the B-channel) even for a
      // successful connection. So we just handle these situations like a
      // reason value of CAPI_OK.
      std::cout << "Connection " << m_uConnID << ": Reason 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.disconnect_b3_ind.wReason
		<< std::dec
                << " in Disconnect-B3-Ind, will be treated like CAPI_OK"
                << std::endl;
   }
   else
   {
      std::cerr << "Connection " << m_uConnID << ": Reason 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.disconnect_b3_ind.wReason
		<< std::dec
                << " in Disconnect-B3-Ind, error occurred"
                << std::endl;
      m_fConnOk = false;
   }
   
   // perform state transit and response
   m_ncciState = NCCI_STATE_N5;
   capiRespMsg.head = pCapiMsg->head;
   capiRespMsg.head.wCmd = CAPI_RESPONSE (C_DISCONNECT_B3);
   capiRespMsg.head.wLen = sizeof (capiRespMsg.head);
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Disconnect-B3-Resp, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return;
   }
   
   // if we initiated the disconnect-b3-req ourselves, we now must send a
   // disconnect-req
   if (m_fActiveDisconnectB3)
   {
      (void) SendDisconnectReq ();
   }
   
   // for incoming connections we just wait for the disconnect-ind
   
} // CCapiStateMachine::HandleDisconnectB3Ind





/*
        handle acknowledge for terminating the current B3 connection
        ------------------------------------------------------------
*/

void CCapiStateMachine::HandleDisconnectB3Conf
   (const CAPIMsg_t *pCapiMsg)
{
   // disconnect-b3-conf is only valid in PLCI state P-ACT and NCCI state N4
   if (m_plciState != PLCI_STATE_PACT ||
       m_ncciState != NCCI_STATE_N4)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // check the info value of the message
   if (pCapiMsg->info.disconnect_b3_conf.wInfo != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.disconnect_conf.wInfo
		<< std::dec
                << " in Disconnect-B3-Conf, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return;
   }
   
   // wait for disconnect-b3-ind
   
} // CCapiStateMachine::HandleDisconnectB3Conf





/*
        handle incoming data block
        --------------------------
*/

void CCapiStateMachine::HandleDataB3Ind
   (const CAPIMsg_t *pCapiMsg)
{
   CAPIMsg_t  capiRespMsg;
   u_int8_t  *pbData;
   unsigned   uRes;
   
   // data blocks are only valid in PLCI state P-ACT and NCCI state N-ACT, but
   // ignored in states N-4, P-0 and P-5
   if (m_connState != CS_RUNNING ||
       m_plciState == PLCI_STATE_P0 || m_plciState == PLCI_STATE_P5 ||
       m_ncciState == NCCI_STATE_N4)
   {
      SendDummyResponse (pCapiMsg);
      return;
   }
   if (m_plciState != PLCI_STATE_PACT ||
       m_ncciState != NCCI_STATE_NACT)
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // extract the data pointer
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
   pbData = (u_int8_t *) C_GET_QWORD (pCapiMsg->info.data_b3_ind.qwData64);
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   pbData = (u_int8_t *) C_GET_DWORD (pCapiMsg->info.data_b3_ind.dwData);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   
   // handle data according to data destination
   if (m_incDataDest == IDD_STORE_TO_FILE)
   {
      (void) write (m_fdDstFile,
                    pbData,
                    (size_t) (pCapiMsg->info.data_b3_ind.wLen));
   }
   else if (m_incDataDest == IDD_ECHO && m_connType != CT_FAXG3)
   {
      EchoDataBlock (pbData, (size_t) (pCapiMsg->info.data_b3_ind.wLen));
   }
   
   if (m_fVerboseOutput)
   {
      std::cout << "Connection " << m_uConnID
                << ": Got Data-B3-Ind, wHandle 0x"
                << std::hex << std::setfill ('0') << std::setw (4)
                << pCapiMsg->info.data_b3_ind.wHandle
	        << std::dec
                << ", wLen " << pCapiMsg->info.data_b3_ind.wLen
                << std::endl;
   }

   // count the data and update the checksum
   m_nNumBytesIncoming += pCapiMsg->info.data_b3_ind.wLen;
   UpdateChecksum (m_uhChecksumIncoming,
                   pbData,
                   (size_t) (pCapiMsg->info.data_b3_ind.wLen));

   // send response
   capiRespMsg.head = pCapiMsg->head;
   capiRespMsg.head.wCmd = CAPI_RESPONSE (C_DATA_B3);
   capiRespMsg.info.data_b3_resp.wHandle = pCapiMsg->info.data_b3_ind.wHandle;
   capiRespMsg.head.wLen = sizeof (capiRespMsg.head) +
                           sizeof (capiRespMsg.info.data_b3_resp);
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Data-B3-Resp, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
   }
   
} // CCapiStateMachine::HandleDataB3Ind





/*
        handle acknowledge for data block to send
        -----------------------------------------
*/

void CCapiStateMachine::HandleDataB3Conf
   (const CAPIMsg_t *pCapiMsg)
{
   // data-b3-conf is only valid in PLCI state P-ACT and NCCI states N-ACT, N-3
   // and N-4, but ignored in PLCI states P-0 and P-5
   if (m_connState != CS_RUNNING ||
       m_plciState == PLCI_STATE_P0 || m_plciState == PLCI_STATE_P5)
   {
      return;
   }
   if (m_plciState != PLCI_STATE_PACT ||
       (m_ncciState != NCCI_STATE_NACT &&
        m_ncciState != NCCI_STATE_N3 &&
        m_ncciState != NCCI_STATE_N4))
   {
      HandleUnexpectedMessage (pCapiMsg);
      return;
   }
   
   // remember the handle of the last confirmed data block
   // Note: The confirmations must arrive in the same order, the requests were
   //       sent. So we just need to take the handle of this confirmation as it
   //       _is_ the one of the last block confirmed.
   m_uConfirmedDataHandle = pCapiMsg->info.data_b3_conf.wHandle;
   
   std::cout << "Connection " << m_uConnID << ": Got Data-B3-Conf, wHandle 0x"
             << std::hex << std::setfill ('0') << std::setw (4)
             << m_uConfirmedDataHandle
             << std::dec << std::setfill (' ')
             << std::endl;

   // if this is not an outgoing connection, we are ready, because we only send
   // out data blocks for incoming ones
   if (m_fDirectionIn)
   {
      return;
   }
   
   // if there is no more data to send and all data blocks are confirmed:
   // terminate the connection
   if (m_fdSrcFile == -1 && m_uConfirmedDataHandle == m_uCurrDataHandle)
   {
      if (m_fVerboseOutput)
      {
         std::cout << "Connection " << m_uConnID << ": Got last Data-B3-Conf"
                   << std::endl;
      }
      
      (void) SendDisconnectB3Req ();
      return;
   }
   
   // if there is no more data to send and still some confirmations are
   // pending: wait for them
   if (m_fdSrcFile == -1)
   {
      return;
   }
   
   // so there is still more data to send, do it
   (void) SendNextDataBlock ();
   
} // CCapiStateMachine::HandleDataB3Conf





/*
        handle an unexpected CAPI message
        ---------------------------------
*/

void CCapiStateMachine::HandleUnexpectedMessage
   (const CAPIMsg_t *pCapiMsg)
{
   std::cerr << "Connection " << m_uConnID << ": Message "
             << CapiUt_GetCapiMsgCmdName (pCapiMsg->head.wCmd) << ' '
             << CapiUt_GetCapiMsgSubCmdName (pCapiMsg->head.wCmd)
             << " not expected in current state (connection state \""
             << m_connState << "\", PLCI state " << m_plciState
             << ", NCCI state " << m_ncciState << "), terminate connection"
             << std::endl;
   m_fConnOk = false;
   if ((pCapiMsg->head.wCmd & CAPI_CMDMASK_SUBCMD) == C_IND)
   {
      (void) SendDummyResponse (pCapiMsg);
   }
   if (m_plciState != PLCI_STATE_P0 &&
       m_plciState != PLCI_STATE_P5 && m_plciState != PLCI_STATE_P6)
   {
      (void) SendDisconnectReq ();
      return;
   }
   if (m_plciState == PLCI_STATE_P0 || m_plciState == PLCI_STATE_P6)
   {
      m_connState = CS_ERROR_IDLE;
   }
   
} // CCapiStateMachine::HandleUnexpectedMessage





/*
        start an outgoing connection
        ----------------------------
*/

unsigned CCapiStateMachine::SendConnectReq (void)
{
   CAPIMsg_t      capiMsg;
   unsigned char *p;
   unsigned       uRes;
   
   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   // fill the message
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_CONNECT);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_uController;
   switch (m_connType)
   {
      case CT_VOICE:
         capiMsg.info.connect_req.wCip = CAPI_CIP_3100Hz_AUDIO;
         break;
         
      case CT_DATA_HDLC:
      case CT_DATA_X75:
      default:
         capiMsg.info.connect_req.wCip = CAPI_CIP_UNRESTRICTED_DATA;
         break;
         
      case CT_FAXG3:
         if (m_fSendUUData)
         {
            capiMsg.info.connect_req.wCip = CAPI_CIP_FAX_G2_G3;
         }
         else
         {
            capiMsg.info.connect_req.wCip = CAPI_CIP_3100Hz_AUDIO;
         }
         break;
   }
   p = (unsigned char *) &(capiMsg.info.connect_req.wCip) +
       sizeof (capiMsg.info.connect_req.wCip);
   // called party number
   p = CapiUt_EnterCalledPartyNumber (p, m_strCalledNumber.c_str ());
   // calling party number
   if (m_strCallingNumber.length () > 0)
   {
      p = CapiUt_EnterCallingPartyNumber (p, m_strCallingNumber.c_str ());
   }
   else
   {
      p = CapiUt_EnterEmptyStruct (p);
   }
   // no called party subaddress
   p = CapiUt_EnterEmptyStruct (p);
   // no calling party subaddress
   p = CapiUt_EnterEmptyStruct (p);
   // B-channel protocol data
   p = EnterBProtocol (p);
   // no bearer capability
   p = CapiUt_EnterEmptyStruct (p);
   // no low layer compatibility
   p = CapiUt_EnterEmptyStruct (p);
   // no high layer compatibility
   p = CapiUt_EnterEmptyStruct (p);
   if (m_fSendUUData)
   {
      // additional info for connection type as user-user data
      p = EnterAddInfoForConnTypeUUData (p);
   }
   else
   {
      // no additional info
      p = CapiUt_EnterEmptyStruct (p);
   }
   // total message length
   capiMsg.head.wLen = (u_int16_t) (p - (unsigned char *) &capiMsg);
   
   // send the CAPI message
   uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Connect-Req, abort connection" 
                << std::endl;
      m_plciState = PLCI_STATE_P0;
      m_connState = CS_ERROR_IDLE;
      return (uRes);
   }
   // wait for connect-conf
   m_plciState = PLCI_STATE_P0_1;
   
   return (uRes);
} // CCapiStateMachine::SendConnectReq





/**
 * Send an Alert-Request for a pending Connect-Indication.
 */

unsigned CCapiStateMachine::SendAlertReq (void)
{
   CAPIMsg_t  capiMsg;
   u_int8_t  *pbLastPos;
   unsigned   uRes;
   
   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_ALERT);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_dwPlci;
   pbLastPos = (u_int8_t *) &capiMsg + sizeof (capiMsg.head);
   pbLastPos = CapiUt_EnterEmptyStruct (pbLastPos);
   capiMsg.head.wLen = (u_int16_t) (pbLastPos - (u_int8_t *) &capiMsg);

   // send the CAPI message
   uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Alert-Req, abort connection" 
                << std::endl;
      (void) SendConnectResp (m_uConnIndMsgNum, CAPI_CALL_REJECT_OUT_OF_ORDER);
   }
   
   return (uRes);
} // CCapiStateMachine::SendAlertReq





/*
        answer an incoming call
        -----------------------
*/

unsigned CCapiStateMachine::SendConnectResp
   (unsigned  uMsgNum,
    u_int16_t wReject)
{
   CAPIMsg_t      capiRespMsg;
   unsigned char *p;
   unsigned       uRes;
   
   capiRespMsg.head.wApp  = m_uCapiApplID;
   capiRespMsg.head.wCmd  = CAPI_RESPONSE (C_CONNECT);
   capiRespMsg.head.wNum  = (u_int16_t) uMsgNum;
   capiRespMsg.head.dwCid = m_dwPlci;
   capiRespMsg.info.connect_resp.wReject = wReject;
   p = (unsigned char *) &(capiRespMsg.info.connect_resp.wReject) +
       sizeof (capiRespMsg.info.connect_resp.wReject);
   if (wReject != CAPI_CALL_ACCEPT)
   {
      // if the call is not accepted there is no need to specify a B-channel
      // protocol
      p = CapiUt_EnterEmptyStruct (p);  // default BProtocol
   }
   else
   {
      // enter BProtocol structure
      p = EnterBProtocol (p);
      
   }
   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);
   
   // send the CAPI message
   uRes = capi20_put_message (m_uCapiApplID, &capiRespMsg);
   if (wReject == CAPI_CALL_ACCEPT)
   {
      // if we wanted to accept the call, a state transit is necessary
      if (uRes != CAPI_OK)
      {
         std::cerr << "Connection " << m_uConnID << ": Error 0x"
                   << std::hex << std::setfill ('0') << std::setw (4) << uRes
		   << std::dec
                   << " sending Connect-Resp, abort connection"
                   << std::endl;
         m_plciState = PLCI_STATE_P0;
         m_connState = CS_ERROR_IDLE;
         return (uRes);
      }
      // wait for connect-active-ind
      m_plciState = PLCI_STATE_P4;
   }
   // if we did not want to accept the call, there must be another call in
   // progress, do not disturb it with invalid state changes
   
   // for incoming connections it may be necessary to store incoming data to a
   // file, so open it
   if (uRes == CAPI_OK && wReject == CAPI_CALL_ACCEPT &&
       m_fDirectionIn && m_incDataDest == IDD_STORE_TO_FILE)
   {
      // first append a file name extension according to the current connection
      // type to the destination file name
      m_strDstFileName += std::string (".") +
                          GetFileExtFromConnType (m_connType);

      m_fdDstFile = open (m_strDstFileName.c_str (),
                          O_WRONLY | O_CREAT | O_TRUNC,
                          S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                             S_IROTH | S_IWOTH);
      if (m_fdDstFile == -1)
      {
         std::cerr << "Connection " << m_uConnID
                   << ": Error opening destination file \"" << m_strDstFileName
                   << "\" for incoming call"
                   << std::endl;
      }
   }
   
   return (uRes);
} // CCapiStateMachine::SendConnectResp





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

unsigned CCapiStateMachine::SendDisconnectReq (void)
{
   CAPIMsg_t capiMsg;
   unsigned  uRes;
   
   // if we are already in state P-5 we suppress this request as it is already
   // pending
   if (m_connState != CS_RUNNING ||
       m_plciState == PLCI_STATE_P0 || m_plciState == PLCI_STATE_P5)
   {
      return (CAPI_OK);
   }
   
   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   // in state P-2 we cannot send a Disconnect-Request but must send a negative
   // Connect-Response
   if (m_plciState == PLCI_STATE_P2)
   {
      return (SendConnectResp
                 (m_uConnIndMsgNum, CAPI_CALL_REJECT_NORMAL_CLEARING));
   }
   
   // fill the message
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_DISCONNECT);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_dwPlci;
   // append user-user data for checksum information if so configured and
   // possible
   if (! m_fSendUUData ||
       m_connType == CT_VOICE || m_connType == CT_FAXG3)
   {
      capiMsg.info.any.b [0] = 0;          // empty AdditionalInfo structure
      capiMsg.head.wLen = sizeof (capiMsg.head) +
			  sizeof (capiMsg.info.any.b [0]);
   }
   else
   {
      u_int8_t *p;

      p = (u_int8_t *) &(capiMsg.info.any.b [0]);
      p = EnterAddInfoForChecksumUUData (p);
      capiMsg.head.wLen = (u_int16_t) (p - (u_int8_t *) &capiMsg);
   }
   
   // send the message
   uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
   if (uRes == CAPI_OK)
   {
      // wait for disconnect-ind
      m_plciState = PLCI_STATE_P5;
      m_fActiveDisconnect = true;
      
      std::cout << "Connection " << m_uConnID << ": Disconnect-Req sent"
                << std::endl;
   }
   else
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Disconnect-Req, abort connection"
                << std::endl;
      m_connState = CS_ERROR_IDLE;
   }
   
   return (uRes);
} // CCapiStateMachine::SendDisconnectReq





/*
        terminate the current B3 connection
        -----------------------------------
*/

unsigned CCapiStateMachine::SendDisconnectB3Req (void)
{
   CAPIMsg_t capiMsg;
   unsigned  uRes;
   
   // at the end of every logical connection we send out checksum information if
   // configured
   //(void) SendChecksumUUData ();

   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   // fill the message
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_DISCONNECT_B3);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_dwNcci;
   capiMsg.info.any.b [0] = 0;          // NCPI as an empty structure
   capiMsg.head.wLen = sizeof (capiMsg.head) + sizeof (capiMsg.info.any.b [0]);
   
   // send the message
   uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Disconnect-B3-Req, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return (uRes);
   }
   
   // do state transit
   m_fActiveDisconnectB3 = true;
   m_ncciState = NCCI_STATE_N4;
   
   std::cout << "Connection " << m_uConnID << ": Disconnect-B3-Req sent"
             << std::endl;
   
   // wait for disconnect-b3-ind
   return (CAPI_OK);
} // CCapiStateMachine::SendDisconnectB3Req





/**
 * Send an Info-Request for checksum information if so configured.
 */

unsigned CCapiStateMachine::SendChecksumUUData (void)
{
   CAPIMsg_t  capiMsg;
   u_int8_t  *p;
   unsigned   uRes;

   // if it is not configured to send out user-user data for checksum
   // information just ignore this call
   if (! m_fSendUUData)
   {
      return (CAPI_OK);
   }
   
   // for voice and fax connections it is senseless to provide the number of
   // bytes and a checksum
   if (m_connType == CT_VOICE || m_connType == CT_FAXG3)
   {
      return (CAPI_OK);
   }
   
   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   // fill the message
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_INFO_20);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_dwPlci;
   p = (u_int8_t *) &(capiMsg.info.any.b [0]);
   p = CapiUt_EnterEmptyStruct (p);     // Called Party Number as an empty
                                        // structure
   p = EnterAddInfoForChecksumUUData (p);
   capiMsg.head.wLen = (u_int16_t) (p - (u_int8_t *) &capiMsg);
   
   // send the message
   uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
   if (uRes != CAPI_OK)
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Info-Req, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return (uRes);
   }
   
   return (CAPI_OK);
} // CCapiStateMachine::SendChecksumUUData





/*
        send the next data block for an outgoing connection
        ---------------------------------------------------
*/

unsigned CCapiStateMachine::SendNextDataBlock (void)
{
   CAPIMsg_t      capiMsg;
   unsigned char *pacData;
   unsigned long  ulTimeout;
   int            iRes;
   unsigned       uRes;
   
   // determine the buffer for the next data block
   m_iCurrBlockIdx = (m_iCurrBlockIdx + 1) % BDATA_MAX_SEND_WINDOW;
   pacData = &(m_aaucDataBuf [m_iCurrBlockIdx] [0]);
   
   // read the next data block from the source file
   iRes = read (m_fdSrcFile, pacData, MAX_BDATA_LEN);
   if (iRes < 0)
   {
      std::cerr << "Connection " << m_uConnID
                << ": Error reading from source file \"" << m_strSrcFileName
                << "\", terminate connection: "
                << strerror (errno) << " (" << errno << ")"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return (0xFFFF);
   }
   if (iRes == 0)
   {
      // end-of-file, close the file
      close (m_fdSrcFile);
      m_fdSrcFile = -1;
      
      std::cout << "Connection " << m_uConnID << ": All data sent"
                << std::endl;
      
      // if all data blocks are confirmed, terminate the connection
      if (m_uConfirmedDataHandle == m_uCurrDataHandle)
      {
         return (SendDisconnectB3Req ());
      }
      // there are still some confirmations pending, wait for them
      return (CAPI_OK);
   }
   
   // prepare the next data handle
   m_uCurrDataHandle = (m_uCurrDataHandle + 1) % 0xFFFF;
   
   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   // fill the message
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_DATA_B3);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_dwNcci;
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
   C_PUT_DWORD (capiMsg.info.data_b3_req.dwData, 0);
   C_PUT_QWORD (capiMsg.info.data_b3_req.qwData64, (u_int64_t) pacData);
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   C_PUT_DWORD (capiMsg.info.data_b3_req.dwData, (u_int32_t) pacData);
   C_PUT_QWORD (capiMsg.info.data_b3_req.qwData64, 0);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   capiMsg.info.data_b3_req.wLen    = (u_int16_t) iRes;
   capiMsg.info.data_b3_req.wHandle = m_uCurrDataHandle;
   capiMsg.info.data_b3_req.wFlags  = 0;
   capiMsg.head.wLen = sizeof (capiMsg.head) +
                       sizeof (capiMsg.info.data_b3_req);
   
   // loop to send the message until timeout or it is sent successfully
   ulTimeout = (unsigned long) time (NULL) + 5;
   do
   {
      uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
      if (uRes == CME_PUT_QUEUE_FULL ||
          uRes == CME_BUSY)
      {
         usleep (50000);                // sleep for 50ms
      }
      else if (uRes != CAPI_OK)
      {
         break;
      }
   } while (uRes != CAPI_OK && ulTimeout >= (unsigned long) time (NULL));
   if (uRes == CAPI_OK)
   {
      if (m_fVerboseOutput)
      {
         std::cout << "Connection " << m_uConnID
                   << ": Data-B3-Req sent, wHandle 0x"
                   << std::hex << std::setfill ('0') << std::setw (4)
                   << capiMsg.info.data_b3_req.wHandle
		   << std::dec
                   << ", wLen " << capiMsg.info.data_b3_req.wLen
                   << std::endl;
      }
   }
   else
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Data-B3-Req, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return (uRes);
   }

   // count the data and update the checksum
   m_nNumBytesOutgoing += (size_t) iRes;
   UpdateChecksum (m_uhChecksumOutgoing, (u_int8_t *) pacData, (size_t) iRes);
   
   return (CAPI_OK);
} // CCapiStateMachine::SendNextDataBlock





/**
 * Send a received data block back to the originator.
 */

void CCapiStateMachine::EchoDataBlock
   (u_int8_t *pabData,
    size_t    nLenData)
{
   u_int8_t      *pabTmpData;
   CAPIMsg_t      capiMsg;
   unsigned long  ulTimeout;
   unsigned       uRes;
   
   // if the data block specified is empty, do not send any data out
   if (nLenData == 0 || pabData == NULL)
   {
      return;
   }
   
   // determine the buffer for this data block (must be copied to be valid until
   // a corresponding Data-B3-Confirm is received)
   // Note: In echo operation for incoming connections this cannot absolutely be
   //       guaranteed. We can only hope that the oldest data block is already
   //       confirmed when we need to overwrite its memory.
   m_iCurrBlockIdx = (m_iCurrBlockIdx + 1) % BDATA_MAX_SEND_WINDOW;
   pabTmpData = &(m_aaucDataBuf [m_iCurrBlockIdx] [0]);
   
   // copy the data block to echo into the buffer determined
   if (nLenData > MAX_BDATA_LEN)
   {
      nLenData = MAX_BDATA_LEN;
   }
   memcpy (pabTmpData, pabData, nLenData);
   
   // prepare the next data handle
   m_uCurrDataHandle = (m_uCurrDataHandle + 1) % 0xFFFF;
   
   // create the next request message number
   m_uCurrReqMsgNum = ++m_uhGlobalMessageCounter;
   
   // fill the message
   capiMsg.head.wApp  = m_uCapiApplID;
   capiMsg.head.wCmd  = CAPI_REQUEST (C_DATA_B3);
   capiMsg.head.wNum  = m_uCurrReqMsgNum;
   capiMsg.head.dwCid = m_dwNcci;
#if defined(__alpha__) || defined (__ia64__) || defined (__amd64__) || defined (__sparc64__)
   C_PUT_QWORD (capiMsg.info.data_b3_req.qwData64, (u_int64_t) pabTmpData);
#else /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   C_PUT_DWORD (capiMsg.info.data_b3_req.dwData, (u_int32_t) pabTmpData);
#endif /* __alpha__ || __ia64__ || __amd64__ || __sparc64__ */
   capiMsg.info.data_b3_req.wLen    = (u_int16_t) nLenData;
   capiMsg.info.data_b3_req.wHandle = m_uCurrDataHandle;
   capiMsg.info.data_b3_req.wFlags  = 0;
   capiMsg.head.wLen = sizeof (capiMsg.head) +
                       sizeof (capiMsg.info.data_b3_req);
   
   // loop to send the message until timeout or it is sent successfully
   ulTimeout = (unsigned long) time (NULL) + 5;
   do
   {
      uRes = capi20_put_message (m_uCapiApplID, &capiMsg);
      if (uRes == CME_PUT_QUEUE_FULL ||
          uRes == CME_BUSY)
      {
         usleep (50000);                // sleep for 50ms
      }
      else if (uRes != CAPI_OK)
      {
         break;
      }
   } while (uRes != CAPI_OK && ulTimeout >= (unsigned long) time (NULL));
   if (uRes == CAPI_OK)
   {
      if (m_fVerboseOutput)
      {
         std::cout << "Connection " << m_uConnID
                   << ": Data-B3-Req sent, wHandle 0x"
                   << std::hex << std::setfill ('0') << std::setw (4)
                   << capiMsg.info.data_b3_req.wHandle
		   << std::dec
                   << ", wLen " << capiMsg.info.data_b3_req.wLen
                   << std::endl;
      }
   }
   else
   {
      std::cerr << "Connection " << m_uConnID << ": Error 0x"
                << std::hex << std::setfill ('0') << std::setw (4) << uRes
		<< std::dec
                << " sending Data-B3-Req, terminate connection"
                << std::endl;
      m_fConnOk = false;
      (void) SendDisconnectReq ();
      return;
   }

   // count the data and update the checksum
   m_nNumBytesOutgoing += nLenData;
   UpdateChecksum (m_uhChecksumOutgoing, pabTmpData, nLenData);
   
} // CCapiStateMachine::EchoDataBlock





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

unsigned CCapiStateMachine::SendDummyResponse
   (const CAPIMsg_t *pCapiIndMsg)
{
   CAPIMsg_t      capiRespMsg;
   unsigned char *p;
   
   // some messages must be handled specially
   if (pCapiIndMsg->head.wCmd == CAPI_INDICAT (C_CONNECT))
   {
      return (SendConnectResp (m_uConnIndMsgNum, CAPI_CALL_IGNORE));
   }
   else if (pCapiIndMsg->head.wCmd == CAPI_INDICAT (C_FACILITY))
   {
      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))
   {
      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 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);
   }
   return (capi20_put_message (m_uCapiApplID, &capiRespMsg));
} // CCapiStateMachine::SendDummyResponse





/*
        enter the BProtocol structure into a CAPI message
        -------------------------------------------------
*/

unsigned char *CCapiStateMachine::EnterBProtocol
   (unsigned char *pOrgPos)
{
   CAPIBProtocol_t *pBProt;
   unsigned char   *p;
   
   // enter the B-channel protocol values
   pBProt = (CAPIBProtocol_t *) pOrgPos;
   pBProt->wB1protocol = m_uB1Prot;
   pBProt->wB2protocol = m_uB2Prot;
   pBProt->wB3protocol = m_uB3Prot;
   
   p = (unsigned char *) pBProt + sizeof (*pBProt);
   
   // enter B1-protocol configuration
   switch (m_uB1Prot)
   {
      case CAPI_B1_T30_MODEM:
         {
            CAPIB1ConfigFaxG3_t *pFaxConfig = (CAPIB1ConfigFaxG3_t *) p;
            
            pFaxConfig->wRate          = CAPI_B1CFG_BITRATE_ADAPTIVE;
            pFaxConfig->wTransmitLevel = CAPI_B1CFG_FG3_TX_LEVEL_MED;
            pFaxConfig->wParity        = CAPI_B1CFG_PARITY_NONE;
            pFaxConfig->wStopbits      = CAPI_B1CFG_STOPBIT_ONE;
            
            pFaxConfig->bLength = sizeof (*pFaxConfig) -
                                  sizeof (pFaxConfig->bLength);
            p += pFaxConfig->bLength + 1;
         }
         break;
         
      default:
         // no B1-configuration necessary
         p = CapiUt_EnterEmptyStruct (p);
         break;
   }
   
   // enter B2-protocol configuration
   // for each supported B2-protocol we use defaults or no B2-configuration is
   // necessary
   p = CapiUt_EnterEmptyStruct (p);
   
   // enter B3-protocol configuration
   switch (m_uB3Prot)
   {
      case CAPI_B3_T30_FAX_G3:
         {
            CAPIB3ConfigFaxG3_t *pFaxConfig = (CAPIB3ConfigFaxG3_t *) p;
            
            pFaxConfig->wResolution = m_uSrcFaxG3Resolution;
	    if (m_fSrcFaxG3Sff)
	    {
               pFaxConfig->wFormat = CAPI_FAXG3_FORMAT_SFF;
	    }
	    else
	    {
	       pFaxConfig->wFormat = CAPI_FAXG3_FORMAT_ASCII;
	    }
            p = (unsigned char *) pFaxConfig + sizeof (*pFaxConfig);
            p = CapiUt_EnterString (p, "+                   ");
                                        // simply use an empty station id
            p = CapiUt_EnterString (p, "CAPI Tester");
            
            // set size of B3-configuration
            pFaxConfig->bLength =
               (u_int8_t) (p - (unsigned char *) pFaxConfig - 1);
         }
         break;
         
      default:
         // no B3-configuration necessary
         p = CapiUt_EnterEmptyStruct (p);
         break;
   }
   
   // calculate length of the complete CAPI structure
   pBProt->bLength = (u_int8_t) (p - (unsigned char *) pBProt - 1);
   
   // return pointer behind the last byte written
   return (p);
} // CCapiStateMachine::EnterBProtocol





/**
 * Create an additional info structure with user-user data for the
 * connection type.
 */

u_int8_t *CCapiStateMachine::EnterAddInfoForConnTypeUUData
   (u_int8_t *pbOrgPos)
{
   // create the IA5 data to send
   std::ostringstream oss;
   oss << UUDATA_CONN_TYPE_PREFIX << (int) m_connType;
   assert (oss.str ().length () <= 32);
   
   // the user-user data structure is the third of five sub-structures, so write
   // out two empty structures
   u_int8_t *p = pbOrgPos + 1;
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   
   // write out the user-user data structure (protocol discriminator is "IA5
   // characters")
   *(p++) = (u_int8_t) (oss.str ().length () + 1);
   *(p++) = 0x04;
   memcpy (p, oss.str ().c_str (), oss.str ().length ());
   p += oss.str ().length ();
   
   // now finally write out the two last empty structures
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   
   // set the length of the surrounding additional info structure
   *pbOrgPos = (u_int8_t) ((size_t) (p - pbOrgPos) - 1);
   
   // return a pointer right behind the last byte written
   return (p);
} // CCapiStateMachine::EnterAddInfoForConnTypeUUData





/**
 * Create an additional info structure with user-user data for checksum
 * information.
 */

u_int8_t *CCapiStateMachine::EnterAddInfoForChecksumUUData
   (u_int8_t *pbOrgPos)
{
   // create the IA5 data to send
   std::ostringstream oss;
   oss << UUDATA_DATA_LEN_PREFIX << "0x" << std::hex << m_nNumBytesOutgoing
       << std::dec << ' '
       << UUDATA_CHECKSUM_PREFIX << "0x" << std::hex << m_uhChecksumOutgoing
       << std::dec;
   assert (oss.str ().length () <= 32);
   
   // the user-user data structure is the third of five sub-structures, so write
   // out two empty structures
   u_int8_t *p = pbOrgPos + 1;
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   
   // write out the user-user data structure (protocol discriminator is "IA5
   // characters")
   *(p++) = (u_int8_t) (oss.str ().length () + 1);
   *(p++) = 0x04;
   memcpy (p, oss.str ().c_str (), oss.str ().length ());
   p += oss.str ().length ();
   
   // now finally write out the two last empty structures
   p = CapiUt_EnterEmptyStruct (p);
   p = CapiUt_EnterEmptyStruct (p);
   
   // set the length of the surrounding additional info structure
   *pbOrgPos = (u_int8_t) ((size_t) (p - pbOrgPos) - 1);
   
   // return a pointer right behind the last byte written
   return (p);
} // CCapiStateMachine::EnterAddInfoForChecksumUUData





/**
 * Evaluate user-user data in an additional info structure.
 *
 * @note We assume the CAPI message and especially the additional info
 *       structure is correctly coded. Otherwise we would need to check
 *       several times if the end of the whole CAPI message or of some
 *       structures is reached. But for a (relatively) simple test program
 *       we skip these checks.
 */

u_int8_t *CCapiStateMachine::EvalUUDataFromAddInfo
   (u_int8_t *pbAddInfo)
{
   u_int8_t *p;
   
   // check for valid parameters
   assert (pbAddInfo);
   
   // check for empty additional info structure
   if (*pbAddInfo == 0)
   {
      return (pbAddInfo + 1);
   }
   p = pbAddInfo + 1;
   
   // the first embedded structure is B channel info, must overread it
   p += *p + 1;
   
   // the second is keypad facility, must overread it
   p += *p + 1;

   // now p points to the length byte of the user-user data structure, check for
   // empty structure
   if (*p == 0)
   {
      return (pbAddInfo + *pbAddInfo + 1);
   }
   
   // so there is some user-user data, try to evaluate it as a connection type
   // or as the number of data bytes and a checksum
   (void) EvalUUDataElement (p);
   
   // return a pointer right behind the Additional Info structure
   return (pbAddInfo + *pbAddInfo + 1);
} // CCapiStateMachine::EvalUUDataFromAddInfo





/**
 * Evaluate a user-user data information element.
 */

u_int8_t *CCapiStateMachine::EvalUUDataElement
   (u_int8_t *pbUUData)
{
   u_int8_t *p;
   size_t    nLen;
   
   // check for valid parameters
   assert (pbUUData);
   
   // check for empty structure
   nLen = (size_t) *pbUUData;
   if (nLen == 0)
   {
      return (pbUUData + 1);
   }
   p = pbUUData + 1;
   
   // if the protocol discriminator does not indicate IA5 characters, the IE
   // does not carry any information we could use
   if (*(p++) != 0x04)
   {
      return (pbUUData + *pbUUData + 1);
   }
   --nLen;
   
   // p now points to the real user-user data and nLen contains the number of
   // characters available --> create a string for later evaluation
   std::string str ((char *) p, nLen);

   // try to find all supported data values in the string created
   size_t n;
   n = str.find (UUDATA_CONN_TYPE_PREFIX);
   if (n != std::string::npos)
   {
      n += sizeof (UUDATA_CONN_TYPE_PREFIX) - 1;
      m_connType =
         (ConnectionType_t) strtoul (str.substr (n).c_str (), NULL, 0);
      std::cout << "Connection " << m_uConnID
                << ": Connection type determined to " << m_connType
                << " from user-user data"
                << std::endl;
      switch (m_connType)
      {
         case CT_VOICE:
            m_uB1Prot = CAPI_B1_TRANSPARENT_64;
            m_uB2Prot = CAPI_B2_TRANSPARENT;
            m_uB3Prot = CAPI_B3_TRANSPARENT;
            break;

         case CT_DATA_HDLC:
            m_uB1Prot = CAPI_B1_HDLC_64;
            m_uB2Prot = CAPI_B2_TRANSPARENT;
            m_uB3Prot = CAPI_B3_TRANSPARENT;
            break;

         case CT_DATA_X75:
            m_uB1Prot = CAPI_B1_HDLC_64;
            m_uB2Prot = CAPI_B2_ISO7776_X75_SLP;
            m_uB3Prot = CAPI_B3_TRANSPARENT;
            break;

         case CT_FAXG3:
            m_uB1Prot = CAPI_B1_T30_MODEM;
            m_uB2Prot = CAPI_B2_T30_FAX_G3;
            m_uB3Prot = CAPI_B3_T30_FAX_G3;// maybe we should switch to ..._EXT?
            break;
            
         default:
            // do not change protocol settings for invalid connection type
            break;
      }
   }
   n = str.find (UUDATA_DATA_LEN_PREFIX);
   if (n != std::string::npos)
   {
      n += sizeof (UUDATA_DATA_LEN_PREFIX) - 1;
      m_nNumBytesFromPartner =
         (size_t) strtoul (str.substr (n).c_str (), NULL, 0);
      std::cout << "Connection " << m_uConnID
                << ": Total number of bytes signaled by partner: "
                << m_nNumBytesFromPartner
                << std::endl;
   }
   n = str.find (UUDATA_CHECKSUM_PREFIX);
   if (n != std::string::npos)
   {
      n += sizeof (UUDATA_CHECKSUM_PREFIX) - 1;
      m_uhChecksumFromPartner =
         (unsigned short) strtoul (str.substr (n).c_str (), NULL, 0);
      std::cout << "Connection " << m_uConnID
                << ": Checksum signaled by partner: "
                << m_uhChecksumFromPartner
                << std::endl;
   }
   
   // return a pointer right behind the information element
   return (pbUUData + *pbUUData + 1);
} // CCapiStateMachine::EvalUUDataElement





/**
 * Get a file extension for a connection type value.
 */

const char *CCapiStateMachine::GetFileExtFromConnType
   (CCapiStateMachine::ConnectionType_t connType)
{
   switch (connType)
   {
      case CT_VOICE:            return ("vox");
      case CT_DATA_HDLC:        return ("hdlc");
      case CT_DATA_X75:         return ("x75");
      case CT_FAXG3:            return ("sff");
      default:                  return ("unk");
   }
   
} // CCapiStateMachine::GetFileExtFromConnType





/**
 * Update a checksum value according to a data block.
 */

void CCapiStateMachine::UpdateChecksum
   (unsigned short &uhChecksum,
    u_int8_t       *pabData,
    size_t          nLenData)
{
   if (nLenData == 0 || pabData == NULL)
   {
      return;
   }
   
   u_int8_t *pStart;
   u_int8_t *pEnd;
   
   for (pStart = pabData, pEnd = &(pabData [nLenData]); pStart < pEnd; ++pStart)
   {
      uhChecksum += (unsigned short) *pStart;
   }
   
} // CCapiStateMachine::UpdateChecksum





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





