/* NOTE: this program uses the Protogate DLI/TSI library, and cannot be
*        compiled without several DLI/TSI include files.
*/

/* Source control identifier */
static char rcsid[]=
"@(#)$Id: awsalp.c,v 1.19 1998/10/06 18:01:31 breid Exp $";

/*
*               CONFIDENTIAL & PROPRIETARY INFORMATION
*              Distribution to Authorized Personnel Only
*   Unpublished/Copyright 1992,1993,1994,1995 Simpact Associates, Inc.
*                       All Rights Reserved
*
* This document contains confidential and proprietary information of Simpact
* Associates, Inc, ("Simpact") and is protected by copyright, trade secret and
* other state and federal laws. The possession or receipt of this information
* does not convey any right to disclose its contents, reproduce it, or use, or
* license the use, for manufacture or sale, the information or anything
* described therein. Any use, disclosure, or reproduction without Simpact's
* prior written permission is strictly prohibited.
*
* Software and Technical Data Rights
*
* Simpact software products and related documentation will be furnished
* hereunder with "Restricted Rights" in accordance with:
*
*      A. Subparagraph (c)(1)(ii) of the clause entitled Rights in Technical
*      Data and Computer Software (OCT 1988) located at DFARS 252.227-7013; or
*
*      B. Subparagraph (c)(2) of the clause entitled Commercial Computer
*      Software - Restricted Rights (JUN 1987) located at FAR 52.227.19.
*
*/

/*---------------------------------------------------------------------------

File: awsalp.c

    Contains: `C' source code for aws asynchronous verification
    loop program.

----------------------------------------------------------------------------*/

/****************************************************************************/
/*                             October 1995                                 */
/* This program was significantly modified to convert it to an event driven */
/* program where most of the work is done via the callback function, this   */
/* allows for easy conversion to Windows NT                                 */
/****************************************************************************/

/****************************************************************************
                             How it all works
-----------------------------------------------------------------------------
This loopback program has been written to be compiled and run on SunOS,
Solaris, HPUX, AIX, OSF1, VMS, OpenVMS and Windows NT.
-----------------------------------------------------------------------------
Here is a brief overview of how the program is structured:

main: The main sets up DLI/TSI by calling dlInit and opens the two loopback
      sessions. It then executes a wait loop until the program terminates.
      In the real world, this wait loop is where the program would do its
      other work (except for Windows NT). All I/O completions, for the
      loopback program are handled by the notify_process function which is
      called by the notify_interrupt function which, in turn, is called
      by DLI upon completion of all active I/O completions.

queue_interrupt - This function is called by DLI for I/O completions on
      specific sessions (dli clients). It sets a flag in the "work_array"
      to indicate that there has been an I/O completion for that session.
      It also sets a general work flag which is used as a loop control for
      the notify_process function (see below).

notify_interrupt - This function is called by DLI when it has completed all
      active I/O completions. This routine then calls the notify_process
      function to process all I/O completions. This function protects the
      notify_process from re-entrancy (in this case we use a static flag).
      The user may want to implement the use of signals or semaphores so that
      the main program may sleep if it has no work to do. 

notify_process - This function loops as long as the general work flag is
      set. It calls dlPoll to get the number or reads and writes that are
      complete. It then calls a function to process the writes completed
      (process_writes) and another to process the reads completed
      (process_reads). After all reads and writes have been processed, it
      calls a function that controls the loopback based on the current
      operational system state (process_op_state).

process_writes - This function retrieves write complete buffers, frees the
      allocated memory, displays the ">" symbol on the screen and posts
      another write if the test has not ended.

process_reads - This function retrieves read completes, compares the received
      data, frees the allocated memory, displays the "<" symbol on the
      screen and posts another read.

process_op_state - This function handles most of the operational state
      changes (some are handled by process_reads).
-----------------------------------------------------------------------------
Error handling:
      Some error handling has been abreviated to make the code easier to
      follow. If you use this code as a template for your own application,
      you should evaluate each function's return codes to decide your own
      error handling scheme.
-----------------------------------------------------------------------------
DLI client ID:
      The program takes advantage of the fact that DLI starts its client ID
      numbering from 0 and that the program opens only two connections.
      Therefore we use the dli_cid (client ID number) as an index into the
      session table (SessTbl[]).
-----------------------------------------------------------------------------
DLI configuration file:
      This program is designed to work with the DLI configuration file
      'awsaldcfg', as delivered. Since AWS has a broad range of options,
      no single program will cover all of the options. If you change DLI
      configurations, you may have to modify this program to get it to
      work properly.
-----------------------------------------------------------------------------
*****************************************************************************/

#include <ctype.h>       /* tolower()                               */
#include <stdio.h>       /* printf, fprintf, sprintf, etc.          */
#include <stdlib.h>      /* exit(), sleep(), atoi(), etc.           */
#include <string.h>      /* strcmp(), strlen()                      */
#include <signal.h>      /* signal for control C                    */
#include <time.h>        /* Type time_t, functions time & difftime. */

#ifndef __VMS__
#include <memory.h>      /* memset, memcpy, et al.                  */
#endif

#ifdef WINNT
#include "template.h"
#endif

#include "ascii.h"
#include "freeway.h"
#include "dlicperr.h"
#include "dlierr.h"
#include "dliicp.h"
#include "dliprot.h"
#include "dliusr.h"
#include "dliaws.h"
#include "utils.h"


/*******************/
/* program defines */
/*******************/
#define BIN_FILE         "awsaldcfg.bin"  
#define MAX_BOARDS             8          /* Maximum number of ICP boards. */
#define MAX_LINKS             16          /* Maximum number of ICP links.  */
#define NUM_CONNECTIONS        2          /* Number of loopback connections*/
#define MINS_IN_DAY           (24 * 60)   /* Number of minutes in a day.   */
#define UNINITIALIZED_SESSION -1          /* Session IDs are non-negative. */

#define LINK0                  0          /* link 0 (the even link)        */
#define LINK1                  1          /* link 1                        */

/*-------------------------------------------------*/
/* define the operating status values (this is the */
/* order in which theses states should occur)      */
/*-------------------------------------------------*/
#define STARTUP        0     /* getting user input                        */
#define CONNECTING     1     /* connecting clients                        */
#define TRANSFERRING   2     /* transferring data for test duration       */
#define CLEANUP        3     /* clear pending reads and writes            */
#define WAIT_STATS     4     /* waiting for closes to complete            */
#define STATS_DONE     5     /* statistics reports complete               */
#define WAIT_CLRD      6     /* waiting for statistics to be cleared      */
#define STATS_CLRD     7     /* statistics cleared                        */
#define DISCONNECTING  8     /* waiting for closes to complete            */
#define TEST_ENDED     9     /* test ended                                */

#define SECONDS        1
#define MINUTES        2

/*-----------------------------------------*/
/* general work flag used in callback func */
/*-----------------------------------------*/
#define WORK_FLAG      2

#define Z_AWS_BLOCK_CONTROL_REMAINDER 0 /* After count in aws ctl block */
#define Z_AWS_BLOCK_CONTROL 0           /* Number of bytes in aws ctl block */

/*************************/
/* structure definitions */
/*************************/
typedef struct 
{
    unsigned char       data;           /* Data here            */
} AWS_BLOCK_CONTROL;

/*------------------------------------------
Type definition for a session table entry.
------------------------------------------*/
typedef struct _SESS_TBL_ENTRY
{
    char  cSessName[128];     /* Session name.                              */
    int   iSessID;            /* Session ID returned by dlOpen().           */
    char *pMsg;               /* Pointer to the transmit data.              */
    char *pCmp;               /* Pointer to comparison string for I/P data. */
} SESS_TBL_ENTRY;

