/*****************************************************************************/
/*
                                  HTTP2.c

HTTP/2 is a replacement for how HTTP is expressed "on the wire".  It is not a
ground-up rewrite of the protocol; HTTP methods, status codes and semantics are
the same, and it should be possible to use the same APIs as HTTP/1.x (possibly
with some small additions) to represent the protocol.

The focus of the protocol is on performance; specifically, end-user perceived
latency, network and server resource usage. One major goal is to allow the use
of a single connection from browsers to a Web site.

Some useful (and used) sites:

https://tools.ietf.org/html/rfc7540
https://tools.ietf.org/html/rfc7541
https://en.wikipedia.org/wiki/HTTP/2
https://http2.github.io/
https://http2.github.io/faq/
http://http2-explained.haxx.se/
http://chimera.labs.oreilly.com/books/1230000000545/ch12.html
https://insouciant.org/tech/http-slash-2-considerations-and-tradeoffs/
http://undertow.io/blog/2015/04/27/An-in-depth-overview-of-HTTP2.html
https://blog.newrelic.com/2016/02/17/http2-production/
https://blog.newrelic.com/2016/02/09/http2-best-practices-web-performance/
https://blog.cloudflare.com/tools-for-debugging-testing-and-using-http-2/
https://nghttp2.org/
https://nghttp2.org/documentation/nghttp.1.html
https://nghttp2.org/documentation/h2load.1.html
https://nghttp2.org/blog/2014/11/29/test-your-http-slash-2-server-with-nghttp-client/
chrome://net-internals#http2


HTTP/2 PROCESSING
-----------------
These are indicative processing flows only.  Not all functions involved or
encountered are shown.  Too many if-buts-maybees.  Use WATCH.

   Request Processing
   ~~~~~~~~~~~~~~~~~~
   :
   |
   RequestGet()                            !match the HTTP/2 request preface 
   |
   Http2Preface()                          !either of these two functions are
   Http2SwitchResponse()                   !entry points to HTTP/2 processing
   |
   Http2Create()                           !create HTTP/2 structure and dispose
   |                                       !of initiating request structure
   |
   Http2ClientRead()    <--+      <--+     !receive frame from client
   |                       |         |
   |               {control frames}  |     !HTTP/2 internal processing
   |                       |         :
   +--{not header frames}--+         :     !frame processed by type
   |                                 .
   HpackHeadersFrame()                     !request headers frame
   |                                  
   Http2RequestBegin()                     !initialise request processing
   |
   Http2RequestProcess()                   !begin processing the request
   |
   RequestParseDictionary()                !dictionary contains request
   |
   :                                       !as per any other request
   |
   Http2RequestEnd()                       !end the request HTTP/2 specifics
   |
   :

   Read Frame Processing
   ~~~~~~~~~~~~~~~~~~~~~
   :
   Http2NetClientRead()                    !initiate HTTP/2 connection read loop
   |
   NetIoRead()                             !asynchronous read
   | 
   :
   |
   Http2NetClientReadAst()  <-- +          !asynchronous read completion
   |                            |
   !validate frame              : 
   |                            :
   !switch/case on frame type   :          !call function to process frame
   |                            :
   NetIoRead()                  :          !asynchronous read
   |                            |
   +----------------------------+

   Write Frame Processing
   ~~~~~~~~~~~~~~~~~~~~~~
   :
   NetIoWrite()                            !request standard network call
   |
   Http2NetIoWrite()                       !HTTP/2 write
   |
   Http2NetQueueWrite()   <----- +         !put the write on a queue
   |                             |
   +-->!add I/O to a queue -->   :         !return after putting in queue
   |                             :
   !check queue(s) for I/O       :         !no queued I/O then end processing
   |                             :
   NetIoWrite()  >------------+  :
                              :  :
   Http2NetWriteHeaderAst() <-+  :         !write frame header then data
   |                          :  :
   NetIoWrite()  >------------+  :
                              :  :
   Http2NetWriteDataAst()  <--+  :         !write frame header plus data
   |                             |
   NetIoWrite()  >---------------+


WATCHing via HTTP/2
-------------------
If a WATCHing request and one instantiated by Http2RequestBegin() share the
HTTP/2 connection then none of the HTTP/2 WATCH points will be reported because
down that particular rabbit hole is found only madness.


HTTP/2 DoS Mitigations
----------------------
https://tools.ietf.org/html/rfc7540#section-10.5
https://www.kb.cert.org/vuls/id/605641/
https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-002.md

WASD places explicit (and reasoanble) limits on some HTTP/2 behaviours.
Though exercising these mitigations is beyond the scope of the WASD test-bench.
When mitigations activate the connection is dropped.  Fingers crossed.

1) CVE-2019-9511, also known as Data Dribble
   WASD's asynchronous buffering and lack of stream prioritising mitigates.

2) CVE-2019-9512, also known as Ping Flood
   Http2Ping() checks for unreasonable client ping requests per second.

3) CVE-2019-9513, also known as Resource Loop
   Stream prioritising is not implemented by WASD.

4) CVE-2019-9514, also known as Reset Flood
   Http2ResetStream() checks for unreasonable server stream resets per second.

5) CVE-2019-9515, also known as Settings Flood
   Http2Settings() checks for unreasonable client setting frames per second.

6) CVE-2019-9516, also known as 0-Length Headers Leak
   HpackHeadersFrame() checks for zero-sized client headers.

7) CVE-2019-9517, also known as Internal Data Buffering
   The use of the TCP sliding window in this DoS puts it beyond WASD's control.
   WASD's asynchronous I/O should limit buffering impacts.

8) CVE-2019-9518, also known as Empty Frame Flooding
   Http2NetClientReadAst() checks for unreasonable client frame sizes.


TESTING
-------
Needless to say the major browsers (Chrome, Edge, FireFox, Safari) all
contributed to end-use development.

Indispensible were the |nghttp| and the associated |h2load| tools running on a
Linux Mint (17.3) VM.  Many thanks to the developer(s) of this package.  And of
course for someone educated in computing during the (19)70s, the availability
of VM technology for such purposes is just brilliant!  "But you know, we were
happy in those days, though we were poor."

  o  exercise the basic HTTP/2 functionality

     nghttp -nv https://klaatu.private/

  o  exercise starting HTTP/2 for "http" URIs (RFC 7450 3.2)

     nghttp -nvu http://klaatu.private/

  o  exercise starting HTTP/2 (for "http") with prior knowledge (RFC 7450 3.4)

     nghttp -nv http://klaatu.private/

  o  headers with continuation frames (RFC 7450 6.10)

     nghttp --continuation https://klaatu.private/

  o  basic HTTP/2 loading (number, clients and streams bumped)

     h2load --requests=1000 --clients=5 --max-concurrent-streams=5 https://klaatu.private/

  o  HTTP/2 loading with defined URIs

     h2load --requests=1000 --clients=5 --threads=5 --max-concurrent-streams=5 --input-file=urls.txt

  o  HTTP/1.1 loading with defined URIs

     h2load --h1 --requests=100 --clients=2 --threads=2 --max-concurrent-streams=5 --input-file=urls.txt

The HTTP/2 and HTTP/1.1 with --input-file can be concurrently used in two
separate sessions to simultaneously exercise the server against the two
protocols.  The -w17 -W17 (for example) switches can exercise flow control.

When module WATCHing is compiled-in, defining the WASD_HTTP2_SUBTLE_BREAK
logical name enables code to break request processing in specific but subtle
ways to ensure the server's continued stability in the presence of network or
client issues.  It should be used under load from soemthing akin to |h2load|.



FOR LOCAL REFERENCE
-------------------

   +-----------------------------------------------+
   |                 Length (24)                   |
   +---------------+---------------+---------------+
   |   Type (8)    |   Flags (8)   |
   +-+-------------+---------------+-------------------------------+
   |R|                 Stream Identifier (31)                      |
   +=+=============================================================+
   |                   Frame Payload (0...)                      ...
   +---------------------------------------------------------------+


VERSION HISTORY
---------------
17-AUG-2020  MGD  HTTP2_DEFAULT_WINDOW_SIZE from 1048575 to 131070
05-APR-2020  MGD  Http2Report() remove proxy requests from HTTP/2-HTTP/1 ratio
31-JAN-2020  MGD  fiddle with the code (perhaps optimisations, even bugfixes)
13-JAN-2019  MGD  bugfix; Http2Priority() exclusive bit
17-NOV-2019  MGD  bugfix; Http2CloseConnection() return after RequestEnd()ing
12-OCT-2019  MGD  mitigate potential HTTP/2 DoS vulnerabilities
                    https://www.kb.cert.org/vuls/id/605641/
                    https://tools.ietf.org/html/rfc7540#section-10.5
18-APR-2018  MGD  bugfix; Http2ResetStream() rundown the request in-line 
07-APR-2018  MGD  bugfix; Http2ResetStream() explicitly rundown the request
23-DEC-2017  MGD  bugfix; window update and flow control management
02-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 "HTTP2"

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

BOOL  Http2Enabled;

int  Http2CurrentConnected,
     Http2CurrentProcessing,
     Http2FlowControlCurrent,
     Http2PingTimerSeconds;

uint  Http2InitialWindowSize,
      Http2MaxConcurrentStreams,
      Http2MaxFrameSize,
      Http2MaxHeaderListSize,
      Http2MaxHeaderTableSize,
      Http2StreamIdent;

/* used by VM.c */
const int  Http2StructSize = sizeof(HTTP2_STRUCT);

