/*****************************************************************************/
/*
                               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
---------------
27-OCT-2023  MGD  refinements using https://github.com/summerwind/h2spec
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 const int64  Delta01Sec;

extern int  ConnectCountTotal,
            HttpdTickSecond,
            NetReadBufferSize;

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;

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

REQUEST_STRUCT* Http2RequestBegin (HTTP2_STREAM_STRUCT *s2ptr)

{
   HTTP2_STRUCT  *h2ptr;
   NETIO_STRUCT  *ioptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHMOD (s2ptr->Http2Ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(s2ptr->Http2Ptr), WATCH_MOD_HTTP2,
                 "Http2RequestBegin() open:!&B", s2ptr->StreamOpen);
 
#if SESOLA_MEMORY
   SesolaMemoryControl (-1, FI_LI);
#endif SESOLA_MEMORY

   h2ptr = s2ptr->Http2Ptr;

   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.BeginTime7, &rqptr->rqTime.BeginTime64);
   HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64);

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

   /* populate network I/O structure (see NetAccept()) */
   ioptr->TcpMaxQio = ioptr->TcpMaxQioSet = h2ptr->NetIoPtr->TcpMaxQioSet;
   ioptr->ClientPtr = h2ptr->ClientPtr;
   ioptr->ServicePtr = h2ptr->ServicePtr;
   ioptr->Stream2Ptr = s2ptr;
   /* plus these important items */
   ioptr->WatchTriggerRxPlus = h2ptr->NetIoPtr->WatchTriggerRxPlus;
   ioptr->WatchTriggerTxPlus = h2ptr->NetIoPtr->WatchTriggerTxPlus;

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

   /* populate request structure */
   rqptr->NetworkHttp = 2;
   rqptr->Http2Ptr = h2ptr;
   rqptr->Stream2Ptr = s2ptr;
   rqptr->NetIoPtr = ioptr;
   rqptr->ClientPtr = h2ptr->ClientPtr;
   rqptr->ServicePtr = h2ptr->ServicePtr;
   rqptr->rqDictPtr = DictCreate (rqptr, -1);
   rqptr->RequestState = REQUEST_STATE_HEADER;

   /* if trigger plus then trigger the new request */
   if (ioptr->WatchTriggerRxPlus || ioptr->WatchTriggerTxPlus)
   {
      if (h2ptr != Watch.Http2Ptr)
         if (WatchSetWatch (rqptr, WATCH_NEW_TRIGGER))
            if (ioptr->WatchTriggerRxPlus)
               WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TRIGGER+ rx");
            else
               WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TRIGGER+ tx");
   }
   else
   if (WATCH_NOT_ONE_SHOT(Watch.Category))
   {
      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);

   h2ptr->HeaderLastIdent = s2ptr->Ident;
   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 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));

   return (rqptr);
}

