/*****************************************************************************/
/*
                                  netIO.c

The fundamental structure and code for the a/synchronous $QIO with the network. 
If the NETIO structure contain a Transport Layer Security (secure-sockets)
pointer it implicitly uses the TLS/SSL encrypted equivalent.  If the NEIO
struct contains a pointer to an HTTP/2 stream struct then uses the HTTP/2
pointer within that to perform HTTP/2 I/O (of course the HTTP/2 I/O ultimately
uses a NETIO struct to mediate the actual I/O, most often via TLS/SSL).

Reading and writing data size now have no architectural limit.  It's handled
internally.  Same for SSL/TLS.  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
---------------
15-JUL-2020  MGD  bugfix; NetIoWriteStatus() and NetIoReadStatus()
18-APR-2020  MGD  NetIoQioMaxSeg() tune QIO to TCP MSS
24-DEC-2019  MGD  NetIoWriteStatus() and NetIoReadStatus() count parameter
20-JAN-2018  MGD  refactor NetPeek() into NetIoPeek()
                  NetIoRead() and NetIoWrite() ..IOsb.Status = 0
                  NetIoRead() and NetIoWrite() blocking NetIo..Ast() each I/O
11-AUG-2015  MGD  restructure of network I/O abstractions
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#  undef __VMS_VER
#  define __VMS_VER 70000000
#  undef __CRTL_VER
#  define __CRTL_VER 70000000
#else
#  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
#endif

#include <stdio.h>
#include <ctype.h>

#include <iodef.h>

#include "wasd.h"

#define WASD_MODULE "NETIO"

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

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

extern uint  EfnWait,
             EfnNoWait,
             HttpdTickSecond;

extern char  ErrorSanityCheck[];

extern struct dsc$descriptor TcpIpDeviceDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Allocate (global) memory for the network I/O structure and assign a channel to
the internet template device.  Return a pointer to the structure if successful
or NULL if not.  At a maximum of every second or so reports any channel
assignment failure until the maximum period is exceeded (currently one minute).
*/ 

NETIO_STRUCT* NetIoBegin ()

