/*****************************************************************************/
/*
                                 HTTP2net.c

HTTP/2 network reads and writes.  Reads are multiplexed onto the connection by
the client and similarly multiplexed off at the server end.  The frames read
are then handed off to the appropriate processing function by frame type. 
Writes are serialised onto the connection using a FIFO queue.  Well, four
(prioritised) queues to be precise.  Request (network) reads and writes have
wrapper functions that interface the single HTTP/2 multiplexed connection to an
emulation of the dedicated, per-request network connection.

All request data being written to the network is required to be prepended with
a 9 byte frame header.  Without copying a lot of data between buffers this is
implemented using two, independent writes; the header then the data.  In an
effort to reduce the overall number of such network writes (especially when
transported by SSL), *all* WASD memory allocations have inbuilt space for an
HTTP/2 frame header immediately preceding the returned memory pointer.  This
space has some (not very distant) magic (VM_MAGIC_HTTP2) to indicate it is
present.  So, when this is detected the HTTP/2 frame header fields can be
written to the space immediately preceding the data buffer, saving on the
separate, small (and relatively costly) frame header write, reducing *all*
request data written from two to a single write without adding significant
complication to memory or data buffer management.  Hopefully not too clever for
its own good!  Of course it is also possible that a write originates from
non-allocated memory (e.g. string constant) and does not contain this header
and so requires a separate header write.


VERSION HISTORY
---------------
31-JAN-2020  MGD  fiddle with the code (perhaps optimisations, even bugfixes)
15-FEB-2019  MGD  bugfix; Http2NetQueueWrite() PEEK_8 at w2ptr->type
01-JUL-2018  MGD  bugfix; Http2NetIoWrite() blocking write data must be
                    asynchronously persistent so employ internal buffer(s)
06-JAN-2018  MGD  refactor write code paths (simplify and greater efficiency)
11-FEB-2017  MGD  bugfix; Http2NetQueueWrite() and Http2NetWriteDataAst() 
                    blocking writes are not placed on the request's
                    write list as they are transparent to the request
                  bugfix; Http2NetQueueWrite() deliver via NetIoWriteStatus()
                    using SS$_NORMAL (HTTP/2 I/O) not the request ->VmsStatus
06-AUG-2016  MGD  last stream ident now noted by Http2RequestBegin()
16-AUG-2015  MGD  initial
*/
/*****************************************************************************/

#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 "wasd.h"

#define WASD_MODULE "HTTP2NET"

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

extern uint  Http2ClientPrefaceLength,
             Http2FlowControlCurrent,
             Http2MaxFrameSize;

extern ulong HttpdTime64[];

extern char  ErrorSanityCheck[],
             Http2ClientPreface[];

extern LIST_HEAD  Http2List;

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initial read of the data described below.
*/

int Http2NetClientRead (HTTP2_STRUCT *h2ptr)

{
   int  status;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2NetClientRead() !&X", h2ptr->NetIoPtr);

   h2ptr->ReadBufferCount = 0;

   status = NetIoRead (h2ptr->NetIoPtr, Http2NetClientReadAst, h2ptr,
                       h2ptr->ReadBufferPtr, h2ptr->ReadBufferSize);

   return (status);
}

/*****************************************************************************/
/*
AST of read from client with sufficient data to parse (at least) the frame
header (9 octets) so the length of the frame can be determined.  Multiple reads
may be performed to build up a complete frame in the read buffer (potentially
more than one frame).  Only a complete frame is parsed and processed.  This
function always expects the frame to be located at the beinning of the read
buffer.
*/

void Http2NetClientReadAst (HTTP2_STRUCT *h2ptr)