char  Http2ClientPreface [] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
int  Http2ClientPrefaceLength = sizeof(Http2ClientPreface)-1;

char  *Http2ErrorArray [HTTP2_ERROR_COUNT] =
{
   "graceful shutdown",
   "protocol error detected",
   "implementation fault",
   "flow-control limits exceeded",
   "settings not acknowledged",
   "frame received for closed stream",
   "frame size incorrect",
   "stream not processed",
   "stream cancelled",
   "compression state not updated",
   "TCP connection error for CONNECT method",
   "processing capacity exceeded",
   "negotiated TLS parameters not acceptable",
   "use HTTP/1.1 for the request"
};

LIST_HEAD  Http2List;

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

extern int  NetReadBufferSize;

extern ulong  HttpdTickSecond;

extern ulong  HttpdTime64 [];

extern char  ErrorSanityCheck [];

#define acptr AccountingPtr
extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern LIST_HEAD  RequestList;
extern PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initialise all things HTTP/2.
*/

void Http2Init ()

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

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

   Http2Enabled = Config.cfHttp2.Enabled;

   FaoToStdout ("%HTTPD-I-HTTP2, !AZ\n", Http2Enabled ? "enabled" : "disabled");

   Http2InitialWindowSize = Config.cfHttp2.InitialWindowSize;
   if (!Http2InitialWindowSize)
      Http2InitialWindowSize = HTTP2_DEFAULT_WINDOW_SIZE;
   else
   if (Http2InitialWindowSize < HTTP2_INITIAL_WINDOW_SIZE)
      Http2InitialWindowSize = HTTP2_INITIAL_WINDOW_SIZE;
   else
   if (Http2InitialWindowSize > HTTP2_MAX_WINDOW_SIZE)
      Http2InitialWindowSize = HTTP2_MAX_WINDOW_SIZE;

   Http2MaxConcurrentStreams = Config.cfHttp2.MaxConcurrentStreams;

   if (!Http2MaxConcurrentStreams)
      Http2MaxConcurrentStreams = HTTP2_INITIAL_MAX_CONC_STREAMS;

   if (Http2MaxConcurrentStreams < HTTP2_MIN_INITIAL_MAX_CONC_STREAMS)
      Http2MaxConcurrentStreams = HTTP2_MIN_INITIAL_MAX_CONC_STREAMS;

   Http2MaxFrameSize = Config.cfHttp2.MaxFrameSize;
   if (Http2MaxFrameSize < HTTP2_INITIAL_MAX_FRAME_SIZE)
      Http2MaxFrameSize = HTTP2_INITIAL_MAX_FRAME_SIZE;
   else
   if (Http2MaxFrameSize > HTTP2_MAX_FRAME_SIZE)
      Http2MaxFrameSize = HTTP2_MAX_FRAME_SIZE;

   if (Http2Enabled &&
       NetReadBufferSize < Http2MaxFrameSize)
   {
      NetReadBufferSize = Http2MaxFrameSize;
      FaoToStdout ("%HTTPD-W-HTTP2, network read buffer size \
increased to !UL bytes\n", NetReadBufferSize);
   }
   
   Http2MaxHeaderListSize = Config.cfHttp2.MaxHeaderListSize;
   if (Http2MaxHeaderListSize < HTTP2_INITIAL_HEAD_LIST_SIZE)
      Http2MaxHeaderListSize = HTTP2_INITIAL_HEAD_LIST_SIZE;
   else
   if (Http2MaxHeaderListSize > HTTP2_MAX_HEAD_LIST_SIZE)
      Http2MaxHeaderListSize = HTTP2_MAX_HEAD_LIST_SIZE;

   Http2MaxHeaderTableSize = Config.cfHttp2.MaxHeaderTableSize;
   if (Http2MaxHeaderTableSize < HTTP2_INITIAL_HEAD_TAB_SIZE)
      Http2MaxHeaderTableSize = HTTP2_INITIAL_HEAD_TAB_SIZE;
   else
   if (Http2MaxHeaderTableSize > HTTP2_MAX_HEAD_TAB_SIZE)
      Http2MaxHeaderTableSize = HTTP2_MAX_HEAD_TAB_SIZE;

   Http2PingTimerSeconds = Config.cfHttp2.PingTimerSeconds;
   if (Http2PingTimerSeconds == 0)
      Http2PingTimerSeconds = HTTP2_PING_SECONDS_DEFAULT;
   else
   if (Http2PingTimerSeconds < 0)
      Http2PingTimerSeconds = 0;  /* "NONE" */
   else
   if (Http2PingTimerSeconds > HTTP2_PING_SECONDS_MAX)
      Http2PingTimerSeconds = HTTP2_PING_SECONDS_MAX;

   if (!Http2Enabled) return;
             
   VmHttp2Init ();
}

/*****************************************************************************/
/*
Called by WatchSetWatch().
*/

void Http2SetWatch
(
HTTP2_STRUCT *h2ptr,
int item
)
{
   h2ptr->WatchItem = item;

   if (h2ptr->NetIoPtr)
      if (h2ptr->NetIoPtr->SesolaPtr)
         SesolaSetWatch (h2ptr->NetIoPtr->SesolaPtr, item);
      else
         h2ptr->NetIoPtr->WatchItem = item;
}

/*****************************************************************************/
/*
This is a TLS/SSL (https:) encrypted connection that has specified HTTP/2 via
the TLS ALPN negotiation and transmitted the HTTP/2 client connection preface
(RFC 7540 3.3) --OR-- a clear-text (http:) connection prior-knowledge (RFC 7540
3.4) connection preface.  Create an HTTP/2 structure and then dispose of the
request structure used to initiate the network connection.
*/

BOOL Http2Preface (REQUEST_STRUCT *rqptr)

{
   int  count, idx;
   HTTP2_STRUCT  *h2ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2,
                 "Http2Preface() preface:!UL read:!UL http2:!UL",
                 Http2ClientPrefaceLength, rqptr->NetIoPtr->ReadCount,
                 rqptr->NetIoPtr->ReadCount - Http2ClientPrefaceLength);

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                 "HTTP/2 connection preface (h2)");

   if (!Http2Enabled) return (false);

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 protocol");

   h2ptr = Http2Create (rqptr);

   /* if HTTP/2 traffic has been pipelined with the preface */
   if ((count = rqptr->NetIoPtr->ReadCount - Http2ClientPrefaceLength) > 0)
   {
      if (count > h2ptr->ReadBufferSize)
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      memcpy (h2ptr->ReadBufferPtr,
              rqptr->rqNet.ReadBufferPtr + Http2ClientPrefaceLength,
              count);
      h2ptr->NetIoPtr->ReadStatus = SS$_NORMAL;
      h2ptr->NetIoPtr->ReadCount = count;
      SysDclAst (Http2NetClientReadAst, h2ptr);
   }
   else
      Http2NetClientRead (h2ptr);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateConnected (rqptr, -1);
   NetUpdateConnected (h2ptr, +1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   RequestEnd5 (rqptr);

   return (true);
}

