/* Source control identifier */
static char rcsid[]=
"@(#)$Id: spsalp.c,v 1.21 2002/05/21 23:51:28 jgibbons Exp $";

/*
*               CONFIDENTIAL & PROPRIETARY INFORMATION
*              Distribution to Authorized Personnel Only
*         Unpublished/Copyright 1996 Simpact, Inc.
*                         All Rights Reserved
*
* This document contains confidential and proprietary information of Simpact,
* 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: spsalp.c

Contains: `C' source code for SPS asynchronous verification loop program.
          Note:  The Forward Tell option is used (as opposed to ISO HDLC).

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

/****************************************************************************/
/*                            January 1996                                  */
/* 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, 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 abbreviated 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[]).
-----------------------------------------------------------------------------
*****************************************************************************/

#include <ctype.h>       /* tolower()                               */
#include <stdio.h>       /* printf, fprintf, sprintf, etc.          */
#include <stdlib.h>      /* exit(), 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 "utils.h"

/*******************/
/* program defines */
/*******************/
#if defined(DLITE) || defined(SOLDLITE)
#define BIN_FILE         "spsembcfg.bin"   /* DLI configuration file        */
#else
#define BIN_FILE         "spsaldcfg.bin"   /* DLI configuration file        */
#endif

#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 BLOCKING_INTERVAL    100           /* segmentation interval (1 sec.)*/

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

#define STATION0       3 + (iEvenLink*2)   /* station on the even link   */
#define STATION1       1 + (iEvenLink*2)   /* station on the odd  link   */

/*-------------------------------------------------*/
/* define the operating status values (this is the */
/* order in which theses states should occur)      */
/*-------------------------------------------------*/
#define STARTUP        0     /* getting user input                        */
#define CONNECTING     1     /* opening sessions                          */
#define ATTACHING      2     /* attaching sessions                        */
#define ATTACHED       3     /* attaches complete                         */
#define CONFIGURING    4     /* configuring links                         */
#define CONFIGURED     5     /* configurations complete                   */
#define ENABLING_LINK  6     /* enabling links                            */
#define LINK_ENABLED   7     /* links enabled                             */
#define TRANSFERRING   8     /* transferring data for test duration       */
#define CLEANUP        9     /* clear pending reads and writes            */
#define WAIT_STATS    10     /* test done waiting for statistics report   */
#define STATS_DONE    11     /* statistics report done, start shutdown    */
#define DISABLING     12     /* disabling links                           */
#define INACTIVE      13     /* links inactive                            */
#define DETACHING     14     /* detaching sessions                        */
#define DETACHED      15     /* detaches complete                         */
#define TEST_ENDED    16     /* test ended                                */

#define SECONDS        1
#define MINUTES        2

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

/*************************/
/* structure definitions */
/*************************/
/*------------------------------------------------------------*/
/* Type definition for the SPS Statistics Report format. */
/*------------------------------------------------------------*/
typedef struct _STATS_RPT_BFR
{
    bit16   msg_too_long;
    bit16   dcd_lost;
    bit16   abort_rcvd;
    bit16   rcv_ovrrun;
    bit16   rcv_crcerr;
    bit16   rcv_parerr;
    bit16   rcv_frmerr;
    bit16   xmt_undrun;
    bit16   frame_sent;
    bit16   frame_rcvd;
} STATS_RPT_BFR;

/*-----------------------------------------------*/
/* Type definition for configuration data timers */
/*-----------------------------------------------*/
typedef struct _CONFIG_TIMER
{
    char length;
    char count;
} CONFIG_TIMER;

/*--------------------------------------------*/
/* Type definition for a session table entry. */
/*--------------------------------------------*/
typedef struct _SESS_TBL_ENTRY
{
    char  cSessName[128];     /* Session name.                              */
    int   iSessID;            /* Session ID returned by dlOpen().           */
    short iICPSess;           /* Session ID returned by ICP attach          */
    char *pMsg;               /* Pointer to the transmit data.              */
    char *pCmp;               /* Pointer to comparison string for I/P data. */
    int   WritesCompleted;    /* number of writes completed                 */
    int   WritesAcknowledged; /* number of writes acknowledged              */
    int   ReadsCompleted;     /* number of reads completed                  */
} SESS_TBL_ENTRY;

/****************************/
/* 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, 0, cMsg0, cCmp1, 0, 0},
    {"server0icp0port1 ", UNINITIALIZED_SESSION, 0, cMsg1, cCmp0, 0, 0}};

/*---------------------------------------------------------*/
/* Link/station/timer configuration for the ICP            */
/*   baud=(see BAUD_INDEX and baud table), clock=internal, */
/*   maxframesize=100, locaddr=(1 or 3), remaddr=(3 or 1), */
/*   extended addressing enabled, window=7                 */
/*---------------------------------------------------------*/

/*---------------------------------------------------------------*/
/* Type definition for the link/station configuration data       */
/*---------------------------------------------------------------*/
typedef struct _CONFIG_DATA
{
   bit8         protocol;       /* 0 = BSC, 1 = Async, 2 = SDLC         */
   bit8         clock;          /* 0 = external, 1 = internal clock     */
   bit8         baud_rate;      /* index into baudsc or baudas          */
                                /*      (not req'd if external clock)   */
   bit8         encoding;       /* 0 = NRZ, 1 = NRZI (SDLC only)        */
   bit8         electrical;     /* Electrical protocol for ICP2424      */
   bit8         parity;         /* 0 = none, 1 = odd, 2 = even          */
                                /*      (asynch only)                   */
   bit8         char_len;       /* 7 = 7 bits, 8 = 8 bits               */
                                /*      (asynch only)                   */
   bit8         stop_bits;      /* 1 = 1 stop bit, 2 = 2 stop bits      */
                                /*      (asynch only)                   */
   bit8         crc;            /* 0 = no CRC, 1 = CRC                  */
                                /*      (SDLC always uses CRC)          */
   bit8         syncs;          /* number of leading sync chars (1-8)   */
                                /*      (BSC only)                      */
   bit8         start_char;     /* block start character                */
                                /*      (not used for SDLC)             */
   bit8         stop_char;      /* block end character (asynch only)    */
} CONFIG_DATA;