{
   int  count, length, retval, status;
   uint  flags, ident, type;
   uchar  *bptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
   {
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2NetClientReadAst() !&F !&X !&S !UL",
                 Http2NetClientReadAst,
                 h2ptr->NetIoPtr, h2ptr->NetIoPtr->ReadStatus,
                                  h2ptr->NetIoPtr->ReadCount);
      WatchDataDump (h2ptr->ReadBufferPtr, h2ptr->NetIoPtr->ReadCount);
   }

   /* if the connection has a set VMS status */
   if (h2ptr->NetIoPtr->VmsStatus)
      h2ptr->NetIoPtr->ReadStatus = h2ptr->NetIoPtr->WriteStatus =
            h2ptr->NetIoPtr->VmsStatus;

   /* if the connection presented a read error */
   if (VMSnok (h2ptr->NetIoPtr->ReadStatus))
   {
      Http2CloseConnection (h2ptr);
      return;
   }

   ADD_LONG_QUAD (h2ptr->NetIoPtr->ReadCount, h2ptr->BytesRawRx);
   ADD_LONG_QUAD (h2ptr->NetIoPtr->ReadCount, h2ptr->BytesRawTallyRx);
   h2ptr->ReadBufferCount += h2ptr->NetIoPtr->ReadCount;

   bptr = h2ptr->ReadBufferPtr;
   count = h2ptr->ReadBufferCount;

   while (count >= HTTP2_FRAME_HEADER_SIZE)
   {
      if (h2ptr->ExpectingH2cPreface)
      {
         /* RFC 7540 3.2 Starting HTTP/2 for "http" URIs */
         if (count >= Http2ClientPrefaceLength &&
             MATCH8 (bptr, Http2ClientPreface) &&
             MATCH0 (bptr, Http2ClientPreface, Http2ClientPrefaceLength))
         {
            if (WATCHING (h2ptr, WATCH_HTTP2))
               WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                          "HTTP/2 connection preface (h2c)");
            h2ptr->ExpectingH2cPreface = false;
            count -= Http2ClientPrefaceLength;
            bptr += Http2ClientPrefaceLength;
            continue;
         }
         Http2Error (h2ptr, 0, HTTP2_ERROR_PROTOCOL);
         return;
      }

      HTTP2_PEEK_24 (bptr, length);

      if (WATCHPNT(h2ptr) && (WATCH_CATEGORY(WATCH_HTTP2) ||
                              WATCH_MODULE(WATCH_MOD_HTTP2)))
      {
         if (count >= length)
            Http2WatchFrame (h2ptr, bptr, NULL, count, false);
         else
         {
            HTTP2_PEEK_8 (bptr+3, type);
            HTTP2_PEEK_8 (bptr+4, flags);
            HTTP2_PEEK_32 (bptr+5, ident);
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
"FRAME !SL count:!UL length:!UL type:!UL flags:0x!2ZL ident:!UL",
                       count-length, count, length, type, flags, ident);
         }
      }

      if (length > h2ptr->ServerMaxFrameSize)
      {
        if (WATCHING (h2ptr, WATCH_HTTP2))
           WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                      "HTTP/2 frame size !UL exceeds !UL bytes",
                      length, h2ptr->ServerMaxFrameSize);
         Http2Error (h2ptr, 0, HTTP2_ERROR_SIZE);
         return;
      }

      /* if not a complete frame */
      if (length + HTTP2_FRAME_HEADER_SIZE > count) break;

      HTTP2_GET_24 (bptr, length);
      HTTP2_GET_8 (bptr, type);
      HTTP2_GET_8 (bptr, flags);
      HTTP2_GET_32 (bptr, ident);

      count -= HTTP2_FRAME_HEADER_SIZE;

      h2ptr->FrameCountRx++;
      h2ptr->FrameTallyRx++;
      if (ident)
      {
         h2ptr->FrameRequestCountRx++;
         h2ptr->FrameRequestTallyRx++;
      }

      if (ident)
      {
         if (ident & 0x1 == 0)
         {
            /* client initiated streams must have odd numbered idents */
            if (WATCHING (h2ptr, WATCH_HTTP2))
               WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                          "STREAM error !UL", ident);
            Http2Error (h2ptr, 0, HTTP2_ERROR_PROTOCOL);
            return;
         }

         if (h2ptr->GoAwayIdent)
         {
            /* ignore frames with idents after server signals goaway */
            count -= length;
            bptr += length;
            continue;
         }
      }

      /* mitigate CVE-2019-9518 */
      if (!length && !(flags & HTTP2_FLAG_DATA_END_STR))
      {
         switch (type)
         {
            case HTTP2_FRAME_DATA :
            case HTTP2_FRAME_HEADERS :
            case HTTP2_FRAME_CONTINUATION :
            case HTTP2_FRAME_PUSH_PROMISE :
            if (h2ptr->EmptyFrameLimitCount++ > HTTP2_EMPTY_FRAME_LIMIT_COUNT)
            {
               if (WATCHING (h2ptr, WATCH_HTTP2))
                  WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                             "LIMIT empty frames to !UL/S (DoS?)",
                             HTTP2_EMPTY_FRAME_LIMIT_COUNT);
               Http2Error (h2ptr, 0, HTTP2_ERROR_CALM);
               return;
            }
         }
      }

      switch (type)
      {
         case HTTP2_FRAME_DATA :
              retval = Http2RequestData (h2ptr, flags, ident, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, ident, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_HEADERS :
         case HTTP2_FRAME_CONTINUATION :
              retval = HpackHeadersFrame (&h2ptr->HpackClientTable,
                                          flags, ident, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, ident, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_PRIORITY :
              retval = Http2Priority (h2ptr, flags, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, 0, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_RST_STREAM :
              retval = Http2ResetStream (h2ptr, ident, 0, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, ident, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_SETTINGS :
              retval = Http2Settings (h2ptr, flags, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, 0, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_PUSH_PROMISE :
              /* client cannot server-push (RFC7540 8.2) */
              retval = Http2Error (h2ptr, 0, -(HTTP2_ERROR_PROTOCOL));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_PING :
              retval = Http2Ping (h2ptr, flags, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, 0, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_GOAWAY :
              retval = Http2GoAway (h2ptr, 0, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, 0, -(retval));
              if (retval >= 0) break;
              return;

         case HTTP2_FRAME_WINDOW_UPDATE :
              retval = Http2WindowUpdate (h2ptr, ident, 0, bptr, length);
              if (retval >= 0) break;
              retval = Http2Error (h2ptr, ident, -(retval));
              if (retval >= 0) break;
              return;

         default :
              Http2Error (h2ptr, 0, -(HTTP2_ERROR_PROTOCOL));
              return;
      }

      count -= length;
      bptr += length;
   }

   if (count)
   {
      /* shuffle remaining data to front of buffer */
      memcpy (h2ptr->ReadBufferPtr, bptr, count);
   }

   h2ptr->ReadBufferCount = count;
   bptr = h2ptr->ReadBufferPtr + count;
   length = h2ptr->ReadBufferSize - count;

   /* get the next, or rest of, frame */
   NetIoRead (h2ptr->NetIoPtr, Http2NetClientReadAst, h2ptr, bptr, length);
}

/*****************************************************************************/
/*
Return true if this HTTP/2 stream has outstanding network I/O.
*/ 

BOOL Http2NetIoInProgress (NETIO_STRUCT *ioptr)

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

   ioptr = ((HTTP2_STREAM_STRUCT*)ioptr->Http2StreamPtr)->Http2Ptr->NetIoPtr;

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

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

/*****************************************************************************/
/*
This function wraps the HTTP/1.1 network I/O response reads.  Named
Http2NetIo..() for consistency with NetIo..() and SesolaNetIo..().  It is
called from NetIoRead().  It calls the Http2RequestData() function to handle
the asynchronous HTTP/2 DATA frames from the client (request body) and the
reads by the request body processing code.
*/ 

int Http2NetIoRead (NETIO_STRUCT *ioptr)

{
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   REQUEST_STRUCT  *rqptr;

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

   s2ptr = ioptr->Http2StreamPtr;
   h2ptr = s2ptr->Http2Ptr;
   rqptr = s2ptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                 "Http2NetRead() !&A !&X !UL",
                 ioptr->ReadAstFunction, ioptr->ReadPtr, ioptr->ReadSize);

   /* no such creature as a blocking HTTP/2 read! */
   if (!ioptr->ReadAstFunction)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return (SS$_BUGCHECK);
   }

   return (Http2RequestData (h2ptr, 0, s2ptr->Ident, NULL, 0));
}                 

/*****************************************************************************/
/*
Wraps the HTTP/1.1 network I/O response writes in HTTP/2 DATA frames.  Named
Http2NetIo..() for consistency with NetIo..() and SesolaNetIO..().  It is
called from NetIoWrite().  The function must be able to queue multiple
(sub-)writes should a request write exceed the maximum client accepted frame
size.  Optimally the write buffers will always be sized at or below the maximum
frame size.

All HTTP/2 I/O are by nature asynchronous and so a blocking write (one with a
NULL |AstFunction| parameter) is made asynchronous.  Data persistance must be
guaranteed during the write and so is *always* copied to HTTP/2 internal
buffers and *never* used in-situ, as the calling code may well (*will*)
continue on processing and likely (*will*) modify the data buffer it considers
was *already* written.  An example of where blocking writes are used is the
WATCH facility.

If |DataPtr| is NULL then an empty frame with the end data flag set is sent. 
If the stream associated with the request has been closed then allow the write
to be queued but the status will be delivered as cancelled, not performing the
network I/O but allowing any AST to be delivered.
*/ 

int Http2NetIoWrite
(
NETIO_STRUCT *ioptr,
VOID_AST AstFunction,
void *AstParam,
void *DataPtr,
uint DataLength
)
{
   BOOL  magic;
   int  queue, status;
   HTTP2_STRUCT  *h2ptr;
   HTTP2_HEADER_STRUCT  *hsptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   HTTP2_WRITE_STRUCT  *w2ptr;
   REQUEST_STRUCT  *rqptr;
   VM_STRUCT  *vmptr;

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

   s2ptr = ioptr->Http2StreamPtr;
   h2ptr = s2ptr->Http2Ptr;
   rqptr = s2ptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                 "Http2NetIoWrite() !&A !&X !UL",
                 AstFunction, DataPtr, DataLength);

   if (!AstFunction)
   {
      /* blocking write */
      if (ioptr->VmsStatus)
      {
         ioptr->WriteIOsb.Count = 0;
         ioptr->WriteIOsb.Status = ioptr->VmsStatus;
         return (ioptr->WriteIOsb.Status);
      }
      if (rqptr->Http2Stream.State == HTTP2_STATE_CLOSED ||
          rqptr->Http2Stream.State == HTTP2_STATE_CLOSED_LOC)
      {
         ioptr->WriteIOsb.Count = 0;
         ioptr->WriteIOsb.Status = SS$_VCCLOSED;
         return (ioptr->WriteIOsb.Status);
      }
      ioptr->WriteIOsb.Count = DataLength;
      ioptr->WriteIOsb.Status = SS$_NORMAL;
   }

   if (rqptr->rqPathSet.Http2WriteQueue)
      queue = rqptr->rqPathSet.Http2WriteQueue;
   else
      queue = HTTP2_WRITE_QUEUE_NORMAL;

   if (!DataPtr)
   {
      DataPtr = "";
      DataLength = 0;
   }

   /* check for the appropriate incantation */
   vmptr = (VM_STRUCT*)((uchar*)DataPtr - sizeof(VM_STRUCT));
   magic = false;
   switch (vmptr->magic)
   {
      case VM_MAGIC_CACHE :
      case VM_MAGIC_DECC :
      case VM_MAGIC_EXPAT :
      case VM_MAGIC_GENERAL :
      case VM_MAGIC_HTTP2 :
      case VM_MAGIC_OPENSSL :
      case VM_MAGIC_PERMCACHE :
      case VM_MAGIC_REQUEST : magic = true;
   }

   for (;;)
   {
      /******************/
      /* write frame(s) */
      /******************/

      /* loop for where a write potentially exceeds the max frame size */
#if WATCH_MOD
      if (WATCHING (rqptr, WATCH_HTTP2))
         WatchThis (WATCHITM(rqptr), WATCH_HTTP2, "MAGIC !&B !UL/!UL",
                    magic, DataLength, h2ptr->ClientMaxFrameSize);
#endif

      if (magic)
      {
         /* can use the in-built header of WASD allocated memory */
         w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT));
         w2ptr->HeaderPtr = hsptr = vmptr->h2header;
         w2ptr->DataPtr = DataPtr;
         if (DataLength <= h2ptr->ClientMaxFrameSize)
         {
            w2ptr->DataLength = DataLength;
            /* final or only write delivers any AST */
            w2ptr->AstFunction = AstFunction;
            w2ptr->AstParam = AstParam;
         }
         else
            w2ptr->DataLength = h2ptr->ClientMaxFrameSize;
      }
      else
      {
         /* buffer partial or "blocking" or non-allocated (magic) write */
         if (DataLength <= h2ptr->ClientMaxFrameSize)
            w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) +
                                       DataLength);
         else
            w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) +
                                       h2ptr->ClientMaxFrameSize);
         w2ptr->HeaderPtr = hsptr = w2ptr->header;
         w2ptr->DataPtr = w2ptr->payload;
         if (DataLength <= h2ptr->ClientMaxFrameSize)
         {
            w2ptr->DataLength = DataLength;
            /* final or only write delivers any AST */
            w2ptr->AstFunction = AstFunction;
            w2ptr->AstParam = AstParam;
         }
         else
            w2ptr->DataLength = h2ptr->ClientMaxFrameSize;
         memcpy (w2ptr->DataPtr, DataPtr, w2ptr->DataLength);
      }

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->RequestPtr = rqptr;
      w2ptr->WriteQueue = queue;

      HTTP2_PLACE_24 (hsptr->length, w2ptr->DataLength)
      HTTP2_PLACE_8  (hsptr->type, HTTP2_FRAME_DATA);
      HTTP2_PLACE_8  (hsptr->flags, 0);
      HTTP2_PLACE_32 (hsptr->ident, rqptr->Http2Stream.Ident);

      Http2NetQueueWrite (h2ptr, w2ptr);

      /* on the only or final write then that's it */
      if (DataLength <= h2ptr->ClientMaxFrameSize) return (SS$_NORMAL);

      /* if intermediate write then continue to the next */
      (uchar*)DataPtr += w2ptr->DataLength;
      DataLength -= w2ptr->DataLength;
      /* any magic cannot be used with subsequent writes */
      magic = false;
   }
}