/****************************************************************************/
/*
This is an unencrypted connection (http:) with request header indicating a
desire to upgrade to HTTP/2 (RFC 7540 3.2).  Major browser communities indicate
no interest in providing HTTP/2 over clear-text connections so it's a bit of a
niche but supported by the RFC.  Generate an HTTP/2 101 Switching Protocols
response and then handle the original request in the HTTP/2 environment. 
Exercise using nghttp -nvu http://<host.name>/
*/

BOOL Http2SwitchResponse (REQUEST_STRUCT *rqptr)

{
   static char  Http2Switching [] =
"HTTP/1.1 101 Switching Protocols\r\n\
Upgrade: h2c\r\n\
Connection: Upgrade\r\n\
\r\n";

   int  retval,
        SettingsLength;
   char  *cptr;
   uchar  SettingsBuffer [256];
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   NETIO_STRUCT  *ioptr;


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

   if (WATCHMOD (rqptr, WATCH_MOD_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_RESPONSE,
                 "Http2SwitchResponse()");

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 upgrade");

   if (!Http2Enabled) return (false);

   if (rqptr->rqHeader.UpgradeHttp2onHttp &&
       rqptr->ServicePtr->RequestScheme != SCHEME_HTTP)
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      RequestEnd (rqptr);
      return (true);
   }

   if (rqptr->rqHeader.Http2SettingsPtr != NULL)
   {
      /* span the length of base-64 acceptable characters */
      for (cptr = rqptr->rqHeader.Http2SettingsPtr;
           isalnum(*cptr) || *cptr == '+' || *cptr == '/' || *cptr == '=';
           *cptr++);
      SettingsLength = sizeof(SettingsBuffer);
      retval = base64_decode (SettingsBuffer, &SettingsLength,
                              rqptr->rqHeader.Http2SettingsPtr,
                              cptr - rqptr->rqHeader.Http2SettingsPtr);
   }

   if (rqptr->rqHeader.Http2SettingsPtr == NULL || retval != 0)
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      RequestEnd (rqptr);
      return (true);
   }

   /****************/
   /* 101 response */
   /****************/

   /* no update to status code counters as this is an intermediate response */
   rqptr->rqResponse.HttpStatus = 101;
   rqptr->rqResponse.HeaderSent = true;

   /* blocking write (for the convenience) */
   NetWrite (rqptr, NULL, Http2Switching, sizeof(Http2Switching)-1);

   rqptr->rqResponse.HttpStatus = 0;
   rqptr->rqResponse.HeaderSent = false;

   /******************/
   /* HTTP/2 request */
   /******************/

   if (WATCHING (rqptr, WATCH_CONNECT))
      WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "HTTP/2 protocol");

   /* these that got us here are definitely no longer the case */
   DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "connection", 10);
   DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "http2-settings", 14);
   DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "upgrade", 7);

   /* and the associated flags */
   rqptr->rqHeader.UpgradeHttp2onHttp = false;
   rqptr->rqHeader.ConnectionHttp2Settings = false;
   rqptr->rqHeader.ConnectionUpgrade = false;

   /* see Http2RequestBegin() for parallels with what's done here */

   h2ptr = Http2Create (rqptr);

   h2ptr->ExpectingH2cPreface = true;

   /* attach the request to the HTTP/2 stream */
   Http2RequestBegin2 (rqptr, h2ptr, 1);

   Http2RequestProcess (rqptr);

   Http2NetClientRead (h2ptr);

   /* adjust the connected from the request to the HTTP/2 connection */
   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateProcessing (rqptr, -1);
   NetUpdateConnected (rqptr, -1);
   NetUpdateConnected (h2ptr, +1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   return (true);
}

/*****************************************************************************/
/*
This is where the real fun begins.  Create and initialise an HTTP/2 structure,
using some data from the request structure.  Return a pointer to the structure.
*/

HTTP2_STRUCT* Http2Create (REQUEST_STRUCT *rqptr)

{
   HTTP2_STRUCT  *h2ptr;

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

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

   h2ptr = VmHttp2Get (rqptr->ConnectNumber);

   /* add to the HTTP/2 connection list */
   ListAddHead (&Http2List, h2ptr, LIST_ENTRY_TYPE_HTTP2);

   h2ptr->NetIoPtr = rqptr->NetIoPtr;
   h2ptr->WatchItem = rqptr->WatchItem;
   h2ptr->NetIoPtr->WatchItem = h2ptr->WatchItem;
   PUT_QUAD_QUAD (&rqptr->rqTime.BeginTime64, h2ptr->ConnectTime64);

   /* always use original client data (see MapUrl_SetClientAddress()) */
   if (rqptr->ClientResetPtr)
      h2ptr->ClientPtr = rqptr->ClientResetPtr;
   else
      h2ptr->ClientPtr = rqptr->ClientPtr;

   /* use the same service of the upgrade request */
   h2ptr->ServicePtr = rqptr->ServicePtr;

   /* intialise server protocol parameters */
   h2ptr->ServerInitialWindowSize = Http2InitialWindowSize;
   h2ptr->ServerMaxConcStreams = Http2MaxConcurrentStreams;
   h2ptr->ServerMaxFrameSize = Http2MaxFrameSize;
   h2ptr->ServerMaxHeaderListSize = Http2MaxHeaderListSize;
   h2ptr->ServerMaxHeaderTableSize = Http2MaxHeaderTableSize;
   h2ptr->ServerPushPromise = 0;

   /* intialise client protocol parameters */
   h2ptr->ClientInitialWindowSize = HTTP2_INITIAL_WINDOW_SIZE;
   h2ptr->ClientMaxConcStreams = HTTP2_INITIAL_MAX_CONC_STREAMS;
   h2ptr->ClientMaxFrameSize = HTTP2_INITIAL_MAX_FRAME_SIZE;
   h2ptr->ClientMaxHeaderListSize = HTTP2_INITIAL_HEAD_LIST_SIZE;
   h2ptr->ClientMaxHeaderTableSize = HTTP2_INITIAL_HEAD_TAB_SIZE;
   h2ptr->ClientPushPromise = 0;

   /* HPACK initialisation */
   h2ptr->HpackClientTable.max = Http2MaxHeaderTableSize;
   h2ptr->HpackServerTable.max = Http2MaxHeaderTableSize;
   h2ptr->HpackClientTable.h2ptr = h2ptr->HpackServerTable.h2ptr = h2ptr;

   /* initial flow control */
   h2ptr->ReadWindowSize == h2ptr->ServerInitialWindowSize;
   h2ptr->WriteWindowSize = h2ptr->ClientInitialWindowSize;

   /* send server settings */
   Http2Settings (h2ptr, 0, NULL, 0);

   /* set the initial connection flow-control window size */
   h2ptr->ReadWindowSize = h2ptr->ServerInitialWindowSize *
                           h2ptr->ServerMaxConcStreams;
   Http2WindowUpdate (h2ptr, 0, h2ptr->ReadWindowSize, NULL, 0);

   /* buffer for multiplexed reads */
   h2ptr->ReadBufferSize = h2ptr->ServerMaxFrameSize + 128;
   h2ptr->ReadBufferPtr = VmGet2Heap (h2ptr, h2ptr->ReadBufferSize);

   return (h2ptr);
}

/*****************************************************************************/
/*
Close the HTTP/2 protocol connection with the client.  If there are still
requests associated with the connection wait for them to end (based on
delivered CANCELed I/O).  If there's still HTTP/2 I/O in progress wait for it
to rundown.  Then check for any connection management writes still queued. 
Finally a check for straggling connection I/O in progress.  After that the
connection is removed from the list and the associated memory released in an
indepedent AST.  This function should only be called the once by HTTP/2
processing code when the channel number is non-zero.  After that the
Http2Supervisor() calls it every second until all the above conditions are met. 
Once removed from the connection list it's gone!
*/

void Http2CloseConnection (HTTP2_STRUCT *h2ptr)

