/*****************************************************************************/
/*
                               HTTP2request.c

Conjure up a request structure, populate the dictionary with its request
headers, supply it with a request body if required, execute the request,
populate the (HTTP/2) response header with response header fields and then
run-down the request.  There are obviously life-cycle parallels with its
companion HTTP/1.1 processing.


VERSION HISTORY
---------------
17-AUG-2020  MGD  Http2RequestData() reduce memory consumption
09-AUG-2020  MGD  bugfix; Http2RequestCancel() cancel and abort
29-JUN-2020  MGD  bugfix; Http2RequestData() flow control
06-FEB-2020  MGD  Http2RequestData() reworked from list to simple buffer
31-JAN-2020  MGD  fiddle with the code (perhaps optimisations, even bugfixes)
18-JAN-2019  MGD  status code 418 (teapot) forces connection drop
                  to bugfix; Http2RequestEnd() copy tally rx/tx to request
20-APR-2018  MGD  bugfix; Http2RequestDictHeader() keep accounting abreast
12-MAR-2018  MGD  refactor Http2RequestCancel() into Http2RequestCancelRead()
                    and Http2RequestCancelWrite()
23-DEC-2017  MGD  bugfix; window update and flow control management
15-MAR-2017  MGD  bugfix; Http2RequestEnd() end-of-request (control) frame
                    independent of request itself
06-AUG-2016  MGD  Http2RequestBegin() ensure stream ident not reused
                  bugfix; Http2RequestData() always deliver via NetIoReadAst()
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"
#include "hpack.h"

#define WASD_MODULE "HTTP2REQUEST"

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

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

extern BOOL  HttpdTicking,
             NetCurrentProcessing;

extern int  ConnectCountTotal,
            HttpdTickSecond,
            NetReadBufferSize;

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[];

#define acptr AccountingPtr
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern LIST_HEAD  RequestList;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
A HEADERS frame has been received from the client and HpackProcess() calls
this function to initiate request processing.  There are obvious parallels
between the processing in this and Http2RequestBegin2(), and in RequestBegin().
*/

REQUEST_STRUCT* Http2RequestBegin
(
HTTP2_STRUCT *h2ptr,
uint ident
)
{
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2RequestBegin()");

   if (h2ptr->NetIoPtr->VmsStatus)
   {
      /* refuse streams on a connection with an explicit status */
      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 status !&S",
                    h2ptr->NetIoPtr->VmsStatus);
      return (NULL);
   }

   if (h2ptr->GoAwayLastStreamIdent)
   {
      /* refuse streams on a connection told to goaway! */
      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 goaway ident !UL",
                    h2ptr->GoAwayLastStreamIdent);
      return (NULL);
   }

   if (LIST_GET_COUNT (&h2ptr->StreamList) > h2ptr->ServerMaxConcStreams)
   {
      /* refuse streams exceeding the connection maximum */
      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 stream exceeds !UL",
                    h2ptr->ServerMaxConcStreams);
      return (NULL);
   }

   if (ident <= h2ptr->LastStreamIdent)
   {
      /* refuse a stream ident that is less than that already established */
      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                    "HTTP/2 stream !UL less than !UL",
                    ident, h2ptr->LastStreamIdent);
      return (NULL);
   }

   /* note the most recent stream ident */
   h2ptr->LastStreamIdent = ident;

   /* create a request structure */
   rqptr = VmGetRequest (++ConnectCountTotal);

   /* add entry to the top of the request list */
   ListAddHead (&RequestList, rqptr, LIST_ENTRY_TYPE_REQUEST);

   /* timestamp the request */
   sys$gettim (&rqptr->rqTime.BeginTime64);
   sys$numtim (&rqptr->rqTime.BeginNumTime, &rqptr->rqTime.BeginTime64);
   HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64);

   Http2RequestBegin2 (rqptr, h2ptr, ident);

   return (rqptr);
}

/*****************************************************************************/
/*
Take the the supplied request and attach it to the supplied HTTP/2 stream.
Called from Http2RequestBegin() and Http2SwitchResponse().
*/