/*****************************************************************************/
/*
HTTP/2 network writes are serialised using FIFO lists.  In this current
implementation of WASD HTTP/2 there is limited prioritisation of streams. 
There are four queues/lists.  Connection management writes are to queue zero
(index 0) and have the highest priority, then the high priority (index 1),
normal priority (index 2), and then the lowest (index 3).  The HIGH, NORMAL
(default) and LOW can be set via path mappings.  All writes from a queue are
FIFO and asynchronous (empty AST function notwithstanding).  Request data
writes are subject to RFC 7540 flow control.
*/ 

void Http2NetQueueWrite
(
HTTP2_STRUCT *h2ptr,
HTTP2_WRITE_STRUCT *w2ptr
)
{
   int  cnt, idx, type;
   REQUEST_STRUCT  *rqptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   HTTP2_WRITE_STRUCT  *w22ptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2NetQueueWrite() h2ptr:!&X w2ptr:!&X write-in-prog:!&B",
                 h2ptr, w2ptr, NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr));

   if (w2ptr)
   {
      /*************/
      /* new write */
      /*************/

      /* must always have an AST address to indicate NETIO is in use */
      if (!w2ptr->AstFunction)
         w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST;

      /* add to the HTTP/2 connection's write list */
      ListAddTail (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr,
                   LIST_ENTRY_TYPE_WRITE2);
      if (w2ptr->RequestPtr) w2ptr->RequestPtr->Http2Stream.QueuedWriteCount++;

      cnt = 0;
      for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++)
         cnt += LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx]);
      if (cnt > h2ptr->QueuedWritePeak) h2ptr->QueuedWritePeak = cnt;
   }

   /* if there's a write already in progress */
   if (NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)) return;

   /***************/
   /* check queue */
   /***************/

   /* look through the queues from highest to lowest priority */
   for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++)
   {
      /* if nothing in this queue then look at the next */
      if (!LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx])) continue;

      for (w2ptr = LIST_GET_HEAD (&h2ptr->QueuedWriteList[idx]);
           w2ptr != NULL;
           w2ptr = LIST_GET_NEXT(w2ptr))
      {
         /* double check in case of some unexpected timing issue! */
         if (w2ptr->WriteInProgress) return;

         /* if not a DATA frame and therefore not subject to flow control */
         HTTP2_PEEK_8 (w2ptr->type, type);
         if (type != HTTP2_FRAME_DATA) break;

         /* if not associated with a request */
         if (!(rqptr = w2ptr->RequestPtr)) break;

         s2ptr = &rqptr->Http2Stream;

         /****************/
         /* flow control */
         /****************/

         if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                       "FLOW length:!UL h2size:!UL s2size:!UL",
                       w2ptr->DataLength, h2ptr->WriteWindowSize,
                       s2ptr->WriteWindowSize); 

         if ((int)s2ptr->WriteWindowSize - (int)w2ptr->DataLength < 0 ||
             (int)h2ptr->WriteWindowSize - (int)w2ptr->DataLength < 0)
         {
            if (!s2ptr->FlowControl)
            {
               Http2FlowControlCurrent++;
               h2ptr->FlowControlCurrent++;
               h2ptr->FlowControlTally++;
               s2ptr->FlowControl = true;
               if (WATCHING (rqptr, WATCH_HTTP2))
                  WatchThis (WATCHITM(rqptr), WATCH_HTTP2,
                             "FLOW control ON (!UL/!UL/!UL)",
                             w2ptr->DataLength, s2ptr->WriteWindowSize,
                             h2ptr->WriteWindowSize);
            }
            /* continue to look for a write that can be performed */
            continue;
         }

         h2ptr->FlowFrameTally++;

         if (s2ptr->FlowControl)
         {
            if (Http2FlowControlCurrent) Http2FlowControlCurrent--;
            if (h2ptr->FlowControlCurrent) h2ptr->FlowControlCurrent--;
            s2ptr->FlowControl = false;
            if (WATCHING (rqptr, WATCH_HTTP2))
               WatchThis (WATCHITM(rqptr), WATCH_HTTP2,
                          "FLOW control OFF (!UL/!UL/!UL)",
                          w2ptr->DataLength, s2ptr->WriteWindowSize,
                          h2ptr->WriteWindowSize);
         }
         /* adjust the flow control window in use */
         s2ptr->WriteWindowSize -= w2ptr->DataLength;
         h2ptr->WriteWindowSize -= w2ptr->DataLength;

         /* break from the entry loop */
         break;
      }

      /* break from the queue loop to perform the write */
      if (w2ptr) break;
   }

   /* if nothing in any queue */
   if (idx > HTTP2_WRITE_QUEUE_LOW)
   {
      if (!h2ptr->NetIoPtr->Channel)
         Http2CloseConnection (h2ptr);
      else
      if (h2ptr->NetIoPtr->VmsStatus)
         Http2CloseConnection (h2ptr);
      else
      if (h2ptr->GoAwayLastStreamIdent)
         Http2CloseConnection (h2ptr);
      return;
   }

   /*********/
   /* write */
   /*********/

   w2ptr->WriteInProgress = true;

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
   {
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "!&X queue:!UL",
                 w2ptr, w2ptr->WriteQueue);
      Http2WatchFrame (h2ptr, w2ptr->HeaderPtr,
                       w2ptr->DataPtr, w2ptr->DataLength,
                       w2ptr->HeaderPtr != w2ptr->header);
   }
   else
   if (WATCHING (h2ptr, WATCH_HTTP2))
      Http2WatchFrame (h2ptr, w2ptr->HeaderPtr, w2ptr->DataPtr, 0,
                       w2ptr->HeaderPtr != w2ptr->header);

   /* if something is amiss with the HTTP/2 connection */
   if (h2ptr->NetIoPtr->VmsStatus)
   {
      NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst,
                        w2ptr, h2ptr->NetIoPtr->VmsStatus, 0);
      return;
   }

   if (rqptr = w2ptr->RequestPtr)
   {
      /* half-closed remote can still be sent frames (RFC7540 5.1) */
      if (rqptr->NetIoPtr->VmsStatus ||
          rqptr->Http2Stream.State == HTTP2_STATE_CLOSED ||
          rqptr->Http2Stream.State == HTTP2_STATE_CLOSED_LOC)
      {
         /* ss$_normal means no issue with the underlying HTTP/2 write */
         NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst,
                           w2ptr, SS$_NORMAL, 0);
         return;
      }
      h2ptr->FrameRequestCountTx++;
      h2ptr->FrameRequestTallyTx++;
   }

   h2ptr->FrameCountTx++;
   h2ptr->FrameTallyTx++;

   if ((uchar*)w2ptr->HeaderPtr + HTTP2_FRAME_HEADER_SIZE == w2ptr->DataPtr)
      /* header immediately precedes the data - write single datagram */
      NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr,
                  w2ptr->HeaderPtr, HTTP2_FRAME_HEADER_SIZE +
                                    w2ptr->DataLength);
   else
      /* write header then data independently (two datagrams) */
      NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteHeaderAst, w2ptr,
                  w2ptr->HeaderPtr, HTTP2_FRAME_HEADER_SIZE);
}

