/*****************************************************************************/
/*
                                SesolaNetIo.c

Handles the a/synchronous $QIO with the network for SSL/TLS encrypted traffic. 
Is the SSL/TLS equivalent of NETIO.C

Reading and writing data size now have no architectural limit.  While the
OpenSSL BIO infrastructure could always perform it, WASD now reads and writes
exceeding 65535 bytes per call.  It's handled internally.  With the potential
for data size/length greater than a single QIO the IO status block is
deprecated in favour of |ioptr->Read/WriteCount| and |ioptr->Read/WriteStatus|.


VERSION HISTORY
---------------
25-JUL-2024  MGD  WATCH cipher octets only when [x]SSL is checked
30-JUN-2024  MGD  SesolaNetIoPerMinute() allows socket read size to be set
                  bugfix; Sesola_netio_read_ex() ->TcpMaxQio to ->TcpMaxSeg
                    subtly broke (very) large reads, back to v12.0.0 strategy
02-SEP-2021  MGD  bugfix; SesolaNetIoRead() /bytes = value/
27-FEB-2020  MGD  Sesola_netio_.._ex() seems to be the OpenSSL 1.1.n approach
18-JUN-2019  MGD  Sesola_netio_read_ast() 0 status TCP/IP Services?
                  Sesola_netio_write_ast() 0 status TCP/IP Services?
04-FEB-2019  MGD  Sesola_netio_read() and Sesola_netio_write() if connection
                    broken (channel zero) return zero (SSL shutdown)
17-MAR-2017  MGD  bugfix; SesolaNetIoRead() SSL_read() in-progress
05-JUN-2016  MGD  SesolaNetIoRead() renegotiate application data buffer
11-AUG-2015  MGD  restructure of network I/O abstractions
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

/* VMS related header files */
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#define SESOLA_REQUIRED
#include "Sesola.h"

#define WASD_MODULE "SESOLANETIO"

/***************************************/
#ifdef SESOLA  /* secure sockets layer */
/***************************************/

/******************/
/* global storage */
/******************/

static int  SesolaNetIoReadSize = -1;

/********************/
/* external storage */
/********************/

extern int  EfnWait,
            EfnNoWait;

extern char  ErrorSanityCheck[];

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Called at startup and then every minute.
Check logical name and if present set the socket read size to be that value.
*/ 

void SesolaNetIoPerMinute ()