void Http2RequestBegin2
(
REQUEST_STRUCT  *rqptr,
HTTP2_STRUCT *h2ptr,
uint ident
)
{
   HTTP2_STREAM_STRUCT  *s2ptr;
   NETIO_STRUCT  *ioptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2RequestBegin2()");
 
   rqptr->NetworkHttp = 2;

   rqptr->RequestState = REQUEST_STATE_HEADER;

#if SESOLA_MEMORY
   SesolaMemoryControl (-1, FI_LI);
#endif SESOLA_MEMORY

   /* create network I/O structure (see NetIoBegin()) */
   ioptr = VmGet (sizeof(NETIO_STRUCT));

   /* populate request structure */
   rqptr->NetIoPtr = ioptr;
   rqptr->ClientPtr = h2ptr->ClientPtr;
   rqptr->ServicePtr = h2ptr->ServicePtr;
   rqptr->rqDictPtr = DictCreate (rqptr, -1);

   /* add request (stream) to HTTP/2 list and populate stream structure */
   s2ptr = &rqptr->Http2Stream;
   ListAddHead (&h2ptr->StreamList, s2ptr, LIST_ENTRY_TYPE_REQUEST);
   s2ptr->RequestPtr = rqptr;
   s2ptr->Http2Ptr = h2ptr;
   s2ptr->Ident = ident;
   /* WASD streams start open, never idle */
   s2ptr->State = HTTP2_STATE_OPEN;

   /* populate network I/O structure (see NetAccept()) */
   ioptr->ClientPtr = h2ptr->ClientPtr;
   ioptr->ServicePtr = h2ptr->ServicePtr;
   ioptr->Http2StreamPtr = s2ptr;

   if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
      if (Watch.FilterSet)
      {
         WatchFilterHttpProtocol (rqptr);
         WatchFilterClientService (rqptr);
      }
      else
         WatchSetWatch (rqptr, WATCH_NEW_ITEM);
   else
   if (h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG)
      WatchSetWatch (rqptr, WATCH_NEW_ITEM);

   /* set the initial stream flow-control window size */
   s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize;
   s2ptr->WriteWindowSize = h2ptr->WriteWindowSize;

   /* inform the client of this */
   Http2WindowUpdate (h2ptr, s2ptr->Ident, s2ptr->ReadWindowSize, NULL, 0);

   /* if WATCHing HTTP/2 get a ping from the client to measure RTT */
   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      Http2Ping (h2ptr, 0, NULL, 0);

   h2ptr->RequestCount++;
   h2ptr->RequestCurrent++;
   if (h2ptr->RequestCurrent > h2ptr->RequestPeak)
      h2ptr->RequestPeak = h2ptr->RequestCurrent;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   acptr->Http2RequestCount++;
   if (h2ptr->RequestPeak > acptr->Http2RequestPeak)
      acptr->Http2RequestPeak = h2ptr->RequestPeak;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (Config.cfTimeout.Http2Idle)
      h2ptr->IdleSecond = HttpdTickSecond + Config.cfTimeout.Http2Idle;
   else
      h2ptr->IdleSecond = HttpdTickSecond + HTTP2_TIMEOUT_IDLE_SECONDS;

   /* if it's not already running kick-off the HTTPd ticker */
   if (!HttpdTicking) HttpdTick (0);

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "streams: !UL",
                 LIST_GET_COUNT (&h2ptr->StreamList));
}

/*****************************************************************************/
/*
After the header has been placed in the dictionary continue processing the
request.  Parallels processing performed in RequestParseHttp().

When moving from WATCH report generation back to the WATCH selection request
processing needs to be delayed to allow the WATCHing request to receive the
RST_STREAM frame and shut down WATCHing.  Otherwise the HTTP/2 WATCH rabbit
hole interferes with the new request because the previous request often hasn't
yet concluded WATCHing due to frame multiplex ordering and/or transmission
latency.  This small delay is obvious to the user.
*/

void Http2RequestProcess (REQUEST_STRUCT *rqptr)

{
   static ulong  OneSecondDelta [2] = { -10000000, -1 };

   int  status;
   ushort  slen;
   char  string [32];
   DICT_ENTRY_STRUCT  *denptr;

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

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

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateProcessing (rqptr, +1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT)
      WatchFilterRequestHeader (rqptr);

   if (rqptr->Http2Stream.Http2Ptr->PingMicroSeconds)
   {
      FaoToBuffer (string, sizeof(string), &slen, "!UL.!3ZL", 
                   rqptr->Http2Stream.Http2Ptr->PingMicroSeconds / 1000,
                   rqptr->Http2Stream.Http2Ptr->PingMicroSeconds % 1000);
      DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                  "http2_ping", 10, string, slen);
   }

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                 "HTTP/2 begin !UL with !AZ,!UL",
                 rqptr->Http2Stream.Ident,
                 rqptr->ClientPtr->Lookup.HostName,
                 rqptr->ClientPtr->IpPort); 

   if (WATCHING (rqptr, WATCH_REQUEST))
      if (!rqptr->rqHeader.WatchNewRequest)
      {
         rqptr->rqHeader.WatchNewRequest = true;
         WatchDataFormatted ("|!#*+\n", 38 + Watch.ItemDigits);
      }

   if (WATCHING (rqptr, WATCH_REQUEST_HEADER))
      if (denptr = RequestDictHeader (rqptr))
      {
         WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER,
                    "HEADER !UL bytes", DICT_GET_VALUE_LEN(denptr));
         WatchData (DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr));
      }

   if (Watch.RequestPtr &&
       Watch.RequestPtr->Http2Stream.Http2Ptr &&
       Watch.RequestPtr->Http2Stream.Http2Ptr == rqptr->Http2Stream.Http2Ptr)
   {
      /* delay request processing when WATCHing on same HTTP/2 connection */
      status = sys$setimr (0, &OneSecondDelta,
                           RequestParseDictionary, rqptr, 0);
      if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);
   }
   else
      RequestParseDictionary (rqptr);
}