/*****************************************************************************/
/*
Cancel (all) write(s) associated with the request.
Done here rather than in Http2Request.C because it deals with queues.
Return the count of canceled writes (with WASD should always be 0 or 1).
*/ 

int Http2NetCancelWrite (REQUEST_STRUCT *rqptr)

{
   int  cnt, idx;
   HTTP2_STRUCT  *h2ptr;
   NETIO_STRUCT  *ioptr;
   HTTP2_WRITE_STRUCT  *next2, *w2ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetCancelWrite()");

   h2ptr = rqptr->Http2Stream.Http2Ptr;
   ioptr = h2ptr->NetIoPtr;
   cnt = 0;

   /* look through the queues */
   for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++)
   {
      /* if nothing in this queue then look at the next */
      if (!LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx])) continue;

      for (w2ptr = LIST_GET_HEAD (&h2ptr->QueuedWriteList[idx]);
           w2ptr != NULL;
           w2ptr = next2)
      {
         next2 = LIST_GET_NEXT(w2ptr);
         if (rqptr != w2ptr->RequestPtr) continue;
         if (w2ptr->WriteInProgress) continue;
         cnt++;

         /* remove from the HTTP/2 connection's write list */
         ListRemove (&h2ptr->QueuedWriteList[idx], w2ptr);
         if (rqptr->Http2Stream.QueuedWriteCount)
            rqptr->Http2Stream.QueuedWriteCount--;

         /* if NOT special cases without "real" AST delivery */
         if (w2ptr->AstFunction != Http2Net_WRITE_NO_AST &&
             w2ptr->AstFunction != Http2Net_REQUEST_END_AST)
         {
            /* deliver an AST so adjust the underlying NETIO */
            ioptr = rqptr->NetIoPtr;
            ioptr->WriteIOsb.Count = 0;
            ioptr->WriteIOsb.Status = SS$_CANCEL;
            SysDclAst (NetIoWriteAst, ioptr);
         }

         /* free the allocated write structure */
         VmFreeFrom2Heap (h2ptr, w2ptr, FI_LI);
      }
   }

   return (cnt);
}