{
   char  *sptr;

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

   if (WATCH_MODULE (WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaNetIoPerMinute()");

   /* check for logical name first-up then every minute */
   if (sptr = SysTrnLnm (WASD_SSL_READ_SIZE))
      SesolaNetIoReadSize = abs(atoi(sptr));
   else
      SesolaNetIoReadSize = -1;
}

/*****************************************************************************/
/*
Write application data to the client via the network.  This function uses
SSL_write() to write non-encrypted application data to the network in encrypted
form.  This function provides BLOCKING (non-AST) and NON-BLOCKING I/O
conforming to the functionality provided by NetWrite(). The network write I/O
status block status and count fields are valid for the high-level calling
routine.
*/

int SesolaNetIoWrite
(
NETIO_STRUCT *ioptr,
void *DataPtr,
int DataLength
)
{
   int  bytes, value;
   SESOLA_STRUCT  *sesolaptr;

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

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA,
                 "SesolaNetIoWrite() !&X !&X !UL",
                 ioptr, DataPtr, DataLength);

   sesolaptr = ioptr->SesolaPtr;

   if (DataPtr)
   {
      /* primary call not AST recall */
      if (ioptr->VmsStatus)
      {
         /* deliver explicitly set status */
         ioptr->WriteCount = 0;
         ioptr->WriteStatus = ioptr->VmsStatus;
         if (ioptr->WriteAstFunction) SysDclAst (SesolaNetIoWriteAst, ioptr);
         return (ioptr->WriteStatus);
      }

      sesolaptr->WritePtr = DataPtr;
      sesolaptr->WriteLength = DataLength;
      sesolaptr->WriteCount = ioptr->WriteCount = 0;
      sesolaptr->WriteIOsb.Count = 0;
      sesolaptr->WriteIOsb.Status = 0;

      if (WATCHING (ioptr, WATCH_NETWORK_OCTETS))
      {
         WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                    "WRITE !UL bytes PLAIN (!&?non-blocking\rblocking\r)",
                    DataLength, SESOLA_NETIO_WRITE_IN_PROGRESS(sesolaptr));
         WatchDataDump (DataPtr, DataLength);
      }

      if (Watch.TriggerTxCount)
      {
         if (!ioptr->WatchTriggerTxPlus)
         {
            if (WatchTrigger (DataPtr, DataLength, Watch.TriggerTx))
            {
               ioptr->WatchTriggerTx = true;
               if (Watch.TriggerPlus) ioptr->WatchTriggerTxPlus = true;
            }
         }
         if (ioptr->WatchTriggerTx || ioptr->WatchTriggerTxPlus)
         {
            WatchSetTrigger (ioptr);
            ioptr->WatchTriggerTx = false;
         }
      }
   }
   else
   {
      /* AST recall */
      DataPtr = sesolaptr->WritePtr;
      DataLength = sesolaptr->WriteLength;
   }

   if (!sesolaptr->WriteLength) ioptr->VmsStatus = SS$_BUGCHECK;

   ERR_clear_error ();
   bytes = 0;
   value = SSL_write_ex (sesolaptr->SslPtr, DataPtr, DataLength, &bytes);

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA,
                 "SSL_write() !SL !SL !SL !&B",
                 value, SSL_get_error(sesolaptr->SslPtr,value), bytes,
                 sesolaptr->WriteInProgress);

   /* if non-blocking I/O in progress just return and wait for delivery */
   if (sesolaptr->WriteInProgress) return (SS$_NORMAL);

   if (ioptr->VmsStatus) sesolaptr->WriteIOsb.Status = ioptr->VmsStatus;

   ioptr->WriteCount = 0;
   if (VMSok (sesolaptr->WriteIOsb.Status))
   {
      if (SSL_is_init_finished(sesolaptr->SslPtr))
      {
         if (value > 0)
         {
            sesolaptr->WriteCount = bytes; 
            ioptr->WriteCount = bytes;
            ioptr->WriteStatus = SS$_NORMAL;
         }
         else
            ioptr->WriteStatus = sesolaptr->WriteIOsb.Status;
      }
      else
         ioptr->WriteStatus = SS$_ABORT;
   }
   else
      ioptr->WriteStatus = sesolaptr->WriteIOsb.Status;

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, "!&S !UL",
                 ioptr->WriteStatus, ioptr->WriteCount);

   /* update the stats of the underlying network I/O structure */
   ioptr->BytesRawTx64 += sesolaptr->BytesTallyTx64;
   ioptr->BytesTallyTx64 += sesolaptr->BytesTallyTx64;
   ioptr->BlocksRawTx64 += sesolaptr->BlocksTallyTx64;
   ioptr->BlocksTallyTx64 += sesolaptr->BlocksTallyTx64;

   /* reset the intermediate accumulators used to update the stats */
   sesolaptr->BytesTallyTx64 = 0;
   sesolaptr->BlocksTallyTx64 = 0;

   /* decouple the AST delivery */
   if (ioptr->WriteAstFunction) SysDclAst (SesolaNetIoWriteAst, ioptr);

   return (ioptr->WriteStatus);
}

/*****************************************************************************/
/*
This function decouples SesolaNetIoWrite() from the AST delivery where it might
be recalled to make another write.
*/ 

void SesolaNetIoWriteAst (NETIO_STRUCT *ioptr)

{
   void  *AstParam;
   VOID_AST  AstFunction;

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

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, "SesolaNetIoWriteAst()");

   AstParam = ioptr->WriteAstParam;
   AstFunction = ioptr->WriteAstFunction;
   ioptr->WriteAstFunction = ioptr->WriteAstParam = NULL;
   if (AstFunction) AstFunction (AstParam);
}

/*****************************************************************************/
/*
Implements a required function of a OpenSSL BIO_METHOD.
*/