{
   static ulong  ExitTickSecond,
                 PrevTickSecond;
   int  status;
   ushort  channel;
   char  *cptr;
   NETIO_STRUCT  *ioptr;

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

   if (WATCH_MODULE (WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetIoBegin()");

   status = sys$assign (&TcpIpDeviceDsc, &channel, 0, 0);

   if (VMSnok (status))
   {
      /* manage channel assignment failure */
      if (HttpdTickSecond < ExitTickSecond)
      {
         if (HttpdTickSecond != PrevTickSecond)
         {
            ErrorNoticed (NULL, status, NULL, FI_LI);
            PrevTickSecond = HttpdTickSecond;
            if (!ExitTickSecond)
               ExitTickSecond = HttpdTickSecond + NET_ASSIGN_FAIL_MAX;
         }
         /* hmmm, probably BYTLM exhausted */
         if (status == SS$_EXQUOTA) return (NULL);
         /* shouldn't have exhausted these, but seem to have */
         if (status == SS$_NOIOCHAN) return (NULL);
         /* some other (serious) error */
      }
      ErrorExitVmsStatus (status, "sys$assign()", FI_LI);
   }
   ExitTickSecond = PrevTickSecond = 0;

   ioptr = VmGet (sizeof(NETIO_STRUCT));
   ioptr->Channel = channel;
   ioptr->QioMaxSeg = NETIO_QIO_BYTES_SENTINAL;

   return (ioptr);
}

/*****************************************************************************/
/*
Proxy NETIO does not need the client data structure used by request processing
and so conserve a little memory by not allocating that (bit shonky I know). 
This is noted in NET.H as well.
*/ 

NETIO_STRUCT* NetIoProxyBegin ()

{
   NETIO_STRUCT  *ioptr;

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

   if (WATCH_MODULE (WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetIoProxyBegin()");

   ioptr = VmGet (sizeof(NETIO_STRUCT) - sizeof(CLIENT_STRUCT));
   ioptr->QioMaxSeg = NETIO_QIO_BYTES_SENTINAL;

   return (ioptr);
}

/*****************************************************************************/
/*
Deassign the channel as necessary and free the network I/O structure.
If a TLS/SSL then do the equivalent.
*/ 

void NetIoEnd (NETIO_STRUCT *ioptr)

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoEnd()");

   if (ioptr->SesolaPtr)
      SesolaNetEnd (ioptr->SesolaPtr);
   else
   {
      sys$dassgn (ioptr->Channel);
      VmFree (ioptr, FI_LI);
   }
}

/*****************************************************************************/
/*
QIOing with a maximum of the TCP MSS seems so much more efficient (at least for
VSI AXPVMS VMS V8.4-2L1 and VSI AXPVMS TCPIP V5.7).  The driver must have to do
a lot less work if the QIO data length is no greater than the TCP MSS. 
Defaults to the MSS of ethernet (1460).

System logical name WASD_QIO_MAXSEG will allow this value to be explictly
set.  There is no sanity check on this value.  Logical name is tested once at
first request (i.e. at server startup).
*/ 

void NetIoQioMaxSeg (NETIO_STRUCT *ioptr)

{
   static int  QioMaxSeg = -1;

   char  *cptr;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoQioMaxSeg()");

   if (QioMaxSeg < 0)
      if (cptr = SysTrnLnm ("WASD_QIO_MAXSEG"))
         QioMaxSeg = atoi (cptr);
      else
         QioMaxSeg = 0;

   TcpIpSocketBufferSize (ioptr);

   if (QioMaxSeg)
      ioptr->QioMaxSeg = QioMaxSeg;
   else
   if (ioptr->TcpIpTcpMaxSeg)
   {
      ioptr->QioMaxSeg = ioptr->TcpIpTcpMaxSeg;
      if (ioptr->QioMaxSeg > NETIO_QIO_BYTES_MAX)
         ioptr->QioMaxSeg = NETIO_QIO_BYTES_MAX;
      else
      if (ioptr->QioMaxSeg < NETIO_QIO_BYTES_MIN)
         ioptr->QioMaxSeg = NETIO_QIO_BYTES_MIN;
   }
   else
      ioptr->QioMaxSeg = NETIO_QIO_BYTES_SENTINAL;

   if (ioptr->QioMaxSeg != ioptr->TcpIpTcpMaxSeg)
      if (WATCHING (ioptr, WATCH_NETWORK))
         WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                    "QIO maxseg:!UL", ioptr->QioMaxSeg);
}

/*****************************************************************************/
/*
Write 'DataLength' bytes located at 'DataPtr' to the client either using either
the "raw" network, or via HTTP/2, or via the Secure Sockets Layer.

If 'AstFunction' zero then use sys$qiow(), waiting for completion. If
an AST completion address is supplied then use sys$qio().  If empty data
buffer is supplied (zero length) then declare an AST to service any AST
routine supplied.  If none then just return.

Explicitly declares any AST routine if an error occurs. The calling function
must not do any error recovery if an AST routine has been supplied but the
associated AST routine must!  If an AST was not supplied then the return
status can be checked.
*/ 

int NetIoWrite
(
NETIO_STRUCT *ioptr,
VOID_AST AstFunction,
void *AstParam,
void *DataPtr,
uint DataLength
)
{
   int  status;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                 "NetIoWrite() !&X !&A !&X !UL",
                 ioptr, AstFunction, DataPtr, DataLength);

   if (DataPtr)
   {
      /* first call */
      if (!ioptr->Http2StreamPtr || AstFunction)
      {
         /* not HTTP/2 or non-blocking */
         if (ioptr->WriteAstFunction)
         {
            HttpdStackTrace (FI_LI);
            status = SS$_BUGCHECK;
            ErrorNoticed (ioptr->RequestPtr, status, "!&A",
                          FI_LI, ioptr->WriteAstFunction);
            return (status);
         }
         ioptr->WriteCount = 0;
         ioptr->WriteStatus = 0;
         ioptr->WriteAstFunction = AstFunction;
         ioptr->WriteAstParam = AstParam;
         ioptr->WritePtr = DataPtr;
         ioptr->WriteLength = DataLength;
         ioptr->WriteIOsb.Count = 0;
         ioptr->WriteIOsb.Status = 0;
      }
   }

   if (ioptr->Http2StreamPtr)
   {
      /**********/
      /* HTTP/2 */
      /**********/

      status = Http2NetIoWrite (ioptr, AstFunction, AstParam,
                                DataPtr, DataLength);
      return (status);
   }

   if (ioptr->SesolaPtr)
   {
      /*****************/
      /* secure socket */
      /*****************/

      status = SesolaNetIoWrite (ioptr, DataPtr, DataLength);
      return (status);
   }

   if (ioptr->VmsStatus)
   {
      /*********************************/
      /* deliver explicitly set status */
      /*********************************/

      if (ioptr->WriteAstFunction)
         SysDclAst (NetIoWriteAst, ioptr);
      else
         NetIoWriteAst (ioptr);
      return (ioptr->VmsStatus);
   }

   if (WATCHING (ioptr, WATCH_NETWORK_OCTETS))
   {
      int  dlen = ioptr->WriteLength - ioptr->WriteCount;
      if (dlen > ioptr->QioMaxSeg) dlen = ioptr->QioMaxSeg;
      WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                  "WRITE !UL (!UL/!UL) bytes (!&?non-blocking\rblocking\r)",
                  dlen, ioptr->WriteCount, ioptr->WriteLength,
                  ioptr->WriteAstFunction);
      WatchDataDump ((uchar*)ioptr->WritePtr + ioptr->WriteCount, dlen);
   }

   if (!ioptr->WriteLength)
      status = ioptr->WriteStatus = SS$_BUGCHECK;
   else
   if (ioptr->WriteAstFunction)
   {
      /*******************/
      /* non-blocking IO */
      /*******************/

      /* limit size of QIO */
      DataLength = ioptr->WriteLength - ioptr->WriteCount;
      if (DataLength > ioptr->QioMaxSeg) DataLength = ioptr->QioMaxSeg;

      status = sys$qio (EfnNoWait, ioptr->Channel,
                        IO$_WRITEVBLK, &ioptr->WriteIOsb,
                        &NetIoWriteAst, ioptr,
                        (uchar*)ioptr->WritePtr + ioptr->WriteCount,
                        DataLength,
                        0, 0, 0, 0);

      if (VMSnok (status)) ioptr->WriteStatus = status;
   }
   else
   {
      /***************/
      /* blocking IO */
      /***************/

      while (ioptr->WriteCount < ioptr->WriteLength)
      {
         /* limit size of QIO */
         DataLength = ioptr->WriteLength - ioptr->WriteCount;
         if (!DataLength) DataLength = NETIO_QIO_BYTES_SENTINAL;

         status = sys$qiow (EfnWait, ioptr->Channel,
                            IO$_WRITEVBLK, &ioptr->WriteIOsb, 0, 0,
                            (uchar*)ioptr->WritePtr + ioptr->WriteCount,
                            DataLength,
                            0, 0, 0, 0);

         if (VMSnok (status))
            ioptr->WriteStatus = status;
         else
            NetIoWriteAst (ioptr);

         if (VMSnok (ioptr->WriteStatus)) break;
      }
   }

   /****************/
   /* check status */
   /****************/

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

   /* if resource wait enabled the only quota not waited for is ASTLM */
   if (status == SS$_EXQUOTA)
   {
      /* no ASTs means not much of anything else can happen so just exit! */
      sys$canexh(0);
      /* make the message a little more meaningful */
      sys$exit (SS$_EXASTLM);
   }

   /* write failed, call AST explicitly, status in the IOsb */
   ioptr->WriteIOsb.Status = status;
   ioptr->WriteIOsb.Count = 0;
   if (ioptr->WriteAstFunction)
      SysDclAst (NetIoWriteAst, ioptr);
   else
      NetIoWriteAst (ioptr);

   return (status);
}