/*****************************************************************************/
/*
Post-process the HTTP/2 (9 octet) header write.  If OK then write the payload.
*/ 

void Http2NetWriteHeaderAst (HTTP2_WRITE_STRUCT *w2ptr)

{
   HTTP2_STRUCT  *h2ptr;
   NETIO_STRUCT  *io2ptr;

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

   h2ptr = w2ptr->Http2Ptr;
   io2ptr = h2ptr->NetIoPtr;

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2NetWriteHeaderAst() !&F !&X !&S !UL",
                 Http2NetWriteHeaderAst,
                 w2ptr, io2ptr->WriteStatus, io2ptr->WriteCount);

   /* if explicitly set (error) status */
   if (io2ptr->VmsStatus) io2ptr->WriteStatus = io2ptr->VmsStatus;

   if (VMSnok (io2ptr->WriteStatus))
   {
      /* the connection has failed (or at least the last write) */
      SysDclAst (Http2NetWriteDataAst, w2ptr);
      return;
   }

   ADD_LONG_QUAD (io2ptr->WriteCount, h2ptr->BytesRawTx);
   ADD_LONG_QUAD (io2ptr->WriteCount, h2ptr->BytesRawTallyTx);

   NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr,
               w2ptr->DataPtr, w2ptr->DataLength);
}