{
   BOOL  OneShot;
   int  idx;
   HTTP2_STREAM_STRUCT  *s2ptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
"Http2CloseConnection() !&F chan:!UL list:!UL read:!&B write:!&B",
                 Http2CloseConnection,
                 h2ptr->NetIoPtr->Channel,
                 LIST_GET_COUNT (&h2ptr->StreamList),
                 NETIO_READ_IN_PROGRESS(h2ptr->NetIoPtr),
                 NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr));

   h2ptr->NetIoPtr->VmsStatus = SS$_VCCLOSED;

   /* if there are still requests associated with this connection */
   if (LIST_NOT_EMPTY (&h2ptr->StreamList))
   {
      for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList);
           s2ptr != NULL;
           s2ptr = LIST_GET_NEXT(s2ptr))
      {
         rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr;
         if (rqeptr->RequestState >= REQUEST_STATE_ABORT)
            Http2RequestCancel (rqeptr);
         else
         {
            rqeptr->Http2Stream.State = HTTP2_STATE_CLOSED;
            rqeptr->NetIoPtr->VmsStatus = SS$_VCCLOSED;
            RequestAbort (rqeptr);
         }
      }
      return;
   }

   /* if there are still queued writes waiting */
   for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++)
      if (LIST_NOT_EMPTY (&h2ptr->QueuedWriteList[idx])) return;

   /* any last gasp I/O still in progress */
   if (NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)) return;
   if (NETIO_READ_IN_PROGRESS(h2ptr->NetIoPtr))
   {
      sys$cancel (h2ptr->NetIoPtr->Channel);
      return;
   }

   /***********/
   /* finally */
   /***********/

   if (WATCHPNT(h2ptr))
   {
      if (WATCH_CATEGORY(WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, 
"FRAMES tx:!UL rx:!UL REQUESTS tx:!UL rx:!UL total:!UL peak:!UL",
                    h2ptr->FrameCountTx, h2ptr->FrameCountRx,
                    h2ptr->FrameRequestCountTx, h2ptr->FrameRequestCountRx,
                    h2ptr->RequestCount, h2ptr->RequestPeak);

      if (WATCH_CATEGORY(WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 closed");
      else
      if (WATCH_CATEGORY(WATCH_CONNECT))
         WatchThis (WATCHITM(h2ptr), WATCH_CONNECT, "HTTP/2 closed");
   }

   NetIoEnd (h2ptr->NetIoPtr);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetUpdateConnected (h2ptr, -1);
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   /* remove from the HTTP/2 connection list */
   ListRemove (&Http2List, h2ptr);

   VmFree2Heap (h2ptr, FI_LI);

   /* if this HTTP/2 connection was being (one-shot) WATCHed */
   OneShot = h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG;

   VmHttp2Free (h2ptr, FI_LI);

   if (OneShot) SysDclAst (RequestEnd, Watch.RequestPtr); 
}

/*****************************************************************************/
/*
An HTTP/2 error has been encountered.  Signal this to the client as a non-fatal
stream error or a fatal connection error.  Return zero to continue, non-zero to
abort the connection.
*/

int Http2Error
(
HTTP2_STRUCT *h2ptr,
uint ident,
uint error
)
{
   int  retval;
   char  *cptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2Error() !SL", error);

   if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchError (h2ptr, error);

   switch (error)
   {
      /* stream oriented non-fatal errors */
      case HTTP2_ERROR_NONE :
      case HTTP2_ERROR_REFUSED :
      case HTTP2_ERROR_CANCEL :
      case HTTP2_ERROR_CALM :
      case HTTP2_ERROR_HTTP11 :
           retval = Http2ResetStream (h2ptr, ident, 0, NULL, 0);
           if (retval >= 0) return (0);
           Http2GoAway (h2ptr, -(int)retval, NULL, 0);
           return (HTTP2_ERROR_INTERNAL);
           break;
      /* connection breaking errors */
      case HTTP2_ERROR_PROTOCOL :
      case HTTP2_ERROR_INTERNAL :
      case HTTP2_ERROR_FLOW :
      case HTTP2_ERROR_TIMEOUT :
      case HTTP2_ERROR_CLOSED :
      case HTTP2_ERROR_SIZE :
      case HTTP2_ERROR_COMPRESS :
      case HTTP2_ERROR_CONNECT :
      case HTTP2_ERROR_SECURITY :
           Http2GoAway (h2ptr, error, NULL, 0);
           return (error);
           break;
      default :
           Http2GoAway (h2ptr, HTTP2_ERROR_INTERNAL, NULL, 0);
           return (HTTP2_ERROR_INTERNAL);
   }
}

/*****************************************************************************/
/*
Send and receive GOAWAY frames.
*/

int Http2GoAway
(
HTTP2_STRUCT *h2ptr,
uint error,
uchar *bptr,
uint blen
)
{
   uint  size, stream;
   uchar  *cptr, *czptr, *sptr, *zptr;
   uchar  buf [256];
   HTTP2_WRITE_STRUCT  *w2ptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2GoAway() bptr:!8XL blen:!UL", blen);

   if (bptr != NULL)
   {
      /**********************/
      /* goaway from client */
      /**********************/

      if (blen < 8) return (-(HTTP2_ERROR_SIZE));

      if (WATCHING (h2ptr, WATCH_HTTP2))
         Http2WatchGoaway (h2ptr, bptr, blen);

      HTTP2_GET_32 (bptr, stream);
      stream &= 0x7fffffff;
      HTTP2_GET_32 (bptr, error);
      zptr = (sptr = buf) + sizeof(buf)-1;
      if (blen > 8)
      {
         czptr = (cptr = bptr) + blen - 8;
         while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++;
      }
      *sptr = '\0';

      if (stream) return (-(HTTP2_ERROR_PROTOCOL));

      h2ptr->GoAwayLastStreamIdent = stream;
   }
   else
   {
      /********************/
      /* goaway to client */
      /********************/

      h2ptr->GoAwayIdent = h2ptr->LastStreamIdent;

      if (!bptr) bptr = "";
      for (cptr = bptr; *cptr; cptr++);
      size = cptr - bptr;
      if (size <= (HTTP2_WRITE_PAYLOAD - 8)) size = 0;

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

      sptr = bptr;
      bptr = w2ptr->payload;
      HTTP2_PUT_32 (bptr, h2ptr->GoAwayIdent);
      HTTP2_PUT_32 (bptr, error);
      while (*bptr) *bptr++ = *sptr++;
      *bptr = '\0';

      HTTP2_PLACE_24 (w2ptr->length, bptr - w2ptr->payload);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_GOAWAY);
      HTTP2_PLACE_8  (w2ptr->flags, 0);
      HTTP2_PLACE_32 (w2ptr->ident, 0);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->AstParam = h2ptr;
      /* send the goaway frame and once sent close the connection */
      w2ptr->AstFunction = Http2CloseConnection;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = bptr - w2ptr->payload;

      if (WATCHING (h2ptr, WATCH_HTTP2))
         Http2WatchGoaway (h2ptr, w2ptr->DataPtr, w2ptr->DataLength);

      Http2NetQueueWrite (h2ptr, w2ptr);
   }

   /* if not gone after this period then close the connection anyway */
   h2ptr->GoAwaySecond = HttpdTickSecond + HTTP2_TIMEOUT_GOAWAY_SECONDS;

   return (0);
}

/*****************************************************************************/
/*
Send and receive PING frames.
*/

int Http2Ping
(
HTTP2_STRUCT *h2ptr,
uint flags,
uchar *bptr,
uint blen
)
{
   uchar  *sptr;
   ulong  ResultTime64 [QUAD2];
   HTTP2_WRITE_STRUCT  *w2ptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2Ping() blen:!UL", blen);

   if (bptr != NULL)
   {
      /********************/
      /* ping from client */
      /********************/

      if (blen != 8) return (-(HTTP2_ERROR_PROTOCOL));

      if (flags & HTTP2_FLAG_PING_ACK)
      {
         /*******************/
         /* ack from client */
         /*******************/

         /* client acknowledging server ping */
         if (QUAD_EQUAL_QUAD (bptr, h2ptr->PingTime64))
         {
            /* the number of 100 nano-seconds (up to 100 seconds) */
            sys$gettim (ResultTime64);
            SUB_QUAD_QUAD (h2ptr->PingTime64, ResultTime64);
            if (ResultTime64[1])
               (int)h2ptr->PingMicroSeconds = -1;
            else
               h2ptr->PingMicroSeconds = ResultTime64[0] / 10;
         }
         else
            (int)h2ptr->PingMicroSeconds = -1;

         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                       (int)h2ptr->PingMicroSeconds == -1 ?
                          "PING client ERROR" :
                          "PING !UL.!3ZLmS",
                       h2ptr->PingMicroSeconds / 1000,
                       h2ptr->PingMicroSeconds % 1000);

         h2ptr->PingBackTickSecond = 0;
         PUT_ZERO_QUAD (h2ptr->PingTime64);
         return (0);
      }

      /*****************/
      /* ack to client */
      /*****************/

      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                    "PING server ACKNOWLEDGE");

      /* mitigate CVE-2019-9512 */
      if (h2ptr->PingLimitCount++ > HTTP2_PING_LIMIT_COUNT)
      {
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                       "LIMIT client ping to !UL/S (DoS?)",
                       HTTP2_PING_LIMIT_COUNT);
         return (-(HTTP2_ERROR_CALM));
      }

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

      /* replay the client payload */
      sptr = w2ptr->payload;
      while (blen--) *sptr++ = *bptr++;

      HTTP2_PLACE_24 (w2ptr->length, 8);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_PING);
      HTTP2_PLACE_8  (w2ptr->flags, HTTP2_FLAG_PING_ACK);
      HTTP2_PLACE_32 (w2ptr->ident, 0);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = 8;
   }
   else
   {
      /******************/
      /* ping to client */
      /******************/

      /* only one outstanding with the specified timeout */
      if (h2ptr->PingBackTickSecond)
         if (HttpdTickSecond < h2ptr->PingBackTickSecond)
            return (0);

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

      /* generate and note the ping time */
      sys$gettim (h2ptr->PingTime64);
      PUT_QUAD_QUAD (h2ptr->PingTime64, w2ptr->payload);
      h2ptr->PingBackTickSecond = HttpdTickSecond + HTTP2_PING_RESPONSE_MAX;

      HTTP2_PLACE_24 (w2ptr->length, 8);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_PING);
      HTTP2_PLACE_8  (w2ptr->flags, 0);
      HTTP2_PLACE_32 (w2ptr->ident, 0);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = 8;

      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "PING to client");
   }

   Http2NetQueueWrite (h2ptr, w2ptr);

   return (0);
}