/*****************************************************************************/
/*
AST from NetIoWrite().  Call the AST function.
*/ 

void NetIoWriteAst (NETIO_STRUCT *ioptr)

{
   void  *AstParam;
   VOID_AST  AstFunction;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                 "NetIoWriteAst() !&F !&X !&X !&S !UL !UL !UL !&A",
                 &NetIoWriteAst, ioptr, ioptr->VmsStatus,
                 ioptr->WriteIOsb.Status, ioptr->WriteIOsb.Count,
                 ioptr->WriteLength, ioptr->WriteCount+ioptr->WriteIOsb.Count,
                 ioptr->WriteAstFunction);

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

   ioptr->WriteStatus = ioptr->WriteIOsb.Status;

   if (ioptr->WriteIOsb.Status == SS$_NORMAL &&
       !(ioptr->WriteCount || ioptr->WriteLength))
   {
      /* special case, do not tally or report via WATCH */
      if (AstFunction = ioptr->WriteAstFunction)
      {
         AstParam = ioptr->WriteAstParam;
         ioptr->WriteAstFunction = ioptr->WriteAstParam = NULL;
         AstFunction (AstParam);
      }
      return;
   }

   if (WATCHPNT(ioptr))
   {
      /* the SS$_WASECC is returned when GZIPing network data */
      if (ioptr->WriteIOsb.Status != SS$_WASECC)
      {
         if (WATCH_CATEGORY(WATCH_NETWORK))
            WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
"WRITE !&S !UL (!UL/!UL) bytes (!&?non-blocking\rblocking\r)",
                       ioptr->WriteIOsb.Status,
                       ioptr->WriteIOsb.Count,
                       ioptr->WriteCount + ioptr->WriteIOsb.Count,
                       ioptr->WriteLength, ioptr->WriteAstFunction);
         if (WATCH_CATEGORY(WATCH_RESPONSE))
            if (VMSnok (ioptr->WriteIOsb.Status))
               WatchThis (WATCHITM(ioptr), WATCH_RESPONSE,
                          "NETWORK !&S (!&?non-blocking\rblocking\r)",
                          ioptr->WriteIOsb.Status, ioptr->WriteAstFunction);
      }
   }


   if (VMSok (ioptr->WriteIOsb.Status))
   {
      ADD_LONG_QUAD (1, ioptr->BlocksRawTx);
      ADD_LONG_QUAD (1, ioptr->BlocksTallyTx);
      ADD_LONG_QUAD (ioptr->WriteIOsb.Count, ioptr->BytesRawTx)
      ADD_LONG_QUAD (ioptr->WriteIOsb.Count, ioptr->BytesTallyTx)
      ioptr->WriteCount += ioptr->WriteIOsb.Count;
      if (ioptr->WriteAstFunction)
      {
         if (ioptr->WriteCount < ioptr->WriteLength)
         {
            /* continue to write */
            NetIoWrite (ioptr, NULL, NULL, NULL, 0);
            return;
         }
      }
   }
   else
   {
      ioptr->WriteErrorCount++;
      /* just note the first error status */
      if (!ioptr->WriteErrorStatus)
         ioptr->WriteErrorStatus = ioptr->WriteIOsb.Status;
      if (ioptr->WriteIOsb.Status &&
          !(ioptr->WriteIOsb.Status == SS$_ABORT ||
            ioptr->WriteIOsb.Status == SS$_CANCEL ||
            ioptr->WriteIOsb.Status == SS$_CONNECFAIL ||
            ioptr->WriteIOsb.Status == SS$_IVCHAN ||
            ioptr->WriteIOsb.Status == SS$_LINKDISCON ||
            ioptr->WriteIOsb.Status == SS$_TIMEOUT ||
            ioptr->WriteIOsb.Status == SS$_UNREACHABLE ||
            ioptr->WriteIOsb.Status == SS$_VCCLOSED))
      {
         char  *sptr = "(none)";     
         REQUEST_STRUCT *rqptr;
         if (rqptr = ioptr->RequestPtr)
            if (rqptr->rqHeader.RequestUriPtr)
               sptr = rqptr->rqHeader.RequestUriPtr;
         ErrorNoticed (NULL, ioptr->WriteIOsb.Status, "!AZ", FI_LI, sptr);
      }
   }
      
   if (AstFunction = ioptr->WriteAstFunction)
   {
      AstParam = ioptr->WriteAstParam;
      ioptr->WriteAstFunction = ioptr->WriteAstParam = NULL;
      AstFunction (AstParam);
   }
}

