/*****************************************************************************/ /* HTTP2net.c HTTP/2 network reads and writes. Reads are multiplexed onto the connection by the client and similarly multiplexed off at the server end. The frames read are then handed off to the appropriate processing function by frame type. Writes are serialised onto the connection using a FIFO queue. Well, four (prioritised) queues to be precise. Request (network) reads and writes have wrapper functions that interface the single HTTP/2 multiplexed connection to an emulation of the dedicated, per-request network connection. All request data being written to the network is required to be prepended with a 9 byte frame header. Without copying a lot of data between buffers this is implemented using two, independent writes; the header then the data. In an effort to reduce the overall number of such network writes (especially when transported by SSL), *all* WASD memory allocations have inbuilt space for an HTTP/2 frame header immediately preceding the returned memory pointer. This space has some (not very distant) magic (VM_MAGIC_HTTP2) to indicate it is present. So, when this is detected the HTTP/2 frame header fields can be written to the space immediately preceding the data buffer, saving on the separate, small (and relatively costly) frame header write, reducing *all* request data written from two to a single write without adding significant complication to memory or data buffer management. Hopefully not too clever for its own good! Of course it is also possible that a write originates from non-allocated memory (e.g. string constant) and does not contain this header and so requires a separate header write. VERSION HISTORY --------------- 25-AUG-2024 MGD bugfix; request I/O accounting with HTTP/2 04-MAR-2024 MGD refine HTTP/2 flow control 27-OCT-2023 MGD refinements using https://github.com/summerwind/h2spec 27-JUL-2022 MGD bugfix; when using file cache magic buffers 31-JAN-2020 MGD fiddle with the code (perhaps optimisations, even bugfixes) 15-FEB-2019 MGD bugfix; Http2NetQueueWrite() PEEK_8 at w2ptr->type 01-JUL-2018 MGD bugfix; Http2NetIoWrite() blocking write data must be asynchronously persistent so employ internal buffer(s) 06-JAN-2018 MGD refactor write code paths (simplify and greater efficiency) 11-FEB-2017 MGD bugfix; Http2NetQueueWrite() and Http2NetWriteDataAst() blocking writes are not placed on the request's write list as they are transparent to the request bugfix; Http2NetQueueWrite() deliver via NetIoWriteStatus() using SS$_NORMAL (HTTP/2 I/O) not the request ->VmsStatus 06-AUG-2016 MGD last stream ident now noted by Http2RequestBegin() 16-AUG-2015 MGD initial */ /*****************************************************************************/ #ifdef WASD_VMS_V7 # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 #else # ifdef WASD_VMS_V7 # undef _VMS__V6__SOURCE # define _VMS__V6__SOURCE # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 # endif #endif #include #include #include "wasd.h" #define WASD_MODULE "HTTP2NET" /********************/ /* external storage */ /********************/ extern uint Http2ClientPrefaceLength, Http2MaxFrameSize; extern ulong HttpdTickSecond; extern char ErrorSanityCheck[], Http2ClientPreface[]; extern LIST_HEAD Http2List; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Initial read of the data described below. */ int Http2NetClientRead (HTTP2_STRUCT *h2ptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetClientRead() !&X", h2ptr->NetIoPtr); h2ptr->ReadBufferCount = 0; /* stop reading when the HTTP/2 connection is closing */ if (h2ptr->ConnectClosing) return (SS$_NORMAL); h2ptr->ReadInUseCount++; status = NetIoRead (h2ptr->NetIoPtr, Http2NetClientReadAst, h2ptr, h2ptr->ReadBufferPtr, h2ptr->ReadBufferSize); return (status); } /*****************************************************************************/ /* AST of read from client with sufficient data to parse (at least) the frame header (9 octets) so the length of the frame can be determined. Multiple reads may be performed to build up a complete frame in the read buffer (potentially more than one frame). Only a complete frame is parsed and processed. This function always expects the frame to be located at the beinning of the read buffer. */ void Http2NetClientReadAst (HTTP2_STRUCT *h2ptr) { int count, length, retval, status; uint flags, ident, type; uchar *bptr; HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) { WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetClientReadAst() !&F !UL !&X !&S !UL", Http2NetClientReadAst, h2ptr->ReadInUseCount, h2ptr->NetIoPtr, h2ptr->NetIoPtr->ReadStatus, h2ptr->NetIoPtr->ReadCount); WatchDataDump (h2ptr->ReadBufferPtr, h2ptr->NetIoPtr->ReadCount); } if (h2ptr->ReadInUseCount != 1) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); if (h2ptr->ReadInUseCount) h2ptr->ReadInUseCount--; if (h2ptr->ConnectClosing) { Http2ConnectClose (h2ptr); return; } /* if the connection has a set VMS status */ if (h2ptr->NetIoPtr->VmsStatus) h2ptr->NetIoPtr->ReadStatus = h2ptr->NetIoPtr->WriteStatus = h2ptr->NetIoPtr->VmsStatus; /* if the connection presented a read error */ if (VMSnok (h2ptr->NetIoPtr->ReadStatus)) { Http2ConnectClose (h2ptr); return; } h2ptr->BytesRawRx64 += h2ptr->NetIoPtr->ReadCount; h2ptr->BytesRawTallyRx64 += h2ptr->NetIoPtr->ReadCount; h2ptr->ReadBufferCount += h2ptr->NetIoPtr->ReadCount; bptr = h2ptr->ReadBufferPtr; count = h2ptr->ReadBufferCount; while (count >= HTTP2_FRAME_HEADER_SIZE) { if (h2ptr->ExpectingH2cPreface) { /* RFC 7540 3.2 Starting HTTP/2 for "http" URIs */ if (count >= Http2ClientPrefaceLength && MATCH8 (bptr, Http2ClientPreface) && MATCH0 (bptr, Http2ClientPreface, Http2ClientPrefaceLength)) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 connection preface (h2c)"); h2ptr->ExpectingH2cPreface = false; count -= Http2ClientPrefaceLength; bptr += Http2ClientPrefaceLength; continue; } Http2DoError (h2ptr, 0, HTTP2_ERROR_PROTOCOL, FI_LI); return; } HTTP2_PEEK_24 (bptr, length); if (WATCHPNT(h2ptr) && (WATCH_CATEGORY(WATCH_HTTP2) || WATCH_MODULE(WATCH_MOD_HTTP2))) { if (count >= length) Http2WatchFrame (h2ptr, bptr, NULL, count, false); else { HTTP2_PEEK_8 (bptr+3, type); HTTP2_PEEK_8 (bptr+4, flags); HTTP2_PEEK_32 (bptr+5, ident); WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "FRAME !SL count:!UL length:!UL type:!UL flags:0x!2ZL ident:!UL", count-length, count, length, type, flags, ident); } } if (length > h2ptr->ClientMaxFrameSize || length > h2ptr->ServerMaxFrameSize) { retval = Http2DoError (h2ptr, 0, HTTP2_ERROR_SIZE, FI_LI); if (retval < 0) { Http2NetClientEnd (h2ptr, ident); return; } } /* if not a complete frame */ if (length + HTTP2_FRAME_HEADER_SIZE > count) break; HTTP2_GET_24 (bptr, length); HTTP2_GET_8 (bptr, type); HTTP2_GET_8 (bptr, flags); HTTP2_GET_32 (bptr, ident); /* ignore any reserved bit */ ident &= 0x7fffffff; count -= HTTP2_FRAME_HEADER_SIZE; h2ptr->FrameCountRx++; h2ptr->FrameTallyRx++; /* mitigate CVE-2019-9518 */ if (!length && !(flags & HTTP2_FLAG_END_STR)) { switch (type) { case HTTP2_FRAME_DATA : case HTTP2_FRAME_HEADERS : case HTTP2_FRAME_CONTINUATION : case HTTP2_FRAME_PUSH_PROMISE : if (h2ptr->EmptyFrameLimitCount++ > HTTP2_EMPTY_FRAME_LIMIT_COUNT) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "LIMIT empty frames to !UL/S (DoS?)", HTTP2_EMPTY_FRAME_LIMIT_COUNT); /* calm is a stream error not fatal correction */ Http2DoError (h2ptr, 0, HTTP2_ERROR_CALM, FI_LI); count -= length; bptr += length; continue; } } } s2ptr = NULL; if (ident) { /*********/ /* ident */ /*********/ h2ptr->FrameRequestCountRx++; h2ptr->FrameRequestTallyRx++; ident &= 0x7fffffff; if (!(ident & 1)) { /* client initiated streams must have odd numbered idents */ retval = Http2DoError (h2ptr, 0, HTTP2_ERROR_PROTOCOL, FI_LI); return; } if (h2ptr->HeaderThisIdent) { if (type != HTTP2_FRAME_CONTINUATION) { retval = Http2DoError (h2ptr, 0, HTTP2_ERROR_PROTOCOL, FI_LI); break; } } if (h2ptr->GoAwayIdent) { /* ignore frames with idents after server signals goaway */ count -= length; bptr += length; continue; } /* locate the stream */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr && s2ptr->Ident != ident; s2ptr = LIST_GET_NEXT(s2ptr)); if (!s2ptr) { /* stream not found */ if (ident < h2ptr->LastStreamIdent) { retval = Http2DoError (h2ptr, 0, HTTP2_ERROR_PROTOCOL, FI_LI); break; } /* generate a new stream */ s2ptr = Http2StreamGet (h2ptr, ident); } if (s2ptr->StreamCancel) { /* just ignore cancelled streams */ Http2StreamReset (h2ptr, s2ptr, HTTP2_ERROR_CLOSED, NULL, 0); count -= length; bptr += length; continue; } } switch (type) { case HTTP2_FRAME_DATA : if (s2ptr && s2ptr->StreamClosed) { /* special case where data received for closed stream */ Http2StreamReset (h2ptr, s2ptr, HTTP2_ERROR_CLOSED, NULL, 0); retval = 0; break; } retval = Http2RequestData (h2ptr, s2ptr, flags, bptr, length); if (retval < 0) Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_HEADERS : case HTTP2_FRAME_CONTINUATION : retval = HpackHeadersFrame (s2ptr, type, flags, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_PRIORITY : retval = Http2Priority (h2ptr, ident, flags, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_RST_STREAM : retval = Http2StreamReset (h2ptr, s2ptr, 0, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_SETTINGS : retval = Http2Settings (h2ptr, ident, flags, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_PUSH_PROMISE : /* client cannot server-push (RFC7540 8.2) */ retval = -HTTP2_ERROR_PROTOCOL; retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_PING : retval = Http2Ping (h2ptr, ident, flags, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_GOAWAY : retval = Http2GoAway (h2ptr, ident, 0, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; case HTTP2_FRAME_WINDOW_UPDATE : retval = Http2WindowUpdate (h2ptr, s2ptr, ident, 0, bptr, length); if (retval < 0) retval = Http2DoError (h2ptr, ident, retval, FI_LI); break; default : /* just ignore any unknown frame type */ if (h2ptr->HeaderThisIdent) { /* except between header frames */ retval = Http2DoError (h2ptr, 0, HTTP2_ERROR_PROTOCOL, FI_LI); break; } retval = 0; } /* if Http2DoError() was a fail connection */ if (retval < 0) { Http2NetClientEnd (h2ptr, ident); return; } count -= length; bptr += length; } if (count) { /* shuffle remaining data to front of buffer */ memcpy (h2ptr->ReadBufferPtr, bptr, count); } h2ptr->ReadBufferCount = count; bptr = h2ptr->ReadBufferPtr + count; length = h2ptr->ReadBufferSize - count; /* stop reading when the HTTP/2 connection is closing */ if (h2ptr->ConnectClosing) return; h2ptr->ReadInUseCount++; /* get the next, or rest of, frame */ NetIoRead (h2ptr->NetIoPtr, Http2NetClientReadAst, h2ptr, bptr, length); } /*****************************************************************************/ /* Look for the stream ident and end it. */ void Http2NetClientEnd ( HTTP2_STRUCT *h2ptr, uint ident ) { HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ /* locate the request corresponding to the stream */ for (s2ptr = LIST_GET_HEAD(&h2ptr->StreamList); s2ptr && s2ptr->Ident != ident; s2ptr = LIST_GET_NEXT(s2ptr)); if (s2ptr) Http2StreamClose (s2ptr); } /*****************************************************************************/ /* Flag as cancelled any outstanding request I/O (read and write). */ void Http2NetIoCancel (NETIO_STRUCT *ioptr) { HTTP2_STREAM_STRUCT *s2ptr; /*********/ /* begin */ /*********/ s2ptr = ioptr->Stream2Ptr; s2ptr->StreamCancel = true; if (s2ptr->RequestPtr) Http2NetCancelWrite (s2ptr->RequestPtr); } /*****************************************************************************/ /* Return true if this HTTP/2 stream has outstanding network I/O. */ BOOL Http2NetIoInProgress (NETIO_STRUCT *ioptr) { /*********/ /* begin */ /*********/ ioptr = ((HTTP2_STREAM_STRUCT*)ioptr->Stream2Ptr)->Http2Ptr->NetIoPtr; if (ioptr->SesolaPtr) return (SesolaNetIoInProgress (ioptr->SesolaPtr)); return (ioptr->WriteAstFunction || ioptr->ReadAstFunction); } /*****************************************************************************/ /* This function wraps the HTTP/1.1 network I/O response reads. Named Http2NetIo..() for consistency with NetIo..() and SesolaNetIo..(). It is called from NetIoRead(). It calls the Http2RequestData() function to handle the asynchronous HTTP/2 DATA frames from the client (request body) and the reads by the request body processing code. */ int Http2NetIoRead (NETIO_STRUCT *ioptr) { HTTP2_STRUCT *h2ptr; HTTP2_STREAM_STRUCT *s2ptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ s2ptr = ioptr->Stream2Ptr; h2ptr = s2ptr->Http2Ptr; rqptr = s2ptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetRead() !&A !&X !UL", ioptr->ReadAstFunction, ioptr->ReadPtr, ioptr->ReadSize); /* no such creature as a blocking HTTP/2 read! */ if (!ioptr->ReadAstFunction) { ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); return (SS$_BUGCHECK); } return (Http2RequestData (h2ptr, s2ptr, 0, NULL, 0)); } /*****************************************************************************/ /* Wraps the HTTP/1.1 network I/O response writes in HTTP/2 DATA frames. Named Http2NetIo..() for consistency with NetIo..() and SesolaNetIO..(). It is called from NetIoWrite(). The function must be able to queue multiple (sub-)writes should a request write exceed the maximum client accepted frame size. Optimally the write buffers will always be sized at or below the maximum frame size. All HTTP/2 I/O are by nature asynchronous and so a blocking write (one with a NULL |AstFunction| parameter) is made asynchronous. Data persistance must be guaranteed during the write and so is *always* copied to HTTP/2 internal buffers and *never* used in-situ, as the calling code may well (*will*) continue on processing and likely (*will*) modify the data buffer it considers was *already* written. An example of where blocking writes are used is the WATCH facility. If |DataPtr| is NULL then an empty frame with the end data flag set is sent. If the stream associated with the request has been closed then allow the write to be queued but the status will be delivered as cancelled, not performing the network I/O but allowing any AST to be delivered. */ int Http2NetIoWrite ( NETIO_STRUCT *ioptr, VOID_AST AstFunction, void *AstParam, void *DataPtr, uint DataLength ) { BOOL cache, magic; int queue, status; HTTP2_STRUCT *h2ptr; HTTP2_HEADER_STRUCT *hsptr; HTTP2_STREAM_STRUCT *s2ptr; HTTP2_WRITE_STRUCT *w2ptr; REQUEST_STRUCT *rqptr; VM_STRUCT *vmptr; /*********/ /* begin */ /*********/ s2ptr = ioptr->Stream2Ptr; h2ptr = s2ptr->Http2Ptr; rqptr = s2ptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetIoWrite() !&A !&X !UL", AstFunction, DataPtr, DataLength); if (!AstFunction) { /* blocking write */ if (ioptr->VmsStatus) { ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = ioptr->VmsStatus; return (ioptr->WriteIOsb.Status); } ioptr->WriteIOsb.Count = DataLength; ioptr->WriteIOsb.Status = SS$_NORMAL; } if (rqptr->rqPathSet.Http2WriteQueue) queue = rqptr->rqPathSet.Http2WriteQueue; else queue = HTTP2_WRITE_QUEUE_NORMAL; if (!DataPtr) { DataPtr = ""; DataLength = 0; } if (rqptr->rqCache.EntryPtr && rqptr->rqCache.EntryPtr->InUseCount > 1) { cache = true; magic = false; } else { /* check for the appropriate incantation */ vmptr = (VM_STRUCT*)((uchar*)DataPtr - sizeof(VM_STRUCT)); switch (vmptr->magic) { case VM_MAGIC_CACHE : case VM_MAGIC_DECC : case VM_MAGIC_EXPAT : case VM_MAGIC_GENERAL : case VM_MAGIC_HTTP2 : case VM_MAGIC_OPENSSL : case VM_MAGIC_PERMCACHE : case VM_MAGIC_REQUEST : magic = true; break; default: magic = false; } cache = false; } if (Watch.TriggerTxCount) { if (h2ptr != Watch.Http2Ptr) { if (!ioptr->WatchTriggerRxPlus && !ioptr->WatchTriggerTxPlus) { if (WatchTrigger (DataPtr, DataLength, Watch.TriggerTx)) { ioptr->WatchTriggerTx = true; if (Watch.TriggerPlus) ioptr->WatchTriggerTxPlus = true; } } if (ioptr->WatchTriggerTx || ioptr->WatchTriggerTxPlus) { WatchSetTrigger (ioptr); ioptr->WatchTriggerTx = false; } } } for (;;) { /******************/ /* write frame(s) */ /******************/ #if WATCH_MOD /* loop for where a write potentially exceeds the max frame size */ if (WATCHING (rqptr, WATCH_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_HTTP2, "!AZ!AZ!AZ !UL/!UL", magic ? "MAGIC" : "", cache ? "CACHE" : "", !(magic || cache) ? "BUFFER" : "", DataLength, h2ptr->ClientMaxFrameSize); #endif if (magic) { /* can use the in-built header of WASD allocated memory */ w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); w2ptr->HeaderPtr = vmptr->h2header; w2ptr->DataPtr = DataPtr; if (DataLength <= h2ptr->ClientMaxFrameSize) { w2ptr->DataLength = DataLength; /* final or only write delivers any AST */ w2ptr->AstFunction = AstFunction; w2ptr->AstParam = AstParam; } else w2ptr->DataLength = h2ptr->ClientMaxFrameSize; if (WATCHING (ioptr, WATCH_NETWORK_OCTETS)) WatchDataDump (w2ptr->DataPtr, w2ptr->DataLength); hsptr = w2ptr->HeaderPtr; HTTP2_PLACE_24 (hsptr->length, w2ptr->DataLength) HTTP2_PLACE_8 (hsptr->type, HTTP2_FRAME_DATA); HTTP2_PLACE_8 (hsptr->flags, 0); HTTP2_PLACE_32 (hsptr->ident, rqptr->Stream2Ptr->Ident); } else if (cache) { /* handle cached file where magic is already in use */ w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = DataPtr; if (DataLength <= h2ptr->ClientMaxFrameSize) { w2ptr->DataLength = DataLength; /* final or only write delivers any AST */ w2ptr->AstFunction = AstFunction; w2ptr->AstParam = AstParam; } else w2ptr->DataLength = h2ptr->ClientMaxFrameSize; } else { /* buffer partial or "blocking" or non-allocated (magic) write */ if (DataLength <= h2ptr->ClientMaxFrameSize) w2ptr = Http2GetWriteStruct (h2ptr, DataLength, FI_LI); else w2ptr = Http2GetWriteStruct (h2ptr, h2ptr->ClientMaxFrameSize, FI_LI); w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; if (DataLength <= h2ptr->ClientMaxFrameSize) { w2ptr->DataLength = DataLength; /* final or only write delivers any AST */ w2ptr->AstFunction = AstFunction; w2ptr->AstParam = AstParam; } else w2ptr->DataLength = h2ptr->ClientMaxFrameSize; memcpy (w2ptr->DataPtr, DataPtr, w2ptr->DataLength); } w2ptr->Http2Ptr = h2ptr; w2ptr->RequestPtr = rqptr; w2ptr->WriteQueue = queue; HTTP2_PLACE_24 (w2ptr->length, w2ptr->DataLength) HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_DATA); HTTP2_PLACE_8 (w2ptr->flags, 0); HTTP2_PLACE_32 (w2ptr->ident, rqptr->Stream2Ptr->Ident); Http2NetQueueWrite (h2ptr, w2ptr); /* on the only or final write then that's it */ if (DataLength <= h2ptr->ClientMaxFrameSize) return (SS$_NORMAL); /* if intermediate write then continue to the next */ (uchar*)DataPtr += w2ptr->DataLength; DataLength -= w2ptr->DataLength; /* any magic cannot be used with subsequent writes */ magic = false; } } /*****************************************************************************/ /* HTTP/2 network writes are serialised using FIFO lists. In this current implementation of WASD HTTP/2 there is limited prioritisation of streams. There are four queues/lists. Connection management writes are to queue zero (index 0) and have the highest priority, then the high priority (index 1), normal priority (index 2), and then the lowest (index 3). The HIGH, NORMAL (default) and LOW can be set via path mappings. All writes from a queue are FIFO and asynchronous (empty AST function notwithstanding). Request data writes are subject to RFC 9113 (was 7540) flow control. Where write window size(s) become constrained individual DATA writes may fragmented into multiple writes. If a queued write is too large for the window size then as much as can be written is, with the remainder of that data queued into another, separate and independent write, which in turn can be partially written and so on and so forth, until the remainder becomes small enough to be the last fragment. */ void Http2NetQueueWrite ( HTTP2_STRUCT *h2ptr, HTTP2_WRITE_STRUCT *w2ptr ) { int cnt, idx, flow, status, type, DataLength; void *AstParam, *DataPtr; REQUEST_STRUCT *rqptr; HTTP2_STREAM_STRUCT *s2ptr; HTTP2_WRITE_STRUCT *w22ptr; VOID_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetQueueWrite() h2ptr:!&X w2ptr:!&X write-in-prog:!&B", h2ptr, w2ptr, NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)); if (w2ptr) { /*************/ /* new write */ /*************/ /* must always have an AST address to indicate NETIO is in use */ if (!w2ptr->AstFunction) w2ptr->AstFunction = w2ptr->AstParam = Http2Net_WRITE_NO_AST; /* add to the HTTP/2 connection's write list */ ListAddTail (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr, LIST_ENTRY_TYPE_WRITE2); if (w2ptr->RequestPtr) w2ptr->RequestPtr->Stream2Ptr->QueuedWriteCount++; cnt = 0; for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) cnt += LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx]); if (cnt > h2ptr->QueuedWritePeak) h2ptr->QueuedWritePeak = cnt; } /* if there's a write already in progress */ if (NETIO_WRITE_IN_PROGRESS(h2ptr->NetIoPtr)) return; /***************/ /* check queue */ /***************/ /* look through the queues from highest to lowest priority */ for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) { /* if nothing in this queue then look at the next */ if (!LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx])) continue; for (w2ptr = LIST_GET_HEAD (&h2ptr->QueuedWriteList[idx]); w2ptr != NULL; w2ptr = w22ptr) { /* double check in case of some unexpected timing issue! */ if (w2ptr->WriteInProgress) return; /* careful about pulling (flow control) mats */ w22ptr = LIST_GET_NEXT(w2ptr); /* if not a DATA frame and therefore not subject to flow control */ HTTP2_PEEK_8 (w2ptr->type, type); if (type != HTTP2_FRAME_DATA) break; /* if not impacting flow control */ if (!w2ptr->DataLength) break; /* if not associated with a request */ if (!(rqptr = w2ptr->RequestPtr)) break; /* if SS$_VCCLOSED or other specific status just do it */ if (h2ptr->NetIoPtr->VmsStatus) break; if (!(s2ptr = rqptr->Stream2Ptr)) break; /* outstanding write for (in process of being) closed stream */ if (s2ptr->StreamClosed) continue; /****************/ /* flow control */ /****************/ /* if stream or connection is flow blocked look for another */ if ((cnt = Http2FlowCheck (h2ptr, s2ptr, w2ptr)) <= 0) { if (cnt < 0) Http2StreamDrop (s2ptr); continue; } if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "FLOW length:!UL h2size:!SL s2size:!SL", w2ptr->DataLength, h2ptr->WriteWindowSize, s2ptr->WriteWindowSize); /* adjust the flow control window in use */ s2ptr->WriteWindowSize -= w2ptr->DataLength; h2ptr->WriteWindowSize -= w2ptr->DataLength; /* break to perform the write */ break; } /* break from the queue loop to perform the write */ if (w2ptr) break; } /* if nothing in any queue */ if (idx > HTTP2_WRITE_QUEUE_LOW) { if (!h2ptr->NetIoPtr->Channel) Http2ConnectClose (h2ptr); else if (h2ptr->NetIoPtr->VmsStatus) Http2ConnectClose (h2ptr); else if (h2ptr->GoAwayLastStreamIdent) Http2ConnectClose (h2ptr); return; } /*********/ /* write */ /*********/ w2ptr->WriteInProgress = true; if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) { WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "!&X queue:!UL", w2ptr, w2ptr->WriteQueue); Http2WatchFrame (h2ptr, w2ptr->HeaderPtr, w2ptr->DataPtr, w2ptr->DataLength, w2ptr->HeaderPtr != w2ptr->header); } else if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchFrame (h2ptr, w2ptr->HeaderPtr, w2ptr->DataPtr, 0, w2ptr->HeaderPtr != w2ptr->header); /* if something is amiss with the HTTP/2 connection */ if (h2ptr->NetIoPtr->VmsStatus) { NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, h2ptr->NetIoPtr->VmsStatus, 0); return; } if (rqptr = w2ptr->RequestPtr) { /* (RFC7540 5.1) */ if (rqptr->NetIoPtr->VmsStatus) { /* SS$_NORMAL means no issue with the underlying HTTP/2 write */ NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, SS$_NORMAL, 0); return; } h2ptr->FrameRequestCountTx++; h2ptr->FrameRequestTallyTx++; } h2ptr->FrameCountTx++; h2ptr->FrameTallyTx++; if ((uchar*)w2ptr->HeaderPtr + HTTP2_FRAME_HEADER_SIZE == w2ptr->DataPtr) /* header immediately precedes the data - write single datagram */ NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, w2ptr->HeaderPtr, HTTP2_FRAME_HEADER_SIZE + w2ptr->DataLength); else /* write header then data independently (two datagrams) */ NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteHeaderAst, w2ptr, w2ptr->HeaderPtr, HTTP2_FRAME_HEADER_SIZE); } /*****************************************************************************/ /* Deliver (all) write(s) associated with the request with an error status (most commonly with WASD only the one). Writes are delivered rather than cancelled because some are associated with consumed resources (e.g. scripts) which need to be released. */ BOOL Http2NetCancelWrite (REQUEST_STRUCT *rqptr) { int cnt, idx; HTTP2_STRUCT *h2ptr; NETIO_STRUCT *ioptr; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetCancelWrite()"); h2ptr = rqptr->Http2Ptr; ioptr = h2ptr->NetIoPtr; /* IO struct is in use */ if (ioptr->WriteAstFunction) return (true); /* look through the queues */ for (idx = HTTP2_WRITE_QUEUE_MAX; idx <= HTTP2_WRITE_QUEUE_LOW; idx++) { /* if nothing in this queue then look at the next */ if (!LIST_GET_COUNT (&h2ptr->QueuedWriteList[idx])) continue; for (w2ptr = LIST_GET_HEAD (&h2ptr->QueuedWriteList[idx]); w2ptr != NULL; w2ptr = LIST_GET_NEXT(w2ptr)) { if (rqptr != w2ptr->RequestPtr) continue; if (w2ptr->WriteInProgress) return (false); if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_HTTP2, "CANCEL request !UL write", rqptr->ConnectNumber); w2ptr->WriteInProgress = true; NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, SS$_VCCLOSED, 0); return (true); } } return (false); } /*****************************************************************************/ /* Post-process the HTTP/2 (9 octet) header write. If OK then write the payload. */ void Http2NetWriteHeaderAst (HTTP2_WRITE_STRUCT *w2ptr) { HTTP2_STRUCT *h2ptr; NETIO_STRUCT *io2ptr; /*********/ /* begin */ /*********/ h2ptr = w2ptr->Http2Ptr; io2ptr = h2ptr->NetIoPtr; if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetWriteHeaderAst() !&F !&X !&S !UL", Http2NetWriteHeaderAst, w2ptr, io2ptr->WriteStatus, io2ptr->WriteCount); /* if explicitly set (error) status */ if (io2ptr->VmsStatus) io2ptr->WriteStatus = io2ptr->VmsStatus; if (VMSnok (io2ptr->WriteStatus)) { /* the connection has failed (or at least the last write) */ SysDclAst (Http2NetWriteDataAst, w2ptr); return; } h2ptr->BytesRawTx64 += io2ptr->WriteCount; h2ptr->BytesRawTallyTx64 += io2ptr->WriteCount; NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, w2ptr->DataPtr, w2ptr->DataLength); } /*****************************************************************************/ /* Post-process the payload write (or combined header and payload write). Delivers (any) AST. Then queue the next write. */ void Http2NetWriteDataAst (HTTP2_WRITE_STRUCT *w2ptr) { uint type, WriteLength; void *AstParam; HTTP2_STRUCT *h2ptr; HTTP2_STREAM_STRUCT *s2ptr; NETIO_STRUCT *ioptr, *io2ptr; REQUEST_STRUCT *rqptr; VOID_AST AstFunction; /*********/ /* begin */ /*********/ h2ptr = w2ptr->Http2Ptr; io2ptr = h2ptr->NetIoPtr; if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetWriteDataAst() !&F h2ptr:!&X w2ptr:!&X !&S !UL", Http2NetWriteDataAst, h2ptr, w2ptr, io2ptr->WriteStatus, io2ptr->WriteCount); /* if explicitly set (error) status */ if (io2ptr->VmsStatus) io2ptr->WriteStatus = io2ptr->VmsStatus; h2ptr->BytesRawTx64 += io2ptr->WriteCount; h2ptr->BytesRawTallyTx64 += io2ptr->WriteCount; /* remove from the HTTP/2 connection's write list */ ListRemove (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr); w2ptr->WriteInProgress = false; if (rqptr = w2ptr->RequestPtr) { /********************/ /* request response */ /********************/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "rqptr:!&X ast:!&A", rqptr, w2ptr->AstFunction); if (s2ptr = rqptr->Stream2Ptr) if (s2ptr->QueuedWriteCount) s2ptr->QueuedWriteCount--; ioptr = rqptr->NetIoPtr; /* keep the request accounting representative */ ioptr->BlocksRawTx64++; ioptr->BlocksTallyTx64++; ioptr->BytesRawTx64 += io2ptr->WriteCount; ioptr->BytesTallyTx64 += io2ptr->WriteCount; if (w2ptr->AstFunction != Http2Net_WRITE_NO_AST) { /* deliver an AST so adjust the underlying NETIO */ if (ioptr->VmsStatus) { /* use explicit request I/O status */ ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = ioptr->VmsStatus; } else { /* use actual network I/O status */ ioptr->WriteIOsb.Count = io2ptr->WriteCount; ioptr->WriteIOsb.Status = io2ptr->WriteStatus; } /* post-process the request I/O */ ioptr->WriteCount = ioptr->WriteLength = 0; SysDclAst (NetIoWriteAst, ioptr); } } else { /**************************/ /* HTTP/2 control message */ /**************************/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "AstFunction !&A 0x!8XL", w2ptr->AstFunction, w2ptr->AstParam); AstParam = w2ptr->AstParam; AstFunction = w2ptr->AstFunction; if (w2ptr->AstFunction != Http2Net_WRITE_NO_AST) AstFunction (AstParam); } Http2FreeWriteStruct (h2ptr, w2ptr, FI_LI); /********/ /* next */ /********/ Http2NetQueueWrite (h2ptr, NULL); } /****************************************************************************/ /* The presence of an AST function is used to indicate a NETIO in progress. For I/O environments that cannot block (viz. HTTP/2) this as an AST target can be detected during post processing and not actually called. Could have used a magic number of some sort but this seemed cleaner. */ void Http2Net_WRITE_NO_AST (void *param) { if (WATCH_MODULE (WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Net_WRITE_NO_AST() !&F", Http2Net_WRITE_NO_AST); ErrorNoticed (NULL, SS$_BUGCHECK, "Http2Net_WRITE_NO_AST()", FI_LI); } /*****************************************************************************/ /* Write an end-of-stream frame (at end of output) to the client. This is the final element of the request's HTTP/2 data stream. */ void Http2NetWriteEnd (REQUEST_STRUCT *rqptr) { int queue; HTTP2_STRUCT *h2ptr; HTTP2_WRITE_STRUCT *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetWriteEnd() !&F", Http2NetWriteEnd); h2ptr = rqptr->Http2Ptr; if (rqptr->rqPathSet.Http2WriteQueue) queue = rqptr->rqPathSet.Http2WriteQueue; else queue = HTTP2_WRITE_QUEUE_NORMAL; w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); HTTP2_PLACE_24 (w2ptr->length, 0); HTTP2_PLACE_8 (w2ptr->type, HTTP2_FRAME_DATA); HTTP2_PLACE_8 (w2ptr->flags, HTTP2_FLAG_END_STR); HTTP2_PLACE_32 (w2ptr->ident, rqptr->Stream2Ptr->Ident); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; /* wind up the request */ w2ptr->AstFunction = RequestEnd2; w2ptr->AstParam = rqptr; Http2NetQueueWrite (h2ptr, w2ptr); } /*****************************************************************************/ /* Disconnect network connections. Provides comparable HTTP/2 functionality to NetControl(). Note that this is not an elegant "goaway". The network channel is just cancelled running down any I/O. */ int Http2NetControl (int ConnectNumber) { int PurgeCount; HTTP2_STRUCT *h2ptr, *w22ptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2NetControl()"); PurgeCount = 0; for (h2ptr = LIST_GET_HEAD(&Http2List); h2ptr != NULL; h2ptr = w22ptr) { /* get (any) next in list in case the current connection is closed */ w22ptr = LIST_GET_NEXT(h2ptr); if (ConnectNumber <= -2) { /* purge all (including only HTTP2) */ Http2GoAway (h2ptr, 0, HTTP2_ERROR_NONE, NULL, 0); Http2ConnectClose (h2ptr); PurgeCount++; } else if (ConnectNumber == h2ptr->ConnectNumber) { /* purge matching */ Http2GoAway (h2ptr, 0, HTTP2_ERROR_NONE, NULL, 0); Http2ConnectClose (h2ptr); PurgeCount++; } else if (LIST_IS_EMPTY (&h2ptr->StreamList)) { /* purge idle */ Http2GoAway (h2ptr, 0, HTTP2_ERROR_NONE, NULL, 0); Http2ConnectClose (h2ptr); PurgeCount++; } } if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "purged: !UL", PurgeCount); return (PurgeCount); } /*****************************************************************************/