/*-------------------------------------------------------*/
/* Type definition for the AWS Statistics Report format. */
/*-------------------------------------------------------*/
typedef unsigned short  USHORT;
typedef long int        int32;
typedef struct _STATS_RPT_BFR
{
   USHORT   dcd_change;              /* Times DCD came high     */
   USHORT   cts_change;              /* Times CTS came high     */
   USHORT   abort_rcvd;              /* Number of Breaks rcvd   */
   USHORT   rcv_ovrrun;              /* Recieve overruns        */
   USHORT   rcv_crcerr;              /* Block check errors      */
   USHORT   rcv_parerr;              /* Parity errors           */
   USHORT   rcv_frmerr;              /* Framing errors          */
   USHORT   xmt_undrun;              /* Trnasimt underruns      */
   int32   char_sent;               /* Characters sent         */
   int32   char_rcvd;               /* Characters rcvd         */
   int32   frame_sent;              /* Frames sent.            */
   int32   frame_rcvd;              /* Frames rcvd             */
} STATS_RPT_BFR;

/****************************/
/* program global variables */
/****************************/
/*-------------------------------------------------------------------*/
/* This is the test data that is written.  Even-numbered ports write */
/* data to the next highest odd-numbered port, and vice versa.       */
/*-------------------------------------------------------------------*/
static char cMsg0[] =
    {"ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 abcdefghijklmnopqrstuvwxyz "};
static char cMsg1[] =
    {"BCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 abcdefghijklmnopqrstuvwxyz A"};

/*------------------------------------------------------------------------*/
/* These are the "control" strings.  After one of the above messages is   */
/* sent, the string is rotated for the next time, so we don't continually */
/* send the same data repeatedly.  When the message is read in, then, it  */
/* will differ from what is now contained in the above strings.  The      */
/* strings below are the strings against which input data is compared.    */
/* These are also rotated after a successful comparison.                  */
/*------------------------------------------------------------------------*/
static char cCmp0[] =
    {"ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 abcdefghijklmnopqrstuvwxyz "};
static char cCmp1[] =
    {"BCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 abcdefghijklmnopqrstuvwxyz A"};

/*----------------------------------------------------*/
/* The session table, with one entry per connection   */
/* the connection name may be modified after the user */
/* selects the ICP and ports for the loopback         */
/*----------------------------------------------------*/
static SESS_TBL_ENTRY SessTbl[NUM_CONNECTIONS] =
   {{"server0icp0port0 ", UNINITIALIZED_SESSION, cMsg0,  cCmp1},
    {"server0icp0port1 ", UNINITIALIZED_SESSION, cMsg1,  cCmp0}};

/*-----------------------------*/
/* currrent operational status */
/*-----------------------------*/
int op_state = STARTUP;

/*-------------------------------------------*/
/* general work array used in callback func  */
/* to indicate I/O completeions for a client */
/*-------------------------------------------*/
int work_array[WORK_FLAG+1];

/*---------------------------------------*/
/* The number of active sessions...      */
/* when equal to 2, everone is connected */
/*---------------------------------------*/
int active_sessions = 0;     

int writes_outstanding = 0; /* number of pending writes */
time_t end_time;

int  iMaxBfrSize;            /* The max ICP buffer size.        */
int  iMinutes;               /* Time duration for test.         */

STATS_RPT_BFR  pStats[NUM_CONNECTIONS];  /* Storage for each link's stats.  */

/*---------------------------------------------------------------------
The following two variables are declared in the header file dliusr.h:

extern int   dlerrno;
extern short iICPStatus;
---------------------------------------------------------------------*/

/************************/
/* functions prototypes */
/************************/
#ifdef __STDC__
void notify_process();
void process_op_state(int dli_cid, DLI_SESS_STAT sess_stat);
void process_writes(int dli_cid, short WritesDone);
void process_reads(int dli_cid, int ReadsDone);
void print_stats();
int notify_interrupt(char *usr_data);
int queue_interrupt(char *usr_data, int dli_cid);
int BfrSize(int iSessID);
void QReads(int dli_cid, int iBfrSize, int num_reads);
void QWrites(int cid, unsigned usNbrWr);
void WriteCommand(int cid, int command);
void CancelWrites(int cid);
void CancelReads(int cid);
int GetNumericEntry(char *pPrompt, int iDefault, int iLow, int iHigh);
void mrkt(int length, int units);
int time_ended();
void need_help();
int unpack_msg(int dli_cid, int nChars, char *pBuf);
static void crash( int SigNbr );
#else

void notify_process();
void process_op_state();
void process_writes();
void process_reads();
void print_stats();
int notify_interrupt();
int queue_interrupt();
int BfrSize();
void QReads();
void QWrites();
void WriteCommand();
void CancelWrites();
void CancelReads();
int GetNumericEntry();
void mrkt ();
int time_ended ();
void need_help();
int unpack_msg();
void crash();
#endif

/****************************************************************************
*
* Function: main
*
* Purpose: Send a series of strings to the Freeway server and verify that the
*          strings read match those sent.  Adjacent pairs of ICP ports
*          (e.g. ports 4 & 5) communicate with each other using asynchronous
*          I/O.
*
* Inputs: None.
*
* Outputs: None.
*
*****************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 13 Aug 94   John W.    Original coding.
*
* 06 Oct 94   John W.    Added call to CancelAllIO prior to processing the
*                        Statistics Report.  This corrects a microVAX timeout
*                        problem.
*
* 11 Nov 94   John W.    Moved the logic for closing a session into a separate
*                        function (to mirror what was done with the logic which
*                        opens sessions.
*
* 07 Apr 95   John W.    Modified user input processing.
*
* 18 jul 97   Jeff G.    Added call to sleep(1), to reduce the CPU load of the
*                        loop while waiting for the end of the test;
*                        allow 0-minute test;
*                        changed the cSessName element of SESS_TBL_ENTRY to an
*                        array rather than a pointer to avoid writing
*                        into a string (which some compilers don't allow).
* 08 sep 97   Jeff G.    Added code to the while loop at the end of this
*                        routine to protect against a lost message preventing
*                        the program from terminating.
*
* 12 JUN 98   Matt A.    Modified to poll for Session Status not Sys. Config
*                        in BfrSize().
*
*****************************************************************************/
#ifdef WINNT
 void main(int argc, char *argv[])
