/*****************************************************************************/ /* 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), 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 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! VERSION HISTORY --------------- 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, VmStructSize; extern ulong HttpdTime64[]; 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 presented a read error */ if (VMSnok (h2ptr->NetIoPtr->ReadStatus)) { if (!h2ptr->NetIoPtr->VmsStatus) h2ptr->NetIoPtr->VmsStatus = h2ptr->NetIoPtr->ReadStatus; Http2CloseConnection (h2ptr); return; } /* if the connection has a set VMS status */ if (h2ptr->NetIoPtr->VmsStatus) { /* stop reading from the client */ Http2CloseConnection (h2ptr); return; } ADD_LONG_QUAD (h2ptr->NetIoPtr->ReadCount, h2ptr->BytesRawRx); ADD_LONG_QUAD (h2ptr->NetIoPtr->ReadCount, h2ptr->BytesRawTallyRx); 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); 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; } } 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 (rqptr->Http2Stream.Http2Ptr, 0, rqptr->Http2Stream.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 are by nature asynchronous and so a blocking write (one with a NULL |AstFunction| parameter) is made asynchronous. An example of where blocking writes are used is the WATCH facility. For blocking writes an autonomous HTTP/2 write buffer is allocated, the data copied into that and queued, and the caller returned to none-the-wiser. 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 ) { int magic, 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) { /* no such thing as an HTTP/2 blocking write */ if (ioptr->VmsStatus) { /* use explicit request I/O status */ ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = ioptr->VmsStatus; return (ioptr->VmsStatus); } /* just assume the write is successful */ ioptr->WriteIOsb.Count = DataLength; ioptr->WriteIOsb.Status = SS$_NORMAL; } if (rqptr->rqPathSet.Http2WriteQueue) queue = rqptr->rqPathSet.Http2WriteQueue; else queue = HTTP2_WRITE_QUEUE_NORMAL; /* look for the allocated memory magic (see VM.H and VM.C) */ vmptr = (VM_STRUCT*)((uchar*)DataPtr - VmStructSize); if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "!8XL !8XL", vmptr->h2magic, vmptr->magic); /* if it will fit in a single frame and there's magic available */ magic = (DataLength <= h2ptr->ClientMaxFrameSize && vmptr->h2magic == VM_H2MAGIC && vmptr->magic == VM_MAGIC2); for (;;) { /******************/ /* write frame(s) */ /******************/ /* loop for where a write potentially exceeds the max frame size */ if (WATCHMOD (rqptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(rqptr), WATCH_MOD_HTTP2, "length:!UL frame:!UL", DataLength, h2ptr->ClientMaxFrameSize); if (magic) { w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); w2ptr->InbuiltPtr = hsptr = vmptr->h2header; w2ptr->DataPtr = w2ptr->payload; } else { w2ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT) + h2ptr->ClientMaxFrameSize); hsptr = w2ptr->header; w2ptr->DataPtr = w2ptr->payload; } w2ptr->Http2Ptr = h2ptr; w2ptr->RequestPtr = rqptr; w2ptr->WriteQueue = queue; HTTP2_PLACE_8 (hsptr->type, HTTP2_FRAME_DATA); HTTP2_PLACE_8 (hsptr->flags, 0); HTTP2_PLACE_32 (hsptr->ident, rqptr->Http2Stream.Ident); if (DataLength <= h2ptr->ClientMaxFrameSize) { /***********************/ /* only or final write */ /***********************/ w2ptr->DataLength = DataLength; HTTP2_PLACE_24 (hsptr->length, DataLength) if (!magic) memcpy (w2ptr->DataPtr, DataPtr, DataLength); /* on the final write deliver any AST */ if (w2ptr->AstFunction = AstFunction) w2ptr->AstParam = AstParam; Http2NetQueueWrite (h2ptr, w2ptr); return (SS$_NORMAL); } /**********************/ /* intermediate write */ /**********************/ w2ptr->DataLength = h2ptr->ClientMaxFrameSize; HTTP2_PLACE_24 (hsptr->length, h2ptr->ClientMaxFrameSize) if (!magic) memcpy (w2ptr->DataPtr, DataPtr, h2ptr->ClientMaxFrameSize); (uchar*)DataPtr += h2ptr->ClientMaxFrameSize; DataLength -= h2ptr->ClientMaxFrameSize; Http2NetQueueWrite (h2ptr, w2ptr); } } /*****************************************************************************/ /* 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; REQUEST_STRUCT *rqptr; HTTP2_STREAM_STRUCT *s2ptr; HTTP2_WRITE_STRUCT *next2, *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_BLOCKING_WRITE_AST; /* add to the HTTP/2 connection's write list */ ListAddTail (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr, LIST_ENTRY_TYPE_WRITE2); /* add to the request's write list */ if (w2ptr->RequestPtr) { /* the entry in the request write list is just a placemarker */ w22ptr = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); w22ptr->Write2Ptr = w2ptr; w2ptr->Write2Ptr = w22ptr; ListAddTail (&w2ptr->RequestPtr->Http2Stream.WriteList, w22ptr, LIST_ENTRY_TYPE_WRITE2); } 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)) { /* if not a DATA frame and therefore not subject to flow control */ if (w2ptr->type != HTTP2_FRAME_DATA) break; if ((rqptr = w2ptr->RequestPtr) == NULL) break; s2ptr = &rqptr->Http2Stream; if (rqptr->NetIoPtr->VmsStatus) { /* deliver request with the indicated status */ if (s2ptr->FlowControl) { if (Http2FlowControlCurrent) Http2FlowControlCurrent--; if (h2ptr->FlowControlCurrent) h2ptr->FlowControlCurrent--; s2ptr->FlowControl = false; } /* ss$_normal means no issue with the underlying HTTP/2 write */ NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, SS$_NORMAL); return; } /****************/ /* flow control */ /****************/ 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->VmsStatus) Http2CloseConnection (h2ptr); else if (h2ptr->GoAwayLastStreamIdent) Http2CloseConnection (h2ptr); return; } if (h2ptr->NetIoPtr->VmsStatus) { /* something is amiss with the HTTP/2 connection */ NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, h2ptr->NetIoPtr->VmsStatus); return; } /*********/ /* write */ /*********/ if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) { WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "!&X queue:!UL", w2ptr, w2ptr->WriteQueue); Http2WatchFrame (h2ptr, w2ptr->InbuiltPtr ? w2ptr->InbuiltPtr : w2ptr->header, w2ptr->DataPtr, w2ptr->DataLength); } else if (WATCHING (h2ptr, WATCH_HTTP2)) Http2WatchFrame (h2ptr, w2ptr->InbuiltPtr ? w2ptr->InbuiltPtr : w2ptr->header, NULL, 0); if (w2ptr->RequestPtr) { if (w2ptr->RequestPtr->NetIoPtr->VmsStatus) { /* ss$_normal means no issue with the underlying HTTP/2 write */ NetIoWriteStatus (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, SS$_NORMAL); return; } h2ptr->FrameRequestCountTx++; h2ptr->FrameRequestTallyTx++; } h2ptr->FrameCountTx++; h2ptr->FrameTallyTx++; /* if using the allocated memory inbuilt HTTP/2 header */ if (w2ptr->InbuiltPtr) NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, w2ptr->InbuiltPtr, HTTP2_FRAME_HEADER_SIZE + w2ptr->DataLength); else /* if no payload or the payload immediately follows the header */ if (w2ptr->DataLength == 0 || w2ptr->DataPtr == w2ptr->payload) NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteDataAst, w2ptr, w2ptr->header, HTTP2_FRAME_HEADER_SIZE + w2ptr->DataLength); else /* write the 9 octet header first which then writes the data */ NetIoWrite (h2ptr->NetIoPtr, Http2NetWriteHeaderAst, w2ptr, w2ptr->header, HTTP2_FRAME_HEADER_SIZE); } /*****************************************************************************/ /* Post-process the HTTP/2 (9 octet) header write. If OK then write the payload. */ void Http2NetWriteHeaderAst (HTTP2_WRITE_STRUCT *w2ptr) { HTTP2_STRUCT *h2ptr; /*********/ /* begin */ /*********/ h2ptr = w2ptr->Http2Ptr; if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetWriteHeaderAst() !&F !&X !&S !UL", Http2NetWriteHeaderAst, w2ptr, h2ptr->NetIoPtr->WriteStatus, h2ptr->NetIoPtr->WriteCount); if (VMSnok (h2ptr->NetIoPtr->WriteStatus)) { /* the connection has failed (or at least the last write) */ if (!h2ptr->NetIoPtr->VmsStatus) h2ptr->NetIoPtr->VmsStatus = h2ptr->NetIoPtr->WriteStatus; SysDclAst (Http2NetWriteDataAst, w2ptr); return; } ADD_LONG_QUAD (h2ptr->NetIoPtr->WriteCount, h2ptr->BytesRawTx); ADD_LONG_QUAD (h2ptr->NetIoPtr->WriteCount, h2ptr->BytesRawTallyTx); 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; REQUEST_STRUCT *rqptr; VM_STRUCT *vmptr; VOID_AST AstFunction; /*********/ /* begin */ /*********/ h2ptr = w2ptr->Http2Ptr; if (WATCHMOD (h2ptr, WATCH_MOD_HTTP2)) WatchThis (WATCHITM(h2ptr), WATCH_MOD_HTTP2, "Http2NetWriteDataAst() !&F h2ptr:!&X w2ptr:!&X !&S !UL", Http2NetWriteDataAst, h2ptr, w2ptr, h2ptr->NetIoPtr->WriteStatus, h2ptr->NetIoPtr->WriteCount); /* if the connection has failed (or at least the last write) */ if (VMSnok (h2ptr->NetIoPtr->WriteStatus)) if (!h2ptr->NetIoPtr->VmsStatus) h2ptr->NetIoPtr->VmsStatus = h2ptr->NetIoPtr->WriteStatus; ADD_LONG_QUAD (h2ptr->NetIoPtr->WriteCount, h2ptr->BytesRawTx); ADD_LONG_QUAD (h2ptr->NetIoPtr->WriteCount, h2ptr->BytesRawTallyTx); /* remove from the HTTP/2 connection's write list */ ListRemove (&h2ptr->QueuedWriteList[w2ptr->WriteQueue], w2ptr); if (rqptr = w2ptr->RequestPtr) { /********************/ /* request response */ /********************/ /* remove and free the placemarker from the request's write list */ ListRemove (&rqptr->Http2Stream.WriteList, w2ptr->Write2Ptr); VmFreeFrom2Heap (h2ptr, w2ptr->Write2Ptr, FI_LI); w2ptr->Write2Ptr = NULL; if (w2ptr->InbuiltPtr) { /* restore the HTTP/2 header space magic */ vmptr = (VM_STRUCT*)((uchar*)w2ptr->DataPtr - VmStructSize); vmptr->h2magic = VM_H2MAGIC; } /* special case that should not be processed by NetIoWriteAst() */ if (w2ptr->AstFunction == Http2Net_REQUEST_END_AST) RequestEnd (rqptr); else /* if not a "blocking" write then adjust the underlying NETIO */ if (w2ptr->AstFunction != Http2Net_BLOCKING_WRITE_AST) { /* this is the underlying request's I/O structure */ 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 = h2ptr->NetIoPtr->WriteCount; ioptr->WriteIOsb.Status = h2ptr->NetIoPtr->WriteStatus; } /* post-process the request I/O */ ioptr->WriteCount = ioptr->WriteLength = 0; 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_BLOCKING_WRITE_AST) AstFunction (AstParam); } /* free the allocated write structure */ VmFreeFrom2Heap (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_BLOCKING_WRITE_AST (void *param) { if (WATCH_MODULE (WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "Http2Net_BLOCKING_WRITE_AST() !&F", Http2Net_BLOCKING_WRITE_AST); ErrorNoticed (NULL, SS$_BUGCHECK, "Http2Net_BLOCKING_WRITE_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 = VmGet2Heap (h2ptr, sizeof(HTTP2_WRITE_STRUCT)); 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->DataPtr = w2ptr->payload; w2ptr->WriteQueue = queue; /* it is placed in the request stream write list to delay rundown */ 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. If no criteria supplied then disconnects all connections without requests in-progress. If ConnectNumber then disconnect that number, or if -1 then all 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 == -1) { /* purge all */ Http2CloseConnection (h2ptr); PurgeCount++; } else if (ConnectNumber == h2ptr->ConnectNumber) { /* purge matching */ Http2CloseConnection (h2ptr); PurgeCount++; } else if (LIST_IS_EMPTY (&h2ptr->StreamList)) { /* purge idle */ Http2CloseConnection (h2ptr); PurgeCount++; } } if (WATCH_MODULE(WATCH_MOD_HTTP2)) WatchThis (WATCHALL, WATCH_MOD_HTTP2, "purged: !UL", PurgeCount); return (PurgeCount); } /*****************************************************************************/