/*****************************************************************************/
/*
                                    wsb.c

WebSocket test-bench and gremlin inducing client.

This command-line program is designed to test and exercise the WASD WebSocket
environment during development.  It also reports on data transfered per second
and so can be used to metric the raw throughput of a given WASD WebSocket
environment and platform.  This utility uses the client-specific functionality
of the WSLIBCL.C library as well as the standard WebSockets support of WSLIB.C 
The complementary WebSocket server (script, application) is WS_BENCH.C.

This utility is not intended to be a paradigm of WebSocket programming virtue. 
It is a pragmatic test-bench and while it might be preferable to have some
independent, reference test-bench, back here in mid-2011 as the RFC begins to
gel (again) there is precious little available.  At least WASD will have
consistent implementation quirks :-)

The three primary functions implementing test behaviour are; ClientToServer()
performing the client-end writes, ServerToClient() supplying the processing of
reads from the server responses, and PushToServer() proving random-interval
writes to the server.

The WSB (and wsLIB) can only handle messages sizes up to 4GB (not the
full 63 bit size permitted by WebSocket protocol).  It is also better to send
multiple smaller messages rather than one humungous one because the WSB
allocates message memory before filling it appropriately (and allocating 4GB
*might* take a relative while).


ECHO TEST
---------
The client utility simply generates frames of the specified length and content
and type and sends them to the server.  The server echoes these frames back to
the client (first UTF-8 to ASCII decoding then ASCII to UTF-8 encoding text)
where it is compared to what has been sent.  Errors are reported.


PUSH TEST
---------
Both the client and send send unsolicited frames to each other at random
intervals (from 50mS to 1600mS) containing the specified length and content
type.  The receiving entity then echos these back ina frame for comparison
checking by the sender.


PING/PONG TEST
--------------
The /PING and /PONG qualifiers cause server and client agents to ping each
other at random intervals during other test sequences.


USAGE EXAMPLES
--------------
$ WSB == "$WASD_EXE:WSB"

$ WSB /DO=ECHO /NUMBER=1000 /CONCURRENT=50 /QBF /REPEAT=100

Sends one thousand requests, fifty at a time, sending "The quick brown fox ..."
one hundred times to the server (UTF-8 encoded, though is really only 7 bit
ASCII) each of which is decoded and then reencoded and sent it back where the
client compares that echo.

$ WSB /DO=ECHO /NUMBER=1000 /CONCURRENT=10 /BINARY /REPEAT=500 /BREAK

Sends one thousand requests, ten at a time, sending randomly generated binary
content to the server five hundred times, which echos it back and the client
reporting any discrepancy.  Connections are broken randomly during each
transaction to test resiliancy of WASD and server script/library.

$ WSB /DO=PUSH /NUMBER=100 /CONCURRENT=10 /REPEAT=500 /SIZE=64K /TEXT /PING

Sends one hundred requests, ten at a time, each sending fifty mssages of
sixty-four kilobytes, both the client and server each asynchronously sending
randomly generated UTF-8 text content, at random intervals, which the other
echos back and the server reporting any discrepancy.  It generates asynchronous
traffic in both directions.  In addition ping frames are sent from client to
server at random intervals and any non-pong response notified.


PLATFORM THROUGHPUT METRIC
--------------------------
Measure of raw message and byte throughput for a series of messages of various
sizes can provide useful information on the underlying maximum messaging
characteristics of a given WASD/VMS platform.  Data traverses the full

  WSB -> TCP/IP -> WASD -> script/application -> WASD -> TCP/IP -> WSB

communication and IPC pathways.  Of course this may also be performed between
autonomous VMS platforms in which case it also includes the physical network.

$ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=0
$ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=16
$ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=64
$ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=256
$ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=1024
$ WSB /DO=ECHO /THROUGHPUT /REPEAT=1000 /SIZE=65K

The /THROUGHPUT qualifier sends WebSocket binary messages but DOES NOT fill
those messages with any specific data (it remains as zeroes).  This saves the
overhead of buffer filling and allows more of the system's resources to be
devoted to the transferring data.  Of course the same test can be undertaken
using /BINARY, /QBF and /TEXT data (with expected and observed reduced
throughput metrics).


QUALIFIERS
----------
/BINARY            send random data as binary frames
/BREAK[=<integer>] randomly break connection at one in <integer> (default 5%)
/BRIEF             brief statistics
/CONCURRENT=       number of concurrent runs (default 1)
/COUNT=            total number of individual requests (synonym for /NUMBER)
/DYNAMIC           dynamically allocated buffers
/DO=ECHO           echo various contents
/DO=PING           send a standalone PING to the server
/DO=PONG           send a standalone, unsolicited PONG to the server
/DO=PUSH           asynchronous push content in both directions
/HEARTBEAT=<integer>  add periodic ping to the mix at the specified seconds
/MRS=<integer>     allows the maximum record size of the socket to be specified
/NUMBER=           total number of individual requests (default 1)
/PING              add PING frames randomly to the test
/PONG              add unsolicited PONG and server PING/PONGs into the mix
/PROXY=<string>    make a proxied WebSocket request
/RANDOM=<integer>  random events occur at one in <integer> (default 5%) 
/REPEAT=           <integer> number of messages within the request (default 1)
/SIZE=<integer>    message size in bytes (can use 'G', 'M', or 'k')
/QBF               send 'quick brown fox' as text frames (7 bit ASCII)
/[NO]STAGGER       initial connections staggered one after the other (default)
/TEXT              send strings of the characters 0..255 as text frames (UTF-8)
/THROUGHPUT        send whatever's in the buffer as binary data


COPYRIGHT
---------
Copyright (C) 2011-2020 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
05-FEB-2012  MGD  v1.1.0, guess what a proxied request might look like
25-JUN-2011  MGD  v1.0.0, initial development (happy birthday Sis)
*/
/*****************************************************************************/