#else
int main()
#endif
{
    char cLine[80];  /* General purpose line buffer.  */
    int  ii;         /* FOR loop index.               */
    int  iICPNbr;    /* ICP board to run the test on. */
    int  iEvenLink;  /* Even link in the pair to test.*/

#ifdef WINNT
    open_console();
#else
    /*--------------------------------------------------*/
    /* set up to catch ctrl C to stop test or terminate */
    /*--------------------------------------------------*/
    signal(SIGINT, crash);
#endif

    /*--------------------------------*/
    /* ask the user if they need help */
    /*--------------------------------*/
    need_help();

    /*-------------------------------------*/
    /* Get the time duration for the test. */
    /*-------------------------------------*/
    sprintf(cLine, "Minutes to run (1-%d) [1]? ", MINS_IN_DAY);
    iMinutes = GetNumericEntry(cLine, 1, 0, MINS_IN_DAY);

    /*-----------------------------------*/
    /* Get the ICP board number to test. */
    /*-----------------------------------*/
    sprintf(cLine, "ICP board on which to run test (0-%d) [0]? ",MAX_BOARDS-1);
    iICPNbr = GetNumericEntry(cLine, 0, 0, MAX_BOARDS - 1);

    /*-----------------------*/
    /* Get link pair to run. */
    /*-----------------------*/
    sprintf(cLine,"Even port number (0, 2, ..., %d) [0]? ",(MAX_LINKS & ~1)-2);

    do
    {
        if ((iEvenLink = GetNumericEntry(cLine,0,0,(MAX_LINKS & ~1) - 2)) & 1)
            printf("?? Must be even-numbered link\n");
    } while (iEvenLink & 1);

    /*-------------------------------------------------------------------*/
    /* Build the session names with the new ICP and port values. Session */
    /* names are of the form "server0icp0port0", so the new ICP value    */
    /* is stuck into position 10 and the port in position(s) 15 (16)     */
    /*-------------------------------------------------------------------*/
    for (ii = 0;  ii < NUM_CONNECTIONS;  ii++)
    {
        /*-----------------------*/
        /* change the ICP number */
        /*-----------------------*/
        sprintf(cLine, "%d", iICPNbr);
        SessTbl[ii].cSessName[10] = *cLine;

        /*-------------------------*/
        /* change the port numbers */
        /*-------------------------*/
        sprintf(cLine, "%d", iEvenLink + ii);
        SessTbl[ii].cSessName[15] = '\0';
        strcat (SessTbl[ii].cSessName, cLine);
    }

    /*-------------------------------------------*/
    /* tell the operator what we are about to do */
    /*-------------------------------------------*/
    printf("\nAWS Asynchronous Port-To-Port Loopback Program.\n");
    printf("   Test duration in minutes: %d minute\n", iMinutes);
    printf("   ICP board number: %d\n", iICPNbr);
    printf("   Ports: %d & %d\n", iEvenLink, iEvenLink + 1);

    /*----------------------------------------------------------*/
    /* Initialize the DLI. dlInit requires 3 parameters         */
    /*   1. the name of the binary configuration file           */
    /*   2. a pointer to a user defined structure (may be NULL) */
    /*   3. a pointer to a callback routine (may be NULL)       */
    /*----------------------------------------------------------*/
    if (dlInit(BIN_FILE, (char *)work_array, notify_interrupt) == ERROR)
    {
        printf("main: dlInit failed with error (%d).\n", dlerrno);
        op_state = TEST_ENDED;
    }

    /*----------------------------------------------------------------*/
    /* Set the operational state to CONNECTING and open the sessions. */
    /*----------------------------------------------------------------*/
    if (op_state != TEST_ENDED)
    {
        op_state = CONNECTING;

        for (ii = 0;  ii < NUM_CONNECTIONS;  ii++)
        {
            /*---------------------------------------------------------------*/
            /* dlOpen requires 2 parameters                                  */
            /*   1. the session name as it appears in the configuration file */
            /*   2. a pointer to the callback routine (may be NULL)          */
            /*---------------------------------------------------------------*/
            if (dlOpen(SessTbl[ii].cSessName, queue_interrupt) == ERROR)
            {
                printf("main: dlOpen failed with error (%d).\n", dlerrno);
                op_state = TEST_ENDED;
            }
            printf ("OPEN SESSION %s \n", SessTbl[ii].cSessName);
        }
    }

    /*--------------------------------------------------------*/
    /* for a windows program, the main must be exited so that */
    /* all of the work can be done in the window's procedure  */
    /*--------------------------------------------------------*/
#ifndef WINNT
    /*--------------------------------*/
    /* wait here until test completes */
    /* for all systems except NT      */
    /*--------------------------------*/
    while (op_state != TEST_ENDED)
    {
        /* do other program functions here */

        /*-------------------------------------------------------*/
        /* the following code is a safety valve to ensure that   */
        /* this loopback terminates properly if, for some reason */
        /* a message is not properly accounted for due to a very */
        /* slow system or congested network                      */
        /*-------------------------------------------------------*/
        if (op_state == CLEANUP && time_ended())
        {
            /*-------------------------------------------------------*/
            /* fake two calls to the "queue_interrupt" callback      */
            /* then call the "notify_interrupt" callback to complete */
            /* any pending actions                                   */
            /*-------------------------------------------------------*/
            work_array[0] = 1;
            work_array[1] = 1;
            work_array[WORK_FLAG] = 1;
            notify_interrupt(NULL);
        }
        else if ( ( op_state == DISCONNECTING ) && time_ended() )
        {
            op_state = TEST_ENDED;
        }

        sleep(1);      /* sleep one second before checking if we should exit */
    }

    dlTerm();

    exit( 0 );
#endif

}

/****************************************************************************
* Function Name:
*      queue_interrupt
*
* Function Description:
*      This function was defined in dlOpen and is called by DLI
*      when an I/O completes for a specific session. This routine sets
*      a flag for the session and a general flag indicating that there
*      is work to be done (the actual work will be preformed by the
*      notifiy_process routine).
*
* Arguments:
*      char *usr     pointer to user interrupt data
*      int dli_cid   DLI session ID
*
* Return Value:
*      none
*****************************************************************************/
#ifdef __STDC__
 int queue_interrupt(char *usr_data, int dli_cid)
#else
int queue_interrupt(usr_data, dli_cid)
    char *usr_data;
    int dli_cid;
#endif
{
    /*---------------------------------------------------*/
    /* make sure this is a good dli_cid                  */
    /* (for this simple program, we know that there      */
    /* will only be 2 cids and DLI always starts with 0) */
    /*---------------------------------------------------*/
    if (dli_cid == 0 || dli_cid == 1)
    {
        /*-------------------------------------------------------*/
        /* just set an indication flag in the client work array, */
        /* action will be taken by the notifiy_process function  */
        /*-------------------------------------------------------*/
        ((int *)usr_data)[dli_cid]   = 1;   /* work for a cid    */
        ((int *)usr_data)[WORK_FLAG] = 1;   /* general work flag */
    }

    return(0);
}

/****************************************************************************
* Function Name:
*      notify_interrupt
*
* Function Description:
*      This function was defined in dlInit and is called by DLI
*      when all I/O completions have been processed. The queue_interrupt
*      function (above) will have been called for each session with
*      work pending. If the notify_process is not already executing,
*      start it now.
*
* Arguments:
*      char *usr_data  pointer to user data which is out work_array
*
* Return Value:
*      0
*****************************************************************************/
#ifdef __STDC__
 int notify_interrupt(char *usr_data)
#else
int notify_interrupt(usr_data)
    char *usr_data;
#endif
{
    static int busy = 0;

    /*-------------------------------------------------------*/
    /* protect ourselves from another call (reentrancy)      */
    /*-------------------------------------------------------*/
    /* the below method of using the busy flag seems to work */
    /* fine since the latency between I/O interrupts in TSI  */
    /* is much longer than the test and increment of 'busy'. */
    /* The while loop is needed for the very slow systems    */
    /* such as the VAX.                                      */
    /*-------------------------------------------------------*/
    /* The user may also use signals or semaphores to invoke */
    /* the notify_processor                                  */
    /*-------------------------------------------------------*/
    if ( !busy++ )
    {
        do
        {
            notify_process();
            busy--;
        } while ( busy );
    }

    return(0);
}