/*****************************************************************************/
/*
Request body PUTed or POSTed by the client.

A simple, single buffer space is used, with additional data appended to any
existing and with partial reads causing the content to be moved down to fill
what's been read.  Client data is not buffered if a read I/O with sufficient
space is already queued.

LOTS of memory copying :-{

The client supplying request body data and the server consuming that data are
completely asynchronous.  As a result client data can arrive before a request
has attempted to read it (and need to be stored) or after the server initiated
a read.  Looking at it the other way, the server can attempt to read data that
hasn't yet been sent by the client (and need to wait for it), or attempt to
read data that has already been received from the client (and been stored). 
In addition the request needs to be notified when the data is exhausted
(ENDOFILE).
*/

int Http2RequestData
(
HTTP2_STRUCT *h2ptr,
uint flags,
uint ident,
uchar *BufferPtr,
uint BufferLength
)
{
   uint  drlength, drsize, padlen, status,
         DataLength;
   uchar  *bptr, *drptr;
   HTTP2_READ_STRUCT  *r2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   NETIO_STRUCT  *ioptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (WATCH_MODULE(WATCH_MOD_HTTP2))
      WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2RequestData() !AZ !SL !UL",
                 BufferPtr ? "DATA" : "REQUEST", BufferPtr, BufferLength);

   /* locate the request corresponding to the stream */
   for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList);
        s2ptr != NULL;
        s2ptr = LIST_GET_NEXT(s2ptr))
   {
      rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr;
      if (rqeptr->Http2Stream.Ident == ident) break;
   }
   if (s2ptr == NULL)
   {
      if (BufferPtr)
      {
         /* request shutdown while data in transit? */
         return (SS$_CANCEL);
      }
      /* must be a stream (request) with that ident */
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   /* ensure it looks reset (to start with) */
   ioptr = rqeptr->NetIoPtr;
   ioptr->ReadIOsb.Count = 0;
   ioptr->ReadIOsb.Status = 0;

   if (BufferPtr == (void*)-1)
   {
      /**************/
      /* cancel I/O */
      /**************/

      if (WATCH_MODULE(WATCH_MOD_HTTP2))
         WatchThis (WATCHALL, WATCH_MOD_HTTP2, "!&A !&S",
                    ioptr->ReadAstFunction, ioptr->VmsStatus);

      /* remove any previously received and buffered data */
      if (s2ptr->DataReadPtr)
      {
         /* REQUEST MEMORY! */
         VmFreeFromHeap (rqeptr, s2ptr->DataReadPtr, FI_LI);
         s2ptr->DataReadPtr = NULL;
         s2ptr->DataReadLength = s2ptr->DataReadSize = 0;
      }
      if (ioptr->ReadAstFunction)
      {
         /* request already waiting */
         if (ioptr->VmsStatus)
            ioptr->ReadIOsb.Status = ioptr->VmsStatus;
         else
            ioptr->ReadIOsb.Status = SS$_CANCEL;
         /* deliver via the NETIO AST delivery decoupler */
         NetIoReadAst (ioptr);
      }
      return (ioptr->ReadIOsb.Status);
   }

   /* data received buffer */
   drptr = s2ptr->DataReadPtr;
   drsize = s2ptr->DataReadSize;
   drlength = s2ptr->DataReadLength;

   if (bptr = BufferPtr)
   {
      /***************/
      /* client data */
      /***************/

      /* client data being made available */
      if (flags & HTTP2_FLAG_DATA_PADDED)
      {
         HTTP2_GET_8 (bptr, padlen);
         DataLength = BufferLength - padlen - 1;   
      }
      else
         DataLength = BufferLength;

      /* keep the request accounting representative */
      ADD_LONG_QUAD (1, ioptr->BlocksRawRx);
      ADD_LONG_QUAD (1, ioptr->BlocksTallyRx);
      ADD_LONG_QUAD (DataLength, ioptr->BytesRawRx);
      ADD_LONG_QUAD (DataLength, ioptr->BytesTallyRx);

      if (WATCH_MODULE(WATCH_MOD_HTTP2))
         WatchThis (WATCHALL, WATCH_MOD_HTTP2,
                    "length:!UL size:!UL eof:!&B request:!&B",
                    DataLength, ioptr->ReadSize,
                    flags & HTTP2_FLAG_DATA_END_STR,
                    ioptr->ReadAstFunction);

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

      /* stream */
      s2ptr->ReadWindowSize -= DataLength;

      /* HTTP/2 connection */
      h2ptr->ReadWindowSize -= DataLength;
      if (h2ptr->ReadWindowSize <= 0)
      {
         h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize *
                                 h2ptr->ServerMaxConcStreams;
         Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0);
      } 

      if (ioptr->ReadAstFunction)
      {
         /***************************/
         /* request already waiting */
         /***************************/

         if (WATCH_MODULE(WATCH_MOD_HTTP2))
            WatchThis (WATCHALL, WATCH_MOD_HTTP2, "length:!UL size:!UL",
                       DataLength, ioptr->ReadSize);

         if (!drlength)
         {
            /****************************/
            /* no data already buffered */
            /****************************/

            if (DataLength <= ioptr->ReadSize)
            {
               /***********************************/
               /* data can fit into the I/O space */
               /***********************************/

               ioptr->ReadIOsb.Status = SS$_NORMAL;
               ioptr->ReadIOsb.Count = DataLength;
               memcpy (ioptr->ReadPtr, bptr, DataLength);
               NetIoReadAst (ioptr);

               /* if window needs update */
               if (s2ptr->ReadWindowSize <= 0)
               {
                  s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize;
                  Http2WindowUpdate (h2ptr, s2ptr->Ident,
                                     s2ptr->ReadWindowSize, NULL, 0);
               }

               return (ioptr->ReadIOsb.Status);
            }
         }
      }

      if (DataLength)
      {
         /**************************/
         /* buffer REQUEST MEMORY! */
         /**************************/

         if (!drsize)
         {
            /* fresh buffer */
            drsize = h2ptr->ServerInitialWindowSize;
            drptr = VmGetHeap (rqeptr, drsize);
         }
         else
         if (drlength + DataLength > drsize)
         {
            /* insufficient space in current buffer */
            drsize += h2ptr->ServerInitialWindowSize;
            drptr = VmReallocHeap (rqeptr, drptr, drsize, FI_LI);
         }

         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                       "BUFFER !UL !UL/!UL", DataLength, drlength, drsize);

         memcpy (drptr + drlength, bptr, DataLength);
         drlength += DataLength;

         s2ptr->DataReadPtr = drptr;
         s2ptr->DataReadSize = drsize;
         s2ptr->DataReadLength = drlength;
      }

      if (DataLength)
      {
         /*******************/
         /* still possible? */
         /*******************/

         /* half-closed local can still receive frames (RFC7540 5.1) */
         if (s2ptr->State == HTTP2_STATE_CLOSED ||
             s2ptr->State == HTTP2_STATE_CLOSED_REM) return (0);

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

         /* stream */
         s2ptr->ReadWindowSize -= DataLength;

         /* HTTP/2 connection */
         h2ptr->ReadWindowSize -= DataLength;
         if (h2ptr->ReadWindowSize <= 0)
         {
            h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize *
                                    h2ptr->ServerMaxConcStreams;
            Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0);
         } 
      }

      if (flags & HTTP2_FLAG_DATA_END_STR)
         s2ptr->State = HTTP2_STATE_CLOSED_REM;

      /* if not a request already waiting */
      if (!ioptr->ReadAstFunction) return (ioptr->ReadIOsb.Status);
      /* otherwise drop through to partially fill the I/O */
   }

   /***********/
   /* request */
   /***********/

   /* request looking for data (called from Http2NetRead()) */
   if (drlength)
   {
      /* there is buffered data */
      ioptr->ReadIOsb.Status = SS$_NORMAL;
      if (drlength <= ioptr->ReadSize)
         ioptr->ReadIOsb.Count = drlength;
      else
         ioptr->ReadIOsb.Count = ioptr->ReadSize;
      memcpy (ioptr->ReadPtr, drptr, ioptr->ReadIOsb.Count);
      s2ptr->DataReadLength -= ioptr->ReadIOsb.Count;
      if (s2ptr->DataReadLength)
      {
         /* inefficient but straightforward - shuffle it down */
         memmove (drptr,
                  drptr + ioptr->ReadIOsb.Count,
                  drlength - ioptr->ReadIOsb.Count); 
      }

      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "UNBUFFER !UL !UL/!UL",
                    ioptr->ReadIOsb.Count, drlength, drsize);

      /* if all the data has been read and the window needs update */
      if (!s2ptr->DataReadLength && s2ptr->ReadWindowSize <= 0)
      {
         s2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize;
         Http2WindowUpdate (h2ptr, s2ptr->Ident, s2ptr->ReadWindowSize, NULL, 0);
      }

      NetIoReadAst (ioptr);
      return (ioptr->ReadIOsb.Status);
   }

   if (h2ptr->NetIoPtr->VmsStatus)
   {
      /* HTTP/2 connection (error) status */
      ioptr->ReadIOsb.Status = h2ptr->NetIoPtr->VmsStatus;
      NetIoReadAst (ioptr);
   }
   else
   if (ioptr->VmsStatus)
   {
      /* commonly SS$_CANCEL */
      ioptr->ReadIOsb.Status = ioptr->VmsStatus;
      NetIoReadAst (ioptr);
   }
   else
   if (s2ptr->State == HTTP2_STATE_CLOSED ||
       s2ptr->State == HTTP2_STATE_CLOSED_LOC ||
       s2ptr->State == HTTP2_STATE_CLOSED_REM)
   {
      ioptr->ReadIOsb.Status = SS$_ENDOFFILE;
      NetIoReadAst (ioptr);
   }

   return (ioptr->ReadIOsb.Status);
}

