/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                 ws_bench.c

A test-bench exerciser server script (application) for the WASD WebSocket
development environment.  This application is designed to be activated by the
command-line program WSB.C  See this for a description of the various
tests/exercises available.


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 any later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY
---------------
25-JUN-2011  MGD  v1.0.0, initial development
*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#define SOFTWAREVN "1.0.0"
#define SOFTWARENM "WS_BENCH"
#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

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include <descrip.h>
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>

#include "wslib.h"

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

#define Debug 0

#define FI_LI "WS_BENCH", __LINE__
#define EXIT_FI_LI(status) { printf ("[%s:%d]", FI_LI); exit(status); }

int  ConcurrentMax,
     ConnectedCount,
     UpdateMax,
     UsageCount;

unsigned int  RandomNumber,
              RandomFiller;

unsigned int  CurrentTime;
unsigned long  CurrentBinTime [2];

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

struct BenchClient {

   int  ClientCount,
        CloseFrame,
        DoEcho,
        DoPush,
        DoPing,
        DoPong,
        MessageSize,
        PingCount,
        PingToo,
        PongToo,
        PushCount,
        PushBufferCount;

   char  *BufferPtr,
         *PushBufferPtr;

   struct WsLibStruct  *WsLibPtr;
};

/* function prototypes */
void AddClient ();
void ClientReadAst (struct WsLibStruct*);
void PingClient (struct WsLibStruct*);
void RemoveClient (struct WsLibStruct*);
void PushClient (struct WsLibStruct*);

/*****************************************************************************/
/*
AST delivery is disabled during client acceptance and the add-client function
is deferred using an AST to help minimise the client setup window with a
potentially busy WebSocket application.
*/

main ()

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

   /* don't want the C-RTL fiddling with the carriage control */
   stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin");

   /* no clients is two minutes in seconds */
   WsLibSetLifeSecs (2*60);

   while (WsLibIsCgiPlus())
   {
      WsLibCgiVar ("");

      sys$setast (0);

      UsageCount++;

      /* reset the PRNG */
      sys$gettim (&RandomNumber);

      AddClient ();

      WsLibCgiPlusEof ();

      sys$setast (1);
   }

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Allocate a client structure and add it to the head of the list.
*/

void AddClient ()

{
   int  len, status;
   char  *cptr;
   struct WsLibStruct  *wsptr;
   struct BenchClient  *clptr;

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

   clptr = calloc (1, sizeof(struct BenchClient));
   if (!clptr) EXIT_FI_LI (vaxc$errno);

   cptr = WsLibCgiVar("FORM_DO");
   if (!strcmp (cptr, "echo"))
      clptr->DoEcho = 1;
   else
   if (!strcmp (cptr, "ping"))
      clptr->DoPing = 1;
   else
   if (!strcmp (cptr, "pong"))
      clptr->DoPong = 1;
   else
   if (!strcmp (cptr, "push"))
      clptr->DoPush = 1;
   else
      EXIT_FI_LI (SS$_BUGCHECK);

   /* buffer not specified or as zero then use wsLIB dynamic buffers */
   if (cptr = WsLibCgiVarNull("FORM_SIZE"))
      clptr->MessageSize = atoi(cptr);
   if (clptr->MessageSize)
   {
      clptr->BufferPtr = calloc (1, clptr->MessageSize);
      if (!clptr->BufferPtr) EXIT_FI_LI (vaxc$errno);
   }

   if (cptr = WsLibCgiVar("FORM_PING")) clptr->PingToo = atoi(cptr);

   /* create a WebSocket library structure for the client */
   if (!(clptr->WsLibPtr = wsptr = WsLibCreate (clptr, RemoveClient)))
   {
      /* failed, commonly on some WebSocket protocol issue */
      free (clptr);
      return;
   }

   /* open the IPC to the WebSocket (mailboxes) */
   status = WsLibOpen (wsptr);
   if (VMSnok(status)) EXIT_FI_LI(status);

   /* no user interaction is two minutes in seconds */
   WsLibSetIdleSecs (wsptr, 2*60);

   /* maxiumum of five seconds without received data */
   WsLibSetReadSecs (wsptr, 5);

   ConnectedCount++;

   WsLibRead (wsptr, clptr->BufferPtr, clptr->MessageSize, ClientReadAst);
}

/*****************************************************************************/
/*
Destroy the wsLIB structure and free the client structure memory.
*/

void RemoveClient (struct WsLibStruct *wsptr)

{
   struct BenchClient  *clptr;

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

   clptr = WsLibGetUserData(wsptr);

   if (clptr->DoPush) sys$cantim (wsptr, 0);

   WsLibDestroy (wsptr);

   free (clptr);

   if (ConnectedCount) ConnectedCount--;
}