#define BSC     0
#define ASYNC       1
#define SDLC        2

#define INT_CLOCK       1
#define EXT_CLOCK       0

#define SC_BAUD_9600    5
#define AC_BAUD_9600    8

#define NRZ     0

#define ELEC_232    0x02
#define ELEC_530    0x0d

/* NOTE: For ASYNC, because of the way that the length of packets is
**       specified in the first two bytes of transmitted data, the test
**       program should only send 8 bit characters
*/
#define NO_PARITY   0
#define ODD_PARITY  1
#define EVEN_PARITY 2

/* NOTE: For ASYNC, because of the way that the length of packets is
**       specified in the first two bytes of transmitted data, the test
**       program should only send 8 bit characters
*/
#define CHAR_LEN    8

#define STOP_BITS       2

#define NO_CRC          0
#define CRC             1

#define SYNCS       4

#define START_CHAR  0x02
#define STOP_CHAR   0x03

static CONFIG_DATA config_data[3] =
   {{BSC  , INT_CLOCK, SC_BAUD_9600,0, ELEC_232,         0,        0,        0,
        CRC, SYNCS, START_CHAR,         0 },
    {ASYNC, INT_CLOCK, AC_BAUD_9600,0, ELEC_232, NO_PARITY, CHAR_LEN,STOP_BITS,
     NO_CRC,     0, START_CHAR, STOP_CHAR },
    {SDLC , INT_CLOCK, SC_BAUD_9600,NRZ,ELEC_232,        0,        0,        0,
       CRC,     0,          0,         0 }};

static long baudac[] = {                 /* STATIC BAUD RATE SELECTIONS  */
                300L,         /* 0 */
                600L,             /* 1 */
                1200L,            /* 2 */
                1800L,            /* 3 */
                2400L,            /* 4 */
                3600L,            /* 5 */
                4800L,            /* 6 */
                7200L,            /* 7 */
                9600L,            /* 8 */
                19200L,           /* 9 */
                38400L,           /* 10 */
                57600L,           /* 11 */
                115200L,          /* 12 */
                230400L,          /* 13 */
                0
                };

static long baudsc[] = {                 /* STATIC BAUD RATE SELECTIONS  */
                300L,             /* 0 */
                600L,             /* 1 */
                1200L,            /* 2 */
                2400L,            /* 3 */
                4800L,            /* 4 */
                9600L,            /* 5 */
                19200L,           /* 6 */
                38400L,           /* 7 */
                57600L,           /* 8 */
                64000L,           /* 9 */
                263300L,          /* 10 */
                307200L,          /* 11 */
                460800L,          /* 12 */
                460800L,          /* 13 */
                0
                };

#define MAX_BAUD_INDEX    13
int baud_index;
int baud_rate;

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

#define CNTS_PER_LINE 36
int print_cnt =0;

int last0 = 1;
int last1 = 1;

/*-------------------------------------------*/
/* 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 iEvenLink;              /* Even link in the pair to test.*/

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.         */
int  iICPNbr, iICPNbr1;      /* ICP boards to run the test on. */
int protocol;

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


