From ac82a3f0c74235be0bcc7e6686b98cd8e480d9c5 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 28 Feb 2024 12:14:33 +0100 Subject: [PATCH] Fix #10805 zero dynamic table (#11445) (#11452) * Fix #10805 zero dynamic table (#11445) * Added test for #10805 Zero Dynamic Table * fixed file header * Added test for #10805 Zero Dynamic Table * Fix for #10805 Zero Dynamic Table Set the correct default size for the table. Always send the max table size on the first encode * updated file header Signed-off-by: gregw --------- Signed-off-by: gregw --- .../jetty/http2/client/DynamicTableTest.java | 185 ++++++++++++++++++ .../jetty/http2/hpack/HpackEncoder.java | 8 +- 2 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/DynamicTableTest.java diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/DynamicTableTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/DynamicTableTest.java new file mode 100644 index 000000000000..d4787aa97b7a --- /dev/null +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/DynamicTableTest.java @@ -0,0 +1,185 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http2.client; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.SettingsFrame; +import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory; +import org.eclipse.jetty.util.Promise; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DynamicTableTest extends AbstractTest +{ + @ParameterizedTest + @CsvSource({"0,-1", "-1,0", "0,0"}) + public void testMaxEncoderTableCapacityZero(int clientMaxCapacity, int serverMaxCapacity) throws Exception + { + start(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + resp.setStatus(200); + resp.getOutputStream().close(); + } + }); + + if (clientMaxCapacity >= 0) + client.setMaxEncoderTableCapacity(clientMaxCapacity); + if (serverMaxCapacity >= 0) + connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).setMaxEncoderTableCapacity(serverMaxCapacity); + + CountDownLatch serverPreface = new CountDownLatch(1); + Session session = newClient(new Session.Listener.Adapter() + { + @Override + public void onSettings(Session session, SettingsFrame frame) + { + serverPreface.countDown(); + } + }); + assertTrue(serverPreface.await(5, TimeUnit.SECONDS)); + + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, true); + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + MetaData.Response response = (MetaData.Response)frame.getMetaData(); + assertEquals(200, response.getStatus()); + latch.countDown(); + } + }); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @CsvSource({"0,-1", "-1,0", "0,0"}) + public void testMaxDecoderTableCapacityZero(int clientMaxCapacity, int serverMaxCapacity) throws Exception + { + start(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + resp.setStatus(200); + resp.getOutputStream().close(); + } + }); + + if (clientMaxCapacity >= 0) + client.setMaxDecoderTableCapacity(clientMaxCapacity); + if (serverMaxCapacity >= 0) + connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).setMaxDecoderTableCapacity(serverMaxCapacity); + + CountDownLatch serverPreface = new CountDownLatch(1); + Session session = newClient(new Session.Listener.Adapter() + { + @Override + public void onSettings(Session session, SettingsFrame frame) + { + serverPreface.countDown(); + } + }); + assertTrue(serverPreface.await(5, TimeUnit.SECONDS)); + + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, true); + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + MetaData.Response response = (MetaData.Response)frame.getMetaData(); + assertEquals(200, response.getStatus()); + latch.countDown(); + } + }); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @ParameterizedTest + @CsvSource({"0,-1", "-1,0", "0,0"}) + public void testMaxTableCapacityZero(int clientMaxCapacity, int serverMaxCapacity) throws Exception + { + start(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + resp.setStatus(200); + resp.getOutputStream().close(); + } + }); + + if (clientMaxCapacity >= 0) + { + client.setMaxDecoderTableCapacity(clientMaxCapacity); + client.setMaxEncoderTableCapacity(clientMaxCapacity); + } + if (serverMaxCapacity >= 0) + { + connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).setMaxEncoderTableCapacity(serverMaxCapacity); + connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).setMaxDecoderTableCapacity(serverMaxCapacity); + } + + CountDownLatch serverPreface = new CountDownLatch(1); + Session session = newClient(new Session.Listener.Adapter() + { + @Override + public void onSettings(Session session, SettingsFrame frame) + { + serverPreface.countDown(); + } + }); + assertTrue(serverPreface.await(5, TimeUnit.SECONDS)); + + MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY); + HeadersFrame frame = new HeadersFrame(metaData, null, true); + CountDownLatch latch = new CountDownLatch(1); + session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter() + { + @Override + public void onHeaders(Stream stream, HeadersFrame frame) + { + MetaData.Response response = (MetaData.Response)frame.getMetaData(); + assertEquals(200, response.getStatus()); + latch.countDown(); + } + }); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index fc1d6c980eca..b066e1ebe771 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -99,10 +99,11 @@ public class HpackEncoder private int _maxHeaderListSize; private int _headerListSize; private boolean _validateEncoding = true; + private boolean _maxDynamicTableSizeSent = false; public HpackEncoder() { - _context = new HpackContext(0); + _context = new HpackContext(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); _debug = LOG.isDebugEnabled(); setMaxTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); setTableCapacity(HpackContext.DEFAULT_MAX_TABLE_CAPACITY); @@ -209,8 +210,11 @@ public void encode(ByteBuffer buffer, MetaData metadata) throws HpackException // If max table size changed, send the correspondent instruction. int tableCapacity = getTableCapacity(); - if (tableCapacity != _context.getMaxDynamicTableSize()) + if (!_maxDynamicTableSizeSent || tableCapacity != _context.getMaxDynamicTableSize()) + { + _maxDynamicTableSizeSent = true; encodeMaxDynamicTableSize(buffer, tableCapacity); + } // Add Request/response meta fields if (metadata.isRequest())