/*****************************************************************************/
/*
Read from WebSocket client has concluded.  Process according to the test.
*/

void ClientReadAst (struct WsLibStruct *wsptr)

{
   int  DataCount;
   char  *DataPtr;
   struct BenchClient  *clptr;

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

   WsLibWatchScript (wsptr, FI_LI, "ClientReadAst() %X!8XL !UL",
                     WsLibReadStatus(wsptr), WsLibReadCount(wsptr)); 

   if (VMSnok (WsLibReadStatus(wsptr)))
   {
      /* WEBSOCKET_INPUT read error (can be EOF) */
      WsLibClose (wsptr, WSLIB_CLOSE_BANG, NULL);
      return;
   }

   clptr = WsLibGetUserData(wsptr);

   if (clptr->PingToo) PingClient(wsptr);

   DataPtr = WsLibReadData(wsptr);
   DataCount = WsLibReadCount(wsptr);

   if (clptr->DoEcho)
   {
      /* just bang it back at the test bench client */
      if (WsLibReadIsBinary (wsptr))
         WsLibSetBinary (wsptr);
      else
      if (WsLibReadIsText (wsptr))
      {
         /* read converted UTF-8 to ASCII, write converting back to UTF-8 */
         WsLibSetAscii (wsptr);
      }
      WsLibWrite (wsptr, DataPtr, DataCount, WSLIB_ASYNCH);
   }
   else
   if (clptr->DoPing || clptr->DoPong)
   {
      /* the pong should be handled transparently by wsLIB */
   }
   else
   if (clptr->DoPush)
   {
      sscanf (DataPtr, "C:%d", &clptr->ClientCount);
      if (!clptr->ClientCount) EXIT_FI_LI (SS$_BUGCHECK);

      if (!clptr->PushBufferPtr)
      {
         clptr->PushBufferPtr = calloc (1, clptr->MessageSize);
         if (!clptr->PushBufferPtr) EXIT_FI_LI (vaxc$errno);
      }
      memcpy (clptr->PushBufferPtr, DataPtr, DataCount);
      clptr->PushBufferCount = DataCount;

      /* first read from client kicks off the push */
      if (!clptr->PushCount) PushClient (wsptr);
   }
   else
       EXIT_FI_LI (SS$_BUGCHECK);

   WsLibRead (wsptr, clptr->BufferPtr, clptr->MessageSize, ClientReadAst);
}

/*****************************************************************************/
/*
Push to the client (client-specified) content at pseudo-random intervals
between approximately 50mS and 1600mS.  Client can change content at any time
(and be able to keep checking it's accurate).  Client must disconnect.  Tests
asynchronous, indeterminate server push.
*/

void PushClient (struct WsLibStruct *wsptr)

{
   int  len, status,
        DataCount;
   unsigned long  DeltaTime [2];
   char  *DataPtr;
   struct BenchClient  *clptr;
 
   /*********/
   /* begin */
   /*********/

   clptr = WsLibGetUserData(wsptr);

   if (clptr->PingToo) PingClient(wsptr);

   clptr->PushCount++;

   DataPtr = clptr->PushBufferPtr;
   DataCount = clptr->PushBufferCount;

   /* zero the first 24 bytes */
   memset (DataPtr, '_', 24);
   /* update the client and server count */
   len = sprintf (DataPtr, "C:%d S:%d", clptr->ClientCount, clptr->PushCount);
   DataPtr[len] = '_';
   /* exercises synchronous write */
   if (WsLibReadIsText (wsptr))
      WsLibSetAscii (wsptr);
   else
      WsLibSetBinary (wsptr);
   WsLibWrite (wsptr, DataPtr, DataCount, 0);

   /* 50mS to 1600ms */
   RandomNumber = RandomNumber * 69069 + 1;
   DeltaTime[0] = -500000 * ((rand() & 31) + 1);
   DeltaTime[1] = -1;

   status = sys$setimr (0, &DeltaTime, PushClient, wsptr, 0);
   if (VMSnok(status)) EXIT_FI_LI (status);
}

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

void PingClient (struct WsLibStruct *wsptr)

{
   int  cnt;
   char  PingMsg [128];
   struct BenchClient  *clptr;

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

   clptr = WsLibGetUserData(wsptr);

   /* one in this many calls */
   RandomNumber = RandomNumber * 69069 + 1;
   if (RandomNumber % clptr->PingToo) return;

   clptr->PingCount++;
   cnt = sprintf (PingMsg, "%s ping #%d", SOFTWAREID, clptr->PingCount);
   WsLibPing (wsptr, PingMsg, cnt);
}

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

