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 5645bdfd605..78174a1965b 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 @@ package org.eclipse.jetty.http3.qpack; 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 @@ import static org.hamcrest.Matchers.is; 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 class QpackTestUtil { 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 00000000000..b0446f304fd --- /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")); + } +}