int Sesola_netio_write_ex
(
BIO *bioptr,
char *DataPtr,
int DataLength,
int *BytesPtr
)
{
   int  status;
   NETIO_STRUCT  *ioptr;
   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = (SESOLA_STRUCT*)BIO_get_data(bioptr);
   ioptr = sesolaptr->NetIoPtr;

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, 
                 "Sesola_netio_write_ex() !&X !&X !UL !&X !&B !&B",
                 ioptr, DataPtr, DataLength, BytesPtr,
                 sesolaptr->WriteInProgress, sesolaptr->WriteInProgress);

   if (sesolaptr->WriteIsComplete)
   {
      /* previous non-blocking write is now complete */
      sesolaptr->WriteInProgress = sesolaptr->WriteIsComplete = false;
      if (VMSok (sesolaptr->WriteIOsb.Status))
      {
         *BytesPtr = sesolaptr->WriteIOsb.Count;
         return (1);
      }
      return (0);
   }

   /* if asynchronous write in progress, retry next call */
   if (sesolaptr->WriteInProgress) return (0);

   /* in case of connection dropped */
   if (!ioptr->Channel)
   {
      BIO_clear_retry_flags (sesolaptr->BioPtr);
      return (0);
   }

   /* no empty writes! */
   if (!DataLength)
   {
      BIO_clear_retry_flags (sesolaptr->BioPtr);
      return (0);
   }

   if (WATCHING (ioptr, WATCH_NETWORK_OCTETS))
   {
      WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                 "WRITE !UL bytes CIPHER (!&?non-blocking\rblocking\r)",
                 DataLength, SESOLA_NETIO_WRITE_IN_PROGRESS(sesolaptr));
      if (WATCHING (ioptr, WATCH_SESOLA))
         WatchDataDump (DataPtr, DataLength);
   }

   if (DataLength > ioptr->TcpMaxQio) DataLength = ioptr->TcpMaxQio;

   if (SESOLA_NETIO_WRITE_IN_PROGRESS(sesolaptr))
   {
      /*******************/
      /* non-blocking IO */
      /*******************/

      BIO_set_retry_write (bioptr);
      sesolaptr->WriteInProgress = true;
      sesolaptr->WriteIsComplete = false;

      status = sys$qio (EfnNoWait, ioptr->Channel,
                        IO$_WRITEVBLK, &sesolaptr->WriteIOsb,
                        &Sesola_netio_write_ast, sesolaptr,
                        sesolaptr->WriteRawPtr = DataPtr, DataLength,
                        0, 0, 0, 0);

      if (VMSok (status)) return (0);

      if (status != SS$_EXQUOTA)
      {
         /* report the status via the AST */
         if (status != SS$_IVCHAN)
            ErrorNoticed (NULL, status, "sys$qio", FI_LI);
         sesolaptr->WriteIOsb.Status = status;
         sesolaptr->WriteIOsb.Count = 0;
         SysDclAst (&Sesola_netio_write_ast, sesolaptr);
         return (0);
      }
   }
   else
   {
      /***************/
      /* blocking IO */
      /***************/

      sesolaptr->WriteInProgress = false;
      sesolaptr->WriteIsComplete = false;

      status = sys$qiow (EfnWait, ioptr->Channel,
                         IO$_WRITEVBLK, &sesolaptr->WriteIOsb, 0, 0,
                         sesolaptr->WriteRawPtr = DataPtr, DataLength,
                         0, 0, 0, 0);

      if (VMSok (status))
         *BytesPtr = sesolaptr->WriteIOsb.Count;
      else
      {
         /* report the status via the AST */
         sesolaptr->WriteIOsb.Status = status;
         sesolaptr->WriteIOsb.Count = 0;
      }

      Sesola_netio_write_ast (sesolaptr);

      if (status != SS$_EXQUOTA) return (1);
   }

   /* with resource wait enabled the only quota not waited for is ASTLM */
   sys$canexh(0);
   /* make the message a little more meaningful */
   sys$exit (SS$_EXASTLM);
}

/*****************************************************************************/
/*
Post-process the write for both blocking and non-block I/O.
*/

void Sesola_netio_write_ast (SESOLA_STRUCT *sesolaptr)

{
   NETIO_STRUCT  *ioptr;

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

   ioptr = sesolaptr->NetIoPtr;

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, 
                 "Sesola_netio_write_ast() !&X !&F !&S !&B !&S !UL",
                 ioptr, &Sesola_netio_write_ast,
                 ioptr->VmsStatus, sesolaptr->WriteInProgress,
                 sesolaptr->WriteIOsb.Status, sesolaptr->WriteIOsb.Count);

   if (ioptr->VmsStatus)
   {
      /* deliver explicitly set status */
      ioptr->WriteIOsb.Status = ioptr->VmsStatus;
      ioptr->WriteIOsb.Count = 0;
   }

   if (WATCHING (ioptr, WATCH_THIS))
   {
      if (WATCH_CATEGORY(WATCH_NETWORK))
         WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                    "WRITE !&S !UL bytes CIPHER (!&?non-blocking\rblocking\r)",
                    sesolaptr->WriteIOsb.Status,
                    sesolaptr->WriteIOsb.Count,
                    SESOLA_NETIO_WRITE_IN_PROGRESS(sesolaptr));
      if (WATCH_CATEGORY(WATCH_RESPONSE))
         if (VMSnok (sesolaptr->WriteIOsb.Status))
            WatchThis (WATCHITM(ioptr), WATCH_RESPONSE,
                       "NETWORK !&S (!&?non-blocking\rblocking\r)",
                       sesolaptr->WriteIOsb.Status,
                       SESOLA_NETIO_WRITE_IN_PROGRESS(sesolaptr));
   }

   if (VMSok (sesolaptr->WriteIOsb.Status))
   {
      sesolaptr->Sesola_write_ErrorCount = 0;
      sesolaptr->BytesTallyTx64 += sesolaptr->WriteIOsb.Count;
   }
   else
   {
      if (sesolaptr->Sesola_write_ErrorCount++ > 8192)
         ErrorExitVmsStatus (sesolaptr->WriteIOsb.Status,
                             ErrorSanityCheck, FI_LI);

      ioptr->WriteErrorCount++;
      /* just note the first error status */
      if (!ioptr->WriteErrorStatus)
         ioptr->WriteErrorStatus = sesolaptr->WriteIOsb.Status;

      if (!(sesolaptr->WriteIOsb.Status == SS$_ABORT ||
            sesolaptr->WriteIOsb.Status == SS$_CANCEL ||
            sesolaptr->WriteIOsb.Status == SS$_CONNECFAIL ||
            sesolaptr->WriteIOsb.Status == SS$_LINKDISCON ||
            sesolaptr->WriteIOsb.Status == SS$_TIMEOUT ||
            sesolaptr->WriteIOsb.Status == SS$_IVCHAN ||
            sesolaptr->WriteIOsb.Status == 0))  /* TCP/IP Services? */
         ErrorNoticed (NULL, sesolaptr->WriteIOsb.Status, NULL, FI_LI);
      sesolaptr->WriteIOsb.Count = 0;
   }

   sesolaptr->BlocksTallyTx64++;

   BIO_clear_retry_flags (sesolaptr->BioPtr);

   /* if blocking I/O then just return */
   if (!sesolaptr->WriteInProgress) return;

   sesolaptr->WriteInProgress = false;
   sesolaptr->WriteIsComplete = true;

   if (sesolaptr->SslStateFunction)
      SysDclAst (sesolaptr->SslStateFunction, sesolaptr);
   else
      SesolaNetIoWrite (ioptr, NULL, 0);
}