/*****************************************************************************/
/*
Deliver the supplied VMS status code via AST while maintaining the network
write in-progress conditions.  As with other I/O the delivery needs to be
decoupled in case it gets called again during that delivery.
*/ 

int NetIoWriteStatus
(
NETIO_STRUCT *ioptr,
VOID_AST AstFunction,
void *AstParam,
int AstStatus,
int WriteCount
)
{
   int  status;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                 "NetIoWriteStatus() !&X !&A !&S",
                 ioptr, AstFunction, AstStatus);

   if (ioptr->WriteAstFunction)
   {
      char  buf [256];
      FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->WriteAstFunction);
      ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI);
   }
   ioptr->WriteAstFunction = AstFunction;
   ioptr->WriteAstParam = AstParam;
   if (!WriteCount) ioptr->WritePtr = NULL;
   ioptr->WriteCount = 0;
   ioptr->WriteLength = WriteCount;
   ioptr->WriteIOsb.Count = WriteCount;
   ioptr->WriteIOsb.Status = AstStatus;

   if (AstFunction)
      SysDclAst (NetIoWriteAst, ioptr);
   else
      NetIoWriteAst (ioptr);

   return (AstStatus);
}

/*****************************************************************************/
/*
Queue up a read from the client over the network. If 'AstFunction' 
is zero then no I/O completion AST routine is called.  If it is non-zero then 
the function pointed to by the parameter is called when the network write 
completes.

Explicitly declares any AST routine if an error occurs. The calling function
must not do any error recovery if an AST routine has been supplied but the
associated AST routine must!  If an AST was not supplied then the return
status can be checked.
*/ 