/****************************************************************************
* Function Name:
*      notify_process
*
* Function Description:
*      This function processes I/O completions. It is called by the
*      notify_interrupt function. It loops while there is work pending
*      (the callback routines may have been called while we were processing).
*      This routine gets the session's status and processes any read and
*      write completions.
*
* Arguments:
*      none
*
* Return Value:
*      none
*****************************************************************************/
void notify_process()
{
    DLI_SESS_STAT sess_stat;
    DLI_OPT_ARGS *dummy;
    int dli_cid;

    /*------------------------------------------------------*/
    /* stay in this loop until there is no more work to do. */
    /* We could be interrupted with more work while in here */
    /*------------------------------------------------------*/
    while (work_array[WORK_FLAG])
    {
        /*----------------------------------------*/
        /* clear the 'general' work flag, it will */
        /* be reset to 1 if an interrupt occurs   */
        /* while we are in this loop              */
        /*----------------------------------------*/
        work_array[WORK_FLAG] = 0;

        /*-------------------------------------------*/
        /* NOTE: DLI allocates IDs starting at zero, */
        /*       so links 0 and 1 will be IDs 0 & 1  */
        /*-------------------------------------------*/
        for (dli_cid = 0; dli_cid < NUM_CONNECTIONS; dli_cid++)
        {
            /*--------------------------------------------*/
            /* see if there is work for this ID (session) */
            /*--------------------------------------------*/
            if (work_array[dli_cid])
            {
                /*------------------------------------*/
                /* clear this ID's work flag, it may  */
                /* be reset while we are in this loop */
                /*------------------------------------*/
                work_array[dli_cid] = 0;

                /*------------------------------------------------------*/
                /* call dlPoll to get status for this ID                */
                /* (the only errors that dlPoll can return for a status */
                /* request are if we have not called dlInit or if the   */
                /* dli_cid is invalid. Since this can not happen in     */
                /* this code, we do not bother to check for errors)     */
                /*------------------------------------------------------*/
                if (dlPoll(dli_cid, DLI_POLL_GET_SESS_STATUS, NULL, 0,
                    (char *)&sess_stat, &dummy) != ERROR)
                {
                    /*------------------------------*/
                    /* process all writes completed */
                    /*------------------------------*/
                    if (sess_stat.iQNumWriteDone)
                        process_writes(dli_cid, sess_stat.iQNumWriteDone);

                    /*-----------------------------*/
                    /* process all reads completed */
                    /*-----------------------------*/
                    if (sess_stat.iQNumReadDone)
                        process_reads(dli_cid, sess_stat.iQNumReadDone);

                    /*-----------------------------------------*/
                    /* now do some additional processing based */
                    /* on the current operational state        */
                    /*-----------------------------------------*/
                    process_op_state(dli_cid, sess_stat);

                }

            }   /* if work for this cid */

        }   /* for loop */

    }   /* while work to do */
}

/****************************************************************************
* Function Name:
*      process_op_state
*
* Function Description:
*      process the following system op_states (special processing needed
*      in each case).
*          CONNECTING    - connecting clients                       
*          TRANSFERRING  - transferring data for test duration      
*          CLEANUP       - wait for pending reads and writes        
*          DISCONNECTING - waiting for closes to complete           
*
* Arguments:
*      int dli_cid   DLI session ID
*      DLI_SESS_STAT sess_stat  DLI session status record from a previous
*                               call to dlPoll
*
* Return Value:
*      none
*****************************************************************************/
#ifdef __STDC__
 void process_op_state(int dli_cid, DLI_SESS_STAT sess_stat)
#else
void process_op_state(dli_cid, sess_stat)
    int dli_cid;
    DLI_SESS_STAT sess_stat;
#endif
{
    /*----------------------------------------------*/
    /* process dlPoll results based on the op state */
    /*----------------------------------------------*/
    switch (op_state)
    {
        case CONNECTING:
            /*-----------------------------------------------*/
            /* if this port is still uninitialized and the   */
            /* session is now ready, save the session Id and */
            /* check for both sessions ready for starting    */
            /* the loop back testing                         */
            /*-----------------------------------------------*/
            if (SessTbl[dli_cid].iSessID == UNINITIALIZED_SESSION
                && sess_stat.iSessStatus == DLI_STATUS_READY)
            {
                /*----------------------------*/
                /* save the port's session ID */
                /*----------------------------*/
                SessTbl[dli_cid].iSessID = dli_cid;

                /*-------------------------------------------*/
                /* if both sessions are up, change the state */
                /* to transfer data packets                  */
                /*-------------------------------------------*/
                if (++active_sessions == NUM_CONNECTIONS)
                {
                    op_state = TRANSFERRING;

                    /*-------------------------*/
                    /* get the max buffer size */
                    /*-------------------------*/
                    iMaxBfrSize = BfrSize(dli_cid);

                    /*-----------------------------------------*/
                    /* post the initial reads for both clients */
                    /*-----------------------------------------*/
                    QReads(LINK0, iMaxBfrSize, 20);
                    QReads(LINK1, iMaxBfrSize, 20);

                    /*-------------------------------------------*/
                    /* set the timer for loop back test duration */
                    /*-------------------------------------------*/
                    mrkt(iMinutes, MINUTES);

                    /*-------------------------*/
                    /* post the initial writes */
                    /*-------------------------*/
                    QWrites( LINK0, 1 ); 
                    QWrites( LINK1, 1 );
            
                }
            }
            break;

        case TRANSFERRING:
            /*-------------------------------------*/
            /* check to see if the test should end */
            /*-------------------------------------*/
            if (time_ended())
            {
                /*------------------------------------*/
                /* time has ended, set the next state */
                /* and a 5 second timeout to exit the */
                /* cleanup state                      */
                /*------------------------------------*/
                op_state = CLEANUP;
                mrkt(5, SECONDS);
            }
            
            break;

        case CLEANUP:
            /*---------------------------------------------*/
            /* check for all reads and writes completed or */
            /* 5 second time has expired                   */
            /*---------------------------------------------*/
            if (!writes_outstanding || time_ended())
            {
                /*-----------------------------------------------------*/
                /* all reads/writes are complete, or we have timed out */
                /* set the next state                                  */
                /*-----------------------------------------------------*/
                op_state = WAIT_STATS;

                /*-------------------------------*/
                /* cancel all outstanding writes */
                /* leave reads posted for now    */
                /*-------------------------------*/
                CancelWrites(LINK0);
                CancelWrites(LINK1);

                /*----------------------------------------------*/
                /* request the statistics report for both links */
                /*----------------------------------------------*/
                WriteCommand(LINK0, DLI_PROT_GET_STATISTICS_REPORT);
                WriteCommand(LINK1, DLI_PROT_GET_STATISTICS_REPORT);

            }
            break;

        case STATS_DONE:
            /*-------------------------------------------*/
            /* statistics are cleard, set the next state */
            /*-------------------------------------------*/
            op_state = WAIT_CLRD;

            /*-------------------------------------*/
            /* clear the statistics for both links */
            /*-------------------------------------*/
            WriteCommand(LINK0, DLI_PROT_CLR_STATISTICS);
            WriteCommand(LINK1, DLI_PROT_CLR_STATISTICS);
            break;

        case STATS_CLRD:
            /*-------------------------------------------*/
            /* statistics are cleard, set the next state */
            /*-------------------------------------------*/
            op_state = DISCONNECTING;

            /*------------------------------*/
            /* cancel all outstanding reads */
            /*------------------------------*/
            CancelReads(LINK0);
            CancelReads(LINK1);

            /*---------------------*/
            /* close both sessions */
            /*---------------------*/
            dlClose(LINK0, DLI_CLOSE_FORCE);
            dlClose(LINK1, DLI_CLOSE_FORCE);

            break;

        case DISCONNECTING:
            /*------------------------------------------------*/
            /* if this port is still initialized and the      */
            /* session is now NOT ready, clear the session Id */
            /* and check for both sessions disconnected       */
            /*------------------------------------------------*/
            if (SessTbl[dli_cid].iSessID != UNINITIALIZED_SESSION
                && sess_stat.iSessStatus != DLI_STATUS_READY)
            {
                /*-----------------------------*/
                /* clear the port's session ID */
                /*-----------------------------*/
                SessTbl[dli_cid].iSessID = UNINITIALIZED_SESSION;

                /*-----------------------------------------------*/
                /* if both sessions are closed, change the state */
                /*-----------------------------------------------*/
                if (--active_sessions == 0)
                {
                    op_state = TEST_ENDED;
                    printf("\nLoopback test complete\n");
                }
            }

            break;

    }   /* switch */
}