/*****************************************************************************/
/*
HTTP/2 priority/dependency is not currently implemented.
Weight is weight plus one, 1..256.
*/

int Http2Priority
(
HTTP2_STRUCT *h2ptr,
uint flags,
uchar *bptr,
uint blen
)
{
   uint  stream, value, weight;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2Priority() blen:!UL", blen);

   if (blen != 5) return (-(HTTP2_ERROR_SIZE));

   HTTP2_GET_32 (bptr, stream);
   HTTP2_GET_8 (bptr, weight);

   return (0);
}

/*****************************************************************************/
/*
Mark the stream as closed.  When the server closes a stream (as when a request
times out) send a RST_STREAM frame to the client.
*/

int Http2ResetStream
(
HTTP2_STRUCT *h2ptr,
uint ident,
uint error,
uchar *bptr,
uint blen
)
{
   HTTP2_STREAM_STRUCT  *s2ptr;
   HTTP2_WRITE_STRUCT  *w2ptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2ResetStream() blen:!UL ident:!UL error:!UL",
                 blen, ident, error);

   if (ident == 0) return (-(HTTP2_ERROR_PROTOCOL));

   /* 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)
   {
      /* no stream found with that ident */
      if (ident > h2ptr->LastStreamIdent) return (-(HTTP2_ERROR_PROTOCOL));
      /* could be a hangover from a previous (closed) stream so ignore */
      return (HTTP2_ERROR_NONE);
   }

   if (bptr != NULL)
   {
      /*******************/
      /* reset by client */
      /*******************/

      if (blen != 4) return (-(HTTP2_ERROR_SIZE));

      /* RFC 7540 6.4 */
      if (s2ptr->State == HTTP2_STATE_IDLE) return (-(HTTP2_ERROR_PROTOCOL));

      /* mitigate CVE-2019-9514 */
      if (h2ptr->StreamResetLimitCount++ > HTTP2_STREAM_RESET_LIMIT_COUNT)
      {
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                       "LIMIT server stream resets to !UL/S (DoS?)",
                       HTTP2_STREAM_RESET_LIMIT_COUNT);
         return (-(HTTP2_ERROR_CALM));
      }

      s2ptr->State = HTTP2_STATE_CLOSED_REM;

      RequestAbort (rqeptr);
   }
   else
   {
      /*******************/
      /* reset by server */
      /*******************/

      if (s2ptr->State == HTTP2_STATE_CLOSED_REM ||
          s2ptr->State == HTTP2_STATE_CLOSED_LOC)
         s2ptr->State = HTTP2_STATE_CLOSED;
      else
         s2ptr->State = HTTP2_STATE_CLOSED_LOC;

      if (h2ptr->NetIoPtr->VmsStatus) s2ptr->State = HTTP2_STATE_CLOSED;

      if (s2ptr->State != HTTP2_STATE_CLOSED)
      {
         w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT));

         HTTP2_PLACE_24 (w2ptr->length, 4);
         HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_RST_STREAM);
         HTTP2_PLACE_8  (w2ptr->flags, 0);
         HTTP2_PLACE_32 (w2ptr->ident, ident);

         w2ptr->Http2Ptr = h2ptr;
         w2ptr->HeaderPtr = w2ptr->header;
         w2ptr->DataPtr = w2ptr->payload;
         w2ptr->DataLength = 4;
         HTTP2_PLACE_32 (w2ptr->payload, error);

         Http2NetQueueWrite (h2ptr, w2ptr);
      }
   }

   return (HTTP2_ERROR_NONE);
}

/*****************************************************************************/
/*
Send and receive HTTP/2 settings.
*/

int Http2Settings
(
HTTP2_STRUCT *h2ptr,
uint flags,
uchar *bptr,
uint blen
)
{
   uint  count, setting, value;
   uchar  *aptr;
   HTTP2_WRITE_STRUCT  *w2ptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2Settings() flags:0x!2XL len:!UL (!UL)",
                 flags, blen, blen / 6);

   if (bptr != NULL)
   {
      /***************/
      /* from client */
      /***************/

      /* all setting are in multiples of 6 octets */
      if (blen % 6) return (-(HTTP2_ERROR_SIZE));
      count = blen / 6;

      if (flags & HTTP2_FLAG_SETTINGS_ACK)
      {
         /**************/
         /* client ack */
         /**************/

         /* payload must be empty */
         if (count) return (-(HTTP2_ERROR_PROTOCOL));
         return (0);
      }

      /*********************/
      /* client setting(s) */
      /*********************/

      if (WATCHING (h2ptr, WATCH_HTTP2))
         Http2WatchSettings (h2ptr, "client", bptr, blen);

      /* mitigate CVE-2019-9515 */
      if (h2ptr->SettingsLimitCount++ > HTTP2_SETTINGS_LIMIT_COUNT)
      {
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                       "LIMIT client settings to !UL/S (DoS?)",
                       HTTP2_SETTINGS_LIMIT_COUNT);
         return (-(HTTP2_ERROR_CALM));
      }

      while (count--)
      {
         HTTP2_GET_8 (bptr, setting);
         HTTP2_GET_32 (bptr, value);
         /* process client requested settings */

         switch (setting)
         {
            case HTTP2_SETTING_MAX_HEAD_TABLE_SIZE :
                 h2ptr->ClientMaxHeaderTableSize = value;
                 h2ptr->HpackClientTable.max = value;
                 break;

            case HTTP2_SETTING_ENABLE_PUSH :
                 h2ptr->ClientPushPromise = value;
                 break;

            case HTTP2_SETTING_MAX_CONC_STREAMS :
                 h2ptr->ClientMaxConcStreams = value;
                 break;

            case HTTP2_SETTING_INIT_WIN_SIZE :
                 Http2FlowControl (h2ptr, value);
                 break;

            case HTTP2_SETTING_MAX_FRAME_SIZE :
                 h2ptr->ClientMaxFrameSize = value;
                 break;

            case HTTP2_SETTING_MAX_HEAD_LIST_SIZE :
                 h2ptr->ClientMaxHeaderListSize = value;
                 break;

            default :
                 /* ignore unknown settings RFC 7540 6.5.2 */
         }
      }


      /*****************/
      /* ack to client */
      /*****************/

      if (WATCHING (h2ptr, WATCH_HTTP2))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                    "SETTINGS server ACKNOWLEDGE");

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

      HTTP2_PLACE_24 (w2ptr->length, 0);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_SETTINGS);
      HTTP2_PLACE_8  (w2ptr->flags, HTTP2_FLAG_SETTINGS_ACK);
      HTTP2_PLACE_32 (w2ptr->ident, 0);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = 0;
   }
   else
   {
      /*************/
      /* to client */
      /*************/

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

      bptr = w2ptr->payload;

      HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_HEAD_TABLE_SIZE);
      HTTP2_PUT_32 (bptr, h2ptr->ServerMaxHeaderTableSize);

      HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_CONC_STREAMS);
      HTTP2_PUT_32 (bptr, h2ptr->ServerMaxConcStreams);

      HTTP2_PUT_16 (bptr, HTTP2_SETTING_INIT_WIN_SIZE);
      HTTP2_PUT_32 (bptr, h2ptr->ServerInitialWindowSize);

      HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_FRAME_SIZE);
      HTTP2_PUT_32 (bptr, h2ptr->ServerMaxFrameSize);

      HTTP2_PUT_16 (bptr, HTTP2_SETTING_MAX_HEAD_LIST_SIZE);
      HTTP2_PUT_32 (bptr, h2ptr->ServerMaxHeaderListSize);

      HTTP2_PLACE_24 (w2ptr->length, bptr - w2ptr->payload);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_SETTINGS);
      HTTP2_PLACE_8  (w2ptr->flags, 0); 
      HTTP2_PLACE_32 (w2ptr->ident, 0);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = bptr - w2ptr->payload;

      if (WATCHING (h2ptr, WATCH_HTTP2))
         Http2WatchSettings (h2ptr, "server", w2ptr->DataPtr,
                                              w2ptr->DataLength);
   }

   Http2NetQueueWrite (h2ptr, w2ptr);

   return (0);
}