int NetIoRead
(
NETIO_STRUCT *ioptr,
VOID_AST AstFunction,
void *AstParam,
void *DataPtr,
uint DataSize
)
{
   int  status;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                  "NetIoRead() !&X !&A !&X !UL",
                  ioptr, AstFunction, DataPtr, DataSize);

   if (DataPtr)
   {
      /* first call */
      if (ioptr->ReadAstFunction)
      {
         char  buf [256];
         HttpdStackTrace (FI_LI);
         FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->ReadAstFunction);
         status = SS$_BUGCHECK;
         ErrorNoticed (ioptr->RequestPtr, status, buf, FI_LI);
         return (status);
      }
      ioptr->ReadCount = 0;
      ioptr->ReadStatus = 0;
      ioptr->ReadAstFunction = AstFunction;
      ioptr->ReadAstParam = AstParam;
      ioptr->ReadPtr = DataPtr;
      ioptr->ReadSize = DataSize;
      ioptr->ReadIOsb.Count = 0;
      ioptr->ReadIOsb.Status = 0;
   }

   if (ioptr->Http2StreamPtr)
   {
      /**********/
      /* HTTP/2 */
      /**********/

      status = Http2NetIoRead (ioptr);
      return (status);
   }

   if (ioptr->SesolaPtr)
   {
      /*****************/
      /* secure socket */
      /*****************/

      status = SesolaNetIoRead (ioptr, DataPtr, DataSize);
      return (status);
   }

   if (WATCHING (ioptr, WATCH_NETWORK))
      WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                 "!AZ !UL/!UL bytes (!&?non-blocking\rblocking\r)",
                 ioptr->ReadSize & NETIO_DATA_FILL_BUF ? "FILL" : "READ",
                 ioptr->ReadCount, ioptr->ReadSize & ~NETIO_DATA_FILL_BUF,
                 ioptr->ReadAstFunction);

   if (ioptr->VmsStatus)
   {
      /*********************************/
      /* deliver explicitly set status */
      /*********************************/

      if (ioptr->ReadAstFunction)
         SysDclAst (NetIoReadAst, ioptr);
      else
         NetIoReadAst (ioptr);
      return (ioptr->VmsStatus);
   }

   if (!ioptr->ReadSize)
      status = ioptr->ReadStatus = SS$_BUGCHECK;
   else
   if (ioptr->ReadAstFunction)
   {
      /*******************/
      /* non-blocking IO */
      /*******************/

      DataSize = ioptr->ReadSize & ~NETIO_DATA_FILL_BUF;
      DataSize -= ioptr->ReadCount;
      if (DataSize > ioptr->QioMaxSeg) DataSize = ioptr->QioMaxSeg;

      status = sys$qio (EfnNoWait, ioptr->Channel,
                        IO$_READVBLK, &ioptr->ReadIOsb,
                        &NetIoReadAst, ioptr,
                        (uchar*)ioptr->ReadPtr+ioptr->ReadCount, DataSize,
                        0, 0, 0, 0);

      if (VMSnok (status)) ioptr->ReadStatus = status;
   }
   else
   {
      /***************/
      /* blocking IO */
      /***************/

      for (;;)
      {
         DataSize = ioptr->ReadSize & ~NETIO_DATA_FILL_BUF;
         DataSize -= ioptr->ReadCount;
         if (DataSize > ioptr->QioMaxSeg) DataSize = ioptr->QioMaxSeg;

         status = sys$qiow (EfnWait, ioptr->Channel,
                            IO$_READVBLK, &ioptr->ReadIOsb, 0, 0,
                            (uchar*)ioptr->ReadPtr+ioptr->ReadCount, DataSize,
                            0, 0, 0, 0);

         if (VMSnok (status))
            ioptr->ReadStatus = status;
         else
            NetIoReadAst (ioptr);

         if (VMSnok (ioptr->ReadStatus)) break;
      }
   }

   /****************/
   /* check status */
   /****************/

   /* if I/O successful */
   if (VMSok (status)) return (status);

   /* with resource wait enabled the only quota not waited for is ASTLM */
   if (status == SS$_EXQUOTA)
   {
      /* no ASTs means not much of anything else can happen so just exit! */
      sys$canexh(0);
      /* make the message a little more meaningful */
      sys$exit (SS$_EXASTLM);
   }

   /* write failed, call AST explicitly, status in the IOsb */
   ioptr->ReadIOsb.Status = status;
   ioptr->ReadIOsb.Count = 0;
   if (ioptr->ReadAstFunction)
      SysDclAst (NetIoReadAst, ioptr);
   else
      NetIoReadAst (ioptr);

   return (status);
}