#define SOFTWAREVN "1.1.0"
#define SOFTWARENM "WSB"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

#define COPYRIGHT_DATES "2011-2020"

#define COPYRIGHT_FULL \
"Copyright (C) 2011,2012 Mark G.Daniel\n\
This program, comes with ABSOLUTELY NO WARRANTY.\n\
This is free software, and you are welcome to redistribute it under the\n\
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version.\n\
http://www.gnu.org/licenses/gpl.txt\n"

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unixio.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <libclidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lib$routines.h>
#include <ssdef.h>
#include <stsdef.h>
#include <starlet.h>
#include <syidef.h>

#include "wslib.h"

#define BOOL int
#define TRUE 1
#define FALSE 0
 
/* this definition is not available in older VMS versions */
#ifndef EFN$C_ENF
#  define EFN$C_ENF 128  /* Event No Flag (no stored state) */
#endif

#ifndef SS$_FISH
#  define SS$_FISH 2928  /* for earlier VAXen */
#endif

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define DEFAULT_BUFFER_SIZE 80
#define DEFAULT_BREAK 20   /* one in twenty (~5%) */
#define DEFAULT_RANDOM 20   /* one in twenty (~5%) */
#define DEFAULT_READ_BUFFER_SIZE 16384
#define DEFAULT_SERVER_PORT 80
#define DEFAULT_ECHO_CHUNK 1024
#define MAX_CONNECTION_ERROR_COUNT 30
#define HEARTBEAT_AT 100

/* lib$cvtf_from_internal_time() complains about about a zero elapsed time! */
#define LIB$_DELTIMREQ 1410052

/* mainly to allow easy use of the __unaligned directive */
#define ULONGPTR __unaligned unsigned long*
#define USHORTPTR __unaligned unsigned short*
#define INT64PTR __unaligned __int64*

#define EXIT_LINE(status) { printf ("[WSB:%d]", __LINE__); exit(status); }

#define QUAD_TO_FLOAT(lptr) \
   ((float)(((unsigned long*)lptr)[0]) + \
    (float)(((unsigned long*)lptr)[1]) * 4294967295.0)

struct AnIOsb
{
   unsigned short  iosb$w_status;
   unsigned short  iosb$w_bcnt;
   unsigned int    iosb$l_reserved;
};

struct ConnectionStruct
{
   int  ContentCount,
        ContentLength,
        ConnectNumber,
        CountDown,
        FrameHeaderLength,
        PayloadLength,
        PayloadCount,
        PingBufferCount,
        PushCount,
        PushTimerId,
        ReadBufferCount,
        ReadBufferSize,
        WebCloseHandshake,
        WebSocketVersion,
        WriteBufferCount,
        WriteBufferSize;

   unsigned short  Channel;

   unsigned char  FrameHeader [2+2+6];

   char  HttpHeader [256],
         PingBuffer [256],
         WebSocketUrl,
         WebSocketHost;

   char  *ReadBufferPtr,
         *WriteBufferPtr;

   struct AnIOsb  SocketIOsb,
                  ReadIOsb,
                  WriteIOsb;

   struct WsLibStruct  *WsLibPtr;
};

char  Utility [] = "WSB";

unsigned long  CvtTimeFloatSeconds = LIB$K_DELTA_SECONDS_F;

BOOL  Debug,
      CliBinaryFrame,
      CliBreak,
      CliBrief,
      CliDynamic,
      CliQbfFrame,
      CliTextFrame,
      CliPing,
      CliPong,
      CliStagger = TRUE,
      CliThroughPut,
      DoEcho,
      DoPing,
      DoPong,
      DoPush,
      DoShowHelp,
      DoShowVersion;

int  BreakRequestCount,
     BreakResponseCount,
     BreakRequestLast,
     BreakResponseLast,
     CliBufferSize = DEFAULT_BUFFER_SIZE,
     CliConcurrency = 1,
     CliCount = 1,
     CliHeartBeat = 0,
     CliSocketMrs = 0,
     CliNumber = 1,
     CliRandom = DEFAULT_RANDOM,
     ClientPingCount,
     ClientPongCount,
     ConcurrentCount,
     ConnectionCount,
     ConnectionErrorCount,
     EfnEnf = EFN$C_ENF,
     FailedRequestCount,
     HeartBeatAt = HEARTBEAT_AT,
     HttpProtocol = 11,
     HttpReadErrorCount,
     PongOkCount,
     ReadBufferSize,
     RequestCount,
     ResponseErrorCount,
     ServerPort,
     ServerPingCount,
     ServerPongCount,
     ServerPongFailCount,
     TotalBytesSent,
     WebSocketErrorCount,
     WebSocketProtocolVersion = 8;

unsigned long  ReadTotal [2],
               ReadMsgTotal [2],
               WriteTotal [2],
               WriteMsgTotal [2];

char  *CliProxyServer,
      *RequestUriPtr;

char  CloseAdieu [128],
      CommandLine [256],
      ServerHost [256],
      ServerSoftware [256],
      RequestUri [256];

extern int  WsLibClBreakCount,
            WsLibClBreakEvery;

/* function prototypes */
void BinaryFill (char*, int);
char* BytesPerSecond (float);
char* BytesOf (float);
void ClientToServer (struct WsLibStruct*);
void CloseConnection (struct WsLibStruct*);
void ConnectionAst (struct WsLibStruct*);
void ConnectToServer ();
float DurationSeconds (unsigned long*);
void GetParameters (int, char**);
void MessageCallback (struct WsLibStruct*);
void ParseURL ();
void PongCallback (struct WsLibStruct*);
void PushEvent (struct ConnectionStruct*);
void PushServer (struct ConnectionStruct*);
void QbfFill (char*, int);
BOOL RandomEvent ();
void ServerReadAst (struct WsLibStruct*);
void ServerToClient (struct WsLibStruct*);
void ServerWriteAst (struct WsLibStruct*);
void ShowHelp ();
int strsame (char*, char*, int);
void UpdateTotals (struct WsLibStruct*);
void Utf8Fill (char*, int);