/*****************************************************************************/
/*
Post-process the payload write (or combined header and payload write).
Delivers (any) AST.  Then queue the next write.
*/ 

void Http2NetWriteDataAst (HTTP2_WRITE_STRUCT *w2ptr)

{
   uint  WriteLength;
   void  *AstParam;
   HTTP2_STRUCT  *h2ptr;
   NETIO_STRUCT  *ioptr, *io2ptr;
   REQUEST_STRUCT  *rqptr;
   VOID_AST  AstFunction;

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

   h2ptr = w2ptr->Http2Ptr;
   io2ptr = h2ptr->NetIoPtr;

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
"Http2NetWriteDataAst() !&F h2ptr:!&X w2ptr:!&X !&S !UL", Http2NetWriteDataAst,
                 h2ptr, w2ptr, io2ptr->WriteStatus, io2ptr->WriteCount);

   /* if explicitly set (error) status */
   if (io2ptr->VmsStatus) io2ptr->WriteStatus = io2ptr->VmsStatus;

   ADD_LONG_QUAD (io2ptr->WriteCount, h2ptr->BytesRawTx);
   ADD_LONG_QUAD (io2ptr->WriteCount, h2ptr->BytesRawTallyTx);

   /* remove from the HTTP/2 connection's write list */
   ListRemove (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr);
   w2ptr->WriteInProgress = false;

   if (rqptr = w2ptr->RequestPtr)
   {
      /********************/
      /* request response */
      /********************/

      if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                    "rqptr:!&X ast:!&A", rqptr, w2ptr->AstFunction);

     if (rqptr->Http2Stream.QueuedWriteCount)
         rqptr->Http2Stream.QueuedWriteCount--;

      if (w2ptr->AstFunction == Http2Net_REQUEST_END_AST)
      {
         /* special case that should not be processed by NetIoWriteAst() */
         SysDclAst (Http2RequestEnd2, rqptr);
      }
      else
      if (w2ptr->AstFunction != Http2Net_WRITE_NO_AST)
      {
         /* deliver an AST so adjust the underlying NETIO */
         ioptr = rqptr->NetIoPtr;
         if (ioptr->VmsStatus)
         {
            /* use explicit request I/O status */
            ioptr->WriteIOsb.Count = 0;
            ioptr->WriteIOsb.Status = ioptr->VmsStatus;
         }
         else
         {
            /* use actual network I/O status */
            ioptr->WriteIOsb.Count = io2ptr->WriteCount;
            ioptr->WriteIOsb.Status = io2ptr->WriteStatus;
         }
         /* post-process the request I/O */
         ioptr->WriteCount = ioptr->WriteLength = 0;
         SysDclAst (NetIoWriteAst, ioptr);
      }
   }
   else
   {
      /**************************/
      /* HTTP/2 control message */
      /**************************/

      if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "AstFunction !&A 0x!8XL",
                    w2ptr->AstFunction, w2ptr->AstParam);

      AstParam = w2ptr->AstParam;
      AstFunction = w2ptr->AstFunction;
      if (AstFunction != Http2Net_WRITE_NO_AST) AstFunction (AstParam);
   }

   /* free the allocated write structure */
   VmFreeFrom2Heap (h2ptr, w2ptr, FI_LI);

   /********/
   /* next */
   /********/

   Http2NetQueueWrite (h2ptr, NULL);
}