/*****************************************************************************/
/*
AST from NetIoRead().  Call the AST function.
*/ 

void NetIoReadAst (NETIO_STRUCT *ioptr)

{
   void  *AstParam;
   VOID_AST  AstFunction;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                 "NetIoReadAst() !&F !&X !&X !&S !UL !UL !&A",
                 NetIoReadAst, ioptr, ioptr->VmsStatus,
                 ioptr->ReadIOsb.Status, ioptr->ReadIOsb.Count,
                 ioptr->ReadCount, ioptr->ReadAstFunction);

   if (VMSok (ioptr->ReadIOsb.Status))
   {
      /* zero bytes with a normal status (once seen with TGV-Multinet) */
//      if (!ioptr->ReadIOsb.Count) ioptr->ReadIOsb.Status = SS$_ABORT;
   }

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

   ioptr->ReadStatus = ioptr->ReadIOsb.Status;

   if (ioptr->ReadIOsb.Status == SS$_NORMAL &&
       !(ioptr->ReadCount || ioptr->ReadSize))
   {
      /* special case, do not tally or report via WATCH */
      if (AstFunction = ioptr->ReadAstFunction)
      {
         AstParam = ioptr->ReadAstParam;
         ioptr->ReadAstFunction = ioptr->ReadAstParam = NULL;
         AstFunction (AstParam);
      }
      return;
   }

   if (WATCHPNT(ioptr))
   {
      if (WATCH_CATEGORY(WATCH_NETWORK) ||
          WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
      {
         WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
"!AZ !&S !UL (!UL/!UL) bytes (!&?non-blocking\rblocking\r)",
                     ioptr->ReadSize & NETIO_DATA_FILL_BUF ? "FILL" : "READ",
                     ioptr->ReadIOsb.Status,
                     ioptr->ReadIOsb.Count,
                     ioptr->ReadCount + ioptr->ReadIOsb.Count,
                     ioptr->ReadSize & ~NETIO_DATA_FILL_BUF,
                     ioptr->ReadAstFunction);
         if (WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
            if (VMSok(ioptr->ReadIOsb.Status))
               WatchDataDump (ioptr->ReadPtr + ioptr->ReadCount,
                              ioptr->ReadIOsb.Count);
      }
   }

   if (VMSok (ioptr->ReadIOsb.Status))
   {
      ADD_LONG_QUAD (1, ioptr->BlocksRawRx);
      ADD_LONG_QUAD (1, ioptr->BlocksTallyRx);
      ADD_LONG_QUAD (ioptr->ReadIOsb.Count, ioptr->BytesRawRx)
      ADD_LONG_QUAD (ioptr->ReadIOsb.Count, ioptr->BytesTallyRx)
      ioptr->ReadCount += ioptr->ReadIOsb.Count;
      if (ioptr->ReadAstFunction)
      {
         if (ioptr->ReadSize & NETIO_DATA_FILL_BUF)
         {
            if (ioptr->ReadCount < (ioptr->ReadSize & ~NETIO_DATA_FILL_BUF))
            {
               /* read more to fill buffer */
               NetIoRead (ioptr, NULL, NULL, NULL, 0);
               return;
            }
         }
      }
   }
   else
   {
      ioptr->ReadErrorCount++;
      /* just note the first error status */
      if (!ioptr->ReadErrorStatus)
         ioptr->ReadErrorStatus = ioptr->ReadIOsb.Status;
      if (!(ioptr->ReadIOsb.Status == SS$_ABORT ||
            ioptr->ReadIOsb.Status == SS$_CANCEL ||
            ioptr->ReadIOsb.Status == SS$_CONNECFAIL ||
            ioptr->ReadIOsb.Status == SS$_IVCHAN ||
            ioptr->ReadIOsb.Status == SS$_LINKDISCON ||
            ioptr->ReadIOsb.Status == SS$_TIMEOUT ||
            ioptr->ReadIOsb.Status == SS$_UNREACHABLE ||
            ioptr->ReadIOsb.Status == SS$_VCCLOSED))
      {
         char  *sptr = "(none)";     
         REQUEST_STRUCT *rqptr;
         if (rqptr = ioptr->RequestPtr)
            if (rqptr->rqHeader.RequestUriPtr)
               sptr = rqptr->rqHeader.RequestUriPtr;
         ErrorNoticed (NULL, ioptr->ReadIOsb.Status, "!AZ", FI_LI, sptr);
      }
   }

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

/*****************************************************************************/
/*
Deliver the supplied VMS status code via AST while maintaining the network
write in-progress conditions.  As with other I/O the delivery needs to be
decoupled in case it gets called again during that delivery.  Primarily used to
indicate SS$_CANCEL in HTTP/2 processing.
*/ 

void NetIoReadStatus
(
NETIO_STRUCT *ioptr,
VOID_AST AstFunction,
void *AstParam,
int AstStatus,
uint ReadCount
)
{
   int  status;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                 "NetIoReadStatus() !&X !UL !&A !&S",
                 ioptr, ReadCount, AstFunction, AstStatus);

   if (ioptr->ReadAstFunction)
   {
      char  buf [256];
      FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->ReadAstFunction);
      ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI);
   }
   ioptr->ReadAstFunction = AstFunction;
   ioptr->ReadAstParam = AstParam;
   if (!ReadCount)
   {
      ioptr->ReadPtr = NULL;
      ioptr->ReadSize = 0;
   }
   ioptr->ReadCount = 0;
   ioptr->ReadIOsb.Count = ReadCount;
   ioptr->ReadIOsb.Status = AstStatus;

   if (AstFunction)
      SysDclAst (NetIoReadAst, ioptr);
   else
      NetIoReadAst (ioptr);
}

