/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.coyote.http2; import java.nio.ByteBuffer; import org.junit.Assert; import org.junit.Test; /** * Unit tests for Section 5.ยง of * <a href="https://tools.ietf.org/html/rfc7540">RFC 7540</a>. * <br> * The order of tests in this class is aligned with the order of the * requirements in the RFC. */ public class TestHttp2Section_5_1 extends Http2TestBase { @Test public void testIdleStateInvalidFrame01() throws Exception { http2Connect(); sendWindowUpdate(3, 200); parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[1]-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]-[")); } @Test public void testIdleStateInvalidFrame02() throws Exception { http2Connect(); sendData(3, new byte[] {}); parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[1]-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]-[")); } // TODO: reserved local // TODO: reserved remote @Test public void halfClosedRemoteInvalidFrame() throws Exception { http2Connect(); // This half-closes the stream since it includes the end of stream flag sendSimpleGetRequest(3); readSimpleGetResponse(); Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace()); output.clearTrace(); // This should trigger a stream error sendData(3, new byte[] {}); parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[3]-[" + Http2Error.STREAM_CLOSED.getCode() + "]-[")); } @Test public void testClosedInvalidFrame01() throws Exception { // HTTP2 upgrade http2Connect(); // Build the simple request byte[] frameHeader = new byte[9]; ByteBuffer headersPayload = ByteBuffer.allocate(128); buildSimpleGetRequest(frameHeader, headersPayload, null, 3); // Remove the end of stream and end of headers flags frameHeader[4] = 0; // Process the request writeFrame(frameHeader, headersPayload); // Send a rst sendRst(3, Http2Error.INTERNAL_ERROR.getCode()); // Then try sending some data (which should fail) sendData(3, new byte[] {}); parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith("3-RST-[" + Http2Error.STREAM_CLOSED.getCode() + "]")); } @Test public void testClosedInvalidFrame02() throws Exception { http2Connect(); // Stream 1 is closed. This should trigger a stream error sendData(1, new byte[] {}); parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[1]-[" + Http2Error.STREAM_CLOSED.getCode() + "]-[")); } // TODO: Invalid frames for each of the remaining states // Section 5.1.1 @Test public void testClientSendEvenStream() throws Exception { // HTTP2 upgrade http2Connect(); // Part 1 byte[] frameHeader = new byte[9]; ByteBuffer headersPayload = ByteBuffer.allocate(128); buildSimpleGetRequestPart1(frameHeader, headersPayload, 4); writeFrame(frameHeader, headersPayload); // headers parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[1]-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]-[")); } @Test public void testClientSendOldStream() throws Exception { http2Connect(); sendSimpleGetRequest(5); readSimpleGetResponse(); Assert.assertEquals(getSimpleResponseTrace(5), output.getTrace()); output.clearTrace(); // Build the simple request on an old stream byte[] frameHeader = new byte[9]; ByteBuffer headersPayload = ByteBuffer.allocate(128); buildSimpleGetRequest(frameHeader, headersPayload, null, 3); os.write(frameHeader); os.flush(); // headers parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[5]-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]-[")); } @Test public void testImplicitClose() throws Exception { http2Connect(); sendPriority(3, 0, 16); sendPriority(5, 0, 16); sendSimpleGetRequest(5); readSimpleGetResponse(); Assert.assertEquals(getSimpleResponseTrace(5), output.getTrace()); output.clearTrace(); // Should trigger an error since stream 3 should have been implicitly // closed. sendSimpleGetRequest(3); parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().startsWith( "0-Goaway-[5]-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]-[")); } @Test public void testExceedMaxActiveStreams() throws Exception { // http2Connect() - modified enableHttp2(1); configureAndStartWebApplication(); openClientConnection(); doHttpUpgrade(); sendClientPreface(); // validateHttp2InitialResponse() - modified parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); Assert.assertEquals("0-Settings-[3]-[1]\n" + "0-Settings-End\n" + "0-Settings-Ack\n" + "0-Ping-[0,0,0,0,0,0,0,1]\n" + getSimpleResponseTrace(1) , output.getTrace()); output.clearTrace(); sendLargeGetRequest(3); sendSimpleGetRequest(5); // Default connection window size is 64k-1. // Initial request will have used 8k leaving 56k-1. // Stream window will be 64k-1. // Expecting // 1 * headers // 56k-1 of body (7 * ~8k) // 1 * error (could be in any order) for (int i = 0; i < 8; i++) { parser.readFrame(true); } parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().contains("5-RST-[" + Http2Error.REFUSED_STREAM.getCode() + "]")); output.clearTrace(); // Connection window is zero. // Stream window is 8k // Release the remaining body sendWindowUpdate(0, (1 << 31) - 2); // Allow for the 8k still in the stream window sendWindowUpdate(3, (1 << 31) - 8193); // 192k of body (24 * 8k) // 1 * error (could be in any order) for (int i = 0; i < 24; i++) { parser.readFrame(true); } } @Test public void testErrorOnWaitingStream() throws Exception { // http2Connect() - modified enableHttp2(1); configureAndStartWebApplication(); openClientConnection(); doHttpUpgrade(); sendClientPreface(); // validateHttp2InitialResponse() - modified parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); parser.readFrame(true); Assert.assertEquals("0-Settings-[3]-[1]\n" + "0-Settings-End\n" + "0-Settings-Ack\n" + "0-Ping-[0,0,0,0,0,0,0,1]\n" + getSimpleResponseTrace(1) , output.getTrace()); output.clearTrace(); sendLargeGetRequest(3); sendSimpleGetRequest(5); // Default connection window size is 64k-1. // Initial request will have used 8k leaving 56k-1. // Stream window will be 64k-1. // Expecting // 1 * headers // 56k-1 of body (7 * ~8k) // 1 * error (could be in any order) for (int i = 0; i < 8; i++) { parser.readFrame(true); } parser.readFrame(true); Assert.assertTrue(output.getTrace(), output.getTrace().contains("5-RST-[" + Http2Error.REFUSED_STREAM.getCode() + "]")); output.clearTrace(); // Connection window is zero. // Stream window is 8k // Expand the stream window too much to trigger an error // Allow for the 8k still in the stream window sendWindowUpdate(3, (1 << 31) - 1); parser.readFrame(true); } }