/*****************************************************************************/
/*
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)

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

   /*********/
   /* 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_NOT_ONE_SHOT(Watch.Category))
      WatchFilterRequestHeader (rqptr);

   if (rqptr->Http2Ptr->PingMicroSeconds)
   {
      FaoToBuffer (string, sizeof(string), &slen, "!UL.!3ZL", 
                   rqptr->Http2Ptr->PingMicroSeconds / 1000,
                   rqptr->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->Stream2Ptr ? rqptr->Stream2Ptr->Ident : 0,
                 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));
      }

   /* plain-text equivalent request header (native being binary) */
   if (Watch.TriggerRxCount)
   {
      if (Watch.Http2Ptr && Watch.Http2Ptr == rqptr->Http2Ptr)
         WatchThis (WATCHALL, WATCH_HTTP2, "TRIGGER excluded");
      else
      {
         ioptr = rqptr->NetIoPtr;
         if (!ioptr->WatchTriggerRxPlus &&
             !ioptr->WatchTriggerTxPlus)
         {
            if (denptr = RequestDictHeader (rqptr))
            {
               if (WatchTrigger (DICT_GET_VALUE(denptr),
                                 DICT_GET_VALUE_LEN(denptr),
                                 Watch.TriggerRx))
               {
                  ioptr->WatchTriggerRx = true;
                  if (Watch.TriggerPlus) ioptr->WatchTriggerRxPlus = true;
               }
            }
         }
         if (ioptr->WatchTriggerRx || ioptr->WatchTriggerRxPlus)
         {
            WatchSetWatch (rqptr, WATCH_NEW_TRIGGER);
            ioptr->WatchTriggerRx = false;
         }
      }
   }

   if (Watch.RequestPtr &&
       Watch.RequestPtr->Http2Ptr &&
       Watch.RequestPtr->Http2Ptr == rqptr->Http2Ptr)
   {
      /* delay request processing when WATCHing on same HTTP/2 connection */
      status = sys$setimr (0, &Delta01Sec,
                           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,
HTTP2_STREAM_STRUCT *s2ptr,
uint flags,
uchar *BufferPtr,
uint BufferLength
)
{
   uint  drlength, drsize, padlen, status,
         DataLength;
   uchar  *bptr, *drptr;
   DICT_ENTRY_STRUCT  *denptr;
   NETIO_STRUCT  *ioptr;
   REQUEST_STRUCT  *rqptr;

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

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

   if (!s2ptr)
      return (Http2Error(h2ptr,-HTTP2_ERROR_PROTOCOL,FI_LI));

   if (!s2ptr->StreamOpen)
      return (Http2Error(h2ptr,-HTTP2_ERROR_PROTOCOL,FI_LI));

   if (s2ptr->StreamDataEnd)
      return (Http2Error(h2ptr,-HTTP2_ERROR_PROTOCOL,FI_LI));

   rqptr = s2ptr->RequestPtr;

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

   /*****************/
   /* data received */
   /*****************/

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

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

      if (flags & HTTP2_FLAG_END_STR) s2ptr->StreamEnd = true;

      if (flags & HTTP2_FLAG_PADDED)
      {
         HTTP2_GET_8 (bptr, padlen);
         if (padlen >= BufferLength)
            return (Http2Error(h2ptr,-HTTP2_ERROR_PROTOCOL,FI_LI));
         DataLength = BufferLength - padlen - 1;   
      }
      else
         DataLength = BufferLength;

      /* keep the request accounting representative */
      ioptr->BlocksRawRx64++;
      ioptr->BlocksTallyRx64++;
      ioptr->BytesRawRx64 += DataLength;
      ioptr->BytesTallyRx64 += DataLength;

      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_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, s2ptr, 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, s2ptr->Ident,
                                     s2ptr->ReadWindowSize, NULL, 0);
               }

               return (0);
            }
         }
      }

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

         if (!drsize)
         {
            /* fresh buffer */
            drsize = h2ptr->ServerInitialWindowSize;
            drptr = VmGetHeap (rqptr, drsize);
         }
         else
         if (drlength + DataLength > drsize)
         {
            /* insufficient space in current buffer */
            drsize += h2ptr->ServerInitialWindowSize;
            drptr = VmReallocHeap (rqptr, 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)
      {
         /****************/
         /* flow control */
         /****************/

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

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

      if (flags & HTTP2_FLAG_END_STR) s2ptr->StreamEnd = true;

      /* if not a request already waiting */
      if (!ioptr->ReadAstFunction) return (DataLength);
      /* 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, s2ptr->Ident,
                            s2ptr->ReadWindowSize, NULL, 0);
      }

      /* request body data */
      if (Watch.TriggerRxCount)
      {
         if (h2ptr != Watch.Http2Ptr)
         {
            if (!ioptr->WatchTriggerRxPlus &&
                !ioptr->WatchTriggerTxPlus)
            {
               if (WatchTrigger (ioptr->ReadPtr, ioptr->ReadCount,
                                 Watch.TriggerRx))
               {
                  ioptr->WatchTriggerRx = true;
                  if (Watch.TriggerPlus) ioptr->WatchTriggerRxPlus = true;
               }
            }
            if (ioptr->WatchTriggerRx || ioptr->WatchTriggerRxPlus)
            {
               WatchSetTrigger (ioptr);
               ioptr->WatchTriggerRx = false;
            }
         }
      }

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

   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->StreamClosed)
   {
      ioptr->ReadIOsb.Status = SS$_ENDOFFILE;
      NetIoReadAst (ioptr);
   }

   return (0);
}