/*****************************************************************************/
/*
Read application data from the client via the network.  This function uses
SSL_read() to read a stream of encrypted data from the network then provide it
decrypted in the supplied buffer.  This function provides BLOCKING (non-AST)
and NON-BLOCKING I/O conforming to the functionality provided by NetRead(). The
network read I/O status block status and count fields are valid for the
high-level calling routine.
*/

SesolaNetIoRead
(
NETIO_STRUCT *ioptr,
void *DataPtr,
int DataSize
)
{
   int  bytes, error, value;
   SESOLA_STRUCT  *sesolaptr;

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

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA,
                 "SesolaNetIoRead() !&X !&X !UL",
                 ioptr, DataPtr, DataSize);

   sesolaptr = ioptr->SesolaPtr;

   if (DataPtr)
   {
      /* primary call not AST recall */
      if (ioptr->VmsStatus)
      {
         /* deliver explicitly set status */
         ioptr->ReadStatus = ioptr->VmsStatus;
         ioptr->ReadCount = 0;
         if (ioptr->ReadAstFunction) SysDclAst (SesolaNetIoReadAst, ioptr);
         return (ioptr->ReadStatus);
      }

      sesolaptr->ReadPtr = DataPtr;
      sesolaptr->ReadSize = DataSize;
      sesolaptr->ReadCount = 0;
      sesolaptr->ReadIOsb.Count = 0;
      sesolaptr->ReadIOsb.Status = 0;

      if (WATCHING (ioptr, WATCH_NETWORK))
         WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
"!&?FILL\rREAD\r !UL/!UL bytes (!&?non-blocking\rblocking\r)",
                    sesolaptr->ReadSize & NETIO_DATA_FILL_BUF,
                    sesolaptr->ReadCount,
                    sesolaptr->ReadSize & ~NETIO_DATA_FILL_BUF,
                    SESOLA_NETIO_READ_IN_PROGRESS(sesolaptr));
   }

   if (!sesolaptr->ReadSize) ioptr->VmsStatus = SS$_BUGCHECK;

   for (;;)
   {
      DataSize = (sesolaptr->ReadSize & ~NETIO_DATA_FILL_BUF);

      if (ioptr->VmsStatus)
         ioptr->ReadStatus = ioptr->VmsStatus;
      else
      if (VMSnok (sesolaptr->ReadIOsb.Status))
         ioptr->ReadStatus = sesolaptr->ReadIOsb.Status;
      else
      if (sesolaptr->ReadCount >= DataSize)
         ioptr->ReadStatus = SS$_BUGCHECK;

      if (ioptr->ReadStatus)
         if (VMSnok (ioptr->ReadStatus))
         {
            /* break if an issue around the read */
            ioptr->ReadCount = 0;
            break;
         }

      DataSize -= sesolaptr->ReadCount;
      (uchar*)DataPtr = (uchar*)sesolaptr->ReadPtr + sesolaptr->ReadCount;

      if (sesolaptr->VerifyPeerDataSize &&
          sesolaptr->VerifyPeerDataCount > 0)
      {
         /* application data buffered at time of TLS/SSL renegotiate */
         if (sesolaptr->VerifyPeerDataCount > DataSize)
            value = DataSize;
         else
            value = sesolaptr->VerifyPeerDataCount;
         memcpy (DataPtr, sesolaptr->VerifyPeerReadPtr, bytes = value);

         if (WATCHING (ioptr, WATCH_SESOLA))
            WatchThis (WATCHITM(ioptr), WATCH_SESOLA,
                       "BUFFERED client data !UL/!UL !UL",
                       sesolaptr->VerifyPeerDataCount,
                       sesolaptr->VerifyPeerDataSize,
                       value);

         sesolaptr->VerifyPeerDataCount -= value;
         sesolaptr->VerifyPeerReadPtr += value;
         if (!sesolaptr->VerifyPeerDataCount)
         {
            /* reset the buffer freeing the global memory */
            VmFree (sesolaptr->VerifyPeerDataPtr, FI_LI);
            sesolaptr->VerifyPeerDataPtr = sesolaptr->VerifyPeerReadPtr = NULL;
            sesolaptr->VerifyPeerDataSize = 0;
         }
      }
      else
      {
         /* OpenSSL (TLS?) maximum of 16kB will be read at a time */
         ERR_clear_error ();
         bytes = 0;
         value = SSL_read_ex (sesolaptr->SslPtr, DataPtr, DataSize, &bytes);
         /* if non-blocking IO in progress just return and wait for delivery */
         if (sesolaptr->ReadInProgress)
         {
            if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
               WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA,
                          "SSL_read() in-progress");
            return (SS$_NORMAL); 
         }
      }

      error = SSL_get_error (sesolaptr->SslPtr, value);
      if (value > 0)
      {
         sesolaptr->ReadCount += bytes; 
         ioptr->ReadCount += bytes;
         ioptr->ReadStatus = SS$_NORMAL;
      }
      else
      if (value <= 0)
      {
         ioptr->ReadCount = 0;
         if (error == SSL_ERROR_ZERO_RETURN ||
             error == SSL_ERROR_SYSCALL ||
             error == SSL_ERROR_SSL)
            ioptr->ReadStatus = SS$_ABORT;
         else
            ioptr->ReadStatus = sesolaptr->ReadIOsb.Status;
      }

      if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
         WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA,
                    "SSL_read() !SL !SL !SL !SL/!SL !&S",
                    value, error, bytes,
                    sesolaptr->ReadCount, 
                    sesolaptr->ReadSize & ~NETIO_DATA_FILL_BUF,
                    ioptr->ReadStatus);

      if (VMSnok (ioptr->ReadStatus))
      {
         /* deliver explicitly set status */
         if (ioptr->ReadAstFunction) SysDclAst (SesolaNetIoReadAst, ioptr);
         return (ioptr->ReadStatus);
      }

      /* just the one read if not filling a buffer */
      if (!(sesolaptr->ReadSize & NETIO_DATA_FILL_BUF)) break;
   }

   if (WATCHING (ioptr, WATCH_THIS) &&
       (WATCH_CATEGORY(WATCH_NETWORK) ||
        WATCH_CATEGORY(WATCH_NETWORK_OCTETS)))
   {
      WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                 "READ !&S !UL bytes PLAIN (!&?non-blocking\rblocking\r)",
                 ioptr->ReadStatus, ioptr->ReadCount,
                 SESOLA_NETIO_READ_IN_PROGRESS(sesolaptr));
      if (WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
         WatchDataDump (ioptr->ReadPtr, ioptr->ReadCount);
   }

   if (Watch.TriggerRxCount)
   {
      if (!ioptr->WatchTriggerRxPlus)
      {
         if (WatchTrigger (ioptr->ReadPtr, ioptr->ReadCount, Watch.TriggerRx))
         {
            ioptr->WatchTriggerRx = true;
            if (Watch.TriggerPlus) ioptr->WatchTriggerRxPlus = true;
         }
      }
      if (ioptr->WatchTriggerRx || ioptr->WatchTriggerRxPlus)
      {
         WatchSetTrigger (ioptr);
         ioptr->WatchTriggerRx = false;
      }
   }

   /* update the stats of the underlying network I/O structure */
   ioptr->BytesRawRx64 += sesolaptr->BytesTallyRx64;
   ioptr->BytesTallyRx64 += sesolaptr->BytesTallyRx64;
   ioptr->BlocksRawRx64 += sesolaptr->BlocksTallyRx64;
   ioptr->BlocksTallyRx64 += sesolaptr->BlocksTallyRx64;

   /* reset the intermediate accumulators used to update the stats */
   sesolaptr->BytesTallyRx64 = 0;
   sesolaptr->BlocksTallyRx64 = 0;

   /* decouple the AST delivery */
   if (ioptr->ReadAstFunction) SysDclAst (SesolaNetIoReadAst, ioptr);

   return (ioptr->ReadStatus);
}

