| /* |
| * 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 6.9 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_6_9 extends Http2TestBase { |
| |
| @Test |
| public void testZeroWindowUpdateConnection() throws Exception { |
| http2Connect(); |
| |
| sendWindowUpdate(0, 0); |
| |
| handleGoAwayResponse(1); |
| } |
| |
| |
| @Test |
| public void testZeroWindowUpdateStream() throws Exception { |
| http2Connect(); |
| |
| sendSimplePostRequest(3, null, false); |
| sendWindowUpdate(3, 0); |
| |
| parser.readFrame(true); |
| |
| Assert.assertEquals("3-RST-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]\n", |
| output.getTrace()); |
| } |
| |
| |
| @Test |
| public void testWindowUpdateOnClosedStream() throws Exception { |
| http2Connect(); |
| |
| // Should not be an error so should be nothing to read |
| sendWindowUpdate(1, 200); |
| |
| // So the next request should process normally |
| sendSimpleGetRequest(3); |
| readSimpleGetResponse(); |
| Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace()); |
| } |
| |
| |
| // TODO: Test always accounting for changes in flow control windows even if |
| // the frame is in error. |
| |
| |
| @Test |
| public void testWindowUpdateWrongLength() throws Exception { |
| http2Connect(); |
| |
| byte[] zeroLengthWindowFrame = new byte[9]; |
| // Length zero |
| setOneBytes(zeroLengthWindowFrame, 3, FrameType.WINDOW_UPDATE.getIdByte()); |
| // No flags |
| // Stream 1 |
| ByteUtil.set31Bits(zeroLengthWindowFrame, 5, 1); |
| |
| os.write(zeroLengthWindowFrame); |
| os.flush(); |
| |
| handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR); |
| } |
| |
| |
| @Test |
| public void testEmptyDataFrameWithNoAvailableFlowControl() throws Exception { |
| http2Connect(); |
| |
| // Default connection window size is 64k - 1. Initial request will have |
| // used 8k (56k -1). |
| |
| // Use up the remaining connection window. These requests require 56k |
| // but there is only 56k - 1 available. |
| for (int i = 3; i < 17; i += 2) { |
| sendSimpleGetRequest(i); |
| readSimpleGetResponse(); |
| } |
| output.clearTrace(); |
| |
| // It should be possible to send a request that generates an empty |
| // response at this point |
| sendEmptyGetRequest(17); |
| // Headers |
| parser.readFrame(true); |
| // Body |
| parser.readFrame(true); |
| |
| // Release Stream 15 which is waiting for a single byte. |
| sendWindowUpdate(0, 1024); |
| |
| Assert.assertEquals(getEmptyResponseTrace(17), output.getTrace()); |
| } |
| |
| |
| @Test |
| public void testWindowSizeTooLargeStream() throws Exception { |
| http2Connect(); |
| |
| // Set up stream 3 |
| sendSimplePostRequest(3, null, false); |
| |
| // Super size the flow control window. |
| sendWindowUpdate(3, (1 << 31) - 1); |
| |
| parser.readFrame(true); |
| |
| Assert.assertEquals("3-RST-[" + Http2Error.FLOW_CONTROL_ERROR.getCode() + "]\n", |
| output.getTrace()); |
| } |
| |
| |
| @Test |
| public void testWindowSizeTooLargeConnection() throws Exception { |
| http2Connect(); |
| |
| // Super size the flow control window. |
| sendWindowUpdate(0, (1 << 31) - 1); |
| |
| handleGoAwayResponse(1, Http2Error.FLOW_CONTROL_ERROR); |
| } |
| |
| |
| @Test |
| public void testWindowSizeAndSettingsFrame() throws Exception { |
| http2Connect(); |
| |
| // Set up a POST request that echoes the body back |
| byte[] headersFrameHeader = new byte[9]; |
| ByteBuffer headersPayload = ByteBuffer.allocate(128); |
| byte[] dataFrameHeader = new byte[9]; |
| ByteBuffer dataPayload = ByteBuffer.allocate(8 * 1024); |
| |
| buildPostRequest(headersFrameHeader, headersPayload, false, |
| dataFrameHeader, dataPayload, null, 3); |
| |
| // Write the headers |
| writeFrame(headersFrameHeader, headersPayload); |
| |
| // Now use a settings frame to reduce the size of the flow control |
| // window. |
| sendSettings(0, false, new SettingValue(4, 4 * 1024)); |
| // Ack |
| parser.readFrame(true); |
| Assert.assertEquals("0-Settings-Ack\n", output.getTrace()); |
| output.clearTrace(); |
| |
| // Write the body |
| writeFrame(dataFrameHeader, dataPayload); |
| |
| // Window size updates after reading POST body |
| parser.readFrame(true); |
| parser.readFrame(true); |
| Assert.assertEquals( |
| "0-WindowSize-[8192]\n" + |
| "3-WindowSize-[8192]\n", |
| output.getTrace()); |
| output.clearTrace(); |
| |
| // Read stream 3 headers and first part of body |
| parser.readFrame(true); |
| parser.readFrame(true); |
| Assert.assertEquals( |
| "3-HeadersStart\n" + |
| "3-Header-[:status]-[200]\n" + |
| "3-Header-[date]-["+ DEFAULT_DATE + "]\n" + |
| "3-HeadersEnd\n" + |
| "3-Body-4096\n", output.getTrace()); |
| output.clearTrace(); |
| |
| // Now use a settings frame to further reduce the size of the flow |
| // control window. This should make the stream 3 window negative |
| sendSettings(0, false, new SettingValue(4, 2 * 1024)); |
| // Ack |
| parser.readFrame(true); |
| Assert.assertEquals("0-Settings-Ack\n", output.getTrace()); |
| output.clearTrace(); |
| |
| // Now use a settings frame to increase the size of the flow control |
| // window. The stream 3 window should still be negative |
| sendSettings(0, false, new SettingValue(4, 3 * 1024)); |
| // Ack |
| parser.readFrame(true); |
| Assert.assertEquals("0-Settings-Ack\n", output.getTrace()); |
| output.clearTrace(); |
| |
| // Do a POST that won't be affected by the above limit |
| sendSimplePostRequest(5, null); |
| // Window size updates after reading POST body |
| parser.readFrame(true); |
| parser.readFrame(true); |
| Assert.assertEquals( |
| "0-WindowSize-[128]\n" + |
| "5-WindowSize-[128]\n", |
| output.getTrace()); |
| output.clearTrace(); |
| // Headers + body |
| parser.readFrame(true); |
| parser.readFrame(true); |
| Assert.assertEquals( |
| "5-HeadersStart\n" + |
| "5-Header-[:status]-[200]\n" + |
| "5-Header-[content-length]-[128]\n" + |
| "5-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" + |
| "5-HeadersEnd\n" + |
| "5-Body-128\n" + |
| "5-EndOfStream\n", output.getTrace()); |
| output.clearTrace(); |
| |
| // Now use a settings frame to restore the size of the flow control |
| // window. |
| sendSettings(0, false, new SettingValue(4, 64 * 1024 - 1)); |
| |
| // Settings ack and stream 3 body are written from different threads. |
| // Order depends on server side timing. Handle both possibilities. |
| parser.readFrame(true); |
| String trace = output.getTrace(); |
| String settingsAck = "0-Settings-Ack\n"; |
| String endOfStreamThree = "3-Body-4096\n3-EndOfStream\n"; |
| |
| if (settingsAck.equals(trace)) { |
| // Ack the end of stream 3 |
| output.clearTrace(); |
| parser.readFrame(true); |
| Assert.assertEquals(endOfStreamThree, output.getTrace()); |
| } else { |
| // End of stream 3 thenack |
| Assert.assertEquals(endOfStreamThree, output.getTrace()); |
| output.clearTrace(); |
| parser.readFrame(true); |
| Assert.assertEquals(settingsAck, output.getTrace()); |
| } |
| output.clearTrace(); |
| } |
| |
| |
| @Test |
| public void testWindowSizeTooLargeViaSettings() throws Exception { |
| http2Connect(); |
| |
| // Set up stream 3 |
| sendSimplePostRequest(3, null, false); |
| |
| // Increase the flow control window but keep it under the limit |
| sendWindowUpdate(3, 1 << 30); |
| |
| // Now increase beyond the limit via a settings frame |
| sendSettings(0, false, new SettingValue(4, 1 << 30)); |
| // Ack |
| parser.readFrame(true); |
| Assert.assertEquals("3-RST-[" + Http2Error.FLOW_CONTROL_ERROR.getCode() + "]\n", |
| output.getTrace()); |
| |
| } |
| } |