/****************************************************************************/
/*
Any $QIO I/O currently outstanding?
*/

BOOL NetIoInProgress (NETIO_STRUCT *ioptr)

{
   int  channel, status;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoInProgress()");

   if (ioptr->Http2StreamPtr)
      return (Http2NetIoInProgress (ioptr->Http2StreamPtr));

   if (ioptr->SesolaPtr)
      return (SesolaNetIoInProgress (ioptr->SesolaPtr));

   return (ioptr->WriteAstFunction || ioptr->ReadAstFunction);
}

/****************************************************************************/
/*
Cancel network I/O in-progress.
*/

void NetIoCancel (NETIO_STRUCT *ioptr)

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoCancel()");

   ioptr->VmsStatus = SS$_CANCEL;

   if (ioptr->Http2StreamPtr)
      Http2RequestCancel (((HTTP2_STREAM_STRUCT*)ioptr->Http2StreamPtr)->RequestPtr);
   else
   if (ioptr->SesolaPtr)
      SesolaNetIoCancel (ioptr);
   else
   if (ioptr->WriteAstFunction || ioptr->ReadAstFunction)
      sys$cancel (ioptr->Channel);
}

/****************************************************************************/
/*
Just close the socket (channel), bang!
*/

int NetIoCloseSocket (NETIO_STRUCT *ioptr)