/*****************************************************************************/
/*
Client setting change to initial window size initiates connection-wide flow
control window adjustment.  The resulting window size can be negative (RFC 7540
6.9.2).  Blocked flow control may now resume, or vice-versa.
*/

void Http2FlowControl
(
HTTP2_STRUCT *h2ptr,
uint value
)
{
   int  adjust;
   HTTP2_STREAM_STRUCT  *s2ptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2FlowControl() !UL", value);

   adjust = -(h2ptr->ClientInitialWindowSize - value);

   for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList);
        s2ptr != NULL;
        s2ptr = LIST_GET_NEXT(s2ptr))
      s2ptr->WriteWindowSize += adjust;

   h2ptr->ClientInitialWindowSize = value;

   /* resume writing if flow control was blocking and now permits */
   Http2NetQueueWrite (h2ptr, NULL);
}

/*****************************************************************************/
/*
Flow control feedback from the client.
*/

int Http2WindowUpdate
(
HTTP2_STRUCT *h2ptr,
uint ident,
uint update,
uchar *bptr,
uint blen
)
{
   uint  size;
   HTTP2_STREAM_STRUCT  *s2ptr;
   REQUEST_STRUCT  *rqeptr;
   HTTP2_WRITE_STRUCT  *w2ptr;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2,
                 "Http2WindowUpdate() blen:!UL ident:!UL", blen, ident);

   if (bptr != NULL)
   {
      /***************/
      /* from client */
      /***************/

      if (blen != 4) return (-(HTTP2_ERROR_SIZE));

      HTTP2_GET_32 (bptr, update);

      if (WATCHING (h2ptr, WATCH_HTTP2) &&
          !WATCHING1S(h2ptr) && !WATCHING2(h2ptr))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                    "WINDOW client ident:!UL update:!UL", ident, update);

      if (update == 0 || update & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL));

      if (ident)
      {
         /**********/
         /* stream */
         /**********/

         /* 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)
         {
            /* no stream found with that ident */
            if (ident > h2ptr->LastStreamIdent)
               return (-(HTTP2_ERROR_PROTOCOL));
   
            /* could be a hangover from a previous (closed) stream so ignore */
            return (HTTP2_ERROR_NONE);
         }

         size = (s2ptr->WriteWindowSize += update);
      }
      else
      {
         /**************/
         /* connection */
         /**************/

         size = (h2ptr->WriteWindowSize += update);
      }

      /* the most significant bit indicates overflow */
      if (size & 0x80000000) return (-(HTTP2_ERROR_PROTOCOL));

      /* resume writing if flow control was blocking and now permits */
      if (!NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr))
         Http2NetQueueWrite (h2ptr, NULL);
   }
   else
   {
      /*************/
      /* to client */
      /*************/

      if (WATCHING (h2ptr, WATCH_HTTP2) &&
                    !WATCHING1S(h2ptr) && !WATCHING2(h2ptr))
         WatchThis (WATCHITM(h2ptr), WATCH_HTTP2,
                    "WINDOW server ident:!UL update:!UL", ident, update);

      if (update == 0 || update & 0x80000000)
      {
         /* note and ignore */
         ErrorNoticed (NULL, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         return (0);
      }

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

      HTTP2_PLACE_24 (w2ptr->length, 4);
      HTTP2_PLACE_8  (w2ptr->type, HTTP2_FRAME_WINDOW_UPDATE);
      HTTP2_PLACE_8  (w2ptr->flags, 0);
      HTTP2_PLACE_32 (w2ptr->ident, ident);
      HTTP2_PLACE_32 (w2ptr->payload, update);

      w2ptr->Http2Ptr = h2ptr;
      w2ptr->HeaderPtr = w2ptr->header;
      w2ptr->DataPtr = w2ptr->payload;
      w2ptr->DataLength = 4;

      Http2NetQueueWrite (h2ptr, w2ptr);
   }

   return (0);
}

/*****************************************************************************/
/*
Return a pointer to a string respresenting the supplied error code.
*/

char* Http2ErrorString (int error)

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

   if (error > HTTP2_ERROR_COUNT) return ("*UNKNOWN*");
   return (Http2ErrorArray[error]);
}

/*****************************************************************************/
/*
Scan the HTTP/2 connection list for connections that have been idle for the
timeout period and close them down elegantly.
*/

BOOL Http2Supervisor ()

{
   HTTP2_STRUCT  *h2ptr, *next2;

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

/**
   if (WATCH_MODULE(WATCH_MOD_HTTP2))
      WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Supervisor()");
**/

   /* process the connection list from least to most recent */
   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 (h2ptr->IdleSecond &&
          h2ptr->IdleSecond < HttpdTickSecond)
      {
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT idle");
         Http2CloseConnection (h2ptr);
         continue;
      }

      if (h2ptr->GoAwaySecond &&
          h2ptr->GoAwaySecond < HttpdTickSecond)
      {
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT goaway");
         Http2CloseConnection (h2ptr);
         continue;
      }

      if (h2ptr->NetIoPtr->VmsStatus)
      {
         /* with each call check if the connection can be closed */
         Http2CloseConnection (h2ptr);
         continue;
      }

      if (h2ptr->GoAwayLastStreamIdent)
      {
         /* connection has been told to goaway */
         Http2CloseConnection (h2ptr);
         continue;
      }

      if (h2ptr->PingBackTickSecond &&
          h2ptr->PingBackTickSecond < HttpdTickSecond)
      {
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIMEOUT ping");
         h2ptr->PingBackTickSecond = 0;
      }
      else
      if (Http2PingTimerSeconds &&
          (h2ptr->PingSendTickSecond == 0 ||
           h2ptr->PingSendTickSecond < HttpdTickSecond))
      {
         /* send a ping every so-many seconds */
         if (WATCHING (h2ptr, WATCH_HTTP2))
            WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "TIME to ping");
         Http2Ping (h2ptr, 0, NULL, 0);
         h2ptr->PingSendTickSecond = HttpdTickSecond + Http2PingTimerSeconds;
      }

      /* reset DoS limit counters */
      h2ptr->EmptyFrameLimitCount =
         h2ptr->PingLimitCount =
         h2ptr->SettingsLimitCount =
         h2ptr->StreamResetLimitCount =
         h2ptr->ZeroHeaderLimitCount = 0;
   }

   return (LIST_NOT_EMPTY(&Http2List));
}

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

void Http2Report (REQUEST_STRUCT *rqptr)