/************************/
/* function 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 dli_cid, unsigned usNbrWr);
void AttachSession(int dli_cid, int link_id);
void DetachSession(int dli_cid, int link_id);
void WriteCommand(int cid, int command, int modifier, int station,
          char *data, int length);
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();
void crash(int SigNbr);
void ver_report(char *ver_buf);
#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 AttachSession();
void DetachSession();
void WriteCommand();
void CancelWrites();
void CancelReads();
int GetNumericEntry();
void mrkt ();
int time_ended ();
void need_help();
void crash();
void ver_report();
#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.
*
*****************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
*   08Jul96    FJD       Original version -- cloned from std1200aalp.c .
*   27Jul98    MDA       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 error;

#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 protocol type - 0-BSC, 1-ASYNC, 2-SDLC
    */
    sprintf(cLine, "Enter protocol to run ( 0-BSC, 1-ASYNC, 2-SDLC) [1]? ");
    protocol = GetNumericEntry(cLine, 1, 0, 2);

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

    /*-----------------------------------*/
    /* Get the ICP board numbers to test. */
    /*-----------------------------------*/
    printf("Ports used will be an even/odd pair.  They can be split across\n");
    printf(" two different ICP's.  The DLI config file can even specify\n");
    printf(" that the links can be split across two different Freeways.\n");

    sprintf(cLine, "ICP board for even link (0-%d) [0]? ",MAX_BOARDS-1);
    iICPNbr = GetNumericEntry(cLine, 0, 0, MAX_BOARDS - 1);

    sprintf(cLine, "ICP board for odd link (0-%d) [0]? ",MAX_BOARDS-1);
    iICPNbr1 = GetNumericEntry(cLine, 0, 0, MAX_BOARDS - 1);

    /*
    ** Get baud index.
    */
    if( protocol == ASYNC )
    {
        sprintf(cLine,"Baud index (1-%d) [8->9600]? ",MAX_BAUD_INDEX);
        baud_index = GetNumericEntry(cLine, AC_BAUD_9600, 0, MAX_BAUD_INDEX);
    }
    else
    {
        sprintf(cLine,"Baud index (1-%d) [5->9600]? ",MAX_BAUD_INDEX);
        baud_index = GetNumericEntry(cLine, SC_BAUD_9600, 0, MAX_BAUD_INDEX);
    }

    if( protocol == ASYNC )
    {
        baud_rate =  baudac[baud_index];
    }
    else
    {
        baud_rate =  baudsc[baud_index];
    }
    config_data[protocol].baud_rate = baud_index;

    /*
    ** Get clock.
    */
    sprintf(cLine,"Enter clock source (0-external, 1-internal) [1]? ");
    ii = GetNumericEntry(cLine, INT_CLOCK, 0, 1);

    config_data[protocol].clock = ii;

    /*-----------------------*/
    /* 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 link configuration table date
    */
    for( ii = 0; ii < 3; ii++ )
    {
        config_data[ii].protocol   = config_data[protocol].protocol;
        config_data[ii].clock      = config_data[protocol].clock;
        config_data[ii].baud_rate  = config_data[protocol].baud_rate;
        config_data[ii].encoding   = config_data[protocol].encoding;
        config_data[ii].electrical = config_data[protocol].electrical;
        config_data[ii].parity     = config_data[protocol].parity;
        config_data[ii].char_len   = config_data[protocol].char_len;
        config_data[ii].stop_bits  = config_data[protocol].stop_bits;
        config_data[ii].crc        = config_data[protocol].crc;
        config_data[ii].syncs      = config_data[protocol].syncs;
        config_data[ii].start_char = config_data[protocol].start_char;
        config_data[ii].stop_char  = config_data[protocol].stop_char;
    }

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

    sprintf(cLine, "%d", iICPNbr1);
    SessTbl[1].cSessName[10] = *cLine;

    for (ii = 0;  ii < NUM_CONNECTIONS;  ii++)
    {
        /*-------------------------*/
        /* 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("\nSPS Asynchronous Port-To-Port Loopback Program.\n");
    printf("   For protocol type (0-BSC, 1-ASYNC, 2-SDLC) : %d\n", protocol);
    printf("   Test duration (in minutes) : %d\n", iMinutes);
    printf("   ICP board number %d, link number %d\n", iICPNbr, iEvenLink);
    printf("   ICP board number %d, link number %d\n", iICPNbr1, iEvenLink+1);
    printf("   Baud rate : %d\n", baud_rate );

    /*----------------------------------------------------------*/
    /* 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)       */
    /*----------------------------------------------------------*/
    error = dlInit(BIN_FILE, (char *)work_array, notify_interrupt);
    if (error == ERROR && dlerrno != DLI_INIT_ERR_LOG_INIT_FAILED)
    {
        printf("main: dlInit for file %s, and failed with error (%d) (%s).\n",
           BIN_FILE, dlerrno, dlpErrString(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) (%s).\n",
               dlerrno, dlpErrString(dlerrno));
                op_state = TEST_ENDED;
            }
        }
    }

    /*--------------------------------------------------------*/
    /* 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  */
        /* program terminates properly if, for some reason we get    */
        /* stuck waiting for attach completion, detach completion -- */
        /* or cleanup completion wherein a message is not properly   */
        /* accounted for due to a slow system or congested network   */
        /*-----------------------------------------------------------*/
        switch (op_state)
        {
            case ATTACHING:
            if (time_ended())
            {
                    printf("main: session attach request timed out.\n");
                    op_state = TEST_ENDED;
            }

            break;

            case TRANSFERRING:
            case CLEANUP:
                if (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);
                }

                break;

            case DETACHING:
                if (time_ended())
                {
                    op_state = DETACHED;

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

                    /*---------------------*/
                    /* close both sessions */
                    /*---------------------*/
                    dlClose(LINK0, DLI_CLOSE_FORCE);
                    dlClose(LINK1, DLI_CLOSE_FORCE);
                }
                break;
        }
        sleep( 1 );
    }
    dlTerm();
#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:
*      0
*****************************************************************************/
#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)     */
                /*------------------------------------------------------*/
                dlPoll(dli_cid, DLI_POLL_GET_SESS_STATUS, NULL, 0,
                    (char *)&sess_stat, &dummy);

                /*------------------------------*/
                /* 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 (opening sessions)
*          ATTACHING     - attaching sessions
*          ATTACHED      - attach completions received
*          REPORT_DONE   - version and buffer reports received
*          CONFIGURED    - link configurations processed
*          ACTIVE        - binds complete (links active)
*          TRANSFERRING  - transferring data for test duration
*          CLEANUP       - wait for pending reads and writes
*          STATS_DONE    - statistics report done, close connections
*          INACTIVE      - unbinds complete (links inactive)
*          DETACHING     - detaching sessions
*          DETACHED      - detach completions received
*
* 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, in which case  */
            /* issue the attaches to the sessions            */
            /*-----------------------------------------------*/
            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 */
                /* and issue the session attaches            */
                /*-------------------------------------------*/
                if (++active_sessions == NUM_CONNECTIONS)
                {
                    op_state = ATTACHING;
#ifdef PRINTF
           printf("\nTo ATTACHING\n");
#endif

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

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

                    /*-----------------------------------------------*/
                    /* issue the session attaches, with 10-sec timer */
                    /* timeout will cause immediate TEST_ENDED.      */
                    /*-----------------------------------------------*/
                    mrkt(60,SECONDS);
                    AttachSession(LINK0,iEvenLink  );
                    AttachSession(LINK1,iEvenLink+1);
                }
            }
            break;

        case ATTACHING:
            /*---------------------------------------------*/
            /* if the attaches still aren't complete after */
            /* 10 seconds, terminate the test.             */
            /*---------------------------------------------*/
            if (time_ended())
            {
               printf("main: session attach request timed out.\n");
               op_state = TEST_ENDED;
            }
            break;

        case ATTACHED:
            /*---------------------------------------------------------*/
            /* we get here when the link attaches have completed       */
            /* successfully.  Start the link configurations.           */
            /*---------------------------------------------------------*/
            op_state = CONFIGURING;