/*****************************************************************************/
/*
This function decouples SesolaNetIoRead() from the AST delivery where it might
be recalled to make another read.
*/ 

void SesolaNetIoReadAst (NETIO_STRUCT *ioptr)

{
   void  *AstParam;
   VOID_AST  AstFunction;

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

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, "SesolaNetIoReadAst()");

   AstParam = ioptr->ReadAstParam;
   AstFunction = ioptr->ReadAstFunction;
   ioptr->ReadAstFunction = ioptr->ReadAstParam = NULL;
   if (AstFunction) AstFunction (AstParam);
}

/*****************************************************************************/
/*
Implements a required function of a OpenSSL BIO_METHOD.  It provides blocking
and more importantly non-blocking reads of (encrypted) data from the network
connection.  With blocking IO it returns immediately.
*/

int Sesola_netio_read_ex
(
BIO *bioptr,
char *DataPtr,
int DataSize,
int *BytesPtr
)
{
   int  status;
   NETIO_STRUCT  *ioptr;
   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = (SESOLA_STRUCT*)BIO_get_data(bioptr);
   ioptr = sesolaptr->NetIoPtr;

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, 
                 "Sesola_netio_read() !&X !&X !&X !UL !&B !&B",
                 ioptr, DataPtr, DataSize, BytesPtr,
                 sesolaptr->ReadInProgress, sesolaptr->ReadIsComplete);

   if (sesolaptr->ReadIsComplete)
   {
      /* previous non-blocking read is now complete */
      sesolaptr->ReadInProgress = sesolaptr->ReadIsComplete = false;
      if (VMSok (sesolaptr->ReadIOsb.Status))
      {
         *BytesPtr = sesolaptr->ReadIOsb.Count;
         return (sesolaptr->ReadIOsb.Count);
      }
      return (0);
   }

   /* if non-blocking read (already) in progress, retry next call */
   if (sesolaptr->ReadInProgress) return (-1);

   /* in case of connection dropped */
   if (!ioptr->Channel)
   {
      BIO_clear_retry_flags (sesolaptr->BioPtr);
      return (0);
   }

   /* no null buffers! */
   if (!DataSize)
   {
      BIO_clear_retry_flags (sesolaptr->BioPtr);
      return (0);
   }

   if (WATCHING (ioptr, WATCH_NETWORK_OCTETS))
      WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                 "READ !UL bytes max CIPHER (!&?non-blocking\rblocking\r)",
                 DataSize, SESOLA_NETIO_READ_IN_PROGRESS(sesolaptr));

   if (SesolaNetIoReadSize >= 0)
   {
      if (SesolaNetIoReadSize > 0 && DataSize > SesolaNetIoReadSize)
         DataSize = SesolaNetIoReadSize;
      else
      if (DataSize > ioptr->TcpMaxSeg)
         DataSize = ioptr->TcpMaxSeg;

      if (WATCHING (ioptr, WATCH_NETWORK))
         WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                    "WASD_SSL_READ_SIZE !UL (!UL)",
                    SesolaNetIoReadSize, DataSize);
   }
   else
   if (DataSize > ioptr->TcpMaxSeg)
      DataSize = ioptr->TcpMaxSeg;

   if (SESOLA_NETIO_READ_IN_PROGRESS(sesolaptr))
   {
      /*******************/
      /* non-blocking IO */
      /*******************/

      BIO_set_retry_read (bioptr);
      sesolaptr->ReadInProgress = true;
      sesolaptr->ReadIsComplete = false;
 
      status = sys$qio (EfnNoWait, ioptr->Channel,
                        IO$_READVBLK, &sesolaptr->ReadIOsb,
                        &Sesola_netio_read_ast, sesolaptr,
                        sesolaptr->ReadRawPtr = DataPtr, DataSize,
                        0, 0, 0, 0);

      /* return indicating non-blocking I/O */
      if (VMSok (status)) return (-1);

      if (status != SS$_EXQUOTA)
      {
         /* report the status via the AST */
         ErrorNoticed (NULL, status, "sys$qio", FI_LI);
         sesolaptr->ReadIOsb.Status = status;
         sesolaptr->ReadIOsb.Count = 0;
         SysDclAst (&Sesola_netio_read_ast, sesolaptr);
         return (-1);
      }
   }
   else
   {
      /***************/
      /* blocking IO */
      /***************/

      sesolaptr->ReadInProgress = false;
      sesolaptr->ReadIsComplete = false;
 
      /* see HTTP detection in Sesola_netio_read_ast() */
      if (sesolaptr->ReadIOsb.Status == SS$_BADESCAPE) return (0);

      status = sys$qiow (EfnWait, ioptr->Channel,
                         IO$_READVBLK, &sesolaptr->ReadIOsb, 0, 0,
                         sesolaptr->ReadRawPtr = DataPtr, DataSize,
                         0, 0, 0, 0);

      if (VMSok (status))
         *BytesPtr = sesolaptr->ReadIOsb.Count;
      else
      {
         /* report the status via the AST */
         sesolaptr->ReadIOsb.Status = status;
         sesolaptr->ReadIOsb.Count = 0;
      }

      Sesola_netio_read_ast (sesolaptr);

      if (status != SS$_EXQUOTA) return (1);
   }

   /* with resource wait enabled the only quota not waited for is ASTLM */
   sys$canexh(0);
   /* make the message a little more meaningful */
   sys$exit (SS$_EXASTLM);
}