/*****************************************************************************/
/*
*/

int main
(
int argc,
char *argv[]
)
{
   static char  SyiVersion [8];
   static struct
   {
      short  buf_len;
      short  item;
      char   *buf_addr;
      short  *short_ret_len;
   }
   SyiItem [] =
   {
     { sizeof(SyiVersion), SYI$_VERSION, (char*)&SyiVersion, 0 },
     { 0,0,0,0 }
   };

   int  cnt, status,
        VmsVersion;
   float  BytesFloat = 0.0,
          ReadFloat,
          ReadMsgFloat,
          SecsFloat,
          WriteFloat,
          WriteMsgFloat;
   char  *cptr, *sptr, *uptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (getenv("WSB$DBUG")) Debug = TRUE;

   GetParameters (argc, argv);

   if (Debug) setenv ("WATCH_SCRIPT", "*", 1);

   if (DoShowVersion)
   {
      fprintf (stdout, "%%%s-I-VERSION, %s\n%s",
               Utility, SOFTWAREID, COPYRIGHT_FULL);
      exit (SS$_NORMAL);
   }

   if (DoShowHelp) ShowHelp ();

   sprintf (CloseAdieu, "%s bids adieu!", SOFTWAREID);

   if (VMSnok (status = sys$getsyi (0, 0, 0, &SyiItem, 0, 0, 0)))
      exit (status);
   VmsVersion = ((SyiVersion[1]-48) * 10) + (SyiVersion[3]-48);
   if (Debug) fprintf (stdout, "VmsVersion: %d\n", VmsVersion);
   if (VmsVersion < 70) EfnEnf = 0;

   /* at each count there is an opportunity so ... */
   if (CliBreak)
   {
      CliBreak *= CliCount;
      WsLibClBreakEvery = CliBreak;
   }

   ParseURL ();

   uptr = RequestUri;

   if (cptr = CliProxyServer)
   {
      /* make it a proxied request */
      uptr += sprintf (RequestUri, "http://%s:%d", ServerHost, ServerPort);
      /* change the connected-to host and port */
      sptr = ServerHost;
      while (*cptr && *cptr != ':' && *cptr != '/') *sptr++ = *cptr++;
      *sptr = '\0';
      if (*cptr++ == ':')
         if (isdigit(*cptr)) ServerPort = atoi(cptr);
   }

   if (DoEcho)
   {
      sprintf (uptr, "/cgiplus-bin/ws_bench?do=echo&size=%d%s%s",
               CliDynamic ? 0 : CliBufferSize,
               CliPing ? "&ping=20" : "",
               CliPong ? "&pong=20" : "");
      RequestUriPtr = RequestUri;
   }
   else
   if (DoPush)
   {
      sprintf (uptr, "/cgiplus-bin/ws_bench?do=push&size=%d%s%s",
               CliDynamic ? 0 : CliBufferSize,
               CliPing ? "&ping=20" : "",
               CliPong ? "&pong=20" : "");
      RequestUriPtr = RequestUri;
   }
   else
   if (DoPing || DoPong)
   {
      sprintf (uptr, "/cgiplus-bin/ws_bench?do=%s&size=%d",
               DoPing ? "ping" : "pong",
               CliDynamic ? 0 : CliBufferSize);
      RequestUriPtr = RequestUri;
   }
   else
      exit (SS$_NORMAL);
    
   /******/
   /* go */
   /******/

   DurationSeconds (NULL);

   if (CliStagger)
   {
      /* connections to the server will be one after the other */
      ConnectToServer ();
   }
   else
   {
      /* this throws the concurrent total at the server at once! */
      sys$setast (0);
      for (cnt = 0; cnt < CliConcurrency; cnt++)
      {
         status = sys$dclast (&ConnectToServer, 0, 0);
         if (VMSnok (status)) EXIT_LINE (status);
      }
      sys$setast (1);
   }

   sys$hiber ();

   SecsFloat = DurationSeconds (NULL);

   /*********/
   /* stats */
   /*********/

   fprintf (stdout, "%%%s-I-STATS, %d total connections\n",
            Utility, ConnectionCount);
   if (CliBreak)
      fprintf (stdout, "%d broken connections\n",
               WsLibClBreakCount);
   if (DoPing || CliPing)
      fprintf (stdout,
"%d client pings, %d server pongs, %d server pings\n",
               ClientPingCount, ServerPongCount, ServerPingCount);

   if (CliBrief) exit (SS$_NORMAL);

   ReadFloat = QUAD_TO_FLOAT(&ReadTotal);
   ReadMsgFloat = QUAD_TO_FLOAT(&ReadMsgTotal);
   WriteFloat = QUAD_TO_FLOAT(&WriteTotal);
   WriteMsgFloat = QUAD_TO_FLOAT(&WriteMsgTotal);
   fprintf (stdout, "Duration: %.3f seconds\n", SecsFloat);
   fprintf (stdout, "Tx: %.0f msgs at %.0f/S, %s at %s\n",
            WriteMsgFloat, WriteMsgFloat/SecsFloat,
            BytesOf(WriteFloat), BytesPerSecond(WriteFloat/SecsFloat));
   fprintf (stdout, "Rx: %.0f msgs at %.0f/S, %s at %s\n",
            ReadMsgFloat, ReadMsgFloat/SecsFloat,
            BytesOf(ReadFloat), BytesPerSecond(ReadFloat/SecsFloat));
   fprintf (stdout, "Total: %.0f msgs at %.0f/S, %s at %s\n",
            WriteMsgFloat+ReadMsgFloat, (WriteMsgFloat+ReadMsgFloat)/SecsFloat,
            BytesOf(WriteFloat+ReadFloat),
            BytesPerSecond((WriteFloat+ReadFloat)/SecsFloat));

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Loop through the specified number of connections (or attempts thereof).  
Maintain, as best it can be, the specified number of simultaneous connections. 

If a connection attempt fails just quit there for that particular loop, leave 
a bit of time (as reads occur) to allow the server to recover.
*/
 
void ConnectToServer ()

{
   int  status;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ConnectToServer()\n");

   if (ConnectionCount >= CliNumber)
   {
      /* reached the total number of requests */
      if (!ConcurrentCount) sys$wake (0, 0);
      return;
   }

   ConnectionCount++;
   ConcurrentCount++;

   if (HeartBeatAt && !(ConnectionCount % HeartBeatAt))
      fprintf (stdout, "Completed %d requests\n", ConnectionCount);

   /* init connection structure */
   cxptr = calloc (1, sizeof(struct ConnectionStruct));
   if (!cxptr) EXIT_LINE (vaxc$errno);

   cxptr->ConnectNumber = ConnectionCount;
   cxptr->CountDown = CliCount;

   cxptr->WriteBufferSize = CliBufferSize;
   /* allocate some buffer size even for a zero-sized payload */
   cxptr->WriteBufferPtr = calloc (1, cxptr->WriteBufferSize+256);
   if (!cxptr->WriteBufferPtr) EXIT_LINE (vaxc$errno);

   cxptr->ReadBufferSize = cxptr->WriteBufferSize;
   cxptr->ReadBufferPtr = calloc (1, cxptr->ReadBufferSize);
   if (!cxptr->ReadBufferPtr) EXIT_LINE (vaxc$errno);

   cxptr->WsLibPtr = WsLibCreate (cxptr, &CloseConnection);

   if (CliSocketMrs) WsLibClSetSocketMrs (cxptr->WsLibPtr, CliSocketMrs);

   WsLibSetRoleClient (cxptr->WsLibPtr);
   WsLibSetMsgCallback (cxptr->WsLibPtr, &MessageCallback);

   /* maxiumum of five seconds without received data */
   WsLibSetReadSecs (cxptr->WsLibPtr, 5);

   WsLibSetPongCallback (cxptr->WsLibPtr, &PongCallback);

   if (CliHeartBeat) WsLibSetPingSecs (cxptr->WsLibPtr, CliHeartBeat);

   status = WsLibClConnect (cxptr->WsLibPtr,
                            ServerHost, ServerPort, RequestUri,
                            &ConnectionAst);
   if (Debug) fprintf (stdout, "WsLibClConnect() %%X%08.08X\n", status);
}

/****************************************************************************/
/*
Connection to server has been established (or not).
*/

void ConnectionAst (struct WsLibStruct *wsptr)

{
   int  status;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ConnectionAst()\n");

   cxptr = WsLibGetUserData (wsptr);

   if (VMSnok (wsptr->SocketIOsb.iosb$w_status))
   {
      /* connect failed */
      EXIT_LINE (wsptr->SocketIOsb.iosb$w_status);
   }

   if (CliStagger)
   {
      if (ConcurrentCount < CliConcurrency)
      {
         /* initiate the next serial connection */
         status = sys$dclast (&ConnectToServer, 0, 0);
         if (VMSnok (status)) EXIT_LINE (status);
      }
   }

   /* begin to interact with the server (WebSocket) application */
   ClientToServer (wsptr);
}

/****************************************************************************/
/*
Request is concluded.
*/

void CloseConnection (struct WsLibStruct *wsptr)

{
   int  status;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CloseConnection()\n");

   cxptr = WsLibGetUserData (wsptr);

   if (cxptr->PushTimerId) sys$cantim (cxptr->PushTimerId, 0);

   UpdateTotals (wsptr);

   WsLibDestroy (wsptr);

   if (cxptr->WriteBufferPtr) free (cxptr->WriteBufferPtr);
   if (cxptr->ReadBufferPtr) free (cxptr->ReadBufferPtr);

   free (cxptr);

   if (ConcurrentCount) ConcurrentCount--;

   /* replace the just-closed connection (unless limits reached) */
   ConnectToServer ();
}

/****************************************************************************/
/*
Write a frame from the client to the server (WS_BENCH.C) which does the
complementary processing and writes the result(s) back, this being processed
in ServerToClient() below.
*/

void ClientToServer (struct WsLibStruct *wsptr)

{
   int  status;
   struct ConnectionStruct  *cxptr;
   struct WsLibFrmStruct  *frmptr;
   struct WsLibMsgStruct  *msgptr;

   /*********/
   /* begin */
   /*********/

   cxptr = WsLibGetUserData (wsptr);

   if (Debug) fprintf (stdout, "ClientToServer() %d\n", cxptr->CountDown);

   if (WsLibIsClosed (wsptr)) return;

   if (VMSnok (status = WsLibClSocketStatus (wsptr)))
   {
      cxptr->CountDown = 0;
      fprintf (stdout, "%%%s-W-CONNECT, [%d] #%d failed (%%X%08.08X)\n",
                  Utility, __LINE__, cxptr->ConnectNumber,  status);
   }

   if (WsLibClBreakNow (wsptr)) return;

   if (!cxptr->CountDown)
   {
      WsLibClose (wsptr, WSLIB_CLOSE_NORMAL, CloseAdieu);
      return;
   }

   cxptr->WriteBufferCount = CliBufferSize;

   if ((DoPing || DoPong) ||
       ((CliPing || CliPong) && RandomEvent()))
   {
      if (cxptr->WriteBufferSize > sizeof(cxptr->PingBuffer))
         cxptr->PingBufferCount = sizeof(cxptr->PingBuffer);
      else
         cxptr->PingBufferCount = cxptr->WriteBufferSize;

      if (CliBinaryFrame)
         BinaryFill (cxptr->PingBuffer, cxptr->PingBufferCount);
      else
      if (CliTextFrame)
         Utf8Fill (cxptr->PingBuffer, cxptr->PingBufferCount);
      else
      if (CliQbfFrame)
         QbfFill (cxptr->PingBuffer, cxptr->PingBufferCount);
      /* else /throughput is in the buffer! */

      if (DoPing || CliPing)
         WsLibPing (wsptr,
                    cxptr->PingBuffer,
                    cxptr->PingBufferCount);

      /* expect to see this at server end but without any response to client */
      if (DoPong || CliPong)
         WsLibPong (wsptr,
                    cxptr->PingBuffer,
                    cxptr->PingBufferCount);

      if (DoPing || CliPing)
         ClientPingCount++;
      else
         ClientPongCount++;
   }
   else
   if (DoEcho)
   {
      if (CliBinaryFrame)
      {
         WsLibSetBinary (wsptr);
         BinaryFill (cxptr->WriteBufferPtr, cxptr->WriteBufferCount);
      }
      else
      if (CliTextFrame)
      {
         /* at server UTF-8 will be converted to ASCII and then back again */
         WsLibSetUtf8 (wsptr);
         Utf8Fill (cxptr->WriteBufferPtr, cxptr->WriteBufferCount);
      }
      else
      if (CliQbfFrame)
      {
         /* brown foxes are only 7 bit ASCII */
         WsLibSetUtf8 (wsptr);
         QbfFill (cxptr->WriteBufferPtr, cxptr->WriteBufferCount);
      }
      /* else /throughput is in the buffer! */

      WsLibWrite (wsptr,
                  cxptr->WriteBufferPtr,
                  cxptr->WriteBufferCount,
                  ServerWriteAst);
   }
   else
   if (DoPush)
   {
      /* if the first call then initiate server push */
      if (!cxptr->PushTimerId) PushServer (cxptr);
   }

   /* read next frame */
   WsLibRead (wsptr,
              cxptr->ReadBufferPtr,
              cxptr->ReadBufferSize,
              ServerReadAst);

   WsLibClBreakNow (wsptr);
}

/****************************************************************************/
/*
The server (WS_BENCH.C) has processed what was sent by ClientToServer() and
sent it back to the client for checking.  When processed sent next to server.
*/

void ServerToClient (struct WsLibStruct *wsptr)

{
   int  ClientCount,
        ServerCount;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   cxptr = WsLibGetUserData (wsptr);

   if (Debug) fprintf (stdout, "ServerToClient() %d\n", cxptr->CountDown);

   if (WsLibIsClosed (wsptr)) return;

   if (!cxptr->CountDown--)
   {
      WsLibClose (wsptr, WSLIB_CLOSE_NORMAL, CloseAdieu);
      return;
   }

   if (WsLibClBreakNow (wsptr)) return;

   cxptr->ReadBufferCount = WsLibReadCount (wsptr);

   if (DoEcho)
   {
      if ((CliBinaryFrame && !WsLibReadIsBinary(wsptr)) ||
          (CliTextFrame && !WsLibReadIsText(wsptr)) ||
          (CliQbfFrame && !WsLibReadIsText(wsptr)) ||
          (CliThroughPut && !WsLibReadIsBinary(wsptr)) ||
          cxptr->WriteBufferCount != WsLibReadCount(wsptr) ||
          memcmp (cxptr->ReadBufferPtr,
                  cxptr->WriteBufferPtr,
                  cxptr->WriteBufferCount))
      {
         fprintf (stdout,
"%%%s-W-ECHO, [%d] #%d data mismatch\n",
                  Utility, __LINE__, cxptr->ConnectNumber);
         goto ServerToClientShutdown;
      }
   }
   else
   if (DoPush)
   {
      if ((CliBinaryFrame && !WsLibReadIsBinary(wsptr)) ||
          (CliTextFrame && !WsLibReadIsText(wsptr)) ||
          (CliQbfFrame && !WsLibReadIsText(wsptr)) ||
          (CliThroughPut && !WsLibReadIsBinary(wsptr)) ||
          cxptr->ReadBufferCount != cxptr->WriteBufferCount)
      {
         fprintf (stdout, "%%%s-W-PUSH, [%d] #%d data mismatch (%s)\n",
                  Utility, __LINE__, cxptr->ConnectNumber,
                  cxptr->WriteBufferPtr);
         goto ServerToClientShutdown;
      }
      if (memcmp (cxptr->ReadBufferPtr+24,
                  cxptr->WriteBufferPtr+24,
                  cxptr->WriteBufferCount-24))
      {
         /* but ignore slightly stale data */
         sscanf (cxptr->ReadBufferPtr, "C:%d S:%d", &ClientCount, &ServerCount);
         if (ClientCount+1 != cxptr->PushCount)
         {
            fprintf (stdout, "%%%s-W-PUSH, [%d] #%d data mismatch |%s|%s|\n",
                     Utility, __LINE__, cxptr->ConnectNumber,
                     cxptr->WriteBufferPtr, cxptr->ReadBufferPtr);
            goto ServerToClientShutdown;
         }
      }
   }

   WsLibClBreakNow (wsptr);

   ClientToServer (wsptr);
   return;

ServerToClientShutdown:

   FailedRequestCount++;
   WsLibClose (wsptr, WSLIB_CLOSE_NORMAL, CloseAdieu);
   return;
}

/*****************************************************************************/
/*
Push content to the server at pseudo-random intervals between approximately
100mS and 3200mS.  Client can change content at any time.
*/

void PushServer (struct ConnectionStruct *cxptr)

{
   static int  RandomNumber,
               RandomFiller;

   int  len, status;
   unsigned long  DeltaTime [2];
 
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "PushServer() %d\n", cxptr);

   if (WsLibIsClosed (cxptr->WsLibPtr)) return;

   cxptr->PushTimerId = (int)cxptr;

   if (DoPush)
   {
      /* change the pushed content (first 24 bytes are reserved) */
      memset (cxptr->WriteBufferPtr, '_', 24);
      /* update number from client */
      len = sprintf (cxptr->WriteBufferPtr, "C:%d", ++cxptr->PushCount);
      cxptr->WriteBufferPtr[len] = '_';
      /* fill the remainder with "data" */
      if (CliQbfFrame)
      {
         QbfFill (cxptr->WriteBufferPtr+24, cxptr->WriteBufferCount-24);
         WsLibSetAscii (cxptr->WsLibPtr);
      }
      else
      if (CliTextFrame)
      {
         Utf8Fill (cxptr->WriteBufferPtr+24, cxptr->WriteBufferCount-24);
         WsLibSetUtf8 (cxptr->WsLibPtr);
      }
      else
      {
         BinaryFill (cxptr->WriteBufferPtr+24, cxptr->WriteBufferCount-24);
         WsLibSetBinary (cxptr->WsLibPtr);
      }
      WsLibWrite (cxptr->WsLibPtr,
                  cxptr->WriteBufferPtr,
                  cxptr->WriteBufferCount,
                  NULL);
   }

   /* 100mS to 3200ms */
   if (!RandomNumber) sys$gettim (&RandomNumber);
   RandomNumber = RandomNumber * 69069 + 1;
   DeltaTime[0] = -1000000 * ((RandomNumber & 31) + 1);
   DeltaTime[1] = -1;

   status = sys$setimr (0, &DeltaTime, PushServer, cxptr, 0);
   if (VMSnok(status)) EXIT_LINE (status);
}

