From 39ff60f59529d6433d0585b8d28b08f94662bb3e Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 28 Feb 2022 18:47:36 +1100 Subject: [PATCH 01/12] Issue #7635 - implement maxBlockedStreams logic in QpackDecoder Signed-off-by: Lachlan Roberts --- .../jetty/http3/qpack/QpackDecoder.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java index 4129979ca68b..6fc49bf80918 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java @@ -16,7 +16,11 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.MetaData; @@ -50,6 +54,7 @@ public class QpackDecoder implements Dumpable private final List _encodedFieldSections = new ArrayList<>(); private final NBitIntegerParser _integerDecoder = new NBitIntegerParser(); private final InstructionHandler _instructionHandler = new InstructionHandler(); + private final Map _blockedStreams = new HashMap<>(); private int _maxHeaderSize; private int _maxBlockedStreams; @@ -100,7 +105,6 @@ public void setMaxHeaderSize(int maxHeaderSize) public int getMaxBlockedStreams() { - // TODO: implement logic about blocked streams by calling this method. return _maxBlockedStreams; } @@ -172,6 +176,10 @@ public boolean decode(long streamId, ByteBuffer buffer, Handler handler) throws { if (LOG.isDebugEnabled()) LOG.debug("Deferred Decoding: streamId={}, encodedFieldSection={}", streamId, encodedFieldSection); + AtomicInteger blockedFields = _blockedStreams.computeIfAbsent(streamId, id -> new AtomicInteger(0)); + blockedFields.incrementAndGet(); + if (_blockedStreams.size() > _maxBlockedStreams) + throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "exceeded max blocked streams"); _encodedFieldSections.add(encodedFieldSection); } @@ -226,6 +234,7 @@ public void parseInstructions(ByteBuffer buffer) throws QpackException public void streamCancellation(long streamId) { _encodedFieldSections.removeIf(encodedFieldSection -> encodedFieldSection.getStreamId() == streamId); + _blockedStreams.remove(streamId); _metaDataNotifications.removeIf(notification -> notification._streamId == streamId); _instructions.add(new StreamCancellationInstruction(streamId)); notifyInstructionHandler(); @@ -234,12 +243,17 @@ public void streamCancellation(long streamId) private void checkEncodedFieldSections() throws QpackException { int insertCount = _context.getDynamicTable().getInsertCount(); - for (EncodedFieldSection encodedFieldSection : _encodedFieldSections) + Iterator iterator = _encodedFieldSections.iterator(); + while (iterator.hasNext()) { + EncodedFieldSection encodedFieldSection = iterator.next(); if (encodedFieldSection.getRequiredInsertCount() <= insertCount) { + iterator.remove(); long streamId = encodedFieldSection.getStreamId(); MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize); + if (_blockedStreams.get(streamId).decrementAndGet() <= 0) + _blockedStreams.remove(streamId); if (LOG.isDebugEnabled()) LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData); From 6143e4358f621c76ff6393656aa7626680907b3e Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 30 Mar 2022 11:02:30 +1100 Subject: [PATCH 02/12] do not expect section ack for zero required insert count Signed-off-by: Lachlan Roberts --- .../java/org/eclipse/jetty/http3/qpack/QpackDecoder.java | 9 ++++++--- .../java/org/eclipse/jetty/http3/qpack/QpackEncoder.java | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java index 4129979ca68b..5beff005e929 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackDecoder.java @@ -166,7 +166,8 @@ public boolean decode(long streamId, ByteBuffer buffer, Handler handler) throws if (LOG.isDebugEnabled()) LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData); _metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler)); - _instructions.add(new SectionAcknowledgmentInstruction(streamId)); + if (requiredInsertCount > 0) + _instructions.add(new SectionAcknowledgmentInstruction(streamId)); } else { @@ -236,7 +237,8 @@ private void checkEncodedFieldSections() throws QpackException int insertCount = _context.getDynamicTable().getInsertCount(); for (EncodedFieldSection encodedFieldSection : _encodedFieldSections) { - if (encodedFieldSection.getRequiredInsertCount() <= insertCount) + int requiredInsertCount = encodedFieldSection.getRequiredInsertCount(); + if (requiredInsertCount <= insertCount) { long streamId = encodedFieldSection.getStreamId(); MetaData metaData = encodedFieldSection.decode(_context, _maxHeaderSize); @@ -244,7 +246,8 @@ private void checkEncodedFieldSections() throws QpackException LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData); _metaDataNotifications.add(new MetaDataNotification(streamId, metaData, encodedFieldSection.getHandler())); - _instructions.add(new SectionAcknowledgmentInstruction(streamId)); + if (requiredInsertCount > 0) + _instructions.add(new SectionAcknowledgmentInstruction(streamId)); } } } diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java index 7105b5257bf9..e96ba17767fd 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java @@ -193,7 +193,15 @@ public void encode(ByteBuffer buffer, long streamId, MetaData metadata) throws Q requiredInsertCount = entryRequiredInsertCount; } + // We should not expect section acknowledgements for 0 required insert count. sectionInfo.setRequiredInsertCount(requiredInsertCount); + if (requiredInsertCount == 0) + { + streamInfo.remove(sectionInfo); + if (streamInfo.isEmpty()) + _streamInfoMap.remove(streamId); + } + int base = dynamicTable.getBase(); int encodedInsertCount = encodeInsertCount(requiredInsertCount, dynamicTable.getCapacity()); boolean signBit = base < requiredInsertCount; From 685d617a1b5b456ed4baa479e52da39c6e042cde Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 5 May 2022 16:09:55 +1000 Subject: [PATCH 03/12] Issue #7802 - add testing for qpack section acknowledgement Signed-off-by: Lachlan Roberts --- .../jetty/http3/qpack/QpackTestUtil.java | 60 ++++++++++++++ .../qpack/SectionAcknowledgmentTest.java | 79 +++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java index 5645bdfd6054..78174a1965b7 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java @@ -16,6 +16,10 @@ import java.nio.ByteBuffer; import java.util.List; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.NullByteBufferPool; import org.eclipse.jetty.util.BufferUtil; @@ -27,6 +31,23 @@ public class QpackTestUtil { + public static ByteBuffer toBuffer(Instruction... instructions) + { + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool()); + for (Instruction instruction : instructions) + { + instruction.encode(lease); + } + ByteBuffer combinedBuffer = BufferUtil.allocate(Math.toIntExact(lease.getTotalLength())); + BufferUtil.clearToFill(combinedBuffer); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + combinedBuffer.put(buffer); + } + BufferUtil.flipToFlush(combinedBuffer, 0); + return combinedBuffer; + } + public static Matcher equalsHex(String expectedString) { expectedString = expectedString.replaceAll("\\s+", ""); @@ -56,4 +77,43 @@ public static String toHexString(Instruction instruction) { return BufferUtil.toHexString(toBuffer(List.of(instruction))); } + + public static ByteBuffer encode(QpackEncoder encoder, long streamId, MetaData metaData) throws QpackException + { + ByteBuffer buffer = BufferUtil.allocate(1024); + BufferUtil.clearToFill(buffer); + encoder.encode(buffer, streamId, metaData); + BufferUtil.flipToFlush(buffer, 0); + return buffer; + } + + public static HttpFields.Mutable toHttpFields(HttpField field) + { + return HttpFields.build().add(field); + } + + public static MetaData toMetaData(String name, String value) + { + return toMetaData(toHttpFields(new HttpField(name, value))); + } + + public static MetaData toMetaData(String method, String path, String scheme) + { + return toMetaData(method, path, scheme, null); + } + + public static MetaData toMetaData(String method, String path, String scheme, HttpFields.Mutable fields) + { + if (fields == null) + fields = HttpFields.build(); + fields.put(":scheme", scheme); + fields.put(":method", method); + fields.put(":path", path); + return new MetaData(HttpVersion.HTTP_3, fields); + } + + public static MetaData toMetaData(HttpFields fields) + { + return new MetaData(HttpVersion.HTTP_3, fields); + } } diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java new file mode 100644 index 000000000000..b0446f304fdf --- /dev/null +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java @@ -0,0 +1,79 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 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.http3.qpack; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction; +import org.eclipse.jetty.util.BufferUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.eclipse.jetty.http3.qpack.QpackTestUtil.encode; +import static org.eclipse.jetty.http3.qpack.QpackTestUtil.toBuffer; +import static org.eclipse.jetty.http3.qpack.QpackTestUtil.toMetaData; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SectionAcknowledgmentTest +{ + private static final int MAX_BLOCKED_STREAMS = 5; + private static final int MAX_HEADER_SIZE = 1024; + + private QpackEncoder _encoder; + private QpackDecoder _decoder; + private TestDecoderHandler _decoderHandler; + private TestEncoderHandler _encoderHandler; + + @BeforeEach + public void before() + { + _encoderHandler = new TestEncoderHandler(); + _decoderHandler = new TestDecoderHandler(); + _encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS); + _decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE); + } + + @Test + public void testSectionAcknowledgmentForZeroRequiredInsertCountOnDecoder() throws Exception + { + // Encode a header with only a value contained in the static table. + ByteBuffer buffer = encode(_encoder, 0, toMetaData("GET", "/", "http")); + + // No instruction since no addition to table. + Instruction instruction = _encoderHandler.getInstruction(); + assertNull(instruction); + + // Decoding should generate no instruction. + _decoder.decode(0, buffer, _decoderHandler); + instruction = _decoderHandler.getInstruction(); + assertNull(instruction); + } + + @Test + public void testSectionAcknowledgmentForZeroRequiredInsertCountOnEncoder() throws Exception + { + // Encode a header with only a value contained in the static table. + ByteBuffer buffer = encode(_encoder, 0, toMetaData("GET", "/", "http")); + System.err.println(BufferUtil.toDetailString(buffer)); + + // Parsing a section ack instruction on the encoder when we are not expecting it should result in QPACK_DECODER_STREAM_ERROR. + SectionAcknowledgmentInstruction instruction = new SectionAcknowledgmentInstruction(0); + ByteBuffer instructionBuffer = toBuffer(instruction); + QpackException error = assertThrows(QpackException.class, () -> _encoder.parseInstructions(instructionBuffer)); + assertThat(error.getMessage(), containsString("No StreamInfo for 0")); + } +} From 2093f3822e336c2ec50f61acb6c4af6cd5f057c4 Mon Sep 17 00:00:00 2001 From: Lachlan Date: Thu, 12 May 2022 14:31:21 +1000 Subject: [PATCH 04/12] Add TRANSFER_ENCODING violation for MultiPart RFC7578 parser. (#7976) * Add TRANSFER_ENCODING violation for MultiPart RFC7578 parser. * Ignore TRANSFER_ENCODING violation for 8bit and binary. Signed-off-by: Lachlan Roberts --- .../server/MultiPartFormInputStream.java | 33 +++++- .../org/eclipse/jetty/server/Request.java | 18 ++++ .../server/MultiPartFormInputStreamTest.java | 101 ++++++++++++++++++ .../org/eclipse/jetty/server/RequestTest.java | 5 +- 4 files changed, 155 insertions(+), 2 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java index 59afd2f26bde..763ee57036b5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java @@ -30,6 +30,7 @@ import java.nio.file.StandardOpenOption; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.stream.Collectors; import javax.servlet.MultipartConfigElement; @@ -91,6 +92,7 @@ private enum State private final AutoLock _lock = new AutoLock(); private final MultiMap _parts = new MultiMap<>(); + private final EnumSet _nonComplianceWarnings = EnumSet.noneOf(NonCompliance.class); private final InputStream _in; private final MultipartConfigElement _config; private final File _contextTmpDir; @@ -102,6 +104,31 @@ private enum State private volatile int _bufferSize = 16 * 1024; private State state = State.UNPARSED; + public enum NonCompliance + { + TRANSFER_ENCODING("https://tools.ietf.org/html/rfc7578#section-4.7"); + + final String _rfcRef; + + NonCompliance(String rfcRef) + { + _rfcRef = rfcRef; + } + + public String getURL() + { + return _rfcRef; + } + } + + /** + * @return an EnumSet of non compliances with the RFC that were accepted by this parser + */ + public EnumSet getNonComplianceWarnings() + { + return _nonComplianceWarnings; + } + public class MultiPart implements Part { protected String _name; @@ -671,7 +698,11 @@ else if (key.equalsIgnoreCase("content-type")) // Transfer encoding is not longer considers as it is deprecated as per // https://tools.ietf.org/html/rfc7578#section-4.7 - + if (key.equalsIgnoreCase("content-transfer-encoding")) + { + if (!"8bit".equalsIgnoreCase(value) && !"binary".equalsIgnoreCase(value)) + _nonComplianceWarnings.add(NonCompliance.TRANSFER_ENCODING); + } } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 777eb1394884..126766c4c0c9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -66,6 +66,7 @@ import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.ComplianceViolation; import org.eclipse.jetty.http.HostPortHttpField; +import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField; import org.eclipse.jetty.http.HttpField; @@ -2295,6 +2296,7 @@ private Collection getParts(MultiMap params) throws IOException _multiParts = newMultiParts(config); Collection parts = _multiParts.getParts(); + setNonComplianceViolationsOnRequest(); String formCharset = null; Part charsetPart = _multiParts.getPart("_charset_"); @@ -2355,6 +2357,22 @@ else if (getCharacterEncoding() != null) return _multiParts.getParts(); } + private void setNonComplianceViolationsOnRequest() + { + @SuppressWarnings("unchecked") + List violations = (List)getAttribute(HttpCompliance.VIOLATIONS_ATTR); + if (violations != null) + return; + + EnumSet nonComplianceWarnings = _multiParts.getNonComplianceWarnings(); + violations = new ArrayList<>(); + for (MultiPartFormInputStream.NonCompliance nc : nonComplianceWarnings) + { + violations.add(nc.name() + ": " + nc.getURL()); + } + setAttribute(HttpCompliance.VIOLATIONS_ATTR, violations); + } + private MultiPartFormInputStream newMultiParts(MultipartConfigElement config) throws IOException { return new MultiPartFormInputStream(getInputStream(), getContentType(), config, diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java index 5fbd19ed1166..4b067c8682e2 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartFormInputStreamTest.java @@ -18,10 +18,12 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Base64; import java.util.Collection; +import java.util.EnumSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -32,13 +34,17 @@ import javax.servlet.http.Part; import org.eclipse.jetty.server.MultiPartFormInputStream.MultiPart; +import org.eclipse.jetty.server.MultiPartFormInputStream.NonCompliance; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.junit.jupiter.api.Test; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; @@ -1024,6 +1030,99 @@ public void testBase64EncodedContent() throws Exception baos = new ByteArrayOutputStream(); IO.copy(p3.getInputStream(), baos); assertEquals("the end", baos.toString(StandardCharsets.US_ASCII)); + + assertThat(mpis.getNonComplianceWarnings(), equalTo(EnumSet.of(NonCompliance.TRANSFER_ENCODING))); + } + + @Test + public void testFragmentation() throws IOException + { + String contentType = "multipart/form-data, boundary=----WebKitFormBoundaryhXfFAMfUnUKhmqT8"; + String payload1 = + "------WebKitFormBoundaryhXfFAMfUnUKhmqT8\r\n" + + "Content-Disposition: form-data; name=\"field1\"\r\n\r\n" + + "value1" + + "\r\n--"; + String payload2 = "----WebKitFormBoundaryhXfFAMfUnUKhmqT8\r\n" + + "Content-Disposition: form-data; name=\"field2\"\r\n\r\n" + + "value2" + + "\r\n------WebKitFormBoundaryhXfFAMfUnUKhmqT8--\r\n"; + + // Split the content into separate reads, with the content broken up on the boundary string. + AppendableInputStream stream = new AppendableInputStream(); + stream.append(payload1); + stream.append(""); + stream.append(payload2); + stream.endOfContent(); + + MultipartConfigElement config = new MultipartConfigElement(_dirname); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(stream, contentType, config, _tmpDir); + mpis.setDeleteOnExit(true); + + // Check size. + Collection parts = mpis.getParts(); + assertThat(parts.size(), is(2)); + + // Check part content. + assertThat(IO.toString(mpis.getPart("field1").getInputStream()), is("value1")); + assertThat(IO.toString(mpis.getPart("field2").getInputStream()), is("value2")); + } + + static class AppendableInputStream extends InputStream + { + private static final ByteBuffer EOF = ByteBuffer.allocate(0); + private final BlockingArrayQueue buffers = new BlockingArrayQueue<>(); + private ByteBuffer current; + + public void append(String data) + { + append(data.getBytes(StandardCharsets.US_ASCII)); + } + + public void append(byte[] data) + { + buffers.add(BufferUtil.toBuffer(data)); + } + + public void endOfContent() + { + buffers.add(EOF); + } + + @Override + public int read() throws IOException + { + byte[] buf = new byte[1]; + while (true) + { + int len = read(buf, 0, 1); + if (len < 0) + return -1; + if (len > 0) + return buf[0]; + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + if (current == null) + current = buffers.poll(); + if (current == EOF) + return -1; + if (BufferUtil.isEmpty(current)) + { + current = null; + return 0; + } + + ByteBuffer buffer = ByteBuffer.wrap(b, off, len); + buffer.flip(); + int read = BufferUtil.append(buffer, current); + if (BufferUtil.isEmpty(current)) + current = buffers.poll(); + return read; + } } @Test @@ -1062,6 +1161,8 @@ public void testQuotedPrintableEncoding() throws Exception baos = new ByteArrayOutputStream(); IO.copy(p2.getInputStream(), baos); assertEquals("truth=3Dbeauty", baos.toString(StandardCharsets.US_ASCII)); + + assertThat(mpis.getNonComplianceWarnings(), equalTo(EnumSet.of(NonCompliance.TRANSFER_ENCODING))); } @Test diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index 99e3dca600fb..91669cf873ff 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -501,6 +501,7 @@ public void testMultiPart() throws Exception "--AaB03x\r\n" + "content-disposition: form-data; name=\"stuff\"; filename=\"foo.upload\"\r\n" + "Content-Type: text/plain;charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: something\r\n" + "\r\n" + "000000000000000000000000000000000000000000000000000\r\n" + "--AaB03x--\r\n"; @@ -514,7 +515,9 @@ public void testMultiPart() throws Exception LocalEndPoint endPoint = _connector.connect(); endPoint.addInput(request); - assertTrue(endPoint.getResponse().startsWith("HTTP/1.1 200")); + String response = endPoint.getResponse(); + assertTrue(response.startsWith("HTTP/1.1 200")); + assertThat(response, containsString("Violation: TRANSFER_ENCODING")); // We know the previous request has completed if another request can be processed on the same connection. String cleanupRequest = "GET /foo/cleanup HTTP/1.1\r\n" + From a93896b22f1c601b0c87cadb8f7eddc8eae91868 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 12 May 2022 16:10:15 +1000 Subject: [PATCH 05/12] Issue #7977 - prevent possible NPE from UpgradeHttpServletRequest Signed-off-by: Lachlan Roberts --- .../core/server/internal/UpgradeHttpServletRequest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java index e647cb5984d7..e27fe7a7501d 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java @@ -430,7 +430,8 @@ public void setAttribute(String name, Object value) { if (request == null) attributes.put(name, value); - request.setAttribute(name, value); + else + request.setAttribute(name, value); } @Override @@ -438,7 +439,8 @@ public void removeAttribute(String name) { if (request == null) attributes.remove(name); - request.removeAttribute(name); + else + request.removeAttribute(name); } @Override From 2e98a26f036557e9b0e7aa80b598225dd5c22fbe Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 13 May 2022 13:49:10 +1000 Subject: [PATCH 06/12] added extra checks in SectionAcknowledgmentTest Signed-off-by: Lachlan Roberts --- .../jetty/http3/qpack/SectionAcknowledgmentTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java index b0446f304fdf..bc2eda81ce68 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/SectionAcknowledgmentTest.java @@ -15,6 +15,7 @@ import java.nio.ByteBuffer; +import org.eclipse.jetty.http3.qpack.QpackException.SessionException; import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction; import org.eclipse.jetty.util.BufferUtil; import org.junit.jupiter.api.BeforeEach; @@ -25,6 +26,8 @@ import static org.eclipse.jetty.http3.qpack.QpackTestUtil.toMetaData; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -68,12 +71,13 @@ public void testSectionAcknowledgmentForZeroRequiredInsertCountOnEncoder() throw { // Encode a header with only a value contained in the static table. ByteBuffer buffer = encode(_encoder, 0, toMetaData("GET", "/", "http")); - System.err.println(BufferUtil.toDetailString(buffer)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); // Parsing a section ack instruction on the encoder when we are not expecting it should result in QPACK_DECODER_STREAM_ERROR. SectionAcknowledgmentInstruction instruction = new SectionAcknowledgmentInstruction(0); ByteBuffer instructionBuffer = toBuffer(instruction); - QpackException error = assertThrows(QpackException.class, () -> _encoder.parseInstructions(instructionBuffer)); + SessionException error = assertThrows(SessionException.class, () -> _encoder.parseInstructions(instructionBuffer)); + assertThat(error.getErrorCode(), equalTo(QpackException.QPACK_ENCODER_STREAM_ERROR)); assertThat(error.getMessage(), containsString("No StreamInfo for 0")); } } From 0b00c0628ea04000e8758497c1afb03bd0c0d71e Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 13 May 2022 16:11:15 +1000 Subject: [PATCH 07/12] Issue #7635 - add extra testing for QPACK blocked streams Signed-off-by: Lachlan Roberts --- .../jetty/http3/qpack/QpackEncoder.java | 17 ++ .../http3/qpack/internal/StreamInfo.java | 1 + .../InsertCountIncrementInstruction.java | 5 + .../SectionAcknowledgmentInstruction.java | 5 + .../jetty/http3/qpack/BlockedStreamsTest.java | 213 ++++++++++++++++++ .../jetty/http3/qpack/QpackTestUtil.java | 71 ++++++ 6 files changed, 312 insertions(+) create mode 100644 jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java index 7105b5257bf9..d8427466f9c0 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/QpackEncoder.java @@ -106,6 +106,11 @@ public QpackEncoder(Instruction.Handler handler, int maxBlockedStreams) _parser = new EncoderInstructionParser(_instructionHandler); } + Map getStreamInfoMap() + { + return _streamInfoMap; + } + public int getMaxBlockedStreams() { return _maxBlockedStreams; @@ -193,7 +198,15 @@ public void encode(ByteBuffer buffer, long streamId, MetaData metadata) throws Q requiredInsertCount = entryRequiredInsertCount; } + // We should not expect section acknowledgements for 0 required insert count. sectionInfo.setRequiredInsertCount(requiredInsertCount); + if (requiredInsertCount == 0) + { + streamInfo.remove(sectionInfo); + if (streamInfo.isEmpty()) + _streamInfoMap.remove(streamId); + } + int base = dynamicTable.getBase(); int encodedInsertCount = encodeInsertCount(requiredInsertCount, dynamicTable.getCapacity()); boolean signBit = base < requiredInsertCount; @@ -472,9 +485,13 @@ public void onSectionAcknowledgement(long streamId) throws QpackException // The KnownInsertCount should be updated to the earliest sent RequiredInsertCount on that stream. StreamInfo.SectionInfo sectionInfo = streamInfo.acknowledge(); + boolean wasBlocked = sectionInfo.isBlocking(); sectionInfo.release(); _knownInsertCount = Math.max(_knownInsertCount, sectionInfo.getRequiredInsertCount()); + if (wasBlocked && !streamInfo.isBlocked()) + _blockedStreams--; + // If we have no more outstanding section acknowledgments remove the StreamInfo. if (streamInfo.isEmpty()) _streamInfoMap.remove(streamId); diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/StreamInfo.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/StreamInfo.java index 08e7dc09af83..4d229e094b34 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/StreamInfo.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/StreamInfo.java @@ -106,6 +106,7 @@ public void release() { entry.release(); } + _block = false; _entries.clear(); } diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java index 00421c71558e..1a57fe1507fd 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/InsertCountIncrementInstruction.java @@ -29,6 +29,11 @@ public InsertCountIncrementInstruction(int increment) _increment = increment; } + public int getIncrement() + { + return _increment; + } + @Override public void encode(ByteBufferPool.Lease lease) { diff --git a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java index 24ff83265fc9..9d6e916591c8 100644 --- a/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java +++ b/jetty-http3/http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/instruction/SectionAcknowledgmentInstruction.java @@ -29,6 +29,11 @@ public SectionAcknowledgmentInstruction(long streamId) _streamId = streamId; } + public long getStreamId() + { + return _streamId; + } + @Override public void encode(ByteBufferPool.Lease lease) { diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java new file mode 100644 index 000000000000..fa68def9404c --- /dev/null +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java @@ -0,0 +1,213 @@ +package org.eclipse.jetty.http3.qpack; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http3.qpack.QpackException.SessionException; +import org.eclipse.jetty.http3.qpack.internal.instruction.IndexedNameEntryInstruction; +import org.eclipse.jetty.http3.qpack.internal.instruction.InsertCountIncrementInstruction; +import org.eclipse.jetty.http3.qpack.internal.instruction.LiteralNameEntryInstruction; +import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction; +import org.eclipse.jetty.http3.qpack.internal.instruction.SetCapacityInstruction; +import org.eclipse.jetty.util.BufferUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.eclipse.jetty.http3.qpack.QpackTestUtil.encode; +import static org.eclipse.jetty.http3.qpack.QpackTestUtil.toBuffer; +import static org.eclipse.jetty.http3.qpack.QpackTestUtil.toMetaData; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BlockedStreamsTest +{ + private static final int MAX_BLOCKED_STREAMS = 5; + private static final int MAX_HEADER_SIZE = 1024; + + private QpackEncoder _encoder; + private QpackDecoder _decoder; + private TestDecoderHandler _decoderHandler; + private TestEncoderHandler _encoderHandler; + + @BeforeEach + public void before() + { + _encoderHandler = new TestEncoderHandler(); + _decoderHandler = new TestDecoderHandler(); + _encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS); + _decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE); + } + + @Test + public void testBlockedStreams() throws Exception + { + // These settings are determined by HTTP/3 settings frames. + _encoder.setMaxBlockedStreams(2); + _decoder.setMaxBlockedStreams(2); + + // Set capacity of the encoder & decoder to allow entries to be added to the table. + int capacity = 1024; + _encoder.setCapacity(capacity); + Instruction instruction = _encoderHandler.getInstruction(); + assertThat(instruction, instanceOf(SetCapacityInstruction.class)); + _decoder.parseInstructions(QpackTestUtil.toBuffer(instruction)); + + // Encode a new field, which will be added to table. But do not forward insertion instruction to decoder, + // this will cause decoder to become "blocked" on stream 0 until receives the instruction. + HttpField entry1 = new HttpField("name1", "value1"); + ByteBuffer buffer = encode(_encoder, 0, toMetaData("GET", "/", "http", entry1)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); + Instruction instruction1 = _encoderHandler.getInstruction(); + assertThat(instruction1, instanceOf(LiteralNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + + // Decoder will not be able to decode this header until it receives instruction. + boolean decoded = _decoder.decode(0, buffer, _decoderHandler); + assertFalse(decoded); + assertThat(BufferUtil.remaining(buffer), equalTo(0L)); + assertNull(_decoderHandler.getMetaData()); + assertNull(_decoderHandler.getInstruction()); + + // Encode second field with dynamic table, do not forward instruction to decoder. + HttpField entry2 = new HttpField("name1", "value2"); + buffer = encode(_encoder, 1, toMetaData("GET", "/", "http", entry2)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); + Instruction instruction2 = _encoderHandler.getInstruction(); + assertThat(instruction2, instanceOf(IndexedNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + + // Decoder will not be able to decode this header until it receives instruction. + decoded = _decoder.decode(1, buffer, _decoderHandler); + assertFalse(decoded); + assertNull(_decoderHandler.getMetaData()); + assertNull(_decoderHandler.getInstruction()); + + // Give first instruction to get first metadata. + _decoder.parseInstructions(QpackTestUtil.toBuffer(instruction1)); + MetaData metaData = _decoderHandler.getMetaData(); + assertThat(metaData.getFields().size(), equalTo(1)); + assertThat(metaData.getFields().get(entry1.getHeader()), equalTo(entry1.getValue())); + + Instruction inc1 = _decoderHandler.getInstruction(); + assertThat(inc1, instanceOf(InsertCountIncrementInstruction.class)); + assertThat(((InsertCountIncrementInstruction)inc1).getIncrement(), equalTo(1)); + + Instruction ack1 = _decoderHandler.getInstruction(); + assertThat(ack1, instanceOf(SectionAcknowledgmentInstruction.class)); + assertThat(((SectionAcknowledgmentInstruction)ack1).getStreamId(), equalTo(0L)); + + assertNull(_decoderHandler.getMetaData()); + assertNull(_decoderHandler.getInstruction()); + + // Give second instruction to get second metadata. + _decoder.parseInstructions(QpackTestUtil.toBuffer(instruction2)); + metaData = _decoderHandler.getMetaData(); + assertThat(metaData.getFields().size(), equalTo(1)); + assertThat(metaData.getFields().get(entry2.getHeader()), equalTo(entry2.getValue())); + + Instruction inc2 = _decoderHandler.getInstruction(); + assertThat(inc2, instanceOf(InsertCountIncrementInstruction.class)); + assertThat(((InsertCountIncrementInstruction)inc2).getIncrement(), equalTo(1)); + + Instruction ack2 = _decoderHandler.getInstruction(); + assertThat(ack2, instanceOf(SectionAcknowledgmentInstruction.class)); + assertThat(((SectionAcknowledgmentInstruction)ack2).getStreamId(), equalTo(1L)); + + assertNull(_decoderHandler.getMetaData()); + assertNull(_decoderHandler.getInstruction()); + + // The encoder hasn't received any InsertCountIncrementInstruction and so it thinks there are two streams blocked. + // It should only encode literal entries to not risk blocking another stream on the decoder. + HttpField entry3 = new HttpField("name3", "value3"); + buffer = encode(_encoder, 3, toMetaData("GET", "/", "http", entry3)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); + instruction = _encoderHandler.getInstruction(); + assertThat(instruction, instanceOf(LiteralNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + + // Can decode literal entry immediately without any further instructions. + decoded = _decoder.decode(3, buffer, _decoderHandler); + assertTrue(decoded); + metaData = _decoderHandler.getMetaData(); + assertThat(metaData.getFields().size(), equalTo(1)); + assertThat(metaData.getFields().get(entry3.getHeader()), equalTo(entry3.getValue())); + + // No longer referencing any streams that have been acknowledged. + buffer = toBuffer(inc1, ack1, inc2, ack2); + _encoder.parseInstructions(buffer); + assertThat(BufferUtil.remaining(buffer), equalTo(0L)); + assertThat(_encoder.getStreamInfoMap().size(), equalTo(0)); + + // Encoder can now reference entries not acknowledged by the decoder again. + HttpField entry4 = new HttpField("name4", "value4"); + buffer = encode(_encoder, 4, toMetaData("GET", "/", "http", entry4)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); + instruction = _encoderHandler.getInstruction(); + assertThat(instruction, instanceOf(LiteralNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + decoded = _decoder.decode(4, buffer, _decoderHandler); + assertFalse(decoded); + } + + @Test + public void testMaxBlockedStreams() throws Exception + { + // Encoder will risk blocking 1 more stream than the decoder will allow. + _encoder.setMaxBlockedStreams(3); + _decoder.setMaxBlockedStreams(2); + + // Set capacity of the encoder & decoder to allow entries to be added to the table. + int capacity = 1024; + _encoder.setCapacity(capacity); + Instruction instruction = _encoderHandler.getInstruction(); + assertThat(instruction, instanceOf(SetCapacityInstruction.class)); + _decoder.parseInstructions(QpackTestUtil.toBuffer(instruction)); + + // Encode a new field, which will be added to table. But do not forward insertion instruction to decoder, + // this will cause decoder to become "blocked" on stream 0 until receives the instruction. + HttpField entry1 = new HttpField("name1", "value1"); + ByteBuffer buffer = encode(_encoder, 0, toMetaData("GET", "/", "http", entry1)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); + Instruction instruction1 = _encoderHandler.getInstruction(); + assertThat(instruction1, instanceOf(LiteralNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + + // Decoder will not be able to decode this header until it receives instruction. + boolean decoded = _decoder.decode(0, buffer, _decoderHandler); + assertFalse(decoded); + assertThat(BufferUtil.remaining(buffer), equalTo(0L)); + assertNull(_decoderHandler.getMetaData()); + assertNull(_decoderHandler.getInstruction()); + + // Encode second field with dynamic table, do not forward instruction to decoder. + HttpField entry2 = new HttpField("name1", "value2"); + buffer = encode(_encoder, 1, toMetaData("GET", "/", "http", entry2)); + assertThat(BufferUtil.remaining(buffer), greaterThan(0L)); + Instruction instruction2 = _encoderHandler.getInstruction(); + assertThat(instruction2, instanceOf(IndexedNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + + // Decoder will not be able to decode this header until it receives instruction. + decoded = _decoder.decode(1, buffer, _decoderHandler); + assertFalse(decoded); + assertNull(_decoderHandler.getMetaData()); + assertNull(_decoderHandler.getInstruction()); + + // This entry will block a 3rd stream which the decoder must not allow. + HttpField entry3 = new HttpField("name3", "value3"); + ByteBuffer encodedMetadata = encode(_encoder, 3, toMetaData("GET", "/", "http", entry3)); + assertThat(BufferUtil.remaining(encodedMetadata), greaterThan(0L)); + instruction = _encoderHandler.getInstruction(); + assertThat(instruction, instanceOf(LiteralNameEntryInstruction.class)); + assertNull(_encoderHandler.getInstruction()); + + assertThrows(SessionException.class, () -> _decoder.decode(3, encodedMetadata, _decoderHandler)); + } +} diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java index 5645bdfd6054..f7ef407ae632 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java @@ -16,6 +16,10 @@ import java.nio.ByteBuffer; import java.util.List; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.NullByteBufferPool; import org.eclipse.jetty.util.BufferUtil; @@ -27,6 +31,23 @@ public class QpackTestUtil { + public static ByteBuffer toBuffer(Instruction... instructions) + { + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(new NullByteBufferPool()); + for (Instruction instruction : instructions) + { + instruction.encode(lease); + } + ByteBuffer combinedBuffer = BufferUtil.allocate(Math.toIntExact(lease.getTotalLength())); + BufferUtil.clearToFill(combinedBuffer); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + combinedBuffer.put(buffer); + } + BufferUtil.flipToFlush(combinedBuffer, 0); + return combinedBuffer; + } + public static Matcher equalsHex(String expectedString) { expectedString = expectedString.replaceAll("\\s+", ""); @@ -56,4 +77,54 @@ public static String toHexString(Instruction instruction) { return BufferUtil.toHexString(toBuffer(List.of(instruction))); } + + public static ByteBuffer encode(QpackEncoder encoder, long streamId, MetaData metaData) throws QpackException + { + ByteBuffer buffer = BufferUtil.allocate(1024); + BufferUtil.clearToFill(buffer); + encoder.encode(buffer, streamId, metaData); + BufferUtil.flipToFlush(buffer, 0); + return buffer; + } + + public static HttpFields.Mutable toHttpFields(HttpField field) + { + return HttpFields.build().add(field); + } + + public static MetaData toMetaData(String name, String value) + { + return toMetaData(toHttpFields(new HttpField(name, value))); + } + + public static MetaData toMetaData(String method, String path, String scheme) + { + return toMetaData(method, path, scheme, (HttpField)null); + } + + public static MetaData toMetaData(String method, String path, String scheme, HttpField ...fields) + { + HttpFields.Mutable httpFields = HttpFields.build(); + for (HttpField field : fields) + { + httpFields.add(field); + } + + return toMetaData(method, path, scheme, httpFields); + } + + public static MetaData toMetaData(String method, String path, String scheme, HttpFields.Mutable fields) + { + fields = HttpFields.build() + .put(":scheme", scheme) + .put(":method", method) + .put(":path", path) + .add(fields); + return new MetaData(HttpVersion.HTTP_3, fields); + } + + public static MetaData toMetaData(HttpFields fields) + { + return new MetaData(HttpVersion.HTTP_3, fields); + } } From b9689c2f2703d6c5b67454e7373e9846b915567f Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 13 May 2022 16:15:03 +1000 Subject: [PATCH 08/12] add missing licence header Signed-off-by: Lachlan Roberts --- .../jetty/http3/qpack/BlockedStreamsTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java index fa68def9404c..2584c43d52aa 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/BlockedStreamsTest.java @@ -1,3 +1,16 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 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.http3.qpack; import java.nio.ByteBuffer; From ac56b95eba8441f2ee4896355e5a0179d96728d5 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 13 May 2022 16:21:07 +1000 Subject: [PATCH 09/12] fix checkstyle violation Signed-off-by: Lachlan Roberts --- .../test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java index f7ef407ae632..fc12a5906f8d 100644 --- a/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java +++ b/jetty-http3/http3-qpack/src/test/java/org/eclipse/jetty/http3/qpack/QpackTestUtil.java @@ -102,7 +102,7 @@ public static MetaData toMetaData(String method, String path, String scheme) return toMetaData(method, path, scheme, (HttpField)null); } - public static MetaData toMetaData(String method, String path, String scheme, HttpField ...fields) + public static MetaData toMetaData(String method, String path, String scheme, HttpField... fields) { HttpFields.Mutable httpFields = HttpFields.build(); for (HttpField field : fields) From 77f164c43cd41f71888449a88f69a27b50a7379f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 May 2022 09:20:21 +0000 Subject: [PATCH 10/12] Bump hawtio-default from 2.14.5 to 2.15.0 Bumps [hawtio-default](https://github.com/hawtio/hawtio) from 2.14.5 to 2.15.0. - [Release notes](https://github.com/hawtio/hawtio/releases) - [Changelog](https://github.com/hawtio/hawtio/blob/main/CHANGES.md) - [Commits](https://github.com/hawtio/hawtio/compare/hawtio-2.14.5...hawtio-2.15.0) --- updated-dependencies: - dependency-name: io.hawt:hawtio-default dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c155dece9fbc..9825141c67a5 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 31.1-jre 5.1.0 2.2 - 2.14.5 + 2.15.0 4.2.5 4.4.3.Final 11.0.15.Final From 797e1f8eadd4b0bd177442451824bda3181210c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 10:40:40 -0500 Subject: [PATCH 11/12] Bump jackson-core from 2.13.2 to 2.13.3 (#8004) Bumps [jackson-core](https://github.com/FasterXML/jackson-core) from 2.13.2 to 2.13.3. - [Release notes](https://github.com/FasterXML/jackson-core/releases) - [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.13.2...jackson-core-2.13.3) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9825141c67a5..5bb269dcf757 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 4.4.3.Final 11.0.15.Final 2.13.2 - 2.13.2 + 2.13.3 2.13.2.2 1.2.2 1.3.5 From cbfeb5eb42a5ab3fa47dff61fbb3afff67302b9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 14:33:42 -0500 Subject: [PATCH 12/12] Bump jackson-annotations from 2.13.2 to 2.13.3 (#8003) Bumps [jackson-annotations](https://github.com/FasterXML/jackson) from 2.13.2 to 2.13.3. - [Release notes](https://github.com/FasterXML/jackson/releases) - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-annotations dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5bb269dcf757..a9617c03cc04 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ 4.2.5 4.4.3.Final 11.0.15.Final - 2.13.2 + 2.13.3 2.13.3 2.13.2.2 1.2.2