/*****************************************************************************/
/*
Post-process the read for both blocking and non-block I/O.  This function
contains code that attempts to check for HTTP transactions with an SSL service.
*/

void Sesola_netio_read_ast (SESOLA_STRUCT *sesolaptr)

{
   char  *cptr;
   NETIO_STRUCT  *ioptr;

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

   ioptr = sesolaptr->NetIoPtr;

   if (WATCHMOD (ioptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_SESOLA, 
                 "Sesola_netio_read_ast() !&X !&F !&S !&S !UL",
                 ioptr, &Sesola_netio_read_ast, ioptr->VmsStatus,
                 sesolaptr->ReadIOsb.Status, sesolaptr->ReadIOsb.Count);

   if (ioptr->VmsStatus)
   {
      /* deliver explicitly set status */
      ioptr->ReadIOsb.Status = ioptr->VmsStatus;
      ioptr->ReadIOsb.Count = 0;
   }

   if (WATCHING (ioptr, WATCH_THIS) &&
       (WATCH_CATEGORY(WATCH_NETWORK) ||
        WATCH_CATEGORY(WATCH_NETWORK_OCTETS)))
   {
      WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                 "READ !&S !UL bytes CIPHER (!&?non-blocking\rblocking\r)",
                 sesolaptr->ReadIOsb.Status, sesolaptr->ReadIOsb.Count,
                 SESOLA_NETIO_READ_IN_PROGRESS(sesolaptr));
      if (WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
         if (WATCH_CATEGORY(WATCH_SESOLA))
            WatchDataDump (sesolaptr->ReadRawPtr, sesolaptr->ReadIOsb.Count);
   }

   if (VMSok (sesolaptr->ReadIOsb.Status))
   {
      if (!sesolaptr->ReadIOsb.Count)
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      sesolaptr->Sesola_read_ErrorCount = 0;

      /* if first read then check for what might be HTTP */
      if (!ioptr->BytesRawRx64)
      {
         /* first SSL/TLS octet should be 0x16 then major/minor (e.g. 0x0301) */
         cptr = sesolaptr->ReadRawPtr;
         if (*cptr >= 'A' && *cptr <= 'Z' &&
             (SAME4 (cptr, 'GET ') ||
              SAME4 (cptr, 'HEAD') ||
              SAME4 (cptr, 'POST') ||
              SAME4 (cptr, 'CONN') ||
              SAME4 (cptr, 'COPY') ||
              SAME4 (cptr, 'DELE') ||
              SAME4 (cptr, 'LOCK') ||
              SAME4 (cptr, 'MKCO') ||
              SAME4 (cptr, 'MOVE') ||
              SAME4 (cptr, 'OPTI') ||
              SAME4 (cptr, 'PRI ') ||  /* i.e. HTTP/2 preface */
              SAME4 (cptr, 'PROP') ||
              SAME4 (cptr, 'PUT ') ||
              SAME4 (cptr, 'TRAC') ||
              SAME4 (cptr, 'UNLO') ||
              MATCH5 (cptr, "HTTP/")))
         {
            /* ordinarily this status never would occur with network I/O */
            sesolaptr->ReadIOsb.Status = SS$_BADESCAPE;
            sesolaptr->HTTPduringHandshake = true;
         }
         else
         if (SAME4 (cptr, 'SSH-'))
         {
            /* ordinarily this status never would occur with network I/O */
            sesolaptr->ReadIOsb.Status = SS$_BADESCAPE;
            sesolaptr->SSHduringHandshake = true;
            /* store the fifth character from the handshake (version digit) */
            ioptr->SSHversionDigit = cptr[4];
         }
      }

      sesolaptr->BlocksTallyRx64++;
      sesolaptr->BytesTallyRx64 += sesolaptr->ReadIOsb.Count;
   }

   if (VMSnok (sesolaptr->ReadIOsb.Status))
   {
      if (sesolaptr->Sesola_read_ErrorCount++ > 8192)
         ErrorExitVmsStatus (sesolaptr->ReadIOsb.Status,
                             ErrorSanityCheck, FI_LI);

      ioptr->ReadErrorCount++;
      /* just note the first error status */
      if (!ioptr->ReadErrorStatus)
         ioptr->ReadErrorStatus = sesolaptr->ReadIOsb.Status;

      if (!(sesolaptr->ReadIOsb.Status == SS$_ABORT ||
            sesolaptr->ReadIOsb.Status == SS$_BADESCAPE ||   /* see above */
            sesolaptr->ReadIOsb.Status == SS$_CANCEL ||
            sesolaptr->ReadIOsb.Status == SS$_CONNECFAIL ||
            sesolaptr->ReadIOsb.Status == SS$_IVCHAN ||
            sesolaptr->ReadIOsb.Status == SS$_LINKDISCON ||
            sesolaptr->ReadIOsb.Status == SS$_TIMEOUT ||
            sesolaptr->ReadIOsb.Status == SS$_UNREACHABLE ||  /* MultiNet? */
            sesolaptr->ReadIOsb.Status == 0))  /* TCP/IP Services? */
         ErrorNoticed (NULL, sesolaptr->ReadIOsb.Status, NULL, FI_LI);
      sesolaptr->ReadIOsb.Count = 0;
   }

   BIO_clear_retry_flags (sesolaptr->BioPtr);

   /* if blocking I/O then just return */
   if (!sesolaptr->ReadInProgress) return;

   sesolaptr->ReadInProgress = false;
   sesolaptr->ReadIsComplete = true;

   if (sesolaptr->SslStateFunction)
      SysDclAst (sesolaptr->SslStateFunction, sesolaptr);
   else
      SesolaNetIoRead (ioptr, NULL, 0);
}

/****************************************************************************/
/*
Ensure any memory that may have ben allocated (see SesolaClientRequestData())
but not fully consumed (see SesolaNetIoRead()) during client certiicate
renegotiation is freed at the conclusion of each request.
*/

void SesolaNetIoReset (NETIO_STRUCT *ioptr)

{
   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = ioptr->SesolaPtr;

   if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA, "SesolaNetIoReset()");

   if (sesolaptr->VerifyPeerDataSize)
   {
      VmFree (sesolaptr->VerifyPeerDataPtr, FI_LI);
      sesolaptr->VerifyPeerDataPtr = sesolaptr->VerifyPeerReadPtr = NULL;
      sesolaptr->VerifyPeerDataCount = sesolaptr->VerifyPeerDataSize = 0;
   }
}