#ifdef PRINTF
            printf("\nTo CONFIGURING\n");
#endif

            /*---------------------------*/
            /* issue link configurations */
            /* (the ICP doesn't reply)   */
            /*---------------------------*/
            WriteCommand(LINK0, DLI_PROT_CFG_LINK, 0, 0,
                 (char *)&config_data[LINK0],
                 sizeof(config_data[LINK0]));
            WriteCommand(LINK1, DLI_PROT_CFG_LINK, 0, 0,
                 (char *)&config_data[LINK1],
                 sizeof(config_data[LINK1]));
            break;

        case CONFIGURED:
            /*---------------------------------------------*/
            /* The link/station configurations             */
            /* have been processed.                        */
            /* Time to enable the links                    */
            /*---------------------------------------------*/
            op_state = ENABLING_LINK;
#ifdef PRINTF
            printf("\nTo ENABLING_LINK\n");
#endif

            WriteCommand(LINK0, DLI_PROT_SEND_BIND, 0, 0,
                 (char *)NULL, 0);
            WriteCommand(LINK1, DLI_PROT_SEND_BIND, 0, 0,
                 (char *)NULL, 0);
            break;


        case LINK_ENABLED:
            /*---------------------------------------------------------*/
            /* The enabled, set state for loopback transfer            */
            /*---------------------------------------------------------*/
            op_state = TRANSFERRING;
#ifdef PRINTF
           printf("\nTo TRANSFERRING\n");
#endif

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

            /*-------------------------*/
            /* post the initial writes */
            /* (2 to each port)        */
            /*-------------------------*/
            QWrites(LINK0, 4);
            QWrites(LINK1, 4);

            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;
#ifdef PRINTF
           printf("\nTo CLEANUP\n");
#endif
#ifdef PRINTF
           printf("\nSPS Completed. Start cleanup.\n");
#endif
                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;
#ifdef PRINTF
           printf("\nTo WAIT_STATS\n");
#endif

                /*-------------------------------*/
                /* 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, 0, 0,
                         (char *)NULL, 0);
                WriteCommand(LINK1, DLI_PROT_GET_STATISTICS_REPORT, 0, 0,
                         (char *)NULL, 0);
            }
            break;

        case STATS_DONE:
            /*-----------------------------------------*/
            /* statistics are done, set the next state */
            /*-----------------------------------------*/
            op_state = DISABLING;
#ifdef PRINTF
           printf("\nTo DISABLING\n");
#endif

            /*--------------------*/
            /* disable both links */
            /*--------------------*/
            WriteCommand(LINK0, DLI_PROT_SEND_UNBIND, 0, 0,
                 (char *)NULL, 0);
            WriteCommand(LINK1, DLI_PROT_SEND_UNBIND, 0, 0,
                 (char *)NULL, 0);
            break;

    case INACTIVE:
            /*-------------------------------------------*/
            /* the binds are done, set the next state */
            /*-------------------------------------------*/
            op_state = DETACHING;
#ifdef PRINTF
           printf("\nTo DETACHING\n");
#endif

            /*----------------------------------------------*/
            /* issue the session detaches, with 5-sec timer */
            /* timeout will cause immediate TEST_ENDED.     */
            /*----------------------------------------------*/
            DetachSession(LINK0, iEvenLink  );
            DetachSession(LINK1, iEvenLink+1);
            mrkt(5, SECONDS);

            break;

        case DETACHING:
            /*---------------------------------------------*/
            /* if the detaches still aren't complete after */
            /* 5 seconds, terminate the test.              */
            /*---------------------------------------------*/
            if (time_ended())
            {
               op_state = DETACHED;
#ifdef PRINTF
               printf("\nTo DETACHED\n");
#endif
            }

            break;

        case DETACHED:

            /*-------------------------------------------*/
            /* the detaches are done, set the next state */
            /*-------------------------------------------*/
            op_state = TEST_ENDED;
#ifdef PRINTF
           printf("\nTo TEST_ENDED\n");
#endif

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

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

            /*----------------------------*/
            /* clear the port session IDs */
            /*----------------------------*/
            SessTbl[LINK0].iSessID = UNINITIALIZED_SESSION;
            SessTbl[LINK1].iSessID = UNINITIALIZED_SESSION;

            /*---------------------*/
            /* declare end of test */
            /*---------------------*/
            printf("Loopback 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
*****************************************************************************/
#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)
        {
#ifdef PRINTF
            /*-------------------------------*/
            /* display the 'write' indicator */
            /*-------------------------------*/
            if (op_state == TRANSFERRING || op_state == CLEANUP)
            {
                fprintf(stderr,">");
            }
#endif

        }
        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 */

    /*--------------------------------------------------------------*/
    /* NOTE: replacement writes are issued upon receipt of IROTATEs */
    /*--------------------------------------------------------------*/

}


