diff --git a/java/org/apache/coyote/http2/StreamStateMachine.java b/java/org/apache/coyote/http2/StreamStateMachine.java index 70ff971190ec..4537284d46e4 100644 --- a/java/org/apache/coyote/http2/StreamStateMachine.java +++ b/java/org/apache/coyote/http2/StreamStateMachine.java @@ -197,8 +197,10 @@ private enum State { Http2Error.STREAM_CLOSED, FrameType.PRIORITY, FrameType.RST, FrameType.WINDOW_UPDATE), - CLOSED_RST_RX (false, false, false, false, - Http2Error.STREAM_CLOSED, FrameType.PRIORITY), + CLOSED_RST_RX (false, false, false, true, + Http2Error.STREAM_CLOSED, FrameType.PRIORITY, + FrameType.RST, + FrameType.WINDOW_UPDATE), CLOSED_RST_TX (false, false, false, false, Http2Error.STREAM_CLOSED, FrameType.DATA, FrameType.HEADERS, diff --git a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java index 41cd4005e669..46a350233b00 100644 --- a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java +++ b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java @@ -156,29 +156,65 @@ private void doTestHalfClosedRemoteHeadersMaintainsHpackState(boolean useContinu @Test public void testClosedInvalidFrame01() throws Exception { - // HTTP2 upgrade + // RFC 9113, section 5.1 permits an endpoint to treat receipt of any frame other than + // PRIORITY, WINDOW_UPDATE, or RST_STREAM on a closed stream as a connection error of + // type STREAM_CLOSED. Tomcat adopts this stricter policy for the CLOSED_RST_RX state, + // so receiving DATA after RST_STREAM produces a GOAWAY rather than a RST_STREAM. + http2Connect(); - // Build the simple request - byte[] frameHeader = new byte[9]; - ByteBuffer headersPayload = ByteBuffer.allocate(128); - buildSimpleGetRequest(frameHeader, headersPayload, null, 3); + // Complete a full request/response on stream 3 to drive it to CLOSED_TX, + // then send RST_STREAM so the server transitions to CLOSED_RST_RX. + sendSimpleGetRequest(3); + readSimpleGetResponse(); + output.clearTrace(); - // Remove the end of stream and end of headers flags - frameHeader[4] = 0; + sendRst(3, Http2Error.CANCEL.getCode()); - // Process the request - writeFrame(frameHeader, headersPayload); + // DATA on a CLOSED_RST_RX stream must trigger a connection error. + sendData(3, new byte[] {}); - // Send a rst - sendRst(3, Http2Error.INTERNAL_ERROR.getCode()); + handleGoAwayResponse(3, Http2Error.STREAM_CLOSED); + } - // Then try sending some data (which should fail) - sendData(3, new byte[] {}); - parser.readFrame(); - Assert.assertTrue(output.getTrace(), - output.getTrace().startsWith("3-RST-[" + Http2Error.STREAM_CLOSED.getCode() + "]")); + @Test + public void testRstStreamAfterRstStreamReceived() throws Exception { + // RFC 9113, section 5.1: RST_STREAM on a closed stream must be silently ignored. + // A second RST_STREAM after the stream has entered CLOSED_RST_RX must not produce + // a connection error; the connection must remain open. + http2Connect(); + + sendSimpleGetRequest(3); + readSimpleGetResponse(); + output.clearTrace(); + + sendRst(3, Http2Error.CANCEL.getCode()); + sendRst(3, Http2Error.CANCEL.getCode()); + + sendSimpleGetRequest(5); + readSimpleGetResponse(); + Assert.assertTrue(output.getTrace(), output.getTrace().contains(getSimpleResponseTrace(5))); + } + + + @Test + public void testWindowUpdateAfterRstStreamReceived() throws Exception { + // RFC 9113, section 5.1: WINDOW_UPDATE on a closed stream must be silently ignored. + // Receiving WINDOW_UPDATE after the stream has entered CLOSED_RST_RX must not produce + // a connection error; the connection must remain open. + http2Connect(); + + sendSimpleGetRequest(3); + readSimpleGetResponse(); + output.clearTrace(); + + sendRst(3, Http2Error.CANCEL.getCode()); + sendWindowUpdate(3, 100); + + sendSimpleGetRequest(5); + readSimpleGetResponse(); + Assert.assertTrue(output.getTrace(), output.getTrace().contains(getSimpleResponseTrace(5))); }