/****************************************************************************/
/*
Return true if there is the merest whiff of I/O going on.
*/

BOOL SesolaNetIoInProgress (NETIO_STRUCT *ioptr)

{
   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = ioptr->SesolaPtr;

   if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA,
                 "SesolaNetIoInProgress() !&B !&B !&A",
                 sesolaptr->WriteInProgress,
                 sesolaptr->ReadInProgress,
                 sesolaptr->SslStateFunction);

   return (sesolaptr->WriteInProgress ||
           sesolaptr->ReadInProgress ||
           sesolaptr->SslStateFunction);
}

/****************************************************************************/
/*
Explicitly set the status to SS$_CANCEL and cancel any actual I/O.
*/

void SesolaNetIoCancel (NETIO_STRUCT *ioptr)

{
   SESOLA_STRUCT  *sesolaptr;

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

   sesolaptr = ioptr->SesolaPtr;

   if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA))
      WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA, "SesolaNetIoCancel()");

   sesolaptr->NetIoPtr->VmsStatus = SS$_CANCEL;

   if (sesolaptr->WriteInProgress || sesolaptr->ReadInProgress)
      sys$cancel (sesolaptr->NetIoPtr->Channel);
}

/*****************************************************************************/
/*
For compilations without SSL these functions provide LINKage stubs for the
rest of the HTTPd modules, allowing for just recompiling the Sesola module to
integrate the SSL functionality.
*/

/*********************/
#else  /* not SESOLA */
/*********************/

/* external storage */
extern char  ErrorSanityCheck[];
extern WATCH_STRUCT  Watch;

void SesolaNetIoPerMinute ()
{
   if (WATCH_MODULE (WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaNetIoPerMinute()");
}

BOOL SesolaNetIoCancel (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

int SesolaNetIoWrite
(
NETIO_STRUCT *ioptr,
void *DataPtr,
int DataLength
)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

void SesolaNetIoWriteAst (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

int SesolaNetIoRead
(
NETIO_STRUCT *ioptr,
void *DataPtr,
int DataSize
)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

void SesolaNetIoReadAst (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

void Sesola_netio_read_ast (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

void Sesola_netio_write_ast (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

void SesolaNetIoReset (void *sesolaptr)
{
   ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

BOOL SesolaNetIoInProgress (NETIO_STRUCT *ioptr)
{
   return (false);
}

/************************/
#endif  /* ifdef SESOLA */
/************************/

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