/****************************************************************************/
/*
*/

void ServerReadAst (struct WsLibStruct *wsptr)

{
   int  status;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ServerReadAst()\n");

   if (VMSnok (status = WsLibReadStatus(wsptr)))
   {
      /* WEBSOCKET_INPUT read error (can be EOF) */
      if (Debug) fprintf (stdout, "%%X%08.08X\n", status);
      WsLibClose (wsptr, WSLIB_CLOSE_BANG, NULL);
      return;
   }

   cxptr = WsLibGetUserData (wsptr);

   ServerToClient (wsptr);
}

/****************************************************************************/
/*
*/

void ServerWriteAst (struct WsLibStruct *wsptr)

{
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ServerWriteAst()\n");
}

/****************************************************************************/
/*
Using PRNG data to trigger "random" events at the specified proportion.
*/

int RandomEvent ()

{
   static int  RandomNumber,
               RandomFiller;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "RandomEvent()\n");

   if (!RandomNumber) sys$gettim (&RandomNumber);
   RandomNumber = RandomNumber * 69069 + 1;
   if (RandomNumber % CliRandom) return (FALSE);
   return (TRUE);
}

/*****************************************************************************/
/*
Parse the CLI specified URL into it's host-name (and resolved address), port
and path.
*/
 
void ParseURL ()

