/*****************************************************************************/ /* 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 #include #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 = h2ptr->NetIoPtr->TcpMaxQio; 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 ":" 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 ":" */ 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); } /*****************************************************************************/