/*****************************************************************************/
/*
Cancel any outstanding request I/O (read and write).
Then if recalled ensure an aborted request is rundown and ended.
*/

void Http2RequestCancel (REQUEST_STRUCT *rqptr)

{
   HTTP2_STREAM_STRUCT  *s2ptr;

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

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

   s2ptr = &rqptr->Http2Stream;

   if (s2ptr->State != HTTP2_STATE_CLOSED &&
       rqptr->NetIoPtr->VmsStatus != SS$_CANCEL)
   {
      /**********/
      /* cancel */
      /**********/

      s2ptr->State = HTTP2_STATE_CLOSED;
      rqptr->NetIoPtr->VmsStatus = SS$_CANCEL;

      Http2NetCancelWrite (rqptr);

      Http2RequestData (s2ptr->Http2Ptr, 0, s2ptr->Ident, (void*)-1, 0);

      Http2RequestResetStream (rqptr);
   }
   else
   if (rqptr->NetIoPtr->VmsStatus == SS$_CANCEL)
   {
      /*********/
      /* abort */
      /*********/

      if (rqptr->RequestState >= REQUEST_STATE_ABORT)
         if (!NETIO_IN_PROGRESS (rqptr->NetIoPtr))
            if (RequestRundown (rqptr))
            {
               /* only want to do this the once! */
               rqptr->NetIoPtr->VmsStatus = SS$_CANCEL | STS$M_INHIB_MSG;
               SysDclAst (RequestEnd2, rqptr);
            }
   }
}