{
   int  status;
   char  *cptr, *sptr;

   /*********/
   /* begin */
   /*********/

   cptr = RequestUriPtr;
   if (strsame (cptr, "ws://", 5))
   {
      cptr += 5;
      sptr = ServerHost;
      while (*cptr && *cptr != ':' && *cptr != '/') *sptr++ = *cptr++;
      *sptr = '\0';
      if (*cptr == ':')
      {
         cptr++;
         if (isdigit(*cptr)) ServerPort = atoi(cptr);
         while (*cptr && isdigit(*cptr)) cptr++;
      }
      RequestUriPtr = cptr;
   }
   if (!ServerHost[0])
   {
      /* assume the local host is the server */
      gethostname (ServerHost, sizeof(ServerHost));
      if (Debug) fprintf (stdout, "ServerHost |%s|\n", ServerHost);
   }
   if (!ServerPort) ServerPort = 80;
}
 
/****************************************************************************/
/*
Fill the specified buffer with PRNG octets.
*/

void BinaryFill
(
char *BufferPtr,
int BufferSize
)
{
   static unsigned  long  RandomNumber,
                          GetTimFiller;

   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "BinaryFill()\n");

   if (!RandomNumber) sys$gettim (&RandomNumber);

   zptr = (sptr = BufferPtr) + BufferSize;
   while (sptr < zptr)
   {
      RandomNumber = RandomNumber * 69069 + 1;
      cptr = (char*)&RandomNumber;
      *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = *cptr;
   }
}