{
   static char  PageBeginFao [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtrght\">\n\
\
<tr><td></td>\
<th class=\"sbttl\" style=\"width:5em;padding-left:1em;\">HTTP/2</th><td></td>\
<th class=\"sbttl\">HTTP/1.<i>n</i></th><td></td>\
</tr>\n\
\
<tr><th>Requests&nbsp;&nbsp;/Total:</th><td>!&L</td><td>(!UL%)</td>\
<td>!&L</td><td>(!UL%)</td>\
\
<tr><th>/Peak:</th><td>!&L</td><td></td><td>!&L</td></tr>\n\
\
<tr><th>Bytes&nbsp;&nbsp;/Min:</th>\
<td>!&,@SQ</td></td><td><td>!&,@SQ</td></tr>\n\
<tr><th>/Max:</th><td>!&,@SQ</td><td></td><td>!&,@SQ</td></tr>\n\
<tr><th>/Ave:</th><td>!&L</td><td></td><td>!&L</td></tr>\n\
\
<tr><th>Duration&nbsp;&nbsp;/Min:</th>\
<td>!AZ</td></td><td><td>!AZ</td></tr>\n\
<tr><th>/Max:</th><td>!AZ</td><td></td><td>!AZ</td></tr>\n\
<tr><th>/Ave:</th><td>!AZ</td><td></td><td>!AZ</td></tr>\n\
\
<tr><th>Bytes/Sec&nbsp;&nbsp;/Min:</th>\
<td>!&L</td></td><td><td>!&L</td></tr>\n\
<tr><th>/Max:</th><td>!&L</td><td></td><td>!&L</td></tr>\n\
<tr><th>/Ave:</th><td>!&L</td><td></td><td>!&L</td></tr>\n\
\
<tr><th style=\"padding-left:0;\">\
Frames&nbsp;&nbsp;/Total:</th><td>!&,@SQ</td></tr>\n\
<tr><th>/Request:</th><td>!&,@SQ</td><td>(!UL%)</td></tr>\n\
<tr><th>/Average:</th><td>!&L</td></tr>\n\
<tr><th>/Flow Cntrl:</th><td>!&,@SQ</td><td>(!UL%)</td></tr>\n\
<tr><th>/Rx:</th><td>!&,@SQ</td></tr>\n\
<tr><th>/Tx:</th><td>!&,@SQ</td></tr>\n\
\
</table>\n\
</td></tr>\n\
</table>\n";

   /* the final column just adds a little white-space on the page far right */
   static char  Http2TableFao [] =
"<p><table class=\"rghtrght\">\n\
<tr><th colspan=\"4\"></th>\
<th colspan=\"3\" style=\"text-align:center;\"><u>Frames</u></th>\
<th colspan=\"2\" style=\"text-align:center;\"><u>Bytes</u></th>\
<th colspan=\"5\" style=\"text-align:center;\"><u>Write Queue</u></th>\
<th colspan=\"2\" style=\"text-align:center;\"><u>HPACK</u></th>\
<th colspan=\"3\" style=\"text-align:center;\"><u>Requests</u></th>\
</tr>\n\
<tr><th></th>\
<th class=\"sbttl\" style=\"text-align:left;\">Service</th>\
<th class=\"sbttl\" style=\"text-align:left;\">Client</th>\
<th class=\"sbttl\">Duration</th>\
<th class=\"sbttl\">Rx</th>\
<th class=\"sbttl\">Tx</th>\
<th class=\"sbttl\">FC</th>\
<th class=\"sbttl\">Rx</th>\
<th class=\"sbttl\">Tx</th>\
<th class=\"sbttl\">0</th>\
<th class=\"sbttl\">H</th>\
<th class=\"sbttl\">N</th>\
<th class=\"sbttl\">L</th>\
<th class=\"sbttl\">Pk</th>\
<th class=\"sbttl\">Rx</th>\
<th class=\"sbttl\">Tx</th>\
<th class=\"sbttl\">Tot</th>\
<th class=\"sbttl\">Pk</th>\
<th class=\"sbttl\">Cur</th>\
<th class=\"sbttl\">WATCH</th>\
<td style=\"width:99%\"></td>\
</tr>\n\
<tr height=\"5\"></tr>\n";

   /* the empty 99% column just forces the rest left */
   static char  Http2EntryFao [] =
"<tr!AZ>\
<th>!3ZL</th>\
<td style=\"text-align:left;\"><nobr>!AZ//!AZ</nobr></td>\
<td style=\"text-align:left;\"><nobr>!AZ,!UL</nobr></td>\
<td class=\"pdlft1\"><nobr>!AZ</nobr></td>\
<td class=\"pdlft1\">!&L</td>\
<td>!&L</td>\
<td>!UL</td>\
<td class=\"pdlft1\">!&,@SQ</td>\
<td>!&,@SQ</td>\
<td class=\"pdlft1\">!UL</td>\
<td>!UL</td>\
<td>!UL</td>\
<td>!UL</td>\
<td>!UL</td>\
<td class=\"pdlft1\"><a style=\"color:inherit;text-decoration:inherit;\" \
href=\"!AZ?http2=!UL\">!UL%</a></td>\
<td><a style=\"color:inherit;text-decoration:inherit;\" \
href=\"!AZ?http2=!UL\">!UL%</a></td>\
<td class=\"pdlft1\">!&L</td>\
<td>!UL</td>\
<td>!UL</td>\
<td class=\"pdlft1\">!&@</td>\
<td class=\"pshlft\"></td>\
</tr>\n";

   static char  Http2EmptyFao [] =
"<tr class=\"hlght\"><th>000</th>\
<td colspan=\"14\" style=\"text-align:left;\"><i>empty</i></td></tr>\n";

   static char  PageEndFao [] =
"</table>\n\
<p><table class=\"lftlft\">\n\
<tr><td>\n\
<form method=\"GET\" action=\"!AZ\">\n\
<input type=\"submit\" value=\" Purge Idle \">\n\
</form>\n\
</td><td>\n\
<form method=\"GET\" action=\"!AZ\">\n\
<input type=\"submit\" value=\" Force Disconnect \">\n\
</form>\n\
</td></tr>\n\
</table>\n\
!AZ\
</div>\n\
</body>\n\
</html>\n";

   int  idx, status,
        EntryCount,
        RequestHttpCount,
        RequestHttp1Count,
        RequestHttp2Count;
   ulong  Time64 [QUAD2],
          ConnectTime64 [QUAD2],
          FrameAverage [QUAD2],
          FrameRequest [QUAD2],
          FrameTotal [QUAD2];
   ulong  FaoVector [48];
   ulong  *vecptr;
   char  *cptr;
   HTTP2_STRUCT  *h2ptr;

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

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

   AdminPageTitle (rqptr, "HTTP Report");

   vecptr = FaoVector;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   RequestHttp1Count = acptr->RequestHttp11Count + acptr->RequestHttp10Count;
   RequestHttp1Count -= acptr->DoProxyCount;
   RequestHttp2Count = acptr->RequestHttp2Count;
   RequestHttpCount = RequestHttp1Count + RequestHttp2Count;

   *vecptr++ = RequestHttp2Count;
   *vecptr++ = PercentOf (RequestHttp2Count, RequestHttpCount);
   *vecptr++ = RequestHttp1Count;
   *vecptr++ = PercentOf (RequestHttp1Count, RequestHttpCount);
   *vecptr++ = acptr->ProcessingPeak[HTTP2];
   *vecptr++ = acptr->ProcessingPeak[HTTP1];

   *vecptr++ = acptr->BytesPerSecondMinBytes[HTTP2];
   *vecptr++ = acptr->BytesPerSecondMinBytes[HTTP1];
   *vecptr++ = acptr->BytesPerSecondMaxBytes[HTTP2];
   *vecptr++ = acptr->BytesPerSecondMaxBytes[HTTP1];
   if (acptr->RequestHttp2Count)
      *vecptr++ = (ulong)(FLOAT_FROM_QUAD(acptr->BytesPerSecondRawTotal[HTTP2]) /
                          (float)acptr->RequestHttp2Count);
   else
      *vecptr++ = 0;
   if (acptr->RequestHttp11Count + acptr->RequestHttp10Count)
      *vecptr++ = (ulong)(FLOAT_FROM_QUAD(acptr->BytesPerSecondRawTotal[HTTP1]) /
                          (float)(acptr->RequestHttp11Count +
                                  acptr->RequestHttp10Count));
   else
      *vecptr++ = 0;

   *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMin[HTTP2]);
   *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMin[HTTP1]);
   *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMax[HTTP2]);
   *vecptr++ = DurationString (rqptr, acptr->ResponseDurationMax[HTTP1]);
   *vecptr++ = AverageDurationString (rqptr,
                                      acptr->ResponseDuration[HTTP2],
                                      acptr->ResponseDurationCount[HTTP2]);
   *vecptr++ = AverageDurationString (rqptr,
                                      acptr->ResponseDuration[HTTP1],
                                      acptr->ResponseDurationCount[HTTP1]);

   *vecptr++ = acptr->BytesPerSecondMin[HTTP2];
   *vecptr++ = acptr->BytesPerSecondMin[HTTP1];
   *vecptr++ = acptr->BytesPerSecondMax[HTTP2];
   *vecptr++ = acptr->BytesPerSecondMax[HTTP1];
   *vecptr++ = acptr->BytesPerSecondAve[HTTP2];
   *vecptr++ = acptr->BytesPerSecondAve[HTTP1];

   PUT_QUAD_QUAD (acptr->Http2FrameCountRx, FrameTotal);
   ADD_QUAD_QUAD (acptr->Http2FrameCountTx, FrameTotal);
   *vecptr++ = FrameTotal;

   PUT_QUAD_QUAD (acptr->Http2FrameRequestCountRx, FrameRequest);
   ADD_QUAD_QUAD (acptr->Http2FrameRequestCountTx, FrameRequest);
   *vecptr++ = FrameRequest;
   *vecptr++ = QuadPercentOf (FrameRequest, FrameTotal);
   if (acptr->RequestHttp2Count)
      *vecptr++ = (ulong)(FLOAT_FROM_QUAD(FrameTotal) /
                  (float)acptr->RequestHttp2Count);
   else
      *vecptr++ = 0;
   *vecptr++ = acptr->Http2FlowControlCount;
   *vecptr++ = QuadPercentOf (acptr->Http2FlowControlCount,
                              acptr->Http2FlowFrameCount);

   *vecptr++ = acptr->Http2FrameCountRx;
   *vecptr++ = acptr->Http2FrameCountTx;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToNet (rqptr, PageBeginFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   /***********************/
   /* HTTP/2 list entries */
   /***********************/

   status = FaolToNet (rqptr, Http2TableFao, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   EntryCount = 0;
   sys$gettim (&Time64);

   /* process the request list from least to most recent */
   for (h2ptr = LIST_GET_HEAD(&Http2List);
        h2ptr != NULL;
        h2ptr = LIST_GET_NEXT(h2ptr))
   {
      EntryCount++;

      vecptr = FaoVector;

      status = lib$sub_times (&Time64, &h2ptr->ConnectTime64,
                              &ConnectTime64);
      if (VMSnok (status)) ZERO_DELTA_TIME (ConnectTime64);

      *vecptr++ = EntryCount % 2 ? " class=\"hlght\"" : "";
      *vecptr++ = EntryCount;
      *vecptr++ = h2ptr->ServicePtr->RequestSchemeNamePtr;
      *vecptr++ = h2ptr->ServicePtr->ServerHostPort;
      *vecptr++ = h2ptr->ClientPtr->Lookup.HostName;
      *vecptr++ = h2ptr->ClientPtr->IpPort;
      *vecptr++ = DurationString (rqptr, &ConnectTime64);
      *vecptr++ = h2ptr->FrameCountRx;
      *vecptr++ = h2ptr->FrameCountTx;
      *vecptr++ = h2ptr->FlowControlCount;

      *vecptr++ = &h2ptr->BytesRawRx;
      *vecptr++ = &h2ptr->BytesRawTx;

      for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++)
         *vecptr++ = LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx]);
      *vecptr++ = h2ptr->QueuedWritePeak;

      *vecptr++ = ADMIN_REPORT_HPACK;
      *vecptr++ = h2ptr->ConnectNumber;
      if (h2ptr->HpackClientOutputCount)
         *vecptr++ = (h2ptr->HpackClientInputCount * 100) /
                     h2ptr->HpackClientOutputCount;
      else
         *vecptr++ = 0;

      *vecptr++ = ADMIN_REPORT_HPACK;
      *vecptr++ = h2ptr->ConnectNumber;
      if (h2ptr->HpackServerInputCount)
         *vecptr++ = (h2ptr->HpackServerOutputCount * 100) /
                     h2ptr->HpackServerInputCount;
      else
         *vecptr++ = 0;

      *vecptr++ = h2ptr->RequestCount;
      *vecptr++ = h2ptr->RequestPeak;
      *vecptr++ = h2ptr->RequestCurrent;

      if (rqptr->Http2Stream.Http2Ptr != NULL &&
          rqptr->Http2Stream.Http2Ptr->ConnectNumber == h2ptr->ConnectNumber)
         *vecptr++ = "current";
      else
      {
         *vecptr++ =
"<a class=\"abttn bttn090 px1\" href=\"!AZ?at=!UL\">P</a>\
<a class=\"abttn bttn090 px1\" href=\"!AZ?at=!UL&this=!UL\">+</a>\
<a class=\"abttn bttn090 px1\" href=\"!AZ?this=!UL\">W</a>";
         *vecptr++ = ADMIN_REPORT_WATCH;
         *vecptr++ = h2ptr->ConnectNumber;
         *vecptr++ = ADMIN_REPORT_WATCH;
         *vecptr++ = h2ptr->ConnectNumber;
         *vecptr++ = h2ptr->ConnectNumber;
         *vecptr++ = ADMIN_REPORT_WATCH;
         *vecptr++ = h2ptr->ConnectNumber;
      }

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, Http2EntryFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   if (!EntryCount) FaolToNet (rqptr, Http2EmptyFao, NULL);

   /**************/
   /* end report */
   /**************/

   vecptr = FaoVector;

   *vecptr++ = ADMIN_CONTROL_HTTP2_PURGE;
   *vecptr++ = ADMIN_CONTROL_HTTP2_PURGE_ALL,
   *vecptr++ = AdminRefresh();

   FaolToNet (rqptr, PageEndFao, FaoVector);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", NULL);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