/****************************************************************************
* Function Name:
*      process_writes
*
* Function Description:
*      This function retrieves completed writes from DLI and frees the
*      buffers. It then posts more writes if time has not expired.
*
* Arguments:
*      int dli_cid       DLI client ID <assume client is valid>
*      short WritesDone  number of writes completed
*
* Return Value:
*      none
*
* Revision History:
*
*    Date     Engineer      Description
*  -------   ------------   -----------
*  18jul97   Jeff Gibbons   Added call to QWrites() here, and deleted from
*                           process_reads().
*
*****************************************************************************/
#ifdef __STDC__
 void process_writes(int dli_cid, short WritesDone)
#else
void process_writes(dli_cid, WritesDone)
    int dli_cid;
    short WritesDone;
#endif
{
    char *write_buf = NULL;
    DLI_OPT_ARGS *opt_args = NULL;
    int length;
    int ii;

    /*-----------------------------------------------*/
    /* get each write completed, and free the buffer */
    /*-----------------------------------------------*/
    for (ii = 1; ii <= WritesDone; ii++)
    {
        write_buf = NULL;
        opt_args  = NULL;

        /*------------------------------------------------------*/
        /* get the write complete. The only error we are going  */
        /* to worry about is a write timeout                    */
        /*------------------------------------------------------*/
        if (dlPoll(dli_cid, DLI_POLL_WRITE_COMPLETE, &write_buf, &length,
            NULL, &opt_args) != ERROR)
        {
            /*-------------------------------*/
            /* display the 'write' indicator */
            /*-------------------------------*/
            if (op_state == TRANSFERRING)
                fprintf(stderr,">");

        }
        else if (dlerrno == DLI_POLL_ERR_WRITE_TIMEOUT)
            printf("process_writes: dlWrite timed out.\n");

        /*------------------------------------------------*/
        /* free the write buffer and opt args if not NULL */
        /*------------------------------------------------*/
        if (write_buf)
            dlBufFree(write_buf);

        if (opt_args)
            dlBufFree((char *)opt_args);

    }   /* for loop */

    /*----------------------------------------------------*/
    /* if the test has not ended, repost some more writes */
    /*----------------------------------------------------*/
    if (op_state == TRANSFERRING)
        QWrites(dli_cid, WritesDone);
}

/****************************************************************************
* Function Name:
*      process_reads
*
* Function Description:
*      This function retrieves completed reads from DLI and process the
*      data based on the current system operational state. If we are in
*      the transferring state, additional read(s) will be posted.
*      
*      This function also handles the buffer and statistics reports and
*      changes the system op_state upon report completions
*
* Arguments:
*      int dli_cid    DLI client ID <assume client is valid>
*      int ReadsDone  number of reads completed
*
* Return Value:
*      none
*
* Revision History:
*
*    Date     Engineer      Description
*  -------   ------------   -----------
*  18jul97   Jeff Gibbons   Removed call to QWrites(), and added to
*                           process_writes(); added check for abnormal
*                           read completion; changed display of data when
*                           comparison fails.
*
*****************************************************************************/
#ifdef __STDC__
 void process_reads(int dli_cid, int ReadsDone)
#else
void process_reads(dli_cid, ReadsDone)
    int dli_cid;
    int ReadsDone;
#endif
{
    char *read_buf = NULL;
    DLI_OPT_ARGS *opt_args = NULL;
    int iMsgLen = strlen(SessTbl[dli_cid].pMsg);  /* Message length. */
    int length, ii;
    static int stat_link1  = FALSE;
    static int stat_link2  = FALSE;
    static int clrd_link1 = FALSE;
    static int clrd_link2 = FALSE;

    /*----------------------------------------------------*/
    /* get each read completed, and process received data */
    /*----------------------------------------------------*/
    for (ii = 1; ii <= ReadsDone; ii++)
    {
        read_buf = NULL;
        opt_args = NULL;

        /*------------------------------------------------------*/
        /* get the read complete. There are no errors that need */
        /* to be handled from this call since we are retrieving */
        /* messages that dlPoll has already informed us of being*/
        /* ready to be read (no queue empty error is possible)  */
        /*------------------------------------------------------*/
        dlPoll(dli_cid, DLI_POLL_READ_COMPLETE, &read_buf, &length,
            NULL, &opt_args);
        
        /*-----------------------------------------------------*/
        /* process the received data according to the op_state */
        /*-----------------------------------------------------*/
        switch (op_state)
        {
            case TRANSFERRING:
            case CLEANUP:
                /*-----------------------*/
                /* Check the ICP status. */
                /*-----------------------*/
                if (opt_args->iICPStatus != DLI_ICP_ERR_NO_ERR)
                {
                    printf("\n\nprocess_reads: Bad ICP status (%d/%d).\n",
                        opt_args->iICPStatus, iICPStatus);
                }

                /*----------------------------------------------------*/
                /* Check that the type of data we received is what we */
                /* were expecting. The WriteType parameter is set to  */
                /* "Transparent" in the configuration file.           */
                /*----------------------------------------------------*/
                else if (opt_args->usProtCommand != DLI_PROT_RECV_DATA &&
                    opt_args->usProtCommand != 0)
                {
                    printf(
                        "\n\nprocess_reads: Command mismatch (exp=%d; act=%d).\n",
                        DLI_PROT_RECV_DATA, opt_args->usProtCommand);
                }
                else
                {
                    /*----------------------------------------*/
                    /* Anything other than normal completion? */
                    /*----------------------------------------*/
                    if ((opt_args->iProtModifier != DLI_AWS_SC_RCV_END) &&
                        (opt_args->iProtModifier != DLI_AWS_SC_BUFF_FULL) )
                    {
                       printf(
                   "\nprocess_reads on cid %d: iProtMod error (dec=%d  %x).\n",
                    dli_cid, opt_args->iProtModifier, opt_args->iProtModifier);
		       break;
                    }

                    /*----------------------------------------*/
                    /* Check that we actually read some data. */
                    /*----------------------------------------*/
                    if (!length)
		    {
                        printf("\n\nprocess_reads: Zero data bytes read.\n");
		    }
                    else
                    {
                        /*---------------------------------------------*/
                        /* display the '<' to indicate received packet */
                        /*---------------------------------------------*/
                        writes_outstanding--;
                        fprintf(stderr,"<");

                        /*-------------------------------------------*/
                        /* Place a NULL at the end of the received   */
                        /* buffer and compare the input data against */
                        /* what was expected.                        */
                        /*-------------------------------------------*/
                        read_buf[length] = ASCII_NUL;

                        if (strcmp(read_buf, SessTbl[dli_cid].pCmp) != 0)
                        {
                            printf(
                               "\n\nprocess_reads: Data comparison failed.\n");
                            printf("Received = \"%s\"\nExpected = \"%s\"\n",
                                read_buf,SessTbl[dli_cid].pCmp);
                        }
                        else
                        {
                            int II;

                            /*------------------------------*/
                            /* Rotate the comparison string */
                            /* for the next read.           */
                            /*------------------------------*/
                            SessTbl[dli_cid].pCmp[iMsgLen] =
                                SessTbl[dli_cid].pCmp[0];

                            for (II = 0;  II < iMsgLen;  II++)
                                SessTbl[dli_cid].pCmp[II] =
                                    SessTbl[dli_cid].pCmp[II+1];

                            SessTbl[dli_cid].pCmp[iMsgLen] = ASCII_NUL;
                        }

                    }   /* read length OK */

                }   /* no errors */

                break;
            case WAIT_STATS:
                /*----------------------------------------------------*/
                /* are waiting for the statistics reports to complete */
                /*----------------------------------------------------*/
                if (opt_args->usProtCommand==DLI_PROT_GET_STATISTICS_REPORT)
                {
                    /*----------------------------*/
                    /* Copy the statistics to the */
                    /* appropriate storage area.  */
                    /*----------------------------*/
                    memcpy((char*) &(pStats[dli_cid]), read_buf,
                        sizeof(STATS_RPT_BFR));

                    /*-----------------------*/
                    /* set the stats flag(s) */
                    /*-----------------------*/
                    if (dli_cid == 0)
                        stat_link1 = TRUE;
                    else
                        stat_link2 = TRUE;
                }

                /*-----------------------------*/
                /* Print the Statistics Report */
                /* and change the system state */
                /*-----------------------------*/
                if (stat_link2 && stat_link1)
                {
                    print_stats();
                    op_state = STATS_DONE;
                }

                break;
 
            case WAIT_CLRD:
                /*--------------------------*/
		/* reset link status flags  */
		/*--------------------------*/

                /*----------------------------------------------------*/
                /* are waiting for the statistics reports to complete */
                /*----------------------------------------------------*/
                if (opt_args->usProtCommand==DLI_PROT_CLR_STATISTICS)
                {
                    /*----------------------------*/
                    /* Copy the statistics to the */
                    /* appropriate storage area.  */
                    /*----------------------------*/
                    memcpy((char*) &(pStats[dli_cid]), read_buf,
                        sizeof(STATS_RPT_BFR));

                    /*-----------------------*/
                    /* set the stats flag(s) */
                    /*-----------------------*/
                    if (dli_cid == 0)
                        clrd_link1 = TRUE;
                    else
                        clrd_link2 = TRUE;
                }

                if (stat_link2 && stat_link1)
                    op_state = STATS_CLRD;

                break;

        }   /* switch */

        /*-----------------------------------------------*/
        /* free the read buffer and opt args if not NULL */
        /*-----------------------------------------------*/
        if (read_buf)
            dlBufFree(read_buf);

        if (opt_args)
            dlBufFree((char *)opt_args);

    }   /* for loop */

    /*---------------------------------------------------*/
    /* if the test has not ended, repost some more reads */
    /*---------------------------------------------------*/
    if (op_state == TRANSFERRING || op_state == CLEANUP)
        QReads(dli_cid, iMaxBfrSize, ReadsDone);
}