/****************************************************************************/
/*
Fill the buffer with UTF-8 strings of the 8 bit characters 0..255.
*/

void Utf8Fill
(
char *BufferPtr,
int BufferSize
)
{
   unsigned char  ch = 0;
   unsigned char  *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "Utf8Fill()\n");

   zptr = (sptr = (unsigned char*)BufferPtr) + BufferSize;
   while (sptr < zptr)
   {
      if (ch & 0x80)
      {
         if (sptr+1 >= zptr) break;
         *sptr++ = ((ch & 0xc0) >> 6) | 0xc0;
         *sptr++ = (ch & 0x3f) | 0x80;
      }
      else
         *sptr++ = ch;
      if (++ch > 255) ch = 0;
   }
   while (sptr < zptr) *sptr++ = '\0';
}

/****************************************************************************/
/*
Fill the specified buffer with brown foxes.
*/

void QbfFill
(
char *BufferPtr,
int BufferSize
)
{
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "QbfFill()\n");

   cptr = "";
   zptr = (sptr = BufferPtr) + BufferSize;
   while (sptr < zptr)
   {
      if (!*cptr) cptr = "The quick brown fox jumps over the lazy dog.\n";
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
}

/****************************************************************************/
/*
Called each time wsLIB receives a pong from the server application.
*/

void PongCallback (struct WsLibStruct *wsptr)

{
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "PongCallback()\n");

   cxptr = WsLibGetUserData (wsptr);

   ServerPongCount++;

   if (DoPing || DoPong)
   {
      if (cxptr->CountDown-- <= 1)
      {
         WsLibClose (wsptr, WSLIB_CLOSE_NORMAL, CloseAdieu);
         return;
      }
   }
}