/****************************************************************************
* 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
*****************************************************************************/
#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, size, ii;
    static int stat_link1   = FALSE;
    static int stat_link2   = FALSE;
    static int attach_link1 = FALSE;
    static int attach_link2 = FALSE;
    static int config_link1 = FALSE;
    static int config_link2 = FALSE;
    static int enab_link1   = FALSE;
    static int enab_link2   = FALSE;
    static int enab_station1= FALSE;
    static int enab_station2= FALSE;
    static int disab_link1  = FALSE;
    static int disab_link2  = FALSE;
    static int detach_link1 = FALSE;
    static int detach_link2 = FALSE;
    int gg;

    /*----------------------------------------------------*/
    /* 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);


        if( opt_args->iICPStatus )
        {
            printf(" ERROR = %d (0x%x) received for ICP/Prot cmnd  = %d/%d.\n",
                opt_args->iICPStatus,
                opt_args->iICPStatus,opt_args->usICPCommand,
                opt_args->usProtCommand);
        }

        /*-----------------------------------------------------*/
        /* process the received data according to the op_state */
        /*-----------------------------------------------------*/
        switch (op_state)
        {
            case ATTACHING:
                /*--------------------------------------------------*/
                /* are waiting for the session attaches to complete */
                /*--------------------------------------------------*/
                if (opt_args->usICPCommand == DLI_ICP_CMD_ATTACH)
                {
                    /*---------------------------------------*/
                    /* Store ICP session ID in session table */
                    /*---------------------------------------*/
                    SessTbl[dli_cid].iICPSess = opt_args->usProtSessionID;

                    /*------------------------*/
                    /* set the attach flag(s) */
                    /*------------------------*/
                    if (dli_cid == 0)
                        attach_link1 = TRUE;
                    else
                        attach_link2 = TRUE;
                }

                /*----------------------------------------------------*/
                /* if both attaches complete, change the system state */
                /*----------------------------------------------------*/
                if (attach_link2 && attach_link1)
                {
                    op_state = ATTACHED;
#ifdef PRINTF
           printf("\nTo ATTACHED\n");
#endif
                }

                break;

            case CONFIGURING:
                /*-----------------------------------------------------*/
                /* the ICP doesn't respond to valid link/station/timer */
                /* configurations, so we are waiting for the blocking  */
                /* interval responses                                  */
                /*-----------------------------------------------------*/
                if (opt_args->usProtCommand == DLI_PROT_CFG_LINK)
                {
                    if (opt_args->iProtModifier != DLI_ICP_CMD_STATUS_OK )
                    {
                        printf("Error return on the configure link command. ");
                        printf("Configure error = %d\n",
                                                   opt_args->usProtXParms[0]);
                        op_state = INACTIVE;
                        break;
                    }

                    /*-------------------------------*/
                    /* set the configuration flag(s) */
                    /*-------------------------------*/
                    if (dli_cid == LINK0)
                        config_link1 = TRUE;
                    else
                        config_link2 = TRUE;

                    /*--------------------------------------------------*/
                    /* Change the system state if both links configured */
                    /*--------------------------------------------------*/
                    if (config_link2 && config_link1)
                    {
                        op_state = CONFIGURED;
#ifdef PRINTF
           printf("\nTo CONFIGURED\n");
#endif
                    }

                }
                /*--------------------------------------------------*/
                /* However, first we'll get a command reject if the */
                /* link/station/timer configuration was invalid,    */
                /* in which case force session detaches             */
                /*--------------------------------------------------*/
                else if (opt_args->usProtCommand == DLI_PROT_RESP_ERROR)
                {
                    op_state = INACTIVE;
                    printf("process_reads: %s link configuration failed\n",
                           (dli_cid == 0) ? "Even" : "Odd");
                }
                break;

        case ENABLING_LINK:
                /*----------------------------------------------*/
                /* we are waiting for the link active responses */
                /* (prior link inactive responses are ignored)  */
                /*----------------------------------------------*/
                if (opt_args->usICPCommand == DLI_ICP_CMD_BIND)
                {
                    /*-------------------------------*/
                    /* set the enable flag(s)        */
                    /*-------------------------------*/
                    if (dli_cid == LINK0)
                        enab_link1 = TRUE;
                    else
                        enab_link2 = TRUE;

                    /*----------------------------------------------*/
                    /* Change the system state if both links active */
                    /*----------------------------------------------*/
                    if (enab_link2 && enab_link1)
                    {
                        op_state = LINK_ENABLED;
#ifdef PRINTF
           printf("\nTo LINK_ENABLED\n");
#endif
                    }

                }

            break;

            case TRANSFERRING:
            case CLEANUP:
                /*---------------------------------------------------*/
                /* We expect transmit acknowledgments and input data */
                /*---------------------------------------------------*/
                switch (opt_args->usProtCommand)
                {

                    case DLI_PROT_RECV_PING:
                    case DLI_PROT_RECV_UNFORMATTED_DATA:
                    case DLI_PROT_SEND_NORM_DATA:

                        /* Check status */
                        if( opt_args->iICPStatus != 0 )
                        {
                            printf("Link %d error status = %d on NORM_DATA\n",
                              opt_args->usProtLinkID, opt_args->iICPStatus);
                            break;
                        }


                        print_cnt++;
                        if( !( print_cnt % CNTS_PER_LINE ) )
                        {
                            fprintf(stderr," - %d\r", print_cnt/CNTS_PER_LINE);
                        }
                        fprintf(stderr,"%d<", dli_cid);


                        /*----------------------------------------*/
                        /* Check that we actually read some data. */
                        /*----------------------------------------*/
                        if (!length)
                        {
                            printf("\n\nprocess_reads: Zero data bytes.\n");
#ifdef PING
                            op_state = TEST_ENDED;
#endif
                            break;
                        }

                        /*-------------------------------------------*/
                        /* Place a NULL at the end of the received   */
                        /* buffer and compare the input data against */
                        /* what was expected.                        */
                        /*-------------------------------------------*/
                        size = length;
                        writes_outstanding--;
                        if( size != iMsgLen )
                        {
                            printf("Illegal message length - size=%d, exp=%d\n",
                                           size, iMsgLen);
                            size = iMsgLen;
#ifdef PING
                            op_state = TEST_ENDED;
#endif
                        }
                        read_buf[size] = ASCII_NUL;

                        if (strcmp(read_buf,
                                   SessTbl[dli_cid].pCmp) != 0)
                        {
                            printf(
                              "\nprocess_reads: Data comparison failed.\n");
                            printf(
                              "Received = %s\n", read_buf);
                            printf(
                              "Expected = %s\n", SessTbl[dli_cid].pCmp);
                            printf("sizeofs rcvd=%d, exp=%d\n",
                               strlen(read_buf),
                               strlen(SessTbl[dli_cid].pCmp) );

                            strcpy( SessTbl[dli_cid].pCmp, read_buf);
#ifdef PING
                            op_state = TEST_ENDED;
#endif
                        }
                        /*------------------------------*/
                        /* Rotate the comparison string */
                        /* for the next read.           */
                        /*------------------------------*/
                        SessTbl[dli_cid].pCmp[iMsgLen] =
                                        SessTbl[dli_cid].pCmp[0];

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

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

                        SessTbl[dli_cid].ReadsCompleted++;

                        break;

                    case DLI_PROT_RESP_LOCAL_ACK:

                        /* Check status */
                        if( opt_args->iICPStatus != 0 )
                        {
                            if(opt_args->iICPStatus == DLI_ICP_ERR_XMIT_TIMEOUT)
                            {
                                printf("Link %d transmit timeout\n",
                                    opt_args->usProtLinkID);

                                QWrites(dli_cid, 1);
                                break;
                            }
                            else
                            {
                                printf("Link %d error status = %d\n",
                                  opt_args->usProtLinkID, opt_args->iICPStatus);
                                break;
                            }
                        }

                        SessTbl[dli_cid].WritesAcknowledged++;

                        /*----------------------------------------------*/
                        /*   This is data transfer complete.  If        */
                        /*   the test has not ended, post a new write   */
                        /*   to the  session                            */
                        /*----------------------------------------------*/
                        if (op_state == TRANSFERRING)
                            QWrites(dli_cid, 1);

                        break;

                    default:
                        /*----------------------------------------------*/
                        /* Only ISEGs and IROTATEs are valid during the   */
                        /* TRANSFERRING state -- tell user of bad message */
                        /*----------------------------------------------*/
                        printf(
                     "\n\nUnexpected message- ICP/Prot cmds=(%d/%d) mod=%d.\n",
                              opt_args->usICPCommand, 
                              opt_args->usProtCommand, opt_args->iProtModifier);

                        break;

                }

                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;
#ifdef PRINTF
           printf("\nTo STATS_DONE\n");
#endif
                }

                break;

        case DISABLING:
                /*-------------------------------------------------*/
                /* We are waiting for the link inactive responses. */
                /* We also will accept a link exception or a       */
                /* command reject.                                 */
                /*-------------------------------------------------*/
                if ((opt_args->usProtCommand == DLI_PROT_RESP_UNBIND_ACK    ) ||
                    (opt_args->usICPCommand  == DLI_ICP_CMD_UNBIND          ) ||
                    (opt_args->usProtCommand == DLI_PROT_RECV_LINK_EXCEPTION) ||
                    (opt_args->usProtCommand == DLI_PROT_RESP_ERROR         ))
                {
                    /*-------------------------*/
                    /* set the disable flag(s) */
                    /*-------------------------*/
                    if (dli_cid == LINK0)
                        disab_link1 = TRUE;
                    else
                        disab_link2 = TRUE;

                    /*------------------------------------------------*/
                    /* Change the system state if both links inactive */
                    /*------------------------------------------------*/
                    if (disab_link2 && disab_link1)
                    {
                        op_state = INACTIVE;
#ifdef PRINTF
           printf("\nTo INACTIVE\n");
#endif
                    }

                }

                break;

            case DETACHING:
                /*--------------------------------------------------*/
                /* are waiting for the session detaches to complete */
                /*--------------------------------------------------*/
                if (opt_args->usICPCommand == DLI_ICP_CMD_DETACH)
                {
                    /*------------------------*/
                    /* set the detach flag(s) */
                    /*------------------------*/
                    if (dli_cid == 0)
                        detach_link1 = TRUE;
                    else
                        detach_link2 = TRUE;
                }

                /*----------------------------------------------------*/
                /* if both detaches complete, change the system state */
                /*----------------------------------------------------*/
                if (detach_link2 && detach_link1)
                {
                    op_state = DETACHED;
#ifdef PRINTF
           printf("\nTo DETACHED\n");
#endif
                }

                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("\n\nToolkit Statistics Report:\n");
    printf("                                %17s     %17s\n",
        SessTbl[LINK0].cSessName, SessTbl[LINK1].cSessName);
    printf("                         \
        -----------------     -----------------\n");
    printf("   Received messages too long   %11d%22d\n",
        pStats[LINK0].msg_too_long, pStats[LINK1].msg_too_long);
    printf("   Times DCD lost               %11d%22d\n",
        pStats[LINK0].dcd_lost, pStats[LINK1].dcd_lost);
    printf("   Received messages aborted    %11d%22d\n",
        pStats[LINK0].abort_rcvd, pStats[LINK1].abort_rcvd);
    printf("   Receive overruns             %11d%22d\n",
        pStats[LINK0].rcv_ovrrun, pStats[LINK1].rcv_ovrrun);
    printf("   Receive CRC errors           %11d%22d\n",
        pStats[LINK0].rcv_crcerr, pStats[LINK1].rcv_crcerr);
    printf("   Receive parity errors        %11d%22d\n",
        pStats[LINK0].rcv_parerr, pStats[LINK1].rcv_parerr);
    printf("   Receive 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("   Data blocks sent             %11d%22d\n",
        pStats[LINK0].frame_sent, pStats[LINK1].frame_sent);
    printf("   Data blocks received         %11d%22d\n",
        pStats[LINK0].frame_rcvd, pStats[LINK1].frame_rcvd);
    printf("   data writes                  %11d%22d\n",
        SessTbl[LINK0].WritesCompleted, SessTbl[LINK1].WritesCompleted);
    printf("   data writes acked            %11d%22d\n",
        SessTbl[LINK0].WritesAcknowledged, SessTbl[LINK1].WritesAcknowledged);
    printf("   data reads                   %11d%22d\n",
        SessTbl[LINK0].ReadsCompleted, SessTbl[LINK1].ReadsCompleted);
}