/****************************************************************************
* Function Name:
*      print_stats
*
* Function Description:
*      This function prints the link statistics
*
* Arguments:
*      none
*
* Return Value:
*      none
*****************************************************************************/
void print_stats()
{
    printf("\nAWS Statistics Report:\n");
    printf("                                %17s     %17s\n",
        SessTbl[LINK0].cSessName, SessTbl[LINK1].cSessName);
    printf("                         \
        -----------------     -----------------\n");
    printf("   DCD UP                       %11d%22d\n",
        pStats[LINK0].dcd_change, pStats[LINK1].dcd_change);
    printf("   CTS UP                       %11d%22d\n",
        pStats[LINK0].cts_change, pStats[LINK1].cts_change);
    printf("   Breaks Received              %11d%22d\n",
        pStats[LINK0].abort_rcvd, pStats[LINK1].abort_rcvd);
    printf("   Receive overrun errors       %11d%22d\n",
        pStats[LINK0].rcv_ovrrun, pStats[LINK1].rcv_ovrrun);
    printf("   Block check errors           %11d%22d\n",
        pStats[LINK0].rcv_crcerr, pStats[LINK1].rcv_crcerr);
    printf("   Parity errors                %11d%22d\n",
        pStats[LINK0].rcv_parerr, pStats[LINK1].rcv_parerr);
    printf("   Framing errors               %11d%22d\n",
        pStats[LINK0].rcv_frmerr, pStats[LINK1].rcv_frmerr);
    printf("   Transmit underruns           %11d%22d\n",
        pStats[LINK0].xmt_undrun, pStats[LINK1].xmt_undrun);
    printf("   Characters sent              %11d%22d\n",
        pStats[LINK0].char_sent, pStats[LINK1].char_sent);
    printf("   Characters received          %11d%22d\n\n",
        pStats[LINK0].char_rcvd, pStats[LINK1].char_rcvd);
    printf("   Frames sent                  %11d%22d\n",
        pStats[LINK0].frame_sent, pStats[LINK1].frame_sent);
    printf("   Frames received              %11d%22d\n",
        pStats[LINK0].frame_rcvd, pStats[LINK1].frame_rcvd);
}

/******************************************************************************
*
* Function: BfrSize
*
* Purpose: Makes a request to the DLI to obtain the maximum ICP buffer size.
*
* Inputs: iSessID - The session ID to use for the request.
*
* Outputs: The value returned by the function is the ICP buffer size, or ERROR
*          if an error occured.
*
* Error Processing: An alert is written to standard error, and ERROR is
*                   returned to the caller.
*
*******************************************************************************
*
* REVISION HISTORY:
*
*    Date     Initials   Modification
*    ----     --------   ------------
* 31 Jul 94   John W.    Original coding with new DLI interface.
* 09 JUN 98   Matt A.    Modified to poll for Session Status not Sys. Config.
*
******************************************************************************/

int BfrSize( iSessID )
  int         iSessID;
{
  DLI_SESS_STAT  SessStat;

  if ( dlPoll( iSessID,
               DLI_POLL_GET_SESS_STATUS,
               (PCHAR*) NULL,
               (PINT) NULL,
               (PCHAR) &SessStat,
               (PDLI_OPT_ARGS*) NULL ) == ERROR )
  {
    fprintf( stderr, "BfrSize: Request failed (%d).\n", dlerrno );
    return ERROR;
  }

  return SessStat.usMaxSessBufSize;
}

/******************************************************************************
*
* Function: QReads
*
* Purpose: This function issues a number read requests to the DLI.
*
* Inputs: dli_cid  - the DLI client ID number
*
*         iBfrSize - The size of the input buffer.
*
*         num_reads - number of reads to post
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 13 Aug 94   John W.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
 void QReads(int dli_cid, int iBfrSize, int num_reads)
#else
void QReads(dli_cid, iBfrSize, num_reads)
    int dli_cid, iBfrSize, num_reads;
#endif
{
    PCHAR         pBfr = NULL;   /* Pointer to input buffer.       */
    PDLI_OPT_ARGS pOptArgs;      /* Pointer to optional arguments. */
    int           done = FALSE;

    /*---------------------------------------------------*/
    /* Keep posting reads until the input queue is full. */
    /*---------------------------------------------------*/
    while (!done && num_reads--)
    {
        /*----------------------------------------------------------*/
        /* Allocate memory for the optional arguments and clear it. */
        /*----------------------------------------------------------*/
        if ((pOptArgs = (PDLI_OPT_ARGS) dlBufAlloc(sizeof(DLI_OPT_ARGS))) == NULL)
        {
            printf("\n\nQReads: No dynamic memory (dlBufAlloc failed).\n");
            done = TRUE;
        }
        else
        {
            memset((char*) pOptArgs, ASCII_NUL, sizeof(DLI_OPT_ARGS));

            /*----------------------------------------------------------*/
            /* Issue the read request.  Notice that we request one less */
            /* byte than the maximum allowed.  This is because we NULL  */
            /* terminate the input when it's received, so we're just    */
            /* saving a byte for the NUL.                               */
            /*----------------------------------------------------------*/
            
            pBfr = NULL;
            if ((dlRead(dli_cid, &pBfr, iBfrSize - 1,
                pOptArgs) == ERROR) && (dlerrno != DLI_EWOULDBLOCK))
            {
                /*-------------------------------------------------*/
                /* queue full is OK, anything else, report to user */
                /*-------------------------------------------------*/
                if (dlerrno != DLI_READ_ERR_QFULL)
                    printf("\n\nQReads: dlRead failed (%d).\n", dlerrno);

                /*-------------------------------------------------*/
                /* free the allocated optional arguments structure */
                /*-------------------------------------------------*/
                dlBufFree((char *)pOptArgs);
                done = TRUE;
            }
            
        }   /* if not done */

    }   /* while */
}