{
   int  channel, status;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoCloseSocket()");

   if (ioptr->Http2StreamPtr) return (SS$_NORMAL);

   status = sys$dassgn (channel = ioptr->Channel);
   ioptr->Channel = 0;

   if (WATCH_CATEGORY(WATCH_CONNECT))
      WatchThis (WATCHITM(ioptr), WATCH_CONNECT, "CLOSE channel !UL !&S",
                 channel, status);

   return (status);
}

/*****************************************************************************/
/*
Allows *testing* of NetIoWrite() for small, medium, large and huge buffers.
A request to UTI "/$/NetIoWriteTest/?<integer>" will invoke this function. 
Writes responses of ASCII in quantities from 1 byte to whatever.  Non-blocking
by default, negative quantity to make blocking.
*/ 

#if WATCH_MOD

void NetIoWriteTest (REQUEST_STRUCT *rqptr)

{
   int  ch, nonblock, size;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetIoWriteTest()");

   if (rqptr->NotePadPtr != NetIoWriteTest)
   {
      rqptr->NotePadPtr = NetIoWriteTest;
      if (!(cptr = rqptr->rqHeader.QueryStringPtr)) cptr = "32767";
      size = atoi(cptr);
      if (size >= 0) nonblock = 1; else nonblock = 0;
      size = abs(size);

      cptr = VmGetHeap (rqptr, size+32);
      zptr = (sptr = cptr) + size;
      ch = '!';
      while (sptr < zptr)
      {
         while (ch <= '~' && sptr < zptr) *sptr++ = ch++;
         if (sptr < zptr) *sptr++ = '\n';
         ch = '!';
      }

      rqptr->rqResponse.NoGzip = true;
      ResponseHeader (rqptr, 200, "text/plain", size, NULL, NULL);
      if (nonblock)
      {
         NetWrite (rqptr, NetIoWriteTest, cptr, size);
         return;
      }
      NetWrite (rqptr, NULL, cptr, size);
   }

   RequestEnd2 (rqptr);
}

#endif /* WATCH_MOD */

/*****************************************************************************/
/*
Allows *testing* of NetIoRead() for small, medium, large and huge buffers.
Using cURL and a POSTed file to buffer.  Non-blocking by default, negative
quantity <integer> to make blocking.

  $ curl --insecure "-XPOST" --data-binary @<file_name> -
  "http[s]://<host_name>/$/NetIoReadTest/?<integer>"
*/ 

#if WATCH_MOD

void NetIoReadTest (REQUEST_STRUCT *rqptr)

{
   int  nonblock = 1,
        size;
   ushort  slen;
   char  *cptr;
   char  buf [256];

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetIoReadTest()");

   if (rqptr->NotePadPtr != NetIoWriteTest)
   {
      rqptr->NotePadPtr = NetIoWriteTest;
      if (atoi(rqptr->rqHeader.QueryStringPtr) < 0) nonblock = 0;
      size = rqptr->rqHeader.ContentLength;
      cptr = VmGetHeap (rqptr, size+32);
      size |= NETIO_DATA_FILL_BUF;
      if (nonblock)
      {
         NetRead (rqptr, &NetIoReadTest, cptr, size);
         return;
      }
      NetRead (rqptr, NULL, cptr, size);
   }

   ADD_LONG_QUAD (rqptr->NetIoPtr->ReadCount,
                  rqptr->NetIoPtr->BytesRawRx);
   ADD_LONG_QUAD (rqptr->NetIoPtr->ReadCount,
                  rqptr->NetIoPtr->BytesTallyRx);
   FaoToBuffer (buf, sizeof(buf), &slen, "status:!&S count:!UL\n",
                rqptr->NetIoPtr->ReadStatus,
                rqptr->NetIoPtr->ReadCount);
   rqptr->rqResponse.NoGzip = true;
   ResponseHeader (rqptr, 200, "text/plain", slen, NULL, NULL);
   NetWrite (rqptr, NULL, buf, slen);

   RequestEnd2 (rqptr);
}

#endif /* WATCH_MOD */

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