/* * 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.util.logging.Level; import java.util.logging.LogManager; import org.junit.Assert; import org.junit.Test; /** * Unit tests for Section 5.3 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. * * Note: Unit tests for the examples described by each of the figures may be * found in {@link TestAbstractStream}. */ public class TestHttp2Section_5_3 extends Http2TestBase { // Section 5.3.1 @Test public void testStreamDependsOnSelf() throws Exception { http2Connect(); sendPriority(3, 3, 15); parser.readFrame(true); Assert.assertEquals("3-RST-[1]\n", output.getTrace()); } // Section 5.3.2 @Test public void testWeighting() throws Exception { // Temporary debug logging for server side code that allocates capacity // to streams to debug BZ 58659 LogManager.getLogManager().getLogger(Http2UpgradeHandler.class.getName()).setLevel(Level.ALL); http2Connect(); // Default connection window size is 64k - 1. Initial request will have // used 8k (56k -1). Increase it to 57k sendWindowUpdate(0, 1 + 1024); // Use up 56k of the connection window for (int i = 3; i < 17; i += 2) { sendSimpleGetRequest(i); readSimpleGetResponse(); } // Set the default window size to 1024 bytes sendSettings(0, false, new SettingValue(4, 1024)); // Wait for the ack parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); output.clearTrace(); // At this point the connection window should be 1k and any new stream // should have a window of 1k as well // Set up streams A=17, B=19, C=21 sendPriority(17, 0, 15); sendPriority(19, 17, 3); sendPriority(21, 17, 11); // First, process a request on stream 17. This should consume both // stream 17's window and the connection window. sendSimpleGetRequest(17); // 17-headers, 17-1k-body parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); output.clearTrace(); // Send additional requests. Connection window is empty so only headers // will be returned. sendSimpleGetRequest(19); sendSimpleGetRequest(21); // Open up the flow control windows for stream 19 & 21 to more than the // size of a simple request (8k) sendWindowUpdate(19, 16*1024); sendWindowUpdate(21, 16*1024); // Read some frames // 19-headers, 21-headers parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); output.clearTrace(); // At this point 17 is blocked because the stream window is zero and // 19 & 21 are blocked because the connection window is zero. // // To test allocation, the connection window size is increased by 1. // This should result in an allocation of 1 byte each to streams 19 and // 21 but because each stream is processed in a separate thread it is // not guaranteed that both streams will be blocked when the connection // window size is increased. The test therefore sends 1 byte window // updates until a small body has been seen from each stream. Then the // tests sends a larger (1024 byte) window update and checks that it is // correctly distributed between the streams. // // The test includes a margin to allow for the potential differences in // response caused by timing differences on the server. // // The loop below handles 0, 1 or 2 stream being blocked // - If 0 streams are blocked the connection window will be set to one // and that will be consumed by the first stream to attempt to write. // That body frame will be read by the client. The stream will then be // blocked and the loop will start again. // - If 1 stream is blocked, the connection window will be set to one // which will then be consumed by the blocked stream. After writing // the single byte the stream will again be blocked and the loop will // start again. // - If 2 streams are blocked the connection window will be set to one // but one byte will be permitted for both streams (due to rounding in // the allocation). The window size should be -1 (see below). Two // frames (one for each stream will be written) one of which will be // consumed by the client. The loop will start again and the Window // size incremented to zero. No data will be written by the streams // but the second data frame written in the last iteration of the loop // will be read. The loop will then exit since frames from both // streams will have been observed. boolean seen19 = false; boolean seen21 = false; while (!seen19 || !seen21) { sendWindowUpdate(0, 1); parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); int[] data = parseBodyFrame(output.getTrace()); if (data[0] == 19) { seen19 = true; } else if (data[0] == 21) { seen21 = true; } else { // Unexpected stream Assert.fail("Unexpected stream: [" + output.getTrace() + "]"); } if (data[1] > 10) { // Larger than expected body size Assert.fail("Larger than expected body: [" + output.getTrace() + "]"); } output.clearTrace(); } sendWindowUpdate(0, 1024); parser.readFrame(true); // Make sure you have read the big comment before the loop above. It is // possible that the timing of the server threads is such that there are // still small body frames to read. int[] data = parseBodyFrame(output.getTrace()); while (data[1] < 10) { // Debugging Gump failure log.info(output.getTrace()); output.clearTrace(); parser.readFrame(true); data = parseBodyFrame(output.getTrace()); } // Should now have two larger body frames. One has already been read. seen19 = false; seen21 = false; while (!seen19 && !seen21) { // Debugging Gump failure log.info(output.getTrace()); if (data[0] == 19) { seen19 = true; // If everything works instantly this should be 256 but allow a // fairly large margin for timing differences if (data[1] < 236 || data[1] > 276) { Assert.fail("Unexpected body size: [" + output.getTrace() + "]"); } } else if (data[0] == 21) { seen21 = true; // If everything works instantly this should be 768 but allow a // fairly large margin for timing differences if (data[1] < 748 || data[1] > 788) { Assert.fail("Unexpected body size: [" + output.getTrace() + "]"); } } else { Assert.fail("Unexpected stream: [" + output.getTrace() + "]"); } output.clearTrace(); parser.readFrame(true); data = parseBodyFrame(output.getTrace()); } // Debugging Gump failure log.info(output.getTrace()); output.clearTrace(); // Release everything and read all the remaining data sendWindowUpdate(0, 1024 * 1024); sendWindowUpdate(17, 1024 * 1024); // Read remaining frames // 17-7k-body, 19~8k-body, 21~8k-body for (int i = 0; i < 3; i++) { parser.readFrame(true); // Debugging Gump failure log.info(output.getTrace()); } } private int[] parseBodyFrame(String output) { String[] parts = output.trim().split("-"); if (parts.length != 3 || !"Body".equals(parts[1])) { Assert.fail("Unexpected output: [" + output + "]"); } int[] result = new int[2]; result[0] = Integer.parseInt(parts[0]); result[1] = Integer.parseInt(parts[2]); return result; } }