/****************************************************************************/
/*
The presence of an AST function is used to indicate a NETIO in progress.  For
I/O environments that cannot block (viz. HTTP/2) this as an AST target can be
detected during post processing and not actually called.  Could have used a
magic number of some sort but this seemed cleaner.      
*/

void Http2Net_WRITE_NO_AST (void *param)

{
   if (WATCH_MODULE (WATCH_MOD_HTTP2))
      WatchThis (WATCHALL, WATCH_MOD_HTTP2,
                 "Http2Net_WRITE_NO_AST() !&F", Http2Net_WRITE_NO_AST);

   ErrorNoticed (NULL, SS$_BUGCHECK, "Http2Net_WRITE_NO_AST()", FI_LI);
}

/*****************************************************************************/
/*
Write an end-of-stream frame (at end of output) to the client.
This is the final element of the request's HTTP/2 data stream.
*/

void Http2NetWriteEnd (REQUEST_STRUCT *rqptr)

{
   int  queue;                      
   HTTP2_STRUCT  *h2ptr;
   HTTP2_WRITE_STRUCT  *w2ptr, *w22ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetWriteEnd()");

   h2ptr = rqptr->Http2Stream.Http2Ptr;

   if (rqptr->rqPathSet.Http2WriteQueue)
      queue = rqptr->rqPathSet.Http2WriteQueue;
   else
      queue = HTTP2_WRITE_QUEUE_NORMAL;

   w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT));

   HTTP2_PLACE_24 (w2ptr->length, 0);
   HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_DATA);
   HTTP2_PLACE_8  (w2ptr->flags, HTTP2_FLAG_DATA_END_STR);
   HTTP2_PLACE_32 (w2ptr->ident, rqptr->Http2Stream.Ident);

   w2ptr->Http2Ptr = h2ptr;
   w2ptr->HeaderPtr = w2ptr->header;
   w2ptr->DataPtr = w2ptr->payload;
   w2ptr->WriteQueue = queue;

   /* this is a request datagram */
   w2ptr->RequestPtr = rqptr;

   /* detected by Htt2NetWriteDataAst() and specially processed */
   w2ptr->AstFunction = w2ptr->AstParam = Http2Net_REQUEST_END_AST;

   Http2NetQueueWrite (h2ptr, w2ptr);
}