/*****************************************************************************/
/*
Http2ResponsetDictHeader() ... yes, I know.
Response HEADERS frame with table based compression.
Called from NetWrite() this function needs to emulate NetIoWrite().
*/ 

int Http2ResponseDictHeader
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction
)
{
   BOOL  headers, indexit;
   uint  index, length, nlen, nlen2, retval, size, value, vlen;
   uchar  *bptr, *bzptr, *cptr, *nptr, *nptr2, *vptr;
   DICT_ENTRY_STRUCT  *denptr;
   HPACK_TABLE_STRUCT  *tabptr;
   HTTP2_STRUCT  *h2ptr;
   HTTP2_WRITE_STRUCT  *w2ptr, *w22ptr;
   NETIO_STRUCT  *ioptr;

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

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

   if (rqptr->rqPathSet.ResponseHeaderNone &&
      rqptr->rqResponse.HttpStatus / 100 == 2) return (SS$_NORMAL);

   h2ptr = rqptr->Http2Stream.Http2Ptr;

   /* treat subsequent response header as data (for WASD "Xray" facility) */
   if (rqptr->Http2Stream.HeaderSent)
   {
      denptr = ResponseDictHeader (rqptr);
      NetIoWrite (rqptr->NetIoPtr,  AstFunction, rqptr,
                  DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr));
      return (SS$_NORMAL);
   }
   rqptr->Http2Stream.HeaderSent = true;

   tabptr = &h2ptr->HpackServerTable;

   /* with compression this should always be (way more than) enough space */
   size = 8;
   DictIterate (rqptr->rqDictPtr, NULL);
   while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_RESPONSE)) != NULL)
      size += DICT_GET_KEY_LEN(denptr) + DICT_GET_VALUE_LEN(denptr) + 4;
   w2ptr = w22ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) + size);

   bzptr = (bptr = w2ptr->payload) + size;

   denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL,
                        "response_status", 15);

   /* if a header has not been generated */
   if (denptr == NULL) return (SS$_ABORT);

   *bptr++ = (uchar)0x08;  /* static index ":status" without indexing */
   *bptr++ = (uchar)DICT_GET_VALUE_LEN(denptr);
   for (cptr = DICT_GET_VALUE(denptr); *cptr; *bptr++ = (uchar)*cptr++);

   /* set the baseline before these table searches */
   HpackFindInTable (tabptr, NULL, 0, NULL, 0);

   /* calculate the HTTP/1.n equivalent */
   length = sizeof("\r\n")-1;
   DictIterate (rqptr->rqDictPtr, NULL);
   while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_RESPONSE)) != NULL)
   {
      nptr = DICT_GET_KEY(denptr);
      nlen = DICT_GET_KEY_LEN(denptr);
      vptr = DICT_GET_VALUE(denptr);
      vlen = DICT_GET_VALUE_LEN(denptr);

      /* e.g. |set-cookie| field begins with "<integer>:" to allow multiples */
      if (isdigit(*nptr))
      {
         nptr2 = nptr;
         nlen2 = nlen;
         while ((isdigit(*nptr2)) && nlen2)
         {
            nptr2++;
            nlen2--;
         }
         if (nlen2 && *nptr2 == ':')
         {
            nptr = nptr2 + 1;
            nlen = nlen2 - 1;
         }
         /* ignore if somehow just "<integer>:" */
         if (!nlen || !nlen2) continue;
      }

      length += nlen + vlen + sizeof(": \r\n")-1;

      /* look for the name plus value entry */
      index = HpackFindInTable (tabptr, nptr, nlen, vptr, vlen);
      if (index)
      {
         /* indexed header field, indexed name and value (RFC7541 6.1) */
         *bptr = 0x80;
         retval = HpackEncodeInt32 (h2ptr, &bptr, bzptr, 7, index);
         if (retval < 0) return (SS$_BUGCHECK);
      }
      else
      {
         /* if no name plus value entry look for just the name */
         index = HpackFindInTable (tabptr, nptr, nlen, NULL, 0);

         /* observation has demonstrated these are not worth caching */
         if (MATCH15 (nptr, "content-length"))
            indexit = false;
         else
         if (MATCH5 (nptr, "date"))
            indexit = false;
         else
         if (MATCH5 (nptr, "etag"))
            indexit = false;
         else
         if (MATCH14 (nptr, "last-modified"))
            indexit = false;
         else
            indexit = true;

         /* if it won't fit into the table! */
         if (nlen + vlen + 32 > h2ptr->HpackServerTable.max)
            indexit = false;

         if (indexit)
         {
            /* indexed header field, index then value (RFC7541 6.2.1) */
            if (index)
            {
               *bptr = 0x40;
               retval = HpackEncodeInt32 (h2ptr, &bptr, bzptr, 6, index);
               if (retval < 0) return (SS$_BUGCHECK);
            }
            else
            {
               *bptr++ = 0x40;
               retval = HpackEncodeString (h2ptr, &bptr, bzptr, nptr, nlen);
               if (retval < 0) return (SS$_BUGCHECK);
            }
            retval = HpackEncodeString (h2ptr, &bptr, bzptr, vptr, vlen);
            if (retval < 0) return (SS$_BUGCHECK);
            HpackAddToTable (tabptr, nptr, nlen, vptr, vlen);
         }
         else
         {
            /* literal header field without indexing (RFC7541 6.2.2) */
            if (index)
            {
               *bptr = 0x00;
               retval = HpackEncodeInt32 (h2ptr, &bptr, bzptr, 4, index);
               if (retval < 0) return (SS$_BUGCHECK);
            }
            else
            {
               *bptr++ = 0x00;
               retval = HpackEncodeString (h2ptr, &bptr, bzptr, nptr, nlen);
               if (retval < 0) return (SS$_BUGCHECK);
            }
            retval = HpackEncodeString (h2ptr, &bptr, bzptr, vptr, vlen);
            if (retval < 0) return (SS$_BUGCHECK);
         }
      }
      if (bptr > w2ptr->payload + size)
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   value = bptr - w2ptr->payload;

   /* as if it originated from the network */
   ADD_LONG_QUAD (value, rqptr->BytesRx)

   h2ptr->HpackServerInputCount += length;
   h2ptr->HpackServerOutputCount += value;

   if (WATCHING (h2ptr, WATCH_HTTP2) &&
       !WATCHING1S(h2ptr) && !WATCHING2(h2ptr))
   {
      WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                 "RESPONSE header !UL->!UL !UL%",
                 length, value, value * 100 / length);
      if (rqptr->rqPathSet.Http2WriteQueue)
      {
         if (rqptr->rqPathSet.Http2WriteQueue == HTTP2_WRITE_QUEUE_HIGH)
            cptr = "high";
         else
         if (rqptr->rqPathSet.Http2WriteQueue == HTTP2_WRITE_QUEUE_NORMAL)
            cptr = "normal";
         else
            cptr = "low";
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                    "WRITE queue !AZ", cptr);
      }
   }

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
   {
      int  retval;
      retval = HpackHeadersFrame (&h2ptr->HpackServerTable,
                                  (int)-1, rqptr->Http2Stream.Ident,
                                  w2ptr->payload, value);
      if (retval < 0)
      {
         WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                    "HPACK error:!UL \"!AZ\"",
                    -(retval), Http2ErrorString(-(retval)));
         return (SS$_ABORT);
      }

      WatchData (w2ptr->payload, value);
   }

   if (WATCHING (rqptr, WATCH_RESPONSE_HEADER))
   {
      denptr = ResponseDictHeader (rqptr);
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_HEADER,
                 "HEADER !UL bytes", DICT_GET_VALUE_LEN(denptr));
      WatchData (DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr));
   }

   /* this is the underlying request's I/O structure */
   ioptr = rqptr->NetIoPtr;

   /* most headers are going to be without continuation */
   if (value <= h2ptr->ClientMaxFrameSize)
   {
      /********************/
      /* just one headers */
      /********************/

      /* use the originally allocate write structure */
      HTTP2_PLACE_24 (w2ptr->length, value);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_HEADERS);
      HTTP2_PLACE_8  (w2ptr->flags, HTTP2_FLAG_HEAD_END_HEAD);
      HTTP2_PLACE_32 (w2ptr->ident, rqptr->Http2Stream.Ident);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->RequestPtr = rqptr;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = value;

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

      /* called from NetWrite() this needs to emulate NetIoWrite() */
      if (ioptr->WriteAstFunction = AstFunction)
         ioptr->WriteAstParam = rqptr;

      /* let's give response headers a bit of an edge */
      w2ptr->WriteQueue = HTTP2_WRITE_QUEUE_HIGH;

      if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                    "h2ptr:!&X w2ptr:!&X", h2ptr, w2ptr);

      Http2NetQueueWrite (h2ptr, w2ptr);

      /* keep the request accounting representative */
      ADD_LONG_QUAD (1, ioptr->BlocksRawTx);
      ADD_LONG_QUAD (1, ioptr->BlocksTallyTx);
      ADD_LONG_QUAD (length, ioptr->BytesRawTx);
      ADD_LONG_QUAD (length, ioptr->BytesTallyTx);
   }
   else
   {
      /*********************/
      /* with continuation */
      /*********************/

      /* the "original" write structure payload will be cannibalised */
      cptr = w22ptr->payload;
      headers = true;

      while (value)
      {
         if (value <= h2ptr->ClientMaxFrameSize)
            length = value;
         else
            length = h2ptr->ClientMaxFrameSize;
         value -= length;

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

         HTTP2_PLACE_24 (w2ptr->length, length);
         if (headers)
            HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_HEADERS)
         else
            HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_CONTINUATION)
         headers = false;
         if (!value) HTTP2_PLACE_8  (w2ptr->flags, HTTP2_FLAG_HEAD_END_HEAD);
         HTTP2_PLACE_32 (w2ptr->ident, rqptr->Http2Stream.Ident);

         memcpy (w2ptr->payload, cptr, length);
         cptr += length;

         w2ptr->Http2Ptr = h2ptr;
         w2ptr->RequestPtr = rqptr;
         w2ptr->HeaderPtr = w2ptr->header;
         w2ptr->DataPtr = w2ptr->payload;
         w2ptr->DataLength = length;

         if (headers)
            w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST;
         else
         {
            if (!(w2ptr->AstFunction = AstFunction))
               w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST;
            else
               w2ptr->AstParam = rqptr;

            /* called from NetWrite() this needs to emulate NetIoWrite() */
            if (ioptr->WriteAstFunction = AstFunction)
               ioptr->WriteAstParam = rqptr;
         }

         /* let's give response headers a bit of an edge */
         w2ptr->WriteQueue = HTTP2_WRITE_QUEUE_HIGH;

         if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                       "h2ptr:!&X w2ptr:!&X", h2ptr, w2ptr);

         Http2NetQueueWrite (h2ptr, w2ptr);

         /* keep the request accounting representative */
         ADD_LONG_QUAD (1, ioptr->BlocksRawTx);
         ADD_LONG_QUAD (1, ioptr->BlocksTallyTx);
         ADD_LONG_QUAD (length, ioptr->BytesRawTx);
         ADD_LONG_QUAD (length, ioptr->BytesTallyTx);
      }

      /* free the cannibalised write structure */
      VmFreeFrom2Heap (h2ptr, w22ptr, FI_LI);
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Reset the stream associated with this request.
*/ 