/*****************************************************************************/
/*
Http2ResponseDictHeader() ... 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);

   if (!rqptr->Stream2Ptr) return (SS$_ABORT);

   h2ptr = rqptr->Http2Ptr;

   /* treat subsequent response header as data (for WASD "Xray" facility) */
   if (rqptr->Stream2Ptr->ResponseSent)
   {
      denptr = ResponseDictHeader (rqptr);
      NetIoWrite (rqptr->NetIoPtr,  AstFunction, rqptr,
                  DICT_GET_VALUE(denptr), DICT_GET_VALUE_LEN(denptr));
      return (SS$_NORMAL);
   }
   rqptr->Stream2Ptr->ResponseSent = 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 = Http2GetWriteStruct (h2ptr, size, FI_LI);

   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 */
   rqptr->BytesRx64 += value;

   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 (rqptr->Stream2Ptr, 0, 0, 
                                  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);
   }

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

   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));
   }

   /* plain-text equivalent response header (native being binary) */
   if (Watch.TriggerTxCount)
   {
      if (h2ptr != Watch.Http2Ptr)
      {
         if (!ioptr->WatchTriggerRxPlus &&
             !ioptr->WatchTriggerTxPlus)
         {
            if (denptr = ResponseDictHeader (rqptr))
            {
               if (WatchTrigger (DICT_GET_VALUE(denptr),
                                 DICT_GET_VALUE_LEN(denptr),
                                 Watch.TriggerTx))
               {
                  ioptr->WatchTriggerTx = true;
                  if (Watch.TriggerPlus) ioptr->WatchTriggerTxPlus = true;
               }
            }
         }
         if (ioptr->WatchTriggerTx || ioptr->WatchTriggerTxPlus)
         {
            WatchSetTrigger (ioptr);
            ioptr->WatchTriggerTx = false;
         }
      }
   }

   /* 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_END_HEAD);
      HTTP2_PLACE_32 (w2ptr->ident, rqptr->Stream2Ptr->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 */
      ioptr->BlocksRawTx64++;
      ioptr->BlocksTallyTx64++;
      ioptr->BytesRawTx64 += length;
      ioptr->BytesTallyTx64 += length;
   }
   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 = Http2GetWriteStruct (h2ptr, length, FI_LI);

         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_END_HEAD);
         HTTP2_PLACE_32 (w2ptr->ident, rqptr->Stream2Ptr->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 */
         ioptr->BlocksRawTx64++;
         ioptr->BlocksTallyTx64++;
         ioptr->BytesRawTx64 += length;
         ioptr->BytesTallyTx64 += length;
      }

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

   return (SS$_NORMAL);
}

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

int Http2RequestResetStream (REQUEST_STRUCT *rqptr)

{
   int  retval;
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;

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

   h2ptr = rqptr->Http2Ptr;
   s2ptr = rqptr->Stream2Ptr;

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

   retval = Http2StreamReset (h2ptr, s2ptr, HTTP2_ERROR_CANCEL, NULL, 0);

   return (retval);
}

/*****************************************************************************/
/*
Is the request quiescent?
*/ 

BOOL Http2RequestQuiescent (REQUEST_STRUCT *rqptr)

{
   int  retval;
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;

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

   h2ptr = rqptr->Http2Ptr;
   s2ptr = rqptr->Stream2Ptr;

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

   if (rqptr->NetIoPtr->ReadAstFunction) return (false);
   if (rqptr->NetIoPtr->WriteAstFunction) return (false);
   if (s2ptr->DataReadPtr) return (false);
   if (s2ptr->QueuedWriteCount) return (false);

   return (true);
}