/****************************************************************************/
/*
Display non-informational messages output by the wsLIB library.
*/

void MessageCallback (struct WsLibStruct *wsptr)

{
   int  status = SS$_FISH;
   char  *cptr;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "MessageCallback()\n");

   cxptr = WsLibGetUserData (wsptr);

   cptr = WsLibMsgString (wsptr);

   if (*(USHORTPTR)cptr == '%X') status = strtol(cptr+2,NULL,16);

   /* if informational */
   if (status & 1) return;

   if (status == SS$_VCCLOSED) return;

   fprintf (stdout,
"%%%s-W-WSLIB, [#%d] msg callback (WSLIB line %d)\n \\%s\\\n",
            Utility, cxptr->ConnectNumber,
            WsLibMsgLineNumber(wsptr), cptr);
}

/****************************************************************************/
/*
Return a floating-point representation of the elapsed time.  First call starts
the duration.  Second call ends it, resets the duration, and returns the
floating point seconds.  If no pointer to a duration quadword is supplied it
uses internal static storage.  Alpha and IA64 expect CC/FLOAT=IEEE.
*/

float DurationSeconds (unsigned long *StartTimePtr)

{
   static int  LibDeltaSecs = LIB$K_DELTA_SECONDS_F;
   static unsigned long  StartTime [2];

   int  status;
   unsigned long  DeltaBinTime [2],
                  EndBinTime [2];
   float  SecsFloat;

   /*********/
   /* begin */
   /*********/

   if (!StartTimePtr) StartTimePtr = StartTime;

   if (!(StartTimePtr[0] || StartTimePtr[1]))
   {
      sys$gettim (StartTimePtr);
      return (0.0);
   }

   sys$gettim (EndBinTime);

   status = lib$sub_times (&EndBinTime, StartTimePtr, &DeltaBinTime);
   if (VMSnok (status)) exit (status);

   StartTimePtr[0] = StartTimePtr[1] = 0;

#ifdef __ia64
   status = lib$cvts_from_internal_time (&LibDeltaSecs, &SecsFloat,
                                         &DeltaBinTime);
#else
   status = lib$cvtf_from_internal_time (&LibDeltaSecs, &SecsFloat,
                                         &DeltaBinTime);
#  ifdef __ALPHA
   /* lib$cvts_from_internal_time() does not exist with V7.3-2 or earlier */
#  include <cvtdef.h>
#  include <cvt$routines.h>
   if (VMSok(status))
      status = cvt$convert_float (&SecsFloat, CVT$K_VAX_F,
                                  &SecsFloat, CVT$K_IEEE_S, 0);
#  endif
#endif
   if (VMSnok (status)) exit (status);

   return (SecsFloat);
}

/****************************************************************************/
/*
Return a string with a suitable abbreviated bytes.
*/

char* BytesOf (float bytes)

{
   static char  BytesString [32];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "BytesOf() %f\n", bytes);

   if (bytes >= 1000000000.0)
      sprintf (BytesString, "%.1f Gbytes", bytes/1000000000.0);
   else
   if (bytes >= 1000000.0)
      sprintf (BytesString, "%.1f Mbytes", bytes/1000000.0);
   else
   if (bytes >= 1000.0)
      sprintf (BytesString, "%.1f kbytes", bytes/1000.0);
   else
      sprintf (BytesString, "%.0f bytes", bytes);

   return (BytesString);
}

/****************************************************************************/
/*
Return a string with a suitable abbreviated bytes/second.
*/

char* BytesPerSecond (float bps)

{
   static char  BytesPerString [32];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "BytesPerSecond() %f\n", bps);

   if (bps >= 1000000000.0)
      sprintf (BytesPerString, "%.1f GB/S", bps/1000000000.0);
   else
   if (bps >= 1000000.0)
      sprintf (BytesPerString, "%.1f MB/S", bps/1000000.0);
   else
   if (bps >= 1000.0)
      sprintf (BytesPerString, "%.1f kB/S", bps/1000.0);
   else
      sprintf (BytesPerString, "%.0f B/S", bps);

   return (BytesPerString);
}

/****************************************************************************/
/*
Do just as the function name says!
*/

void UpdateTotals (struct WsLibStruct *wsptr)

{
   int  status;
   unsigned long  *ulptr;
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "UpdateTotals()\n");

   cxptr = WsLibGetUserData (wsptr);

   ulptr = WsLibReadTotal (wsptr);
#ifdef __VAX
   lib$addx (ulptr, &ReadTotal, &ReadTotal, 0);
#else
   *(__int64*)&ReadTotal = *(__int64*)&ReadTotal + *ulptr;
#endif

   ulptr = WsLibReadMsgTotal (wsptr);
#ifdef __VAX
   lib$addx (ulptr, &ReadMsgTotal, &ReadMsgTotal, 0);
#else
   *(__int64*)&ReadMsgTotal = *(__int64*)&ReadMsgTotal + *ulptr;
#endif

   ulptr = WsLibWriteTotal (wsptr);
#ifdef __VAX
   lib$addx (ulptr, &WriteTotal, &WriteTotal, 0);
#else
   *(__int64*)&WriteTotal = *(__int64*)&WriteTotal + *ulptr;
#endif

   ulptr = WsLibWriteMsgTotal (wsptr);
#ifdef __VAX
   lib$addx (ulptr, &WriteMsgTotal, &WriteMsgTotal, 0);
#else
   *(__int64*)&WriteMsgTotal = *(__int64*)&WriteMsgTotal + *ulptr;
#endif
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.  This function has
been customized for this utility to allow it to be called multiple times during
the one use.
*/

