/*****************************************************************************/ /* 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 --------------- 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, Http2FlowControlCurrent, Http2MaxFrameSize; 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; 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; /*********/ /* begin */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) { WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetClientReadAst() !&F !&X !&S !UL", Http2NetClientReadAst, h2ptr->NetIoPtr, h2ptr->NetIoPtr->ReadStatus, h2ptr->NetIoPtr->ReadCount); WatchDataDump (h2ptr->ReadBufferPtr, h2ptr->NetIoPtr->ReadCount); } /* 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)) { Http2CloseConnection (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; } Http2Error (h2ptr, 0, HTTP2_ERROR_PROTOCOL); 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->ServerMaxFrameSize) { if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "HTTP/2 frame size !UL exceeds !UL bytes", length, h2ptr->ServerMaxFrameSize); Http2Error (h2ptr, 0, HTTP2_ERROR_SIZE); 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); count -= HTTP2_FRAME_HEADER_SIZE; h2ptr->FrameCountRx++; h2ptr->FrameTallyRx++; if (ident) { h2ptr->FrameRequestCountRx++; h2ptr->FrameRequestTallyRx++; } if (ident) { if (ident & 0x1 == 0) { /* client initiated streams must have odd numbered idents */ if (WATCHING (h2ptr, WATCH_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_HTTP2, "STREAM error !UL", ident); Http2Error (h2ptr, 0, HTTP2_ERROR_PROTOCOL); return; } if (h2ptr->GoAwayIdent) { /* ignore frames with idents after server signals goaway */ count -= length; bptr += length; continue; } } /* mitigate CVE-2019-9518 */ if (!length && !(flags & HTTP2_FLAG_DATA_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); Http2Error (h2ptr, 0, HTTP2_ERROR_CALM); return; } } } switch (type) { case HTTP2_FRAME_DATA : retval = Http2RequestData (h2ptr, flags, ident, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, ident, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_HEADERS : case HTTP2_FRAME_CONTINUATION : retval = HpackHeadersFrame (&h2ptr->HpackClientTable, flags, ident, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, ident, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_PRIORITY : retval = Http2Priority (h2ptr, flags, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, 0, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_RST_STREAM : retval = Http2ResetStream (h2ptr, ident, 0, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, ident, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_SETTINGS : retval = Http2Settings (h2ptr, flags, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, 0, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_PUSH_PROMISE : /* client cannot server-push (RFC7540 8.2) */ retval = Http2Error (h2ptr, 0, -(HTTP2_ERROR_PROTOCOL)); if (retval >= 0) break; return; case HTTP2_FRAME_PING : retval = Http2Ping (h2ptr, flags, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, 0, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_GOAWAY : retval = Http2GoAway (h2ptr, 0, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, 0, -(retval)); if (retval >= 0) break; return; case HTTP2_FRAME_WINDOW_UPDATE : retval = Http2WindowUpdate (h2ptr, ident, 0, bptr, length); if (retval >= 0) break; retval = Http2Error (h2ptr, ident, -(retval)); if (retval >= 0) break; return; default : Http2Error (h2ptr, 0, -(HTTP2_ERROR_PROTOCOL)); 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; /* get the next, or rest of, frame */ NetIoRead (h2ptr->NetIoPtr, Http2NetClientReadAst, h2ptr, bptr, length); } /*****************************************************************************/ /* Return true if this HTTP/2 stream has outstanding network I/O. */ BOOL Http2NetIoInProgress (NETIO_STRUCT *ioptr) { /*********/ /* begin */ /*********/ ioptr = ((HTTP2_STREAM_STRUCT*)ioptr->Http2StreamPtr)->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->Http2StreamPtr; 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, 0, s2ptr->Ident, 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 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->Http2StreamPtr; 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); } if (rqptr->Http2Stream.State == HTTP2_STATE_CLOSED || rqptr->Http2Stream.State == HTTP2_STATE_CLOSED_LOC) { ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = SS$_VCCLOSED; 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; } /* check for the appropriate incantation */ vmptr = (VM_STRUCT*)((uchar*)DataPtr - sizeof(VM_STRUCT)); magic = false; 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; } for (;;) { /******************/ /* write frame(s) */ /******************/ /* loop for where a write potentially exceeds the max frame size */ #if WATCH_MOD if (WATCHING (rqptr, WATCH_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_HTTP2, "MAGIC !&B !UL/!UL", magic, DataLength, h2ptr->ClientMaxFrameSize); #endif if (magic) { /* can use the in-built header of WASD allocated memory */ w2ptr = Http2GetWriteStruct (h2ptr, 0, FI_LI); w2ptr->HeaderPtr = hsptr = 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; } 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 = hsptr = 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 (hsptr->length, w2ptr->DataLength) HTTP2_PLACE_8 (hsptr->type, HTTP2_FRAME_DATA); HTTP2_PLACE_8 (hsptr->flags, 0); HTTP2_PLACE_32 (hsptr->ident, rqptr->Http2Stream.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 7540 flow control. */ void Http2NetQueueWrite ( HTTP2_STRUCT *h2ptr, HTTP2_WRITE_STRUCT *w2ptr ) { int cnt, idx, type; REQUEST_STRUCT *rqptr; HTTP2_STREAM_STRUCT *s2ptr; HTTP2_WRITE_STRUCT *w22ptr; /*********/ /* 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->Http2Stream.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 = LIST_GET_NEXT(w2ptr)) { /* double check in case of some unexpected timing issue! */ if (w2ptr->WriteInProgress) return; /* 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 associated with a request */ if (!(rqptr = w2ptr->RequestPtr)) break; /* if SS$_VCCLOSED or other specific status just do it */ if (h2ptr->NetIoPtr->VmsStatus) break; /****************/ /* flow control */ /****************/ s2ptr = &rqptr->Http2Stream; if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "FLOW length:!UL h2size:!UL s2size:!UL", w2ptr->DataLength, h2ptr->WriteWindowSize, s2ptr->WriteWindowSize); if ((int)s2ptr->WriteWindowSize - (int)w2ptr->DataLength < 0 || (int)h2ptr->WriteWindowSize - (int)w2ptr->DataLength < 0) { if (!s2ptr->FlowControl) { Http2FlowControlCurrent++; h2ptr->FlowControlCurrent++; h2ptr->FlowControlTally++; s2ptr->FlowControl = true; if (WATCHING (rqptr, WATCH_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_HTTP2, "FLOW control ON (!UL/!UL/!UL)", w2ptr->DataLength, s2ptr->WriteWindowSize, h2ptr->WriteWindowSize); } /* continue to look for a write that can be performed */ continue; } h2ptr->FlowFrameTally++; if (s2ptr->FlowControl) { if (Http2FlowControlCurrent) Http2FlowControlCurrent--; if (h2ptr->FlowControlCurrent) h2ptr->FlowControlCurrent--; s2ptr->FlowControl = false; if (WATCHING (rqptr, WATCH_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_HTTP2, "FLOW control OFF (!UL/!UL/!UL)", w2ptr->DataLength, s2ptr->WriteWindowSize, h2ptr->WriteWindowSize); } /* adjust the flow control window in use */ s2ptr->WriteWindowSize -= w2ptr->DataLength; h2ptr->WriteWindowSize -= w2ptr->DataLength; /* break from the entry loop */ 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) Http2CloseConnection (h2ptr); else if (h2ptr->NetIoPtr->VmsStatus) Http2CloseConnection (h2ptr); else if (h2ptr->GoAwayLastStreamIdent) Http2CloseConnection (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) { /* half-closed remote can still be sent frames (RFC7540 5.1) */ if (rqptr->NetIoPtr->VmsStatus || rqptr->Http2Stream.State == HTTP2_STATE_CLOSED || rqptr->Http2Stream.State == HTTP2_STATE_CLOSED_LOC) { /* 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); } /*****************************************************************************/ /* Cancel (all) write(s) associated with the request. Done here rather than in Http2Request.C because it deals with queues. Return the count of canceled writes (with WASD should always be 0 or 1). */ int Http2NetCancelWrite (REQUEST_STRUCT *rqptr) { int cnt, idx; HTTP2_STRUCT *h2ptr; NETIO_STRUCT *ioptr; HTTP2_WRITE_STRUCT *next2, *w2ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetCancelWrite()"); h2ptr = rqptr->Http2Stream.Http2Ptr; ioptr = h2ptr->NetIoPtr; cnt = 0; /* 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 = next2) { next2 = LIST_GET_NEXT(w2ptr); if (rqptr != w2ptr->RequestPtr) continue; if (w2ptr->WriteInProgress) continue; cnt++; /* remove from the HTTP/2 connection's write list */ ListRemove (&h2ptr->QueuedWriteList[idx], w2ptr); if (rqptr->Http2Stream.QueuedWriteCount) rqptr->Http2Stream.QueuedWriteCount--; /* if NOT special cases without "real" AST delivery */ if (w2ptr->AstFunction != Http2Net_WRITE_NO_AST && w2ptr->AstFunction != Http2Net_REQUEST_END_AST) { /* deliver an AST so adjust the underlying NETIO */ ioptr = rqptr->NetIoPtr; ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = SS$_CANCEL; SysDclAst (NetIoWriteAst, ioptr); } Http2FreeWriteStruct (h2ptr, w2ptr, FI_LI); } } return (cnt); } /*****************************************************************************/ /* 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 WriteLength; void *AstParam; HTTP2_STRUCT *h2ptr; 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 (rqptr->Http2Stream.QueuedWriteCount) rqptr->Http2Stream.QueuedWriteCount--; if (w2ptr->AstFunction == Http2Net_REQUEST_END_AST) { /* special case that should not be processed by NetIoWriteAst() */ SysDclAst (Http2RequestEnd2, rqptr); } else if (w2ptr->AstFunction != Http2Net_WRITE_NO_AST) { /* deliver an AST so adjust the underlying NETIO */ ioptr = rqptr->NetIoPtr; 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 (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, *w22ptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "Http2NetWriteEnd()"); h2ptr = rqptr->Http2Stream.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_DATA_END_STR); HTTP2_PLACE_32 (w2ptr->ident, rqptr->Http2Stream.Ident); w2ptr->Http2Ptr = h2ptr; w2ptr->HeaderPtr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; w2ptr->WriteQueue = queue; /* this is a request datagram */ w2ptr->RequestPtr = rqptr; /* detected by Htt2NetWriteDataAst() and specially processed */ w2ptr->AstFunction = w2ptr->AstParam = Http2Net_REQUEST_END_AST; Http2NetQueueWrite (h2ptr, w2ptr); } /*****************************************************************************/ /* Detected by Htt2NetWriteDataAst() and specially processed. Could have used a magic number of some sort but this seemed cleaner. */ void Http2Net_REQUEST_END_AST (void *param) { /*********/ /* begin */ /*********/ if (WATCH_MODULE (WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Net_REQUEST_END_AST() !&F", Http2Net_REQUEST_END_AST); ErrorNoticed (NULL, SS$_BUGCHECK, "Http2Net_REQUEST_END_AST()", FI_LI); } /*****************************************************************************/ /* 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, *next2; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2NetControl()"); PurgeCount = 0; 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 (ConnectNumber <= -2) { /* purge all (including only HTTP2) */ Http2GoAway (h2ptr, HTTP2_ERROR_NONE, NULL, 0); Http2CloseConnection (h2ptr); PurgeCount++; } else if (ConnectNumber == h2ptr->ConnectNumber) { /* purge matching */ Http2GoAway (h2ptr, HTTP2_ERROR_NONE, NULL, 0); Http2CloseConnection (h2ptr); PurgeCount++; } else if (LIST_IS_EMPTY (&h2ptr->StreamList)) { /* purge idle */ Http2GoAway (h2ptr, HTTP2_ERROR_NONE, NULL, 0); Http2CloseConnection (h2ptr); PurgeCount++; } } if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "purged: !UL", PurgeCount); return (PurgeCount); } /*****************************************************************************/