/*****************************************************************************/
/*
Detected by Htt2NetWriteDataAst() and specially processed.  Could have used a
magic number of some sort but this seemed cleaner.
*/ 

void Http2Net_REQUEST_END_AST (void *param)

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

   if (WATCH_MODULE (WATCH_MOD_HTTP2))
      WatchThis (WATCHALL, WATCH_MOD_HTTP2,
                 "Http2Net_REQUEST_END_AST() !&F",
                 Http2Net_REQUEST_END_AST);

   ErrorNoticed (NULL, SS$_BUGCHECK, "Http2Net_REQUEST_END_AST()", FI_LI);
}

/*****************************************************************************/
/*
Disconnect network connections.  If no criteria supplied then disconnects all
connections without requests in-progress.  If ConnectNumber then disconnect
that number, or if -1 then all connections.  Provides comparable HTTP/2
functionality to NetControl().  Note that this is not an elegant "goaway".
The network channel is just cancelled running down any I/O.
*/ 

int Http2NetControl (int ConnectNumber)

{
   int  PurgeCount;
   HTTP2_STRUCT  *h2ptr, *next2;

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

   if (WATCH_MODULE(WATCH_MOD_HTTP2))
      WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2NetControl()");

   PurgeCount = 0;

   for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = next2)
   {
      /* get (any) next in list in case the current connection is closed */
      next2 = LIST_GET_NEXT(h2ptr);

      if (ConnectNumber == -1)
      {
         /* purge all */
         Http2CloseConnection (h2ptr);
         PurgeCount++;
      }
      else
      if (ConnectNumber == h2ptr->ConnectNumber)
      {
         /* purge matching */
         Http2CloseConnection (h2ptr);
         PurgeCount++;
      }
      else
      if (LIST_IS_EMPTY (&h2ptr->StreamList))
      {
         /* purge idle */
         Http2CloseConnection (h2ptr);
         PurgeCount++;
      }
   }

   if (WATCH_MODULE(WATCH_MOD_HTTP2))
      WatchThis (WATCHALL, WATCH_MOD_HTTP2, "purged: !UL", PurgeCount);

   return (PurgeCount);
}

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