void GetParameters
(
int argc,
char *argv[]
)
{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  acnt, status;
   unsigned short  Length;
   char  ch;
   char  *aptr, *cptr, *clptr, *sptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "GetParameters() %d\n", argc);

   /**************/
   /* initialize */
   /**************/

   ReadBufferSize = DEFAULT_READ_BUFFER_SIZE;
   ServerPort = DEFAULT_SERVER_PORT;

   RequestUriPtr = "";
   
   if (argc <= 1) return;

   /* get the entire command line following the verb */
   if (VMSnok (status =
       lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
      exit (status);
   (clptr = CommandLine)[Length] = '\0';
   if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL && *aptr == '/') *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      /**************/
      /* parameters */
      /**************/

      if (strsame (aptr, "/BINARY", 4))
      {
         CliBinaryFrame = TRUE;
         CliQbfFrame = CliTextFrame = CliThroughPut = FALSE;
         continue;
      }
      if (strsame (aptr, "/BREAK=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliBreak = atoi(cptr);
         if (!CliBreak) CliBreak = DEFAULT_BREAK;
         if (CliBreak < 1) CliBreak = 1;
         if (CliBreak > 100) CliBreak = 100;
         continue;
      }
      if (strsame (aptr, "/BRIEF", -1))
      {
         CliBrief = TRUE;
         continue;
      }
      if (strsame (aptr, "/CONCURRENCY=", 5))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliConcurrency = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/COUNT=", 4) ||
          strsame (aptr, "/NUMBER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliNumber = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = TRUE;
         continue;
      }
      if (strsame (aptr, "/DO=ECHO", 8))
      {
         DoEcho = TRUE;
         continue;
      }
      if (strsame (aptr, "/DO=PING", 8))
      {
         DoPing = TRUE;
         continue;
      }
      if (strsame (aptr, "/DO=PONG", 8))
      {
         DoPong = TRUE;
         continue;
      }
      if (strsame (aptr, "/DO=PUSH", 8))
      {
         DoPush = TRUE;
         continue;
      }
      if (strsame (aptr, "/DYNAMIC", 4))
      {
         CliDynamic = TRUE;
         continue;
      }
      if (strsame (aptr, "/HEARTBEAT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliHeartBeat = atoi(cptr);
         if (!CliHeartBeat) CliHeartBeat = 10;
         continue;
      }
      if (strsame (aptr, "/HELP", 4))
      {
         DoShowHelp = TRUE;
         continue;
      }
      if (strsame (aptr, "/MRS=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliSocketMrs = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/PING", 4))
      {
         CliPing = TRUE;
         continue;
      }
      if (strsame (aptr, "/PONG", 4))
      {
         CliPong = TRUE;
         continue;
      }
      if (strsame (aptr, "/PROXY=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliProxyServer = cptr;
         continue;
      }
      if (strsame (aptr, "/QBF", -1))
      {
         CliQbfFrame = TRUE;
         CliBinaryFrame = CliTextFrame = CliThroughPut = FALSE;
         continue;
      }
      if (strsame (aptr, "/RANDOM=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliRandom = atoi(cptr);
         if (!CliRandom) CliRandom = DEFAULT_RANDOM;
         if (CliRandom < 1) CliRandom = 1;
         if (CliRandom > 100) CliRandom = 100;
         continue;
      }
      if (strsame (aptr, "/REPEAT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCount = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/SIZE=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliBufferSize = atoi(cptr);
         while (*cptr && isdigit(*cptr)) cptr++;
         if (toupper(*cptr) == 'G')
            CliBufferSize *= 1000000000;
         else
         if (toupper(*cptr) == 'M')
            CliBufferSize *= 1000000;
         else
         if (tolower(*cptr) == 'k')
            CliBufferSize *= 1000;
         continue;
      }
      if (strsame (aptr, "/STAGGER", 4))
      {
         CliStagger = TRUE;
         continue;
      }
      if (strsame (aptr, "/NOSTAGGER", 4))
      {
         CliStagger = FALSE;
         continue;
      }
      if (strsame (aptr, "/TEXT", 4))
      {
         CliTextFrame = TRUE;
         CliBinaryFrame = CliQbfFrame = CliThroughPut = FALSE;
         continue;
      }
      if (strsame (aptr, "/VERSION", 4))
      {
         DoShowVersion = TRUE;
         continue;
      }
      if (strsame (aptr, "/THROUGHPUT", 4))
      {
         CliThroughPut = TRUE;
         CliBinaryFrame = CliQbfFrame = CliTextFrame = FALSE;
         continue;
      }

      if (*aptr == '/')
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }

      if (!RequestUriPtr[0])
      {
         RequestUriPtr = aptr;
         continue;
      }

      fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
               Utility, aptr);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
}

/*****************************************************************************/
/*
*/
 
void ShowHelp ()
 
{
   /*********/
   /* begin */
   /*********/

   fprintf (stdout,
"%%%s-I-HELP, usage for the WASD WebSocket test-bench :^) utility (%s)\n\
\n\
WSB - CLI test and exercise the WASD WebSocket implementation.\n\
WS_BENCH - the complementary WebSocket server script/application.\n\
\n\
$ WB [qualifiers ...]\n\
\n\
/BINARY /BREAK[=<integer>] /COUNT=<integer> /CONCURRENT=<integer>\n\
/DO=[ECHO|PING|PONG|PUSH] /NUMBER=<integer> /PING /PONG /PROXY=<server> /QBF\n\
/RANDOM=<integer> /REPEAT=<integer> /SIZE=<integer> /[NO]STAGGER /TEXT\n\
\n\
Usage examples:\n\
\n\
$ WSB /NUM=1000 /DO=PUSH\n\
$ WSB /CONC=50 /NUM=1000 /DO=ECHO /PING /BREAK\n\
\n",
   Utility, SOFTWAREID);
 
   exit (SS$_NORMAL);
}
 
/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
TRUE if two strings are the same, or FALSE if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 
 
BOOL strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (FALSE);
      if (count)
         if (!--count) return (TRUE);
   }
   if (*sptr1 || *sptr2)
      return (FALSE);
   else
      return (TRUE);
}
 
/****************************************************************************/