/******************************************************************************
*
* Function: BfrSize
*
* Purpose: This function reads in the system configuration (NOT the link
*          configuration!) from the DLI, and returns the maximum ICP message
*          buffer size.
*
* Inputs: iSessID - The session ID for the request.
*
* Outputs: The function returns the size (in bytes) of the ICP message buffer.
*          ERROR is returned if this could not be accomplished.
*
* Error Processing: the only errors that dlPoll can return for a system
*                   config 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
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 13 Aug 94   John W.    Original coding.
* 27 JUL 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  - DLI client ID
*
*         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));
            pBfr = NULL;

            /*----------------------------------------------------------*/
            /* 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.                               */
            /*----------------------------------------------------------*/
            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) (%s).\n",
               dlerrno, dlpErrString(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
*
*         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. */
#ifdef PRINTF
    int diff, diff0, diff1, per_cent;
#endif

    do
    {
        /*---------------------------*/
        /* Issue the output request. */
        /*---------------------------*/
#ifndef PING
        WriteCommand(dli_cid, DLI_PROT_SEND_NORM_DATA, 0,
                     (dli_cid == LINK0) ? STATION0: STATION1,
                     SessTbl[dli_cid].pMsg, iMsgLen);
#else
        /* for a ping send out one link, to be routed to other link
        **   (specified in arg field)
        */
        WriteCommand(dli_cid, DLI_PROT_SEND_PING,
                     (dli_cid == LINK0) ? iEvenLink+1: iEvenLink,
                     0,
                     SessTbl[dli_cid].pMsg, iMsgLen);
#endif
        SessTbl[dli_cid].WritesCompleted++;
        writes_outstanding++;

        print_cnt++;
        if( !( print_cnt % CNTS_PER_LINE ) )
        {
            fprintf(stderr," - %d\r", print_cnt / CNTS_PER_LINE);
        }
        fprintf(stderr,"%d>", dli_cid);

#ifdef PRINTF
        if( ( print_cnt          ) &&
            ( !(print_cnt % CNTS_PER_LINE)  ) )
        {
            /* every 20 lines see if great discrepency on two links, but only
               over last 20 lines */
            if( ( print_cnt > (CNTS_PER_LINE*20)      ) &&
                ( !( print_cnt/(CNTS_PER_LINE*20) ) ) )
            {
                diff0 = abs( SessTbl[0].WritesCompleted - last0 );
                diff1 = abs( SessTbl[1].WritesCompleted - last1 );
                diff  = abs( diff0 - diff1 );
                per_cent = (diff * 100) / diff1;
                if( per_cent > 10 )
                {
                    if(diff0 > diff1)
                        printf("Link 0 has more completions by %d per cent.\n",
                                      per_cent);
                    else
                        printf("Link 1 has more completions by %d per cent.\n",
                                      per_cent);
                }
                last0 = SessTbl[0].WritesCompleted;
                last1 = SessTbl[1].WritesCompleted;
            }
        }
#endif

        /*--------------------------------------------*/
        /* 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: AttachSession
*
* Purpose: This function is used to issue a session attach to the DLI.
*
* Inputs: dli_cid - The session ID to use in the request.
*
*         link_id - The link ID to use in the request.
*
* Outputs: The function returns OK on success, and ERROR otherwise.
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 20 Mar 96   Dean S.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
 void AttachSession(int dli_cid, int link_id)
#else
void AttachSession(dli_cid, link_id)
    int dli_cid, link_id;
#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("AttachSession: 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_ATTACH;
        pWtOptArgs->usProtCommand   = DLI_ICP_CMD_ATTACH;
        pWtOptArgs->usProtLinkID    = link_id;

        /*------------------------------------------------------------*/
        /* 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(iMaxBfrSize)) == NULL)
            printf("AttachSession: dlBufAlloc failed (%d) (%s).\n",
                   dlerrno, dlpErrString(dlerrno));
        else
        {
            /*--------------------------*/
            /* Write the request to DLI */
            /*--------------------------*/
            if ((dlWrite(dli_cid, pBfr, 0, DLI_WRITE_NORMAL,
                pWtOptArgs) != OK) && (dlerrno != DLI_EWOULDBLOCK))
            {
                printf("AttachSession: dlWrite request failed (%d) (%s).\n",
                       dlerrno, dlpErrString(dlerrno));
            }

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

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


/******************************************************************************
*
* Function: DetachSession
*
* Purpose: This function is used to issue a session attach to the DLI.
*
* Inputs: dli_cid - The session ID to use in the request.
*
*         link_id - The link number
*
* Outputs: The function returns OK on success, and ERROR otherwise.
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 20 Mar 96   Dean S.    Original coding.
*
******************************************************************************/
#ifdef __STDC__
void DetachSession(int dli_cid, int link_id)
#else
void DetachSession(dli_cid, link_id)
    int dli_cid, link_id;
#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("DetachSession: 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_DETACH;
        pWtOptArgs->usProtCommand   = DLI_ICP_CMD_DETACH;
        pWtOptArgs->usProtLinkID    = link_id;
        pWtOptArgs->usProtSessionID = SessTbl[dli_cid].iICPSess;

        /*------------------------------------------------------------*/
        /* 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(iMaxBfrSize)) == NULL)
            printf("DetachSession: dlBufAlloc failed (%d) (%s).\n",
                   dlerrno, dlpErrString(dlerrno));
        else
        {
            /*--------------------------*/
            /* Write the request to DLI */
            /*--------------------------*/
            if ((dlWrite(dli_cid, pBfr, 0, DLI_WRITE_NORMAL,
                pWtOptArgs) != OK) && (dlerrno != DLI_EWOULDBLOCK))
            {
                printf("DetachSession: dlWrite request failed (%d) (%s).\n",
                       dlerrno, dlpErrString(dlerrno));
            }

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

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



/******************************************************************************
*
* 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.
*
*         modifier- The command modifier (if applicable)
*
*         station - The station (if applicable)
*
*         buffer  - The data for the command (if any; may be NULL)
*
*         length  - The length of the data (if any; may be zero)
*
* Outputs: The function returns OK on success, and ERROR otherwise.
*
*******************************************************************************
*
* REVISION HISTORY
*
*    Date     Initials   Description
*    ----     --------   -----------
* 13 Aug 94   John W.    Original coding.
* 20 Mar 96   Dean S.    Modified to add calling parameters for command
*                        modifier, station, data pointer, and data length.
*
******************************************************************************/
#ifdef __STDC__
 void WriteCommand(int dli_cid, int command, int modifier, int station,
          char *buffer, int length)
#else
void WriteCommand(dli_cid, command, modifier, station, buffer, length)
    int   dli_cid, command, modifier, station;
    PCHAR buffer;
    int   length;
#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");
        return;
    }

    /*-----------------------------------------------*/
    /* 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;
    if( command == DLI_PROT_SEND_BIND )
        pWtOptArgs->usICPCommand= DLI_ICP_CMD_BIND;
    if( command == DLI_PROT_SEND_UNBIND )
        pWtOptArgs->usICPCommand= DLI_ICP_CMD_UNBIND;
    pWtOptArgs->usProtCommand   = command;
    pWtOptArgs->iProtModifier   = modifier;
    pWtOptArgs->usProtLinkID    = (dli_cid == LINK0) ? iEvenLink : iEvenLink+1;
    pWtOptArgs->usProtSessionID = SessTbl[dli_cid].iICPSess;

    /*------------------------------------------------------------*/
    /* Allocate an output buffer for the request.  DLI currently  */
    /* requires at least one byte of buffer even when the request */
    /* is wholly contained in the optional arguments              */
    /*------------------------------------------------------------*/
    if ((pBfr = dlBufAlloc((length==0)?iMaxBfrSize:length)) == NULL)
    {
        printf("WriteCommand: dlBufAlloc failed (%d) (%s).\n",
               dlerrno, dlpErrString(dlerrno));
        return;
    }

    /*--------------------------------------*/
    /* Copy specified data to output buffer */
    /*--------------------------------------*/
    if ( buffer )
        memcpy( pBfr, buffer, length );

    /*--------------------------*/
    /* Write the request to DLI */
    /*--------------------------*/
    if ((dlWrite(dli_cid, pBfr, length, DLI_WRITE_NORMAL,
                pWtOptArgs) != OK) && (dlerrno != DLI_EWOULDBLOCK))
    {
        /*--------------------------------------------------*/
        /* dlWrite has failed, inform user and free buffers */
        /*--------------------------------------------------*/
        printf("WriteCommand: dlWrite request failed (%d) (%s).\n",
               dlerrno, dlpErrString(dlerrno));
        dlBufFree(pBfr);
        dlBufFree((char *)pWtOptArgs);
    }

}