/*****************************************************************************/
/*
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 rqend:!&B closed:!&B end:!&B cancel:!&B read:!UL/!UL write:!UL", Http2RequestEnd2,
                 rqptr->Stream2Ptr->Ident,
                 rqptr->Stream2Ptr->RequestEnd,
                 rqptr->Stream2Ptr->StreamClosed,
                 rqptr->Stream2Ptr->StreamEnd,
                 rqptr->Stream2Ptr->StreamCancel,
                 rqptr->Stream2Ptr->DataReadLength,
                 rqptr->Stream2Ptr->DataReadSize,
                 rqptr->Stream2Ptr->QueuedWriteCount);

   s2ptr = rqptr->Stream2Ptr;
   h2ptr = s2ptr->Http2Ptr;

   if (!s2ptr->RequestEnd)
   {
      /* if not closed Http2NetWriteEnd() will AST back to RequestEnd2() */
      if (s2ptr->StreamClosed)
         SysDclAst (RequestEnd2, rqptr);
      else
         Http2NetWriteEnd (rqptr);
      s2ptr->RequestEnd = true;
   }

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

/*****************************************************************************/
/*
A HTTP/2 request can sometimes become 'isolated', particularly if WATCHing.
This function checks there is no I/O anywhere and calls Http2RequestEnd5() to
end the request itself.
*/ 

BOOL Http2RequestEnd4 (REQUEST_STRUCT *rqptr)

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

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                 "Http2RequestEnd4() !&A !&A",
                 rqptr->NetIoPtr->ReadAstFunction,
                 rqptr->NetIoPtr->WriteAstFunction);

   if (Http2NetCancelWrite (rqptr)) return (false);
   if (rqptr->NetIoPtr->ReadAstFunction) return (false);
   if (rqptr->NetIoPtr->WriteAstFunction) return (false);

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

   Http2RequestEnd5 (rqptr);

   return (true);
}

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

void Http2RequestEnd5 (REQUEST_STRUCT *rqptr)

{
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;

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

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

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

   h2ptr = rqptr->Http2Ptr;

   h2ptr->NetIoPtr->WatchTriggerRxPlus = rqptr->NetIoPtr->WatchTriggerRxPlus;
   h2ptr->NetIoPtr->WatchTriggerTxPlus = rqptr->NetIoPtr->WatchTriggerTxPlus;

   if (!h2ptr->NetIoPtr->WatchTriggerRxPlus &&
       !h2ptr->NetIoPtr->WatchTriggerTxPlus) 
      h2ptr->NetIoPtr->WatchItem = 0;

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

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

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   acptr->Http2FrameCountRx64 += h2ptr->FrameTallyRx;
   acptr->Http2FrameCountTx64 += h2ptr->FrameTallyTx;
   acptr->Http2FrameRequestCountRx64 += h2ptr->FrameRequestTallyRx;
   acptr->Http2FrameRequestCountTx64 += h2ptr->FrameRequestTallyTx;
   acptr->BytesRawRx64[HTTP2] += h2ptr->BytesRawTallyRx64;
   acptr->BytesRawTx64[HTTP2] += h2ptr->BytesRawTallyTx64;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   h2ptr->BytesRawTallyRx64 = 0;
   h2ptr->BytesRawTallyTx64 = 0;
   h2ptr->FrameTallyRx = 0;
   h2ptr->FrameTallyTx = 0;
   h2ptr->FrameRequestTallyRx = 0;
   h2ptr->FrameRequestTallyTx = 0;

   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

   /* buffer the request's stream pointer */
   s2ptr = rqptr->Stream2Ptr;

   /* remove the request pointer from the stream! */
   s2ptr->RequestPtr = NULL;

   /* remove all traces of the HTTP/2 connection from the request */
   rqptr->Stream2Ptr = NULL;
   rqptr->Http2Ptr = NULL;

   Http2StreamClose (s2ptr);

   RequestEnd5 (rqptr);

   if (h2ptr->GoAwayLastStreamIdent)
      Http2ConnectClose (h2ptr);
}

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