/******************************************************************************
*
* Function: QWrites
*
* Purpose: To issue a number of write requests to the DLI.
*
* Inputs: dli_cid - The DLI client ID number
*
*         usNbrWr - The number of writes to request.  If zero is given, this is
*                   treated as the maximum unsigned integer plus one (at least
*                   one write is always attempted).
*
* Outputs: pSess - After each write request, the output message is rotated left
*                  one position... hence, the message is rotated usNbrWr
*                  positions to the left on exit.
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 13 Aug 94   John W.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
 void QWrites(int dli_cid, unsigned usNbrWr)
#else
void QWrites(dli_cid, usNbrWr)
    int dli_cid;
    unsigned usNbrWr;
#endif
{
    int   ii;
    int   iMsgLen = strlen(SessTbl[dli_cid].pMsg); /* Message length W/O NULL */
    PCHAR pBfr;                     /* Pointer to Output buffer. */

    do
    {
        /*-----------------------------------------------------------*/
        /* Allocate the output buffer, and copy the message into it. */
        /*-----------------------------------------------------------*/
        pBfr = dlBufAlloc(iMsgLen);
        if (!pBfr)
        {
            printf("\n\nQWrites: dlBufAlloc failed (%d).\n",dlerrno);
            return;
        }

        strcpy(pBfr, SessTbl[dli_cid].pMsg);

        /*---------------------------*/
        /* Issue the output request. */
        /*---------------------------*/
        if ((dlWrite(dli_cid, pBfr, iMsgLen, DLI_WRITE_NORMAL,
            (PDLI_OPT_ARGS) NULL) != OK) && (dlerrno != DLI_EWOULDBLOCK))
        {
            /*-----------------------------------------------------*/
            /* dlWrite has failed, free the buffer and inform user */
            /*-----------------------------------------------------*/
            dlBufFree(pBfr);

            printf("\n\nQWrites: dlWrite failed (%d).\n", dlerrno);
            return;
        }

        writes_outstanding++;

        /*--------------------------------------------*/
        /* Rotate the message for the next iteration. */
        /*--------------------------------------------*/
        SessTbl[dli_cid].pMsg[iMsgLen] = SessTbl[dli_cid].pMsg[0];

        for (ii = 0;  ii < iMsgLen;  ii++)
            SessTbl[dli_cid].pMsg[ii] = SessTbl[dli_cid].pMsg[ii+1];

        SessTbl[dli_cid].pMsg[iMsgLen] = ASCII_NUL;

    } while ( --usNbrWr );
}

/******************************************************************************
*
* Function: WriteCommand
*
* Purpose: This function is used to make an information request to the DLI.
*
* Inputs: dli_cid - The session ID to use in the request.
*
*         Command - The command (i.e. information request) to issue to the DLI.
*
* Outputs: The function returns OK on success, and ERROR otherwise.
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 13 Aug 94   John W.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
 void WriteCommand(int dli_cid, int command)
#else
void WriteCommand(dli_cid, command)
    int dli_cid, command;
#endif
{
    PCHAR          pBfr       = NULL;   /* Output buffer pointer.        */
    PDLI_OPT_ARGS  pWtOptArgs;          /* dlWrite optional arguments.   */

    /*---------------------------------------------*/
    /* Allocate memory for the optional arguments. */
    /*---------------------------------------------*/
    if ((pWtOptArgs = (PDLI_OPT_ARGS) dlBufAlloc(sizeof(DLI_OPT_ARGS))) == NULL)
    {
        printf("WriteCommand: No dynamic memory (dlBufAlloc failed)\n");
    }
    else
    {
        /*-----------------------------------------------*/
        /* Build the optional arguments for the request. */
        /*-----------------------------------------------*/
        memset((char*) pWtOptArgs, ASCII_NUL, sizeof(DLI_OPT_ARGS));
        pWtOptArgs->usFWPacketType = FW_DATA;
        pWtOptArgs->usFWCommand    = FW_ICP_WRITE;
        pWtOptArgs->usICPCommand   = DLI_ICP_CMD_WRITE;
        pWtOptArgs->usProtCommand  = command;

        /*------------------------------------------------------------*/
        /* Allocate an output buffer for the request.  DLI currently  */
        /* requires at lease one byte of buffer even when the request */
        /* is wholly contained in the optional arguements             */
        /*------------------------------------------------------------*/
        if ((pBfr = dlBufAlloc(1)) == NULL)
            printf("WriteCommand: dlBufAlloc failed (%d).\n", dlerrno);
        else
        {
            /*--------------------------*/
            /* Write the request to DLI */
            /*--------------------------*/
            if ((dlWrite(dli_cid, pBfr, 0, DLI_WRITE_NORMAL,
                pWtOptArgs) != OK) && (dlerrno != DLI_EWOULDBLOCK))
            {
                printf("WriteCommand: dlWrite request failed (%d).\n",
                    dlerrno);
            }

        }   /* no error on dlBufAlloc (buffer) */

    }   /* no error on dlBufAlloc (optargs) */
}

/******************************************************************************
*
* Function: CancelWrites
*
* Purpose: To cancel all the pending writes.
*
* Inputs: dli_cid - DLI client ID number
*
* Outputs: none
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 06 Oct 94   John W.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
 void CancelWrites(int dli_cid)
#else
void CancelWrites(dli_cid)
    int dli_cid;
#endif
{
    int             iBytes;     /* Dummy value for the data length. */
    char           *pBfr;       /* Pointer to I/O buffer.           */
    PDLI_OPT_ARGS   pOptArgs;   /* Pointer to optional arguments.   */
    DLBOOLEAN       tfDone;     /* Boolean for loop conditions.     */

    /*-------------------------------------------------*/
    /* cancel all the write requests which are active. */
    /*-------------------------------------------------*/
    tfDone = FALSE;

    while (!tfDone)
    {
        pBfr = (char*) (pOptArgs = NULL);

        if (dlPoll(dli_cid, DLI_POLL_WRITE_CANCEL, &pBfr, &iBytes,
            (PCHAR) NULL, &pOptArgs) == ERROR)
        {
            if (dlerrno != DLI_POLL_ERR_QEMPTY)
            {
                printf(
                    "CancelWrites: dlPoll failed on read cancelation (%d).\n",
                    dlerrno);
            }

            tfDone = TRUE;
        }

        /*----------------------------------------------------------------*/
        /* Release all dynamic memory associated with the cancelled write */
        /*----------------------------------------------------------------*/
        if (pBfr)
        {
            dlBufFree(pBfr);
            writes_outstanding--;
        }

        if (pOptArgs)
            dlBufFree((char *)pOptArgs);
    }
}