/******************************************************************************
*
* 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 write cancellation (%d) (%s).\n",
                       dlerrno, dlpErrString(dlerrno));
            }

            tfDone = TRUE;
        }

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

        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)
        {
            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.  Also, this permits changing
*          the port clocking to internal.
*
* Inputs:  none
*
* Outputs: none
*
*****************************************************************************/
void need_help()
{
    char cLine[80];      /* General purpose line buffer.  */

    /*----------------------------------*/
    /* See if the user wants some help. */
    /*----------------------------------*/
    printf("Need help (H) or no [N] ? ");
#ifdef WINNT
    ntscanf("%s", cLine);
#else
    gets(cLine);
#endif

    if (tolower(*cLine) == 'h')
    {
        printf("\n\
    This program transfers data between a pair of adjacent ports on an ICP\n\
    board.  The first ICP is zero; the first port on an ICP is zero.  The\n\
    program defaults to ICP zero, ports zero and one.\n\n\
    The ICPs and distribution panels are configured at the factory for\n\
    external clocking.  An adjacent port pair is normally connected with a\n\
    Simpact-supplied THREE-headed loopback cable, with the third head of\n\
    the cable connected to your powered up modem.  Your modem supplies\n\
    clocking to move the data, but the data does not reach the modem.  The\n\
    program does not work with an internal clock source unless \"I\" is\n\
    entered in response to the above prompt.\n\n\
    When prompted for values, the range of legal values appears within\n\
    parentheses immediately following the prompt.  The default value then\n\
    appears within square brackets.  To select the default value, simply\n\
    press the RETURN key.  To select a value other than the default, enter\n\
    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