int Http2RequestResetStream (REQUEST_STRUCT *rqptr)

{
   int  retval;
   uint  error, ident;
   HTTP2_STRUCT  *h2ptr;

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

   h2ptr = rqptr->Http2Stream.Http2Ptr;
   ident = rqptr->Http2Stream.Ident;
   error = HTTP2_ERROR_CANCEL;

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2RequestResetStream()");

   retval = Http2ResetStream (h2ptr, ident, error, NULL, 0);

   return (retval);
}

/*****************************************************************************/
/*
Called from RequestEnd2() and Http2NetWriteDataAst() during request run-down.
*/ 

void Http2RequestEnd2 (REQUEST_STRUCT *rqptr)

{
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
"Http2RequestEnd2() !&F ident:!UL state:!UL read:!UL/!UL write:!UL",
                 Http2RequestEnd2,
                 rqptr->Http2Stream.Ident,
                 rqptr->Http2Stream.State,
                 rqptr->Http2Stream.DataReadLength,
                 rqptr->Http2Stream.DataReadSize,
                 rqptr->Http2Stream.QueuedWriteCount);

   s2ptr = &rqptr->Http2Stream;
   h2ptr = s2ptr->Http2Ptr;

   /* cancel request data being read */
   Http2RequestData (h2ptr, 0, s2ptr->Ident, (void*)-1, 0);

   if (!s2ptr->RequestEnd)
   {
      s2ptr->RequestEnd = true;
      if (!(s2ptr->State == HTTP2_STATE_CLOSED ||
            s2ptr->State == HTTP2_STATE_CLOSED_LOC))
         Http2NetWriteEnd (rqptr);
   }

   if (s2ptr->DataReadSize ||
       s2ptr->QueuedWriteCount ||
       NETIO_IN_PROGRESS (rqptr->NetIoPtr)) return;

   ADD_QUAD_QUAD (s2ptr->Http2Ptr->BytesRawTallyRx,
                  rqptr->NetIoPtr->BytesRawRx);
   ADD_QUAD_QUAD (s2ptr->Http2Ptr->BytesRawTallyTx,
                  rqptr->NetIoPtr->BytesRawTx);

   RequestEnd2 (rqptr);
}