See NetTestBreak().
*/

#if WATCH_MOD

void Http2TestBreak (int every)

{
   int  count, total;
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   NETIO_STRUCT  *ioptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (every < 0) every = 1;
   count = total = 0;
   for (h2ptr = LIST_GET_HEAD(&Http2List);
        h2ptr != NULL;
        h2ptr = LIST_GET_NEXT(h2ptr))
   {
      if (++count % every) continue;
      ioptr = h2ptr->NetIoPtr;
      if (ioptr->Channel)
      {
         /* do not kill WATCHing request */
         rqeptr = (REQUEST_STRUCT*)-1;
         for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList);
              s2ptr != NULL;
              s2ptr = LIST_GET_NEXT(s2ptr))
         {
            rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr;
            if (rqeptr == Watch.RequestPtr) break;
         }
         if (rqeptr == Watch.RequestPtr) continue;

         WatchThis (WATCHITM(h2ptr), WATCH_NETWORK, "BREAK2 !AZ",
                    h2ptr->ServicePtr->ServerHostPort);
         sys$dassgn (ioptr->Channel);
         ioptr->Channel = 0;
         total++;

         for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList);
              s2ptr != NULL;
              s2ptr = LIST_GET_NEXT(s2ptr))
         {
            rqeptr = (REQUEST_STRUCT*)s2ptr->RequestPtr;
            if (rqeptr->rqHeader.RequestUriPtr)
               WatchThis (WATCHITM(rqeptr), WATCH_NETWORK, "BREAK2 !AZ !AZ !AZ",
                          rqeptr->ServicePtr->ServerHostPort,
                          rqeptr->rqHeader.MethodName,
                          rqeptr->rqHeader.RequestUriPtr);
            else
               WatchThis (WATCHITM(rqeptr), WATCH_NETWORK, "BREAK2 !AZ 0",
                          rqeptr->ServicePtr->ServerHostPort);
          }
      }
   }
   FaoToStdout ("%HTTPD-I-HTTP2, !%T, !UL/!UL/!UL broken\n",
                0, every, total, count);
}

#endif /* WATCH_MOD */

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