/******************************************************************************
*
* Function: CancelReads
*
* Purpose: To cancel all the pending reads.
*
* Inputs: dli_cid - DLI client ID number
*
* Outputs: none
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 06 Oct 94   John W.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
 void CancelReads(int dli_cid)
#else
void CancelReads(dli_cid)
    int dli_cid;
#endif
{
    int             iBytes;     /* Dummy value for the data length. */
    char           *pBfr;       /* Pointer to I/O buffer.           */
    PDLI_OPT_ARGS   pOptArgs;   /* Pointer to optional arguments.   */
    DLBOOLEAN       tfDone;     /* Boolean for loop conditions.     */

    /*-----------------------------------------------------------------*/
    /* Cancel all the read requests which are active for this session. */
    /*-----------------------------------------------------------------*/
    tfDone = FALSE;

    while (!tfDone)
    {
        pBfr = NULL;
        pOptArgs = NULL;

        if (dlPoll(dli_cid, DLI_POLL_READ_CANCEL, &pBfr, &iBytes, (PCHAR) NULL,
            &pOptArgs) == ERROR)
        {
            if (dlerrno != DLI_POLL_ERR_QEMPTY)
            {
                printf(
                    "CancelAllIO: dlPoll failed on read cancelation (%d).\n",
                    dlerrno);
            }

            tfDone = TRUE;
        }

        /*---------------------------------------------------------------*/
        /* Release all dynamic memory associated with the canceled read. */
        /*---------------------------------------------------------------*/
        if (pBfr)
            dlBufFree(pBfr);

        if (pOptArgs)
            dlBufFree((char *)pOptArgs);
    }
}

/****************************************************************************
*
* Function: need_help
*
* Purpose: To ask the operator if they need help and display
*          some basic help text.
*
* Inputs:  none
*
* Outputs: none
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 16 Aug 96              Modified to work with NT3.5.1 console port
*****************************************************************************/
void need_help()
{
    char cLine[80];      /* General purpose line buffer.           */

    /*--------------------------------
    See if the user wants some help.
    --------------------------------*/
    printf("Need help (Y/N) [N]? ");
#ifdef WINNT
    ntscanf("%s", cLine);
#else
    gets(cLine);
#endif
    if (cLine[0] == 'y' || cLine[0] == 'Y')
    {
        printf("\nThis program transfers data between a pair of adjacent ports on an ICP\n");
        printf("board.  These ports must be connected with the supplied Simpact THREE-\n");
        printf("headed loopback cable.  The third head of the cable must be connected\n");
        printf("to your powered up modem.  Your modem supplies clocking to move the\n");
        printf("data.  The data does not reach the modem, but the program does not\n");
        printf("work without an external clock source.  The configuration file,\n");
        printf("fmpaldcfg, specifies an external clock source, i.e. modem-\n");
        printf("supplied clocking.  The ICP and the distribution panel jumpers are\n");
        printf("configured at the factory for external clocks.  The first ICP is zero;\n");
        printf("the first port is zero.  The program defaults to ICP board zero, ports\n");
        printf("zero and one.\n\n");
        printf("When prompted for values, the range of legal values appears within\n");
        printf("parentheses immediately following the prompt.  The default value then\n");
        printf("appears within square brackets.  To select the default value, simply\n");
        printf("press the RETURN key.  To select a value other than the default, enter\n");
        printf("the desired value followed by the RETURN key.\n\n");
    }
}


/******************************************************************************
*
* Function: GetNumericEntry
*
* Purpose: Prompts the user for numeric input, converts the entry into binary
*          format, and performs validation checking.
*
*          WARNING: This function assumes the desired input is non-negative!
*
* Inputs: pPrompt - A pointer to the prompt string to use.
*
*         iDefault - The default value to be used for the input.
*
*         iLow - The lower bound for the numeric input.
*
*         iHigh - The upper bound for the numeric input.
*
* Outputs: The function returns the value entered.
*
* Error Processing: Several errors could occur in the operator entry... such as
*                   non-numeric characters embedded within the input string,
*                   out-of-range values, etc.  If any error is detected, the
*                   user is reprompted until a valid entry is made.
*
*                   If bad range values are given, the program terminates.
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 06 Apr 95   John W.    Original coding.  This function was developed so test
*                        programs across all supported protocols would have a
*                        standard look to them, and be more robust in their
*                        error handling of user input.  It was spawned from the
*                        GetMinutes(), GetICP() and GetLink() functions in the
*                        original bsc3780alp.c source file (those functions
*                        have been replaced by this one).
*
* 16 Aug 96              Modified to work with NT3.5.1 console port
******************************************************************************/
#ifdef __STDC__
 int GetNumericEntry(char *pPrompt, int iDefault, int iLow, int iHigh)
#else
int GetNumericEntry(pPrompt, iDefault, iLow, iHigh)
    char *pPrompt;
    int  iDefault;
    int  iLow;
    int  iHigh;
#endif
{
    static char *pDfltPrompt = "? ";   /* Default prompt.                 */
    int          iValue;               /* The user entry.                 */
    char         cLine[80];            /* Input buffer.                   */

    /*
    ** Dummy checks.
    */
    if ((iLow < 0) || (iLow > iHigh))
    {
        printf("GetNumericEntry: Bad range values given (%d, %d)\n",
            iLow, iHigh);
        exit(1);
    }

    /*
    ** Get the user entry.
    */
    do
    {
        printf("%s", pPrompt ? pPrompt : pDfltPrompt);
#ifdef WINNT
        ntscanf("%s", cLine);
#else
        gets(cLine);
#endif

        if (strlen(cLine) == 0 || cLine[0] == 13)
            iValue = iDefault;
        else if (!isdigit(cLine[0]))
            iValue = -1;
        else
            iValue = atoi(cLine);

        /*
        ** Check for input error.
        */
        if ((iValue < iLow) || (iValue > iHigh))
        {
            iValue = -1;
            printf("?? Invalid entry (legal range is %d to %d)\n",
                iLow, iHigh);
        }

    } while (iValue < 0);

    return iValue;
}

/****************************************************************************
* Function Name:
*       mrkt
*
* Function Description:
*       This function starts a coarse timer.
*
* Arguments:
*       length  time to run
*       units   length is minutes or SECONDS
*
* Return value:
*       None.
****************************************************************************/
#ifdef __STDC__
 void mrkt (int length, int units)
#else
void mrkt (length, units)
    int length, units;
#endif
{
    end_time = time(NULL);

    if (units == SECONDS)
        end_time += length;
    else
        end_time += length * 60;
}

/****************************************************************************
* Function Name:
*       time_ended
*
* Function Description:
*       This function checks whether the time period last specified in a
*       call to the "mrkt" function has elapsed.
*
* Arguments:
*       None.
*
* Return value:
*       FALSE   = Time period has NOT yet elapsed
*       TRUE    = Time period has elapsed
****************************************************************************/
int time_ended ()
{
    return (time(NULL) >= end_time);
}


/****************************************************************************
* Function Name:
*       crash
*
* Function Description:
*       This function stops the testing and if the testing is already
*       stopped, it terminates DLI and exits
*
* Arguments:
*       None.
*
* Return value:
*       None.
****************************************************************************/
#ifdef __STDC__
void crash( int SigNbr )
#else
void crash( SigNbr )
   int SigNbr;
#endif
{
    static int testing = TRUE;

    printf("\nControl C detected...\n");

    if (testing)
    {
        testing = FALSE;
        end_time = time(NULL);
        signal(SIGINT, crash);
    }
    else
    {
        dlTerm();
        exit(0);
    }
}

#ifdef WINNT
/****************************************************************************
* Function Name:
*       terminate_program
*
* Function Description:
*       This function is called by the Windows template and contains
*       those actions needed to terminate the program (clean up, etc.)
*
* Arguments:
*       none
*
* Return Value:
*       none
****************************************************************************/
void terminate_program()
{
    dlTerm();
}
#endif