/*****************************************************************************/
/*
Final stage of request run-down corresponding to RequestEnd5().
*/ 

void Http2RequestEnd5 (REQUEST_STRUCT *rqptr)

{
   int  HttpStatus;
   HTTP2_STRUCT  *h2ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                 "Http2RequestEnd5() chan:!UL ident:!UL",
                 rqptr->Http2Stream.Http2Ptr->NetIoPtr->Channel,
                 rqptr->Http2Stream.Ident);

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                 "HTTP/2 end !UL with !AZ,!UL",
                 rqptr->Http2Stream.Ident,
                 rqptr->ClientPtr->Lookup.HostName,
                 rqptr->ClientPtr->IpPort); 

   /* dispose of the network I/O structure */
   NetIoEnd (rqptr->NetIoPtr);

   h2ptr = rqptr->Http2Stream.Http2Ptr;

   ListRemove (&h2ptr->StreamList, &rqptr->Http2Stream);

   if (h2ptr->RequestCurrent) h2ptr->RequestCurrent--;

   rqptr->Http2Stream.Ident = 0;
   rqptr->Http2Stream.Http2Ptr = NULL;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   ADD_LONG_QUAD (h2ptr->FrameTallyRx, acptr->Http2FrameCountRx);
   ADD_LONG_QUAD (h2ptr->FrameTallyTx, acptr->Http2FrameCountTx);
   ADD_LONG_QUAD (h2ptr->FrameRequestTallyRx,
                  acptr->Http2FrameRequestCountRx);
   ADD_LONG_QUAD (h2ptr->FrameRequestTallyTx,
                  acptr->Http2FrameRequestCountTx);
   ADD_LONG_QUAD (h2ptr->FlowFrameTally, acptr->Http2FlowFrameCount);
   ADD_LONG_QUAD (h2ptr->FlowControlTally, acptr->Http2FlowControlCount);
   h2ptr->FlowFrameCount += h2ptr->FlowFrameTally;
   h2ptr->FlowFrameTally = 0;
   h2ptr->FlowControlCount += h2ptr->FlowControlTally;
   h2ptr->FlowControlTally = 0;

   ADD_QUAD_QUAD (h2ptr->BytesRawTallyRx, acptr->BytesRawRx[HTTP2]);
   ADD_QUAD_QUAD (h2ptr->BytesRawTallyTx, acptr->BytesRawTx[HTTP2]);

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   h2ptr->FrameTallyRx = 0;
   h2ptr->FrameTallyTx = 0;
   h2ptr->FrameRequestTallyRx = 0;
   h2ptr->FrameRequestTallyTx = 0;
   PUT_ZERO_QUAD (h2ptr->BytesRawTallyRx);
   PUT_ZERO_QUAD (h2ptr->BytesRawTallyTx);

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "streams: !UL",
                 LIST_GET_COUNT (&h2ptr->StreamList));

   /* if WATCHing the HTTP/2 connection then reset the request item */
   if (h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG) rqptr->WatchItem = 0;

#if SESOLA_MEMORY
   SesolaMemoryControl (-1, FI_LI);
#endif SESOLA_MEMORY

   HttpStatus = rqptr->rqResponse.HttpStatus;

   RequestEnd5 (rqptr);

   /* 418 can be used to drop a connection using a "standard" code */
   if (h2ptr->GoAwayLastStreamIdent || HttpStatus == 418)
      Http2CloseConnection (h2ptr);
}

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

