merge hpack/qpack changes to 12 from #9634
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
ca254d394b
commit
422cddf8b1
|
@ -18,9 +18,9 @@ module org.eclipse.jetty.http
|
|||
requires transitive org.eclipse.jetty.io;
|
||||
|
||||
exports org.eclipse.jetty.http;
|
||||
exports org.eclipse.jetty.http.compression;
|
||||
exports org.eclipse.jetty.http.pathmap;
|
||||
exports org.eclipse.jetty.http.content;
|
||||
exports org.eclipse.jetty.http.compression;
|
||||
|
||||
uses org.eclipse.jetty.http.HttpFieldPreEncoder;
|
||||
|
||||
|
|
|
@ -231,5 +231,49 @@ public class HttpTokens
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used when decoding to not decode illegal characters based on RFC9110.
|
||||
* CR, LF, or NUL are replaced with ' ', all other control and multibyte characters
|
||||
* are replaced with '?'. If this is given a legal character the same value will be returned.
|
||||
* <pre>
|
||||
* field-vchar = VCHAR / obs-text
|
||||
* obs-text = %x80-FF
|
||||
* VCHAR = %x21-7E
|
||||
* </pre>
|
||||
* @param c the character to test.
|
||||
* @return the original character or the replacement character ' ' or '?',
|
||||
* the return value is guaranteed to be a valid ISO-8859-1 character.
|
||||
*/
|
||||
public static char sanitizeFieldVchar(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
// A recipient of CR, LF, or NUL within a field value MUST either reject the message
|
||||
// or replace each of those characters with SP before further processing
|
||||
case '\r':
|
||||
case '\n':
|
||||
case 0x00:
|
||||
return ' ';
|
||||
|
||||
default:
|
||||
if (isIllegalFieldVchar(c))
|
||||
return '?';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this is an invalid VCHAR based on RFC9110.
|
||||
* If this not a valid ISO-8859-1 character or a control character
|
||||
* we say that it is illegal.
|
||||
*
|
||||
* @param c the character to test.
|
||||
* @return true if this is invalid VCHAR.
|
||||
*/
|
||||
public static boolean isIllegalFieldVchar(char c)
|
||||
{
|
||||
return (c >= 256 || c < ' ');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
|
||||
package org.eclipse.jetty.http.compression;
|
||||
|
||||
/**
|
||||
* This class contains the Huffman Codes defined in RFC7541.
|
||||
*/
|
||||
public class Huffman
|
||||
{
|
||||
private Huffman()
|
||||
|
|
|
@ -15,29 +15,30 @@ package org.eclipse.jetty.http.compression;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.http.HttpTokens;
|
||||
import org.eclipse.jetty.util.CharsetStringBuilder;
|
||||
|
||||
import static org.eclipse.jetty.http.compression.Huffman.rowbits;
|
||||
import static org.eclipse.jetty.http.compression.Huffman.rowsym;
|
||||
|
||||
/**
|
||||
* <p>Used to decoded Huffman encoded strings.</p>
|
||||
*
|
||||
* <p>Characters which are illegal field-vchar values are replaced with
|
||||
* either ' ' or '?' as described in RFC9110</p>
|
||||
*/
|
||||
public class HuffmanDecoder
|
||||
{
|
||||
public static String decode(ByteBuffer buffer, int length) throws EncodingException
|
||||
{
|
||||
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
|
||||
huffmanDecoder.setLength(length);
|
||||
String decoded = huffmanDecoder.decode(buffer);
|
||||
if (decoded == null)
|
||||
throw new EncodingException("invalid string encoding");
|
||||
|
||||
huffmanDecoder.reset();
|
||||
return decoded;
|
||||
}
|
||||
|
||||
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
|
||||
private final CharsetStringBuilder.Iso88591StringBuilder _builder = new CharsetStringBuilder.Iso88591StringBuilder();
|
||||
private int _length = 0;
|
||||
private int _count = 0;
|
||||
private int _node = 0;
|
||||
private int _current = 0;
|
||||
private int _bits = 0;
|
||||
|
||||
/**
|
||||
* @param length in bytes of the huffman data.
|
||||
*/
|
||||
public void setLength(int length)
|
||||
{
|
||||
if (_count != 0)
|
||||
|
@ -45,6 +46,11 @@ public class HuffmanDecoder
|
|||
_length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param buffer the buffer containing the Huffman encoded bytes.
|
||||
* @return the decoded String.
|
||||
* @throws EncodingException if the huffman encoding is invalid.
|
||||
*/
|
||||
public String decode(ByteBuffer buffer) throws EncodingException
|
||||
{
|
||||
for (; _count < _length; _count++)
|
||||
|
@ -57,19 +63,21 @@ public class HuffmanDecoder
|
|||
_bits += 8;
|
||||
while (_bits >= 8)
|
||||
{
|
||||
int c = (_current >>> (_bits - 8)) & 0xFF;
|
||||
_node = Huffman.tree[_node * 256 + c];
|
||||
if (Huffman.rowbits[_node] != 0)
|
||||
int i = (_current >>> (_bits - 8)) & 0xFF;
|
||||
_node = Huffman.tree[_node * 256 + i];
|
||||
if (rowbits[_node] != 0)
|
||||
{
|
||||
if (Huffman.rowsym[_node] == Huffman.EOS)
|
||||
if (rowsym[_node] == Huffman.EOS)
|
||||
{
|
||||
reset();
|
||||
throw new EncodingException("eos_in_content");
|
||||
}
|
||||
|
||||
// terminal node
|
||||
_utf8.append((byte)(0xFF & Huffman.rowsym[_node]));
|
||||
_bits -= Huffman.rowbits[_node];
|
||||
char c = rowsym[_node];
|
||||
c = HttpTokens.sanitizeFieldVchar(c);
|
||||
_builder.append((byte)c);
|
||||
_bits -= rowbits[_node];
|
||||
_node = 0;
|
||||
}
|
||||
else
|
||||
|
@ -82,27 +90,29 @@ public class HuffmanDecoder
|
|||
|
||||
while (_bits > 0)
|
||||
{
|
||||
int c = (_current << (8 - _bits)) & 0xFF;
|
||||
int i = (_current << (8 - _bits)) & 0xFF;
|
||||
int lastNode = _node;
|
||||
_node = Huffman.tree[_node * 256 + c];
|
||||
_node = Huffman.tree[_node * 256 + i];
|
||||
|
||||
if (Huffman.rowbits[_node] == 0 || Huffman.rowbits[_node] > _bits)
|
||||
if (rowbits[_node] == 0 || rowbits[_node] > _bits)
|
||||
{
|
||||
int requiredPadding = 0;
|
||||
for (int i = 0; i < _bits; i++)
|
||||
for (int j = 0; j < _bits; j++)
|
||||
{
|
||||
requiredPadding = (requiredPadding << 1) | 1;
|
||||
}
|
||||
|
||||
if ((c >> (8 - _bits)) != requiredPadding)
|
||||
if ((i >> (8 - _bits)) != requiredPadding)
|
||||
throw new EncodingException("incorrect_padding");
|
||||
|
||||
_node = lastNode;
|
||||
break;
|
||||
}
|
||||
|
||||
_utf8.append((byte)(0xFF & Huffman.rowsym[_node]));
|
||||
_bits -= Huffman.rowbits[_node];
|
||||
char c = rowsym[_node];
|
||||
c = HttpTokens.sanitizeFieldVchar(c);
|
||||
_builder.append((byte)c);
|
||||
_bits -= rowbits[_node];
|
||||
_node = 0;
|
||||
}
|
||||
|
||||
|
@ -112,14 +122,14 @@ public class HuffmanDecoder
|
|||
throw new EncodingException("bad_termination");
|
||||
}
|
||||
|
||||
String value = _utf8.toCompleteString();
|
||||
String value = _builder.build();
|
||||
reset();
|
||||
return value;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_utf8.reset();
|
||||
_builder.reset();
|
||||
_count = 0;
|
||||
_current = 0;
|
||||
_node = 0;
|
||||
|
|
|
@ -15,20 +15,36 @@ package org.eclipse.jetty.http.compression;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.HttpTokens;
|
||||
|
||||
import static org.eclipse.jetty.http.compression.Huffman.CODES;
|
||||
import static org.eclipse.jetty.http.compression.Huffman.LCCODES;
|
||||
|
||||
/**
|
||||
* <p>Used to encode strings Huffman encoding.</p>
|
||||
*
|
||||
* <p>Characters are encoded with ISO-8859-1, if any multi-byte characters or
|
||||
* control characters are present the encoder will throw {@link EncodingException}.</p>
|
||||
*/
|
||||
public class HuffmanEncoder
|
||||
{
|
||||
private HuffmanEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param s the string to encode.
|
||||
* @return the number of octets needed to encode the string, or -1 if it cannot be encoded.
|
||||
*/
|
||||
public static int octetsNeeded(String s)
|
||||
{
|
||||
return octetsNeeded(CODES, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param b the byte array to encode.
|
||||
* @return the number of octets needed to encode the bytes, or -1 if it cannot be encoded.
|
||||
*/
|
||||
public static int octetsNeeded(byte[] b)
|
||||
{
|
||||
int needed = 0;
|
||||
|
@ -40,22 +56,29 @@ public class HuffmanEncoder
|
|||
return (needed + 7) / 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param buffer the buffer to encode into.
|
||||
* @param s the string to encode.
|
||||
*/
|
||||
public static void encode(ByteBuffer buffer, String s)
|
||||
{
|
||||
encode(CODES, buffer, s);
|
||||
}
|
||||
|
||||
public static void encode(ByteBuffer buffer, byte[] b)
|
||||
{
|
||||
encode(CODES, buffer, b);
|
||||
}
|
||||
|
||||
public static int octetsNeededLC(String s)
|
||||
/**
|
||||
* @param s the string to encode in lowercase.
|
||||
* @return the number of octets needed to encode the string, or -1 if it cannot be encoded.
|
||||
*/
|
||||
public static int octetsNeededLowerCase(String s)
|
||||
{
|
||||
return octetsNeeded(LCCODES, s);
|
||||
}
|
||||
|
||||
public static void encodeLC(ByteBuffer buffer, String s)
|
||||
/**
|
||||
* @param buffer the buffer to encode into in lowercase.
|
||||
* @param s the string to encode.
|
||||
*/
|
||||
public static void encodeLowerCase(ByteBuffer buffer, String s)
|
||||
{
|
||||
encode(LCCODES, buffer, s);
|
||||
}
|
||||
|
@ -67,7 +90,7 @@ public class HuffmanEncoder
|
|||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
char c = s.charAt(i);
|
||||
if (c >= 128 || c < ' ')
|
||||
if (HttpTokens.isIllegalFieldVchar(c))
|
||||
return -1;
|
||||
needed += table[c][1];
|
||||
}
|
||||
|
@ -88,38 +111,8 @@ public class HuffmanEncoder
|
|||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
char c = s.charAt(i);
|
||||
if (c >= 128 || c < ' ')
|
||||
throw new IllegalArgumentException();
|
||||
int code = table[c][0];
|
||||
int bits = table[c][1];
|
||||
|
||||
current <<= bits;
|
||||
current |= code;
|
||||
n += bits;
|
||||
|
||||
while (n >= 8)
|
||||
{
|
||||
n -= 8;
|
||||
buffer.put((byte)(current >> n));
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
current <<= (8 - n);
|
||||
current |= (0xFF >>> n);
|
||||
buffer.put((byte)(current));
|
||||
}
|
||||
}
|
||||
|
||||
private static void encode(final int[][] table, ByteBuffer buffer, byte[] b)
|
||||
{
|
||||
long current = 0;
|
||||
int n = 0;
|
||||
|
||||
for (byte value : b)
|
||||
{
|
||||
int c = 0xFF & value;
|
||||
if (HttpTokens.isIllegalFieldVchar(c))
|
||||
throw new IllegalArgumentException();
|
||||
int code = table[c][0];
|
||||
int bits = table[c][1];
|
||||
|
||||
|
|
|
@ -15,28 +15,21 @@ package org.eclipse.jetty.http.compression;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class NBitIntegerParser
|
||||
/**
|
||||
* Used to decode integers as described in RFC7541.
|
||||
*/
|
||||
public class NBitIntegerDecoder
|
||||
{
|
||||
public static int decode(ByteBuffer buffer, int prefix) throws EncodingException
|
||||
{
|
||||
// TODO: This is a fix for HPACK as it already takes the first byte of the encoded integer.
|
||||
if (prefix != 8)
|
||||
buffer.position(buffer.position() - 1);
|
||||
|
||||
NBitIntegerParser parser = new NBitIntegerParser();
|
||||
parser.setPrefix(prefix);
|
||||
int decodedInt = parser.decodeInt(buffer);
|
||||
if (decodedInt < 0)
|
||||
throw new EncodingException("invalid integer encoding");
|
||||
parser.reset();
|
||||
return decodedInt;
|
||||
}
|
||||
|
||||
private int _prefix;
|
||||
private long _total;
|
||||
private long _multiplier;
|
||||
private boolean _started;
|
||||
|
||||
/**
|
||||
* Set the prefix length in of the integer representation in bits.
|
||||
* A prefix of 6 means the integer representation starts after the first 2 bits.
|
||||
* @param prefix the number of bits in the integer prefix.
|
||||
*/
|
||||
public void setPrefix(int prefix)
|
||||
{
|
||||
if (_started)
|
||||
|
@ -44,11 +37,27 @@ public class NBitIntegerParser
|
|||
_prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an integer from the buffer. If the buffer does not contain the complete integer representation
|
||||
* a value of -1 is returned to indicate that more data is needed to complete parsing.
|
||||
* This should be only after the prefix has been set with {@link #setPrefix(int)}.
|
||||
* @param buffer the buffer containing the encoded integer.
|
||||
* @return the decoded integer or -1 to indicate that more data is needed.
|
||||
* @throws ArithmeticException if the value overflows a int.
|
||||
*/
|
||||
public int decodeInt(ByteBuffer buffer)
|
||||
{
|
||||
return Math.toIntExact(decodeLong(buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a long from the buffer. If the buffer does not contain the complete integer representation
|
||||
* a value of -1 is returned to indicate that more data is needed to complete parsing.
|
||||
* This should be only after the prefix has been set with {@link #setPrefix(int)}.
|
||||
* @param buffer the buffer containing the encoded integer.
|
||||
* @return the decoded long or -1 to indicate that more data is needed.
|
||||
* @throws ArithmeticException if the value overflows a long.
|
||||
*/
|
||||
public long decodeLong(ByteBuffer buffer)
|
||||
{
|
||||
if (!_started)
|
||||
|
@ -86,6 +95,9 @@ public class NBitIntegerParser
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the internal state of the parser.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
_prefix = 0;
|
|
@ -15,8 +15,20 @@ package org.eclipse.jetty.http.compression;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Used to encode integers as described in RFC7541.
|
||||
*/
|
||||
public class NBitIntegerEncoder
|
||||
{
|
||||
private NBitIntegerEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param n the prefix used to encode this long.
|
||||
* @param i the integer to encode.
|
||||
* @return the number of octets it would take to encode the long.
|
||||
*/
|
||||
public static int octetsNeeded(int n, long i)
|
||||
{
|
||||
if (n == 8)
|
||||
|
@ -43,6 +55,12 @@ public class NBitIntegerEncoder
|
|||
return (log + 6) / 7;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param buf the buffer to encode into.
|
||||
* @param n the prefix used to encode this long.
|
||||
* @param i the long to encode into the buffer.
|
||||
*/
|
||||
public static void encode(ByteBuffer buf, int n, long i)
|
||||
{
|
||||
if (n == 8)
|
||||
|
|
|
@ -15,11 +15,23 @@ package org.eclipse.jetty.http.compression;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class NBitStringParser
|
||||
import org.eclipse.jetty.util.CharsetStringBuilder;
|
||||
|
||||
/**
|
||||
* <p>Used to decode string literals as described in RFC7541.</p>
|
||||
*
|
||||
* <p>The string literal representation consists of a single bit to indicate whether huffman encoding is used,
|
||||
* followed by the string byte length encoded with the n-bit integer representation also from RFC7541, and
|
||||
* the bytes of the string are directly after this.</p>
|
||||
*
|
||||
* <p>Characters which are illegal field-vchar values are replaced with
|
||||
* either ' ' or '?' as described in RFC9110</p>
|
||||
*/
|
||||
public class NBitStringDecoder
|
||||
{
|
||||
private final NBitIntegerParser _integerParser;
|
||||
private final NBitIntegerDecoder _integerDecoder;
|
||||
private final HuffmanDecoder _huffmanBuilder;
|
||||
private final StringBuilder _stringBuilder;
|
||||
private final CharsetStringBuilder.Iso88591StringBuilder _builder;
|
||||
private boolean _huffman;
|
||||
private int _count;
|
||||
private int _length;
|
||||
|
@ -34,13 +46,18 @@ public class NBitStringParser
|
|||
VALUE
|
||||
}
|
||||
|
||||
public NBitStringParser()
|
||||
public NBitStringDecoder()
|
||||
{
|
||||
_integerParser = new NBitIntegerParser();
|
||||
_integerDecoder = new NBitIntegerDecoder();
|
||||
_huffmanBuilder = new HuffmanDecoder();
|
||||
_stringBuilder = new StringBuilder();
|
||||
_builder = new CharsetStringBuilder.Iso88591StringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prefix length in of the string representation in bits.
|
||||
* A prefix of 6 means the string representation starts after the first 2 bits.
|
||||
* @param prefix the number of bits in the string prefix.
|
||||
*/
|
||||
public void setPrefix(int prefix)
|
||||
{
|
||||
if (_state != State.PARSING)
|
||||
|
@ -48,6 +65,15 @@ public class NBitStringParser
|
|||
_prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string from the buffer. If the buffer does not contain the complete string representation
|
||||
* then a value of null is returned to indicate that more data is needed to complete parsing.
|
||||
* This should be only after the prefix has been set with {@link #setPrefix(int)}.
|
||||
* @param buffer the buffer containing the encoded string.
|
||||
* @return the decoded string or null to indicate that more data is needed.
|
||||
* @throws ArithmeticException if the string length value overflows a int.
|
||||
* @throws EncodingException if the string encoding is invalid.
|
||||
*/
|
||||
public String decode(ByteBuffer buffer) throws EncodingException
|
||||
{
|
||||
while (true)
|
||||
|
@ -58,11 +84,11 @@ public class NBitStringParser
|
|||
byte firstByte = buffer.get(buffer.position());
|
||||
_huffman = ((0x80 >>> (8 - _prefix)) & firstByte) != 0;
|
||||
_state = State.LENGTH;
|
||||
_integerParser.setPrefix(_prefix - 1);
|
||||
_integerDecoder.setPrefix(_prefix - 1);
|
||||
continue;
|
||||
|
||||
case LENGTH:
|
||||
_length = _integerParser.decodeInt(buffer);
|
||||
_length = _integerDecoder.decodeInt(buffer);
|
||||
if (_length < 0)
|
||||
return null;
|
||||
_state = State.VALUE;
|
||||
|
@ -70,7 +96,7 @@ public class NBitStringParser
|
|||
continue;
|
||||
|
||||
case VALUE:
|
||||
String value = _huffman ? _huffmanBuilder.decode(buffer) : asciiStringDecode(buffer);
|
||||
String value = _huffman ? _huffmanBuilder.decode(buffer) : stringDecode(buffer);
|
||||
if (value != null)
|
||||
reset();
|
||||
return value;
|
||||
|
@ -81,23 +107,24 @@ public class NBitStringParser
|
|||
}
|
||||
}
|
||||
|
||||
private String asciiStringDecode(ByteBuffer buffer)
|
||||
private String stringDecode(ByteBuffer buffer)
|
||||
{
|
||||
for (; _count < _length; _count++)
|
||||
{
|
||||
if (!buffer.hasRemaining())
|
||||
return null;
|
||||
_stringBuilder.append((char)(0x7F & buffer.get()));
|
||||
_builder.append(buffer.get());
|
||||
}
|
||||
return _stringBuilder.toString();
|
||||
|
||||
return _builder.build();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
_state = State.PARSING;
|
||||
_integerParser.reset();
|
||||
_integerDecoder.reset();
|
||||
_huffmanBuilder.reset();
|
||||
_stringBuilder.setLength(0);
|
||||
_builder.reset();
|
||||
_prefix = 0;
|
||||
_count = 0;
|
||||
_length = 0;
|
|
@ -0,0 +1,164 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.http;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.compression.EncodingException;
|
||||
import org.eclipse.jetty.http.compression.HuffmanDecoder;
|
||||
import org.eclipse.jetty.http.compression.HuffmanEncoder;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class HuffmanTest
|
||||
{
|
||||
public static String decode(ByteBuffer buffer, int length) throws EncodingException
|
||||
{
|
||||
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
|
||||
huffmanDecoder.setLength(length);
|
||||
String decoded = huffmanDecoder.decode(buffer);
|
||||
if (decoded == null)
|
||||
throw new EncodingException("invalid string encoding");
|
||||
|
||||
huffmanDecoder.reset();
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
return Stream.of(
|
||||
new String[][]{
|
||||
{"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"},
|
||||
{"D.4.2", "a8eb10649cbf", "no-cache"},
|
||||
{"D.6.1k", "6402", "302"},
|
||||
{"D.6.1v", "aec3771a4b", "private"},
|
||||
{"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
|
||||
{"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"},
|
||||
{"D.6.2te", "640cff", "303"},
|
||||
}).map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] spec={0}")
|
||||
@MethodSource("data")
|
||||
public void testDecode(String specSection, String hex, String expected) throws Exception
|
||||
{
|
||||
byte[] encoded = TypeUtil.fromHexString(hex);
|
||||
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
|
||||
huffmanDecoder.setLength(encoded.length);
|
||||
String decoded = huffmanDecoder.decode(ByteBuffer.wrap(encoded));
|
||||
assertEquals(expected, decoded, specSection);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] spec={0}")
|
||||
@MethodSource("data")
|
||||
public void testEncode(String specSection, String hex, String expected)
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(1024);
|
||||
int pos = BufferUtil.flipToFill(buf);
|
||||
HuffmanEncoder.encode(buf, expected);
|
||||
BufferUtil.flipToFlush(buf, pos);
|
||||
String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
|
||||
assertEquals(hex, encoded, specSection);
|
||||
assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> testDecode8859OnlyArguments()
|
||||
{
|
||||
return Stream.of(
|
||||
// These are valid characters for ISO-8859-1.
|
||||
Arguments.of("FfFe6f", (char)128),
|
||||
Arguments.of("FfFfFbBf", (char)255),
|
||||
|
||||
// RFC9110 specifies these to be replaced as ' ' during decoding.
|
||||
Arguments.of("FfC7", ' '), // (char)0
|
||||
Arguments.of("FfFfFfF7", ' '), // '\r'
|
||||
Arguments.of("FfFfFfF3", ' '), // '\n'
|
||||
|
||||
// We replace control chars with the default replacement character of '?'.
|
||||
Arguments.of("FfFfFfBf", '?') // (char)(' ' - 1)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
|
||||
@MethodSource("testDecode8859OnlyArguments")
|
||||
public void testDecode8859Only(String hexString, char expected) throws Exception
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(hexString));
|
||||
String decoded = decode(buffer, buffer.remaining());
|
||||
assertThat(decoded, equalTo("" + expected));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> testEncode8859OnlyArguments()
|
||||
{
|
||||
return Stream.of(
|
||||
Arguments.of((char)128, (char)128),
|
||||
Arguments.of((char)255, (char)255),
|
||||
Arguments.of((char)0, null),
|
||||
Arguments.of('\r', null),
|
||||
Arguments.of('\n', null),
|
||||
Arguments.of((char)456, null),
|
||||
Arguments.of((char)256, null),
|
||||
Arguments.of((char)-1, null),
|
||||
Arguments.of((char)(' ' - 1), null)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
|
||||
@MethodSource("testEncode8859OnlyArguments")
|
||||
public void testEncode8859Only(char value, Character expectedValue) throws Exception
|
||||
{
|
||||
String s = "value = '" + value + "'";
|
||||
|
||||
// If expected is null we should not be able to encode.
|
||||
if (expectedValue == null)
|
||||
{
|
||||
assertThat(HuffmanEncoder.octetsNeeded(s), equalTo(-1));
|
||||
assertThrows(Throwable.class, () -> encode(s));
|
||||
return;
|
||||
}
|
||||
|
||||
String expected = "value = '" + expectedValue + "'";
|
||||
assertThat(HuffmanEncoder.octetsNeeded(s), greaterThan(0));
|
||||
ByteBuffer buffer = encode(s);
|
||||
String decode = decode(buffer);
|
||||
System.err.println("decoded: " + decode);
|
||||
assertThat(decode, equalTo(expected));
|
||||
}
|
||||
|
||||
private ByteBuffer encode(String s)
|
||||
{
|
||||
ByteBuffer buffer = BufferUtil.allocate(32);
|
||||
BufferUtil.clearToFill(buffer);
|
||||
HuffmanEncoder.encode(buffer, s);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private String decode(ByteBuffer buffer) throws Exception
|
||||
{
|
||||
return decode(buffer, buffer.remaining());
|
||||
}
|
||||
}
|
|
@ -11,11 +11,11 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http3.qpack;
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -29,7 +29,7 @@ public class NBitIntegerParserTest
|
|||
@Test
|
||||
public void testParsingOverByteBoundary()
|
||||
{
|
||||
NBitIntegerParser parser = new NBitIntegerParser();
|
||||
NBitIntegerDecoder decoder = new NBitIntegerDecoder();
|
||||
|
||||
String encoded = "FFBA09";
|
||||
byte[] bytes = TypeUtil.fromHexString(encoded);
|
||||
|
@ -37,11 +37,11 @@ public class NBitIntegerParserTest
|
|||
ByteBuffer buffer1 = BufferUtil.toBuffer(bytes, 0, 2);
|
||||
ByteBuffer buffer2 = BufferUtil.toBuffer(bytes, 2, 1);
|
||||
|
||||
parser.setPrefix(7);
|
||||
int value = parser.decodeInt(buffer1);
|
||||
decoder.setPrefix(7);
|
||||
int value = decoder.decodeInt(buffer1);
|
||||
assertThat(value, is(-1));
|
||||
|
||||
value = parser.decodeInt(buffer2);
|
||||
value = decoder.decodeInt(buffer2);
|
||||
assertThat(value, is(1337));
|
||||
}
|
||||
}
|
|
@ -11,12 +11,12 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http3.qpack;
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -26,7 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
@SuppressWarnings("PointlessArithmeticExpression")
|
||||
public class NBitIntegerTest
|
||||
{
|
||||
private final NBitIntegerParser _parser = new NBitIntegerParser();
|
||||
private final NBitIntegerDecoder _decoder = new NBitIntegerDecoder();
|
||||
|
||||
@Test
|
||||
public void testOctetsNeeded()
|
||||
|
@ -125,8 +125,8 @@ public class NBitIntegerTest
|
|||
public void testDecode(int n, int expected, String encoded)
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||
_parser.setPrefix(n);
|
||||
assertEquals(expected, _parser.decodeInt(buf));
|
||||
_decoder.setPrefix(n);
|
||||
assertEquals(expected, _decoder.decodeInt(buf));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -149,8 +149,8 @@ public class NBitIntegerTest
|
|||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("77EaFF"));
|
||||
buf.position(1);
|
||||
_parser.setPrefix(5);
|
||||
assertEquals(10, _parser.decodeInt(buf));
|
||||
_decoder.setPrefix(5);
|
||||
assertEquals(10, _decoder.decodeInt(buf));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -173,8 +173,8 @@ public class NBitIntegerTest
|
|||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("881f9a0aff"));
|
||||
buf.position(1);
|
||||
_parser.setPrefix(5);
|
||||
assertEquals(1337, _parser.decodeInt(buf));
|
||||
_decoder.setPrefix(5);
|
||||
assertEquals(1337, _decoder.decodeInt(buf));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -197,7 +197,7 @@ public class NBitIntegerTest
|
|||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(TypeUtil.fromHexString("882aFf"));
|
||||
buf.position(1);
|
||||
_parser.setPrefix(8);
|
||||
assertEquals(42, _parser.decodeInt(buf));
|
||||
_decoder.setPrefix(8);
|
||||
assertEquals(42, _decoder.decodeInt(buf));
|
||||
}
|
||||
}
|
|
@ -22,11 +22,12 @@ import org.eclipse.jetty.http.HttpTokens;
|
|||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.compression.EncodingException;
|
||||
import org.eclipse.jetty.http.compression.HuffmanDecoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||
import org.eclipse.jetty.http2.hpack.internal.AuthorityHttpField;
|
||||
import org.eclipse.jetty.http2.hpack.internal.MetaDataBuilder;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.CharsetStringBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -40,6 +41,8 @@ public class HpackDecoder
|
|||
|
||||
private final HpackContext _context;
|
||||
private final MetaDataBuilder _builder;
|
||||
private final HuffmanDecoder _huffmanDecoder;
|
||||
private final NBitIntegerDecoder _integerDecoder;
|
||||
private int _localMaxDynamicTableSize;
|
||||
|
||||
/**
|
||||
|
@ -51,6 +54,8 @@ public class HpackDecoder
|
|||
_context = new HpackContext(localMaxDynamicTableSize);
|
||||
_localMaxDynamicTableSize = localMaxDynamicTableSize;
|
||||
_builder = new MetaDataBuilder(maxHeaderSize);
|
||||
_huffmanDecoder = new HuffmanDecoder();
|
||||
_integerDecoder = new NBitIntegerDecoder();
|
||||
}
|
||||
|
||||
public HpackContext getHpackContext()
|
||||
|
@ -68,7 +73,8 @@ public class HpackDecoder
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("CtxTbl[%x] decoding %d octets", _context.hashCode(), buffer.remaining()));
|
||||
|
||||
// If the buffer is big, don't even think about decoding it
|
||||
// If the buffer is big, don't even think about decoding it.
|
||||
// Huffman may double the size, but it will only be a temporary allocation until detected in MetaDataBuilder.emit().
|
||||
if (buffer.remaining() > _builder.getMaxSize())
|
||||
throw new HpackException.SessionException("431 Request Header Fields too large");
|
||||
|
||||
|
@ -165,11 +171,10 @@ public class HpackDecoder
|
|||
{
|
||||
huffmanName = (buffer.get() & 0x80) == 0x80;
|
||||
int length = integerDecode(buffer, 7);
|
||||
_builder.checkSize(length, huffmanName);
|
||||
if (huffmanName)
|
||||
name = huffmanDecode(buffer, length);
|
||||
else
|
||||
name = toASCIIString(buffer, length);
|
||||
name = toISO88591String(buffer, length);
|
||||
check:
|
||||
for (int i = name.length(); i-- > 0; )
|
||||
{
|
||||
|
@ -206,11 +211,10 @@ public class HpackDecoder
|
|||
// decode the value
|
||||
boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
|
||||
int length = integerDecode(buffer, 7);
|
||||
_builder.checkSize(length, huffmanValue);
|
||||
if (huffmanValue)
|
||||
value = huffmanDecode(buffer, length);
|
||||
else
|
||||
value = toASCIIString(buffer, length);
|
||||
value = toISO88591String(buffer, length);
|
||||
|
||||
// Make the new field
|
||||
HttpField field;
|
||||
|
@ -275,7 +279,14 @@ public class HpackDecoder
|
|||
{
|
||||
try
|
||||
{
|
||||
return NBitIntegerParser.decode(buffer, prefix);
|
||||
if (prefix != 8)
|
||||
buffer.position(buffer.position() - 1);
|
||||
|
||||
_integerDecoder.setPrefix(prefix);
|
||||
int decodedInt = _integerDecoder.decodeInt(buffer);
|
||||
if (decodedInt < 0)
|
||||
throw new EncodingException("invalid integer encoding");
|
||||
return decodedInt;
|
||||
}
|
||||
catch (EncodingException e)
|
||||
{
|
||||
|
@ -283,13 +294,21 @@ public class HpackDecoder
|
|||
compressionException.initCause(e);
|
||||
throw compressionException;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_integerDecoder.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private String huffmanDecode(ByteBuffer buffer, int length) throws HpackException.CompressionException
|
||||
{
|
||||
try
|
||||
{
|
||||
return HuffmanDecoder.decode(buffer, length);
|
||||
_huffmanDecoder.setLength(length);
|
||||
String decoded = _huffmanDecoder.decode(buffer);
|
||||
if (decoded == null)
|
||||
throw new HpackException.CompressionException("invalid string encoding");
|
||||
return decoded;
|
||||
}
|
||||
catch (EncodingException e)
|
||||
{
|
||||
|
@ -297,16 +316,20 @@ public class HpackDecoder
|
|||
compressionException.initCause(e);
|
||||
throw compressionException;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_huffmanDecoder.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public static String toASCIIString(ByteBuffer buffer, int length)
|
||||
public static String toISO88591String(ByteBuffer buffer, int length)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
CharsetStringBuilder.Iso88591StringBuilder builder = new CharsetStringBuilder.Iso88591StringBuilder();
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
builder.append((char)(0x7F & buffer.get()));
|
||||
builder.append(HttpTokens.sanitizeFieldVchar((char)buffer.get()));
|
||||
}
|
||||
return builder.toString();
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.http2.hpack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
|
@ -26,6 +25,7 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTokens;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
|
@ -443,8 +443,8 @@ public class HpackEncoder
|
|||
// leave name index bits as 0
|
||||
// Encode the name always with lowercase huffman
|
||||
buffer.put((byte)0x80);
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLC(name));
|
||||
HuffmanEncoder.encodeLC(buffer, name);
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLowerCase(name));
|
||||
HuffmanEncoder.encodeLowerCase(buffer, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -458,20 +458,9 @@ public class HpackEncoder
|
|||
{
|
||||
// huffman literal value
|
||||
buffer.put((byte)0x80);
|
||||
|
||||
int needed = HuffmanEncoder.octetsNeeded(value);
|
||||
if (needed >= 0)
|
||||
{
|
||||
NBitIntegerEncoder.encode(buffer, 7, needed);
|
||||
HuffmanEncoder.encode(buffer, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not iso_8859_1
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(bytes));
|
||||
HuffmanEncoder.encode(buffer, bytes);
|
||||
}
|
||||
NBitIntegerEncoder.encode(buffer, 7, needed);
|
||||
HuffmanEncoder.encode(buffer, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -481,15 +470,7 @@ public class HpackEncoder
|
|||
for (int i = 0; i < value.length(); i++)
|
||||
{
|
||||
char c = value.charAt(i);
|
||||
if (c < ' ' || c > 127)
|
||||
{
|
||||
// Not iso_8859_1, so re-encode as UTF-8
|
||||
buffer.reset();
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
NBitIntegerEncoder.encode(buffer, 7, bytes.length);
|
||||
buffer.put(bytes, 0, bytes.length);
|
||||
return;
|
||||
}
|
||||
c = HttpTokens.sanitizeFieldVchar(c);
|
||||
buffer.put((byte)c);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,8 +73,8 @@ public class HpackFieldPreEncoder implements HttpFieldPreEncoder
|
|||
else
|
||||
{
|
||||
buffer.put((byte)0x80);
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLC(name));
|
||||
HuffmanEncoder.encodeLC(buffer, name);
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeededLowerCase(name));
|
||||
HuffmanEncoder.encodeLowerCase(buffer, name);
|
||||
}
|
||||
|
||||
HpackEncoder.encodeValue(buffer, huffman, value);
|
||||
|
|
|
@ -68,17 +68,17 @@ public class MetaDataBuilder
|
|||
return _size;
|
||||
}
|
||||
|
||||
public void emit(HttpField field) throws HpackException.SessionException
|
||||
public void emit(HttpField field) throws SessionException
|
||||
{
|
||||
HttpHeader header = field.getHeader();
|
||||
String name = field.getName();
|
||||
if (name == null || name.length() == 0)
|
||||
throw new HpackException.SessionException("Header size 0");
|
||||
throw new SessionException("Header size 0");
|
||||
String value = field.getValue();
|
||||
int fieldSize = name.length() + (value == null ? 0 : value.length());
|
||||
_size += fieldSize + 32;
|
||||
if (_size > _maxSize)
|
||||
throw new HpackException.SessionException("Header size %d > %d", _size, _maxSize);
|
||||
throw new SessionException("Header size %d > %d", _size, _maxSize);
|
||||
|
||||
if (field instanceof StaticTableHttpField staticField)
|
||||
{
|
||||
|
@ -280,20 +280,4 @@ public class MetaDataBuilder
|
|||
_contentLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the max size will not be exceeded.
|
||||
*
|
||||
* @param length the length
|
||||
* @param huffman the huffman name
|
||||
* @throws SessionException in case of size errors
|
||||
*/
|
||||
public void checkSize(int length, boolean huffman) throws SessionException
|
||||
{
|
||||
// Apply a huffman fudge factor
|
||||
if (huffman)
|
||||
length = (length * 4) / 3;
|
||||
if ((_size + length) > _maxSize)
|
||||
throw new HpackException.SessionException("Header too large %d > %d", _size + length, _maxSize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ package org.eclipse.jetty.http2.hpack;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.compression.EncodingException;
|
||||
import org.eclipse.jetty.http.compression.HuffmanDecoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -34,6 +35,32 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
*/
|
||||
public class HpackContextTest
|
||||
{
|
||||
public static String decode(ByteBuffer buffer, int length) throws EncodingException
|
||||
{
|
||||
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
|
||||
huffmanDecoder.setLength(length);
|
||||
String decoded = huffmanDecoder.decode(buffer);
|
||||
if (decoded == null)
|
||||
throw new EncodingException("invalid string encoding");
|
||||
|
||||
huffmanDecoder.reset();
|
||||
return decoded;
|
||||
}
|
||||
|
||||
public static int decodeInt(ByteBuffer buffer, int prefix) throws EncodingException
|
||||
{
|
||||
// This is a fix for HPACK as it already takes the first byte of the encoded integer.
|
||||
if (prefix != 8)
|
||||
buffer.position(buffer.position() - 1);
|
||||
|
||||
NBitIntegerDecoder decoder = new NBitIntegerDecoder();
|
||||
decoder.setPrefix(prefix);
|
||||
int decodedInt = decoder.decodeInt(buffer);
|
||||
if (decodedInt < 0)
|
||||
throw new EncodingException("invalid integer encoding");
|
||||
decoder.reset();
|
||||
return decodedInt;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticName()
|
||||
|
@ -425,10 +452,10 @@ public class HpackContextTest
|
|||
int huff = 0xff & buffer.get();
|
||||
assertTrue((0x80 & huff) == 0x80);
|
||||
|
||||
int len = NBitIntegerParser.decode(buffer, 7);
|
||||
int len = decodeInt(buffer, 7);
|
||||
|
||||
assertEquals(len, buffer.remaining());
|
||||
String value = HuffmanDecoder.decode(buffer, buffer.remaining());
|
||||
String value = decode(buffer, buffer.remaining());
|
||||
|
||||
assertEquals(entry.getHttpField().getValue(), value);
|
||||
}
|
||||
|
|
|
@ -130,31 +130,32 @@ public class HpackTest
|
|||
}
|
||||
catch (HpackException.SessionException e)
|
||||
{
|
||||
assertThat(e.getMessage(), containsString("Header too large"));
|
||||
assertThat(e.getMessage(), containsString("Header size 198 > 164"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeDecodeNonAscii() throws Exception
|
||||
public void encodeNonAscii() throws Exception
|
||||
{
|
||||
HpackEncoder encoder = new HpackEncoder();
|
||||
HpackDecoder decoder = new HpackDecoder(4096, 8192);
|
||||
ByteBuffer buffer = BufferUtil.allocate(16 * 1024);
|
||||
|
||||
HttpFields fields0 = HttpFields.build()
|
||||
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
|
||||
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
|
||||
.add("Cookie", "[\uD842\uDF9F]")
|
||||
.add("custom-key", "[\uD842\uDF9F]");
|
||||
Response original0 = new MetaData.Response(200, null, HttpVersion.HTTP_2, fields0);
|
||||
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer, original0);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
Response decoded0 = (Response)decoder.decode(buffer);
|
||||
HpackException.SessionException throwable = assertThrows(HpackException.SessionException.class, () ->
|
||||
{
|
||||
BufferUtil.clearToFill(buffer);
|
||||
encoder.encode(buffer, original0);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
});
|
||||
|
||||
assertMetaDataSame(original0, decoded0);
|
||||
assertThat(throwable.getMessage(), containsString("Could not hpack encode"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void evictReferencedFieldTest() throws Exception
|
||||
{
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.hpack;
|
||||
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.compression.HuffmanDecoder;
|
||||
import org.eclipse.jetty.http.compression.HuffmanEncoder;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class HuffmanTest
|
||||
{
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
return Stream.of(
|
||||
new String[][]{
|
||||
{"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"},
|
||||
{"D.4.2", "a8eb10649cbf", "no-cache"},
|
||||
{"D.6.1k", "6402", "302"},
|
||||
{"D.6.1v", "aec3771a4b", "private"},
|
||||
{"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
|
||||
{"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"},
|
||||
{"D.6.2te", "640cff", "303"},
|
||||
}).map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] spec={0}")
|
||||
@MethodSource("data")
|
||||
public void testDecode(String specSection, String hex, String expected) throws Exception
|
||||
{
|
||||
byte[] encoded = StringUtil.fromHexString(hex);
|
||||
String decoded = HuffmanDecoder.decode(ByteBuffer.wrap(encoded), encoded.length);
|
||||
assertEquals(expected, decoded, specSection);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] spec={0}")
|
||||
@MethodSource("data")
|
||||
public void testEncode(String specSection, String hex, String expected)
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(1024);
|
||||
int pos = BufferUtil.flipToFill(buf);
|
||||
HuffmanEncoder.encode(buf, expected);
|
||||
BufferUtil.flipToFlush(buf, pos);
|
||||
String encoded = StringUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
|
||||
assertEquals(hex, encoded, specSection);
|
||||
assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
|
||||
@ValueSource(chars = {(char)128, (char)0, (char)-1, ' ' - 1})
|
||||
public void testEncode8859Only(char bad)
|
||||
{
|
||||
String s = "bad '" + bad + "'";
|
||||
|
||||
assertThat(HuffmanEncoder.octetsNeeded(s), Matchers.is(-1));
|
||||
|
||||
assertThrows(BufferOverflowException.class,
|
||||
() -> HuffmanEncoder.encode(BufferUtil.allocate(32), s));
|
||||
}
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.hpack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class NBitIntegerTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testOctetsNeeded()
|
||||
{
|
||||
assertEquals(0, NBitIntegerEncoder.octetsNeeded(5, 10));
|
||||
assertEquals(2, NBitIntegerEncoder.octetsNeeded(5, 1337));
|
||||
assertEquals(1, NBitIntegerEncoder.octetsNeeded(8, 42));
|
||||
assertEquals(3, NBitIntegerEncoder.octetsNeeded(8, 1337));
|
||||
|
||||
assertEquals(0, NBitIntegerEncoder.octetsNeeded(6, 62));
|
||||
assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 63));
|
||||
assertEquals(1, NBitIntegerEncoder.octetsNeeded(6, 64));
|
||||
assertEquals(2, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x01));
|
||||
assertEquals(3, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80));
|
||||
assertEquals(4, NBitIntegerEncoder.octetsNeeded(6, 63 + 0x00 + 0x80 * 0x80 * 0x80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncode()
|
||||
{
|
||||
testEncode(6, 0, "00");
|
||||
testEncode(6, 1, "01");
|
||||
testEncode(6, 62, "3e");
|
||||
testEncode(6, 63, "3f00");
|
||||
testEncode(6, 63 + 1, "3f01");
|
||||
testEncode(6, 63 + 0x7e, "3f7e");
|
||||
testEncode(6, 63 + 0x7f, "3f7f");
|
||||
testEncode(6, 63 + 0x00 + 0x80 * 0x01, "3f8001");
|
||||
testEncode(6, 63 + 0x01 + 0x80 * 0x01, "3f8101");
|
||||
testEncode(6, 63 + 0x7f + 0x80 * 0x01, "3fFf01");
|
||||
testEncode(6, 63 + 0x00 + 0x80 * 0x02, "3f8002");
|
||||
testEncode(6, 63 + 0x01 + 0x80 * 0x02, "3f8102");
|
||||
testEncode(6, 63 + 0x7f + 0x80 * 0x7f, "3fFf7f");
|
||||
testEncode(6, 63 + 0x00 + 0x80 * 0x80, "3f808001");
|
||||
testEncode(6, 63 + 0x7f + 0x80 * 0x80 * 0x7f, "3fFf807f");
|
||||
testEncode(6, 63 + 0x00 + 0x80 * 0x80 * 0x80, "3f80808001");
|
||||
|
||||
testEncode(8, 0, "00");
|
||||
testEncode(8, 1, "01");
|
||||
testEncode(8, 128, "80");
|
||||
testEncode(8, 254, "Fe");
|
||||
testEncode(8, 255, "Ff00");
|
||||
testEncode(8, 255 + 1, "Ff01");
|
||||
testEncode(8, 255 + 0x7e, "Ff7e");
|
||||
testEncode(8, 255 + 0x7f, "Ff7f");
|
||||
testEncode(8, 255 + 0x80, "Ff8001");
|
||||
testEncode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001");
|
||||
}
|
||||
|
||||
public void testEncode(int n, int i, String expected)
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(16);
|
||||
int p = BufferUtil.flipToFill(buf);
|
||||
if (n < 8)
|
||||
buf.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(buf, n, i);
|
||||
BufferUtil.flipToFlush(buf, p);
|
||||
String r = StringUtil.toHexString(BufferUtil.toArray(buf));
|
||||
assertEquals(expected, r);
|
||||
|
||||
assertEquals(expected.length() / 2, (n < 8 ? 1 : 0) + NBitIntegerEncoder.octetsNeeded(n, i));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecode() throws Exception
|
||||
{
|
||||
testDecode(6, 0, "00");
|
||||
testDecode(6, 1, "01");
|
||||
testDecode(6, 62, "3e");
|
||||
testDecode(6, 63, "3f00");
|
||||
testDecode(6, 63 + 1, "3f01");
|
||||
testDecode(6, 63 + 0x7e, "3f7e");
|
||||
testDecode(6, 63 + 0x7f, "3f7f");
|
||||
testDecode(6, 63 + 0x80, "3f8001");
|
||||
testDecode(6, 63 + 0x81, "3f8101");
|
||||
testDecode(6, 63 + 0x7f + 0x80 * 0x01, "3fFf01");
|
||||
testDecode(6, 63 + 0x00 + 0x80 * 0x02, "3f8002");
|
||||
testDecode(6, 63 + 0x01 + 0x80 * 0x02, "3f8102");
|
||||
testDecode(6, 63 + 0x7f + 0x80 * 0x7f, "3fFf7f");
|
||||
testDecode(6, 63 + 0x00 + 0x80 * 0x80, "3f808001");
|
||||
testDecode(6, 63 + 0x7f + 0x80 * 0x80 * 0x7f, "3fFf807f");
|
||||
testDecode(6, 63 + 0x00 + 0x80 * 0x80 * 0x80, "3f80808001");
|
||||
|
||||
testDecode(8, 0, "00");
|
||||
testDecode(8, 1, "01");
|
||||
testDecode(8, 128, "80");
|
||||
testDecode(8, 254, "Fe");
|
||||
testDecode(8, 255, "Ff00");
|
||||
testDecode(8, 255 + 1, "Ff01");
|
||||
testDecode(8, 255 + 0x7e, "Ff7e");
|
||||
testDecode(8, 255 + 0x7f, "Ff7f");
|
||||
testDecode(8, 255 + 0x80, "Ff8001");
|
||||
testDecode(8, 255 + 0x00 + 0x80 * 0x80, "Ff808001");
|
||||
}
|
||||
|
||||
public void testDecode(int n, int expected, String encoded) throws Exception
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString(encoded));
|
||||
buf.position(n == 8 ? 0 : 1);
|
||||
assertEquals(expected, NBitIntegerParser.decode(buf, n));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeExampleD11()
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(16);
|
||||
int p = BufferUtil.flipToFill(buf);
|
||||
buf.put((byte)0x77);
|
||||
buf.put((byte)0xFF);
|
||||
NBitIntegerEncoder.encode(buf, 5, 10);
|
||||
BufferUtil.flipToFlush(buf, p);
|
||||
|
||||
String r = StringUtil.toHexString(BufferUtil.toArray(buf));
|
||||
|
||||
assertEquals("77Ea", r);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeExampleD11() throws Exception
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("77EaFF"));
|
||||
buf.position(2);
|
||||
|
||||
assertEquals(10, NBitIntegerParser.decode(buf, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeExampleD12()
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(16);
|
||||
int p = BufferUtil.flipToFill(buf);
|
||||
buf.put((byte)0x88);
|
||||
buf.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(buf, 5, 1337);
|
||||
BufferUtil.flipToFlush(buf, p);
|
||||
|
||||
String r = StringUtil.toHexString(BufferUtil.toArray(buf));
|
||||
|
||||
assertEquals("881f9a0a", r);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeExampleD12() throws Exception
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("881f9a0aff"));
|
||||
buf.position(2);
|
||||
|
||||
assertEquals(1337, NBitIntegerParser.decode(buf, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeExampleD13()
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(16);
|
||||
int p = BufferUtil.flipToFill(buf);
|
||||
buf.put((byte)0x88);
|
||||
buf.put((byte)0xFF);
|
||||
NBitIntegerEncoder.encode(buf, 8, 42);
|
||||
BufferUtil.flipToFlush(buf, p);
|
||||
|
||||
String r = StringUtil.toHexString(BufferUtil.toArray(buf));
|
||||
|
||||
assertEquals("88Ff2a", r);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeExampleD13() throws Exception
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.wrap(StringUtil.fromHexString("882aFf"));
|
||||
buf.position(1);
|
||||
|
||||
assertEquals(42, NBitIntegerParser.decode(buf, 8));
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import org.eclipse.jetty.http3.frames.Frame;
|
|||
import org.eclipse.jetty.http3.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http3.qpack.QpackDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.QpackEncoder;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.quic.client.ClientProtocolSession;
|
||||
import org.eclipse.jetty.quic.client.ClientQuicSession;
|
||||
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
|
||||
|
@ -64,8 +63,7 @@ public class ClientHTTP3Session extends ClientProtocolSession
|
|||
long encoderStreamId = getQuicSession().newStreamId(StreamType.CLIENT_UNIDIRECTIONAL);
|
||||
QuicStreamEndPoint encoderEndPoint = openInstructionEndPoint(encoderStreamId);
|
||||
InstructionFlusher encoderInstructionFlusher = new InstructionFlusher(quicSession, encoderEndPoint, EncoderStreamConnection.STREAM_TYPE);
|
||||
ByteBufferPool bufferPool = quicSession.getByteBufferPool();
|
||||
this.encoder = new QpackEncoder(bufferPool, new InstructionHandler(encoderInstructionFlusher), configuration.getMaxBlockedStreams());
|
||||
this.encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher), configuration.getMaxBlockedStreams());
|
||||
addBean(encoder);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("created encoder stream #{} on {}", encoderStreamId, encoderEndPoint);
|
||||
|
@ -73,7 +71,7 @@ public class ClientHTTP3Session extends ClientProtocolSession
|
|||
long decoderStreamId = getQuicSession().newStreamId(StreamType.CLIENT_UNIDIRECTIONAL);
|
||||
QuicStreamEndPoint decoderEndPoint = openInstructionEndPoint(decoderStreamId);
|
||||
InstructionFlusher decoderInstructionFlusher = new InstructionFlusher(quicSession, decoderEndPoint, DecoderStreamConnection.STREAM_TYPE);
|
||||
this.decoder = new QpackDecoder(bufferPool, new InstructionHandler(decoderInstructionFlusher), configuration.getMaxResponseHeadersSize());
|
||||
this.decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher), configuration.getMaxResponseHeadersSize());
|
||||
addBean(decoder);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("created decoder stream #{} on {}", decoderStreamId, decoderEndPoint);
|
||||
|
|
|
@ -83,7 +83,7 @@ public class InstructionFlusher extends IteratingCallback
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("flushing {} on {}", instructions, this);
|
||||
|
||||
instructions.forEach(i -> i.encode(accumulator));
|
||||
instructions.forEach(i -> i.encode(bufferPool, accumulator));
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
|
|
|
@ -47,11 +47,11 @@ public class HeadersGenerateParseTest
|
|||
HeadersFrame input = new HeadersFrame(new MetaData.Request(HttpMethod.GET.asString(), uri, HttpVersion.HTTP_3, fields), true);
|
||||
|
||||
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
|
||||
QpackEncoder encoder = new QpackEncoder(bufferPool, instructions -> {}, 100);
|
||||
QpackEncoder encoder = new QpackEncoder(instructions -> {}, 100);
|
||||
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
|
||||
new MessageGenerator(bufferPool, encoder, 8192, true).generate(accumulator, 0, input, null);
|
||||
|
||||
QpackDecoder decoder = new QpackDecoder(bufferPool, instructions -> {}, 8192);
|
||||
QpackDecoder decoder = new QpackDecoder(instructions -> {}, 8192);
|
||||
List<HeadersFrame> frames = new ArrayList<>();
|
||||
MessageParser parser = new MessageParser(new ParserListener()
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.eclipse.jetty.io.ByteBufferPool;
|
|||
|
||||
public interface Instruction
|
||||
{
|
||||
void encode(ByteBufferPool.Accumulator accumulator);
|
||||
void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator);
|
||||
|
||||
/**
|
||||
* <p>A handler for instructions issued by an {@link QpackEncoder} or {@link QpackDecoder}.</p>
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.internal.QpackContext;
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.InsertCountIncrementInstruction;
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction;
|
||||
|
@ -34,7 +34,6 @@ import org.eclipse.jetty.http3.qpack.internal.parser.EncodedFieldSection;
|
|||
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable;
|
||||
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
|
||||
import org.eclipse.jetty.http3.qpack.internal.table.StaticTable;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -53,10 +52,9 @@ public class QpackDecoder implements Dumpable
|
|||
private final QpackContext _context;
|
||||
private final DecoderInstructionParser _parser;
|
||||
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
|
||||
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
|
||||
private final NBitIntegerDecoder _integerDecoder = new NBitIntegerDecoder();
|
||||
private final InstructionHandler _instructionHandler = new InstructionHandler();
|
||||
private final Map<Long, AtomicInteger> _blockedStreams = new HashMap<>();
|
||||
private final ByteBufferPool _bufferPool;
|
||||
private int _maxHeaderSize;
|
||||
private int _maxBlockedStreams;
|
||||
|
||||
|
@ -82,20 +80,14 @@ public class QpackDecoder implements Dumpable
|
|||
/**
|
||||
* @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters, plus 32 per field
|
||||
*/
|
||||
public QpackDecoder(ByteBufferPool bufferPool, Instruction.Handler handler, int maxHeaderSize)
|
||||
public QpackDecoder(Instruction.Handler handler, int maxHeaderSize)
|
||||
{
|
||||
_bufferPool = bufferPool;
|
||||
_context = new QpackContext();
|
||||
_handler = handler;
|
||||
_parser = new DecoderInstructionParser(_instructionHandler);
|
||||
_maxHeaderSize = maxHeaderSize;
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return _bufferPool;
|
||||
}
|
||||
|
||||
QpackContext getQpackContext()
|
||||
{
|
||||
return _context;
|
||||
|
@ -144,6 +136,7 @@ public class QpackDecoder implements Dumpable
|
|||
LOG.debug("Decoding: streamId={}, buffer={}", streamId, BufferUtil.toDetailString(buffer));
|
||||
|
||||
// If the buffer is big, don't even think about decoding it
|
||||
// Huffman may double the size, but it will only be a temporary allocation until detected in MetaDataBuilder.emit().
|
||||
int maxHeaderSize = getMaxHeaderSize();
|
||||
if (buffer.remaining() > maxHeaderSize)
|
||||
throw new QpackException.SessionException(QPACK_DECOMPRESSION_FAILED, "header_too_large");
|
||||
|
@ -179,7 +172,7 @@ public class QpackDecoder implements Dumpable
|
|||
LOG.debug("Decoded: streamId={}, metadata={}", streamId, metaData);
|
||||
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, handler));
|
||||
if (requiredInsertCount > 0)
|
||||
_instructions.add(new SectionAcknowledgmentInstruction(_bufferPool, streamId));
|
||||
_instructions.add(new SectionAcknowledgmentInstruction(streamId));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -245,7 +238,7 @@ public class QpackDecoder implements Dumpable
|
|||
_encodedFieldSections.removeIf(encodedFieldSection -> encodedFieldSection.getStreamId() == streamId);
|
||||
_blockedStreams.remove(streamId);
|
||||
_metaDataNotifications.removeIf(notification -> notification._streamId == streamId);
|
||||
_instructions.add(new StreamCancellationInstruction(_bufferPool, streamId));
|
||||
_instructions.add(new StreamCancellationInstruction(streamId));
|
||||
notifyInstructionHandler();
|
||||
}
|
||||
|
||||
|
@ -269,7 +262,7 @@ public class QpackDecoder implements Dumpable
|
|||
|
||||
_metaDataNotifications.add(new MetaDataNotification(streamId, metaData, encodedFieldSection.getHandler()));
|
||||
if (requiredInsertCount > 0)
|
||||
_instructions.add(new SectionAcknowledgmentInstruction(_bufferPool, streamId));
|
||||
_instructions.add(new SectionAcknowledgmentInstruction(streamId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -360,7 +353,7 @@ public class QpackDecoder implements Dumpable
|
|||
// Add the new Entry to the DynamicTable.
|
||||
Entry entry = new Entry(referencedEntry.getHttpField());
|
||||
dynamicTable.add(entry);
|
||||
_instructions.add(new InsertCountIncrementInstruction(_bufferPool, 1));
|
||||
_instructions.add(new InsertCountIncrementInstruction(1));
|
||||
checkEncodedFieldSections();
|
||||
}
|
||||
|
||||
|
@ -377,7 +370,7 @@ public class QpackDecoder implements Dumpable
|
|||
// Add the new Entry to the DynamicTable.
|
||||
Entry entry = new Entry(new HttpField(referencedEntry.getHttpField().getHeader(), referencedEntry.getHttpField().getName(), value));
|
||||
dynamicTable.add(entry);
|
||||
_instructions.add(new InsertCountIncrementInstruction(_bufferPool, 1));
|
||||
_instructions.add(new InsertCountIncrementInstruction(1));
|
||||
checkEncodedFieldSections();
|
||||
}
|
||||
|
||||
|
@ -392,7 +385,7 @@ public class QpackDecoder implements Dumpable
|
|||
// Add the new Entry to the DynamicTable.
|
||||
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||
dynamicTable.add(entry);
|
||||
_instructions.add(new InsertCountIncrementInstruction(_bufferPool, 1));
|
||||
_instructions.add(new InsertCountIncrementInstruction(1));
|
||||
checkEncodedFieldSections();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.eclipse.jetty.http3.qpack.internal.metadata.Http3Fields;
|
|||
import org.eclipse.jetty.http3.qpack.internal.parser.EncoderInstructionParser;
|
||||
import org.eclipse.jetty.http3.qpack.internal.table.DynamicTable;
|
||||
import org.eclipse.jetty.http3.qpack.internal.table.Entry;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
|
@ -90,7 +89,6 @@ public class QpackEncoder implements Dumpable
|
|||
|
||||
private final AutoLock lock = new AutoLock();
|
||||
private final List<Instruction> _instructions = new ArrayList<>();
|
||||
private final ByteBufferPool _bufferPool;
|
||||
private final Instruction.Handler _handler;
|
||||
private final QpackContext _context;
|
||||
private int _maxBlockedStreams;
|
||||
|
@ -100,20 +98,14 @@ public class QpackEncoder implements Dumpable
|
|||
private int _knownInsertCount = 0;
|
||||
private int _blockedStreams = 0;
|
||||
|
||||
public QpackEncoder(ByteBufferPool bufferPool, Instruction.Handler handler, int maxBlockedStreams)
|
||||
public QpackEncoder(Instruction.Handler handler, int maxBlockedStreams)
|
||||
{
|
||||
_bufferPool = bufferPool;
|
||||
_handler = handler;
|
||||
_context = new QpackContext();
|
||||
_maxBlockedStreams = maxBlockedStreams;
|
||||
_parser = new EncoderInstructionParser(_instructionHandler);
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return _bufferPool;
|
||||
}
|
||||
|
||||
Map<Long, StreamInfo> getStreamInfoMap()
|
||||
{
|
||||
return _streamInfoMap;
|
||||
|
@ -142,7 +134,7 @@ public class QpackEncoder implements Dumpable
|
|||
public void setCapacity(int capacity)
|
||||
{
|
||||
_context.getDynamicTable().setCapacity(capacity);
|
||||
_handler.onInstructions(List.of(new SetCapacityInstruction(_bufferPool, capacity)));
|
||||
_handler.onInstructions(List.of(new SetCapacityInstruction(capacity)));
|
||||
notifyInstructionHandler();
|
||||
}
|
||||
|
||||
|
@ -302,7 +294,7 @@ public class QpackEncoder implements Dumpable
|
|||
{
|
||||
int index = _context.indexOf(entry);
|
||||
dynamicTable.add(new Entry(field));
|
||||
_instructions.add(new DuplicateInstruction(_bufferPool, index));
|
||||
_instructions.add(new DuplicateInstruction(index));
|
||||
notifyInstructionHandler();
|
||||
return true;
|
||||
}
|
||||
|
@ -314,14 +306,14 @@ public class QpackEncoder implements Dumpable
|
|||
{
|
||||
int index = _context.indexOf(nameEntry);
|
||||
dynamicTable.add(new Entry(field));
|
||||
_instructions.add(new IndexedNameEntryInstruction(_bufferPool, !nameEntry.isStatic(), index, huffman, field.getValue()));
|
||||
_instructions.add(new IndexedNameEntryInstruction(!nameEntry.isStatic(), index, huffman, field.getValue()));
|
||||
notifyInstructionHandler();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add the entry without referencing an existing entry.
|
||||
dynamicTable.add(new Entry(field));
|
||||
_instructions.add(new LiteralNameEntryInstruction(_bufferPool, field, huffman));
|
||||
_instructions.add(new LiteralNameEntryInstruction(field, huffman));
|
||||
notifyInstructionHandler();
|
||||
return true;
|
||||
}
|
||||
|
@ -374,7 +366,7 @@ public class QpackEncoder implements Dumpable
|
|||
int index = _context.indexOf(entry);
|
||||
Entry newEntry = new Entry(field);
|
||||
dynamicTable.add(newEntry);
|
||||
_instructions.add(new DuplicateInstruction(_bufferPool, index));
|
||||
_instructions.add(new DuplicateInstruction(index));
|
||||
|
||||
// Should we reference this entry and risk blocking.
|
||||
if (referenceEntry(newEntry, streamInfo))
|
||||
|
@ -392,7 +384,7 @@ public class QpackEncoder implements Dumpable
|
|||
int index = _context.indexOf(nameEntry);
|
||||
Entry newEntry = new Entry(field);
|
||||
dynamicTable.add(newEntry);
|
||||
_instructions.add(new IndexedNameEntryInstruction(_bufferPool, !nameEntry.isStatic(), index, huffman, field.getValue()));
|
||||
_instructions.add(new IndexedNameEntryInstruction(!nameEntry.isStatic(), index, huffman, field.getValue()));
|
||||
|
||||
// Should we reference this entry and risk blocking.
|
||||
if (referenceEntry(newEntry, streamInfo))
|
||||
|
@ -407,7 +399,7 @@ public class QpackEncoder implements Dumpable
|
|||
{
|
||||
Entry newEntry = new Entry(field);
|
||||
dynamicTable.add(newEntry);
|
||||
_instructions.add(new LiteralNameEntryInstruction(_bufferPool, field, huffman));
|
||||
_instructions.add(new LiteralNameEntryInstruction(field, huffman));
|
||||
|
||||
// Should we reference this entry and risk blocking.
|
||||
if (referenceEntry(newEntry, streamInfo))
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package org.eclipse.jetty.http3.qpack.internal;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
@ -173,7 +174,7 @@ public abstract class EncodableEntry
|
|||
{
|
||||
buffer.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(buffer, 7, value.length());
|
||||
buffer.put(value.getBytes());
|
||||
buffer.put(value.getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,9 +182,26 @@ public abstract class EncodableEntry
|
|||
public int getRequiredSize(int base)
|
||||
{
|
||||
String value = getValue();
|
||||
int relativeIndex = _nameEntry.getIndex() - base;
|
||||
int valueLength = _huffman ? HuffmanEncoder.octetsNeeded(value) : value.length();
|
||||
return 1 + NBitIntegerEncoder.octetsNeeded(4, relativeIndex) + 1 + NBitIntegerEncoder.octetsNeeded(7, valueLength) + valueLength;
|
||||
|
||||
int nameOctets;
|
||||
if (_nameEntry.isStatic())
|
||||
{
|
||||
int relativeIndex = _nameEntry.getIndex();
|
||||
nameOctets = NBitIntegerEncoder.octetsNeeded(4, relativeIndex);
|
||||
}
|
||||
else if (_nameEntry.getIndex() < base)
|
||||
{
|
||||
int relativeIndex = base - (_nameEntry.getIndex() + 1);
|
||||
nameOctets = NBitIntegerEncoder.octetsNeeded(4, relativeIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
int relativeIndex = _nameEntry.getIndex() - base;
|
||||
nameOctets = NBitIntegerEncoder.octetsNeeded(3, relativeIndex);
|
||||
}
|
||||
|
||||
return 1 + nameOctets + 1 + NBitIntegerEncoder.octetsNeeded(7, valueLength) + valueLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -229,13 +247,12 @@ public abstract class EncodableEntry
|
|||
}
|
||||
else
|
||||
{
|
||||
// TODO: What charset should we be using? (this applies to the instruction generators as well).
|
||||
buffer.put((byte)(0x20 | allowIntermediary));
|
||||
NBitIntegerEncoder.encode(buffer, 3, name.length());
|
||||
buffer.put(name.getBytes());
|
||||
buffer.put(name.getBytes(StandardCharsets.ISO_8859_1));
|
||||
buffer.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(buffer, 7, value.length());
|
||||
buffer.put(value.getBytes());
|
||||
buffer.put(value.getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +285,6 @@ public abstract class EncodableEntry
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: pass in the HTTP version to avoid hard coding HTTP3?
|
||||
private static class PreEncodedEntry extends EncodableEntry
|
||||
{
|
||||
private final PreEncodedHttpField _httpField;
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.http3.qpack.internal.instruction;
|
||||
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
|
||||
public abstract class AbstractInstruction implements Instruction
|
||||
{
|
||||
private final ByteBufferPool bufferPool;
|
||||
|
||||
protected AbstractInstruction(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
}
|
|
@ -16,17 +16,17 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class DuplicateInstruction extends AbstractInstruction
|
||||
public class DuplicateInstruction implements Instruction
|
||||
{
|
||||
private final int _index;
|
||||
|
||||
public DuplicateInstruction(ByteBufferPool bufferPool, int index)
|
||||
public DuplicateInstruction(int index)
|
||||
{
|
||||
super(bufferPool);
|
||||
_index = index;
|
||||
}
|
||||
|
||||
|
@ -36,16 +36,15 @@ public class DuplicateInstruction extends AbstractInstruction
|
|||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = NBitIntegerEncoder.octetsNeeded(5, _index) + 1;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
byteBuffer.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(byteBuffer, 5, _index);
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
buffer.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(buffer, 5, _index);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,23 +14,24 @@
|
|||
package org.eclipse.jetty.http3.qpack.internal.instruction;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jetty.http.compression.HuffmanEncoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class IndexedNameEntryInstruction extends AbstractInstruction
|
||||
public class IndexedNameEntryInstruction implements Instruction
|
||||
{
|
||||
private final boolean _dynamic;
|
||||
private final int _index;
|
||||
private final boolean _huffman;
|
||||
private final String _value;
|
||||
|
||||
public IndexedNameEntryInstruction(ByteBufferPool bufferPool, boolean dynamic, int index, boolean huffman, String value)
|
||||
public IndexedNameEntryInstruction(boolean dynamic, int index, boolean huffman, String value)
|
||||
{
|
||||
super(bufferPool);
|
||||
_dynamic = dynamic;
|
||||
_index = index;
|
||||
_huffman = huffman;
|
||||
|
@ -53,33 +54,32 @@ public class IndexedNameEntryInstruction extends AbstractInstruction
|
|||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = NBitIntegerEncoder.octetsNeeded(6, _index) + (_huffman ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
|
||||
// First bit indicates the instruction, second bit is whether it is a dynamic table reference or not.
|
||||
byteBuffer.put((byte)(0x80 | (_dynamic ? 0x00 : 0x40)));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 6, _index);
|
||||
buffer.put((byte)(0x80 | (_dynamic ? 0x00 : 0x40)));
|
||||
NBitIntegerEncoder.encode(buffer, 6, _index);
|
||||
|
||||
// We will not huffman encode the string.
|
||||
if (_huffman)
|
||||
{
|
||||
byteBuffer.put((byte)(0x80));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 7, HuffmanEncoder.octetsNeeded(_value));
|
||||
HuffmanEncoder.encode(byteBuffer, _value);
|
||||
buffer.put((byte)(0x80));
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(_value));
|
||||
HuffmanEncoder.encode(buffer, _value);
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBuffer.put((byte)(0x00));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 7, _value.length());
|
||||
byteBuffer.put(_value.getBytes());
|
||||
buffer.put((byte)(0x00));
|
||||
NBitIntegerEncoder.encode(buffer, 7, _value.length());
|
||||
buffer.put(_value.getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,17 +16,17 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class InsertCountIncrementInstruction extends AbstractInstruction
|
||||
public class InsertCountIncrementInstruction implements Instruction
|
||||
{
|
||||
private final int _increment;
|
||||
|
||||
public InsertCountIncrementInstruction(ByteBufferPool bufferPool, int increment)
|
||||
public InsertCountIncrementInstruction(int increment)
|
||||
{
|
||||
super(bufferPool);
|
||||
_increment = increment;
|
||||
}
|
||||
|
||||
|
@ -36,16 +36,15 @@ public class InsertCountIncrementInstruction extends AbstractInstruction
|
|||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = NBitIntegerEncoder.octetsNeeded(6, _increment) + 1;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
byteBuffer.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(byteBuffer, 6, _increment);
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
buffer.put((byte)0x00);
|
||||
NBitIntegerEncoder.encode(buffer, 6, _increment);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -14,29 +14,30 @@
|
|||
package org.eclipse.jetty.http3.qpack.internal.instruction;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.compression.HuffmanEncoder;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class LiteralNameEntryInstruction extends AbstractInstruction
|
||||
public class LiteralNameEntryInstruction implements Instruction
|
||||
{
|
||||
private final boolean _huffmanName;
|
||||
private final boolean _huffmanValue;
|
||||
private final String _name;
|
||||
private final String _value;
|
||||
|
||||
public LiteralNameEntryInstruction(ByteBufferPool bufferPool, HttpField httpField, boolean huffman)
|
||||
public LiteralNameEntryInstruction(HttpField httpField, boolean huffman)
|
||||
{
|
||||
this(bufferPool, httpField, huffman, huffman);
|
||||
this(httpField, huffman, huffman);
|
||||
}
|
||||
|
||||
public LiteralNameEntryInstruction(ByteBufferPool bufferPool, HttpField httpField, boolean huffmanName, boolean huffmanValue)
|
||||
public LiteralNameEntryInstruction(HttpField httpField, boolean huffmanName, boolean huffmanValue)
|
||||
{
|
||||
super(bufferPool);
|
||||
_huffmanName = huffmanName;
|
||||
_huffmanValue = huffmanValue;
|
||||
_name = httpField.getName();
|
||||
|
@ -54,42 +55,41 @@ public class LiteralNameEntryInstruction extends AbstractInstruction
|
|||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = (_huffmanName ? HuffmanEncoder.octetsNeeded(_name) : _name.length()) +
|
||||
(_huffmanValue ? HuffmanEncoder.octetsNeeded(_value) : _value.length()) + 2;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
|
||||
if (_huffmanName)
|
||||
{
|
||||
byteBuffer.put((byte)(0x40 | 0x20));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 5, HuffmanEncoder.octetsNeeded(_name));
|
||||
HuffmanEncoder.encode(byteBuffer, _name);
|
||||
buffer.put((byte)(0x40 | 0x20));
|
||||
NBitIntegerEncoder.encode(buffer, 5, HuffmanEncoder.octetsNeeded(_name));
|
||||
HuffmanEncoder.encode(buffer, _name);
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBuffer.put((byte)(0x40));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 5, _name.length());
|
||||
byteBuffer.put(_name.getBytes());
|
||||
buffer.put((byte)(0x40));
|
||||
NBitIntegerEncoder.encode(buffer, 5, _name.length());
|
||||
buffer.put(_name.getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
if (_huffmanValue)
|
||||
{
|
||||
byteBuffer.put((byte)(0x80));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 7, HuffmanEncoder.octetsNeeded(_value));
|
||||
HuffmanEncoder.encode(byteBuffer, _value);
|
||||
buffer.put((byte)(0x80));
|
||||
NBitIntegerEncoder.encode(buffer, 7, HuffmanEncoder.octetsNeeded(_value));
|
||||
HuffmanEncoder.encode(buffer, _value);
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBuffer.put((byte)(0x00));
|
||||
NBitIntegerEncoder.encode(byteBuffer, 5, _value.length());
|
||||
byteBuffer.put(_value.getBytes());
|
||||
buffer.put((byte)(0x00));
|
||||
NBitIntegerEncoder.encode(buffer, 7, _value.length());
|
||||
buffer.put(_value.getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,17 +16,17 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class SectionAcknowledgmentInstruction extends AbstractInstruction
|
||||
public class SectionAcknowledgmentInstruction implements Instruction
|
||||
{
|
||||
private final long _streamId;
|
||||
|
||||
public SectionAcknowledgmentInstruction(ByteBufferPool bufferPool, long streamId)
|
||||
public SectionAcknowledgmentInstruction(long streamId)
|
||||
{
|
||||
super(bufferPool);
|
||||
_streamId = streamId;
|
||||
}
|
||||
|
||||
|
@ -36,16 +36,15 @@ public class SectionAcknowledgmentInstruction extends AbstractInstruction
|
|||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = NBitIntegerEncoder.octetsNeeded(7, _streamId) + 1;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
byteBuffer.put((byte)0x80);
|
||||
NBitIntegerEncoder.encode(byteBuffer, 7, _streamId);
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
buffer.put((byte)0x80);
|
||||
NBitIntegerEncoder.encode(buffer, 7, _streamId);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,17 +16,17 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class SetCapacityInstruction extends AbstractInstruction
|
||||
public class SetCapacityInstruction implements Instruction
|
||||
{
|
||||
private final int _capacity;
|
||||
|
||||
public SetCapacityInstruction(ByteBufferPool bufferPool, int capacity)
|
||||
public SetCapacityInstruction(int capacity)
|
||||
{
|
||||
super(bufferPool);
|
||||
_capacity = capacity;
|
||||
}
|
||||
|
||||
|
@ -36,16 +36,15 @@ public class SetCapacityInstruction extends AbstractInstruction
|
|||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = NBitIntegerEncoder.octetsNeeded(5, _capacity) + 1;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
byteBuffer.put((byte)0x20);
|
||||
NBitIntegerEncoder.encode(byteBuffer, 5, _capacity);
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
buffer.put((byte)0x20);
|
||||
NBitIntegerEncoder.encode(buffer, 5, _capacity);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,31 +16,30 @@ package org.eclipse.jetty.http3.qpack.internal.instruction;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerEncoder;
|
||||
import org.eclipse.jetty.http3.qpack.Instruction;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.RetainableByteBuffer;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
public class StreamCancellationInstruction extends AbstractInstruction
|
||||
public class StreamCancellationInstruction implements Instruction
|
||||
{
|
||||
private final long _streamId;
|
||||
|
||||
public StreamCancellationInstruction(ByteBufferPool bufferPool, long streamId)
|
||||
public StreamCancellationInstruction(long streamId)
|
||||
{
|
||||
super(bufferPool);
|
||||
_streamId = streamId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBufferPool.Accumulator accumulator)
|
||||
public void encode(ByteBufferPool byteBufferPool, ByteBufferPool.Accumulator accumulator)
|
||||
{
|
||||
int size = NBitIntegerEncoder.octetsNeeded(6, _streamId) + 1;
|
||||
RetainableByteBuffer buffer = getByteBufferPool().acquire(size, false);
|
||||
ByteBuffer byteBuffer = buffer.getByteBuffer();
|
||||
BufferUtil.clearToFill(byteBuffer);
|
||||
byteBuffer.put((byte)0x40);
|
||||
NBitIntegerEncoder.encode(byteBuffer, 6, _streamId);
|
||||
BufferUtil.flipToFlush(byteBuffer, 0);
|
||||
accumulator.append(buffer);
|
||||
RetainableByteBuffer retainableByteBuffer = byteBufferPool.acquire(size, false);
|
||||
ByteBuffer buffer = retainableByteBuffer.getByteBuffer();
|
||||
buffer.put((byte)0x40);
|
||||
NBitIntegerEncoder.encode(buffer, 6, _streamId);
|
||||
BufferUtil.flipToFlush(buffer, 0);
|
||||
accumulator.append(retainableByteBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,8 +16,8 @@ package org.eclipse.jetty.http3.qpack.internal.parser;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.EncodingException;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitStringParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http.compression.NBitStringDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||
|
||||
/**
|
||||
|
@ -26,8 +26,8 @@ import org.eclipse.jetty.http3.qpack.QpackException;
|
|||
public class DecoderInstructionParser
|
||||
{
|
||||
private final Handler _handler;
|
||||
private final NBitStringParser _stringParser;
|
||||
private final NBitIntegerParser _integerParser;
|
||||
private final NBitStringDecoder _stringDecoder;
|
||||
private final NBitIntegerDecoder _integerDecoder;
|
||||
private State _state = State.PARSING;
|
||||
private Operation _operation = Operation.NONE;
|
||||
|
||||
|
@ -66,8 +66,8 @@ public class DecoderInstructionParser
|
|||
public DecoderInstructionParser(Handler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
_stringParser = new NBitStringParser();
|
||||
_integerParser = new NBitIntegerParser();
|
||||
_stringDecoder = new NBitStringDecoder();
|
||||
_integerDecoder = new NBitIntegerDecoder();
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buffer) throws QpackException, EncodingException
|
||||
|
@ -92,13 +92,13 @@ public class DecoderInstructionParser
|
|||
else if ((firstByte & 0x20) != 0)
|
||||
{
|
||||
_state = State.SET_CAPACITY;
|
||||
_integerParser.setPrefix(5);
|
||||
_integerDecoder.setPrefix(5);
|
||||
parseSetDynamicTableCapacity(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = State.DUPLICATE;
|
||||
_integerParser.setPrefix(5);
|
||||
_integerDecoder.setPrefix(5);
|
||||
parseDuplicate(buffer);
|
||||
}
|
||||
break;
|
||||
|
@ -134,20 +134,20 @@ public class DecoderInstructionParser
|
|||
byte firstByte = buffer.get(buffer.position());
|
||||
_referenceDynamicTable = (firstByte & 0x40) == 0;
|
||||
_operation = Operation.INDEX;
|
||||
_integerParser.setPrefix(6);
|
||||
_integerDecoder.setPrefix(6);
|
||||
continue;
|
||||
|
||||
case INDEX:
|
||||
_index = _integerParser.decodeInt(buffer);
|
||||
_index = _integerDecoder.decodeInt(buffer);
|
||||
if (_index < 0)
|
||||
return;
|
||||
|
||||
_operation = Operation.VALUE;
|
||||
_stringParser.setPrefix(8);
|
||||
_stringDecoder.setPrefix(8);
|
||||
continue;
|
||||
|
||||
case VALUE:
|
||||
String value = _stringParser.decode(buffer);
|
||||
String value = _stringDecoder.decode(buffer);
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
|
@ -171,20 +171,20 @@ public class DecoderInstructionParser
|
|||
{
|
||||
case NONE:
|
||||
_operation = Operation.NAME;
|
||||
_stringParser.setPrefix(6);
|
||||
_stringDecoder.setPrefix(6);
|
||||
continue;
|
||||
|
||||
case NAME:
|
||||
_name = _stringParser.decode(buffer);
|
||||
_name = _stringDecoder.decode(buffer);
|
||||
if (_name == null)
|
||||
return;
|
||||
|
||||
_operation = Operation.VALUE;
|
||||
_stringParser.setPrefix(8);
|
||||
_stringDecoder.setPrefix(8);
|
||||
continue;
|
||||
|
||||
case VALUE:
|
||||
String value = _stringParser.decode(buffer);
|
||||
String value = _stringDecoder.decode(buffer);
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
|
@ -201,7 +201,7 @@ public class DecoderInstructionParser
|
|||
|
||||
private void parseDuplicate(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
int index = _integerParser.decodeInt(buffer);
|
||||
int index = _integerDecoder.decodeInt(buffer);
|
||||
if (index >= 0)
|
||||
{
|
||||
reset();
|
||||
|
@ -211,7 +211,7 @@ public class DecoderInstructionParser
|
|||
|
||||
private void parseSetDynamicTableCapacity(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
int capacity = _integerParser.decodeInt(buffer);
|
||||
int capacity = _integerDecoder.decodeInt(buffer);
|
||||
if (capacity >= 0)
|
||||
{
|
||||
reset();
|
||||
|
@ -221,8 +221,8 @@ public class DecoderInstructionParser
|
|||
|
||||
public void reset()
|
||||
{
|
||||
_stringParser.reset();
|
||||
_integerParser.reset();
|
||||
_stringDecoder.reset();
|
||||
_integerDecoder.reset();
|
||||
_state = State.PARSING;
|
||||
_operation = Operation.NONE;
|
||||
_referenceDynamicTable = false;
|
||||
|
|
|
@ -20,8 +20,8 @@ import java.util.List;
|
|||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.compression.EncodingException;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitStringParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http.compression.NBitStringDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.QpackDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||
import org.eclipse.jetty.http3.qpack.internal.QpackContext;
|
||||
|
@ -36,8 +36,8 @@ public class EncodedFieldSection
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EncodedFieldSection.class);
|
||||
|
||||
private final NBitIntegerParser _integerParser = new NBitIntegerParser();
|
||||
private final NBitStringParser _stringParser = new NBitStringParser();
|
||||
private final NBitIntegerDecoder _integerDecoder = new NBitIntegerDecoder();
|
||||
private final NBitStringDecoder _stringDecoder = new NBitStringDecoder();
|
||||
private final List<EncodedField> _encodedFields = new ArrayList<>();
|
||||
|
||||
private final long _streamId;
|
||||
|
@ -111,8 +111,8 @@ public class EncodedFieldSection
|
|||
{
|
||||
byte firstByte = buffer.get(buffer.position());
|
||||
boolean dynamicTable = (firstByte & 0x40) == 0;
|
||||
_integerParser.setPrefix(6);
|
||||
int index = _integerParser.decodeInt(buffer);
|
||||
_integerDecoder.setPrefix(6);
|
||||
int index = _integerDecoder.decodeInt(buffer);
|
||||
if (index < 0)
|
||||
throw new EncodingException("invalid_index");
|
||||
return new IndexedField(dynamicTable, index);
|
||||
|
@ -120,8 +120,8 @@ public class EncodedFieldSection
|
|||
|
||||
private EncodedField parseIndexedFieldPostBase(ByteBuffer buffer) throws EncodingException
|
||||
{
|
||||
_integerParser.setPrefix(4);
|
||||
int index = _integerParser.decodeInt(buffer);
|
||||
_integerDecoder.setPrefix(4);
|
||||
int index = _integerDecoder.decodeInt(buffer);
|
||||
if (index < 0)
|
||||
throw new EncodingException("Invalid Index");
|
||||
|
||||
|
@ -137,13 +137,13 @@ public class EncodedFieldSection
|
|||
boolean allowEncoding = (firstByte & 0x20) != 0;
|
||||
boolean dynamicTable = (firstByte & 0x10) == 0;
|
||||
|
||||
_integerParser.setPrefix(4);
|
||||
int nameIndex = _integerParser.decodeInt(buffer);
|
||||
_integerDecoder.setPrefix(4);
|
||||
int nameIndex = _integerDecoder.decodeInt(buffer);
|
||||
if (nameIndex < 0)
|
||||
throw new EncodingException("invalid_name_index");
|
||||
|
||||
_stringParser.setPrefix(8);
|
||||
String value = _stringParser.decode(buffer);
|
||||
_stringDecoder.setPrefix(8);
|
||||
String value = _stringDecoder.decode(buffer);
|
||||
if (value == null)
|
||||
throw new EncodingException("incomplete_value");
|
||||
|
||||
|
@ -155,13 +155,13 @@ public class EncodedFieldSection
|
|||
byte firstByte = buffer.get(buffer.position());
|
||||
boolean allowEncoding = (firstByte & 0x08) != 0;
|
||||
|
||||
_integerParser.setPrefix(3);
|
||||
int nameIndex = _integerParser.decodeInt(buffer);
|
||||
_integerDecoder.setPrefix(3);
|
||||
int nameIndex = _integerDecoder.decodeInt(buffer);
|
||||
if (nameIndex < 0)
|
||||
throw new EncodingException("invalid_index");
|
||||
|
||||
_stringParser.setPrefix(8);
|
||||
String value = _stringParser.decode(buffer);
|
||||
_stringDecoder.setPrefix(8);
|
||||
String value = _stringDecoder.decode(buffer);
|
||||
if (value == null)
|
||||
throw new EncodingException("invalid_value");
|
||||
|
||||
|
@ -173,13 +173,13 @@ public class EncodedFieldSection
|
|||
byte firstByte = buffer.get(buffer.position());
|
||||
boolean allowEncoding = (firstByte & 0x10) != 0;
|
||||
|
||||
_stringParser.setPrefix(4);
|
||||
String name = _stringParser.decode(buffer);
|
||||
_stringDecoder.setPrefix(4);
|
||||
String name = _stringDecoder.decode(buffer);
|
||||
if (name == null)
|
||||
throw new EncodingException("invalid_name");
|
||||
|
||||
_stringParser.setPrefix(8);
|
||||
String value = _stringParser.decode(buffer);
|
||||
_stringDecoder.setPrefix(8);
|
||||
String value = _stringDecoder.decode(buffer);
|
||||
if (value == null)
|
||||
throw new EncodingException("invalid_value");
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ package org.eclipse.jetty.http3.qpack.internal.parser;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerParser;
|
||||
import org.eclipse.jetty.http.compression.NBitIntegerDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,7 @@ public class EncoderInstructionParser
|
|||
private static final int INSERT_COUNT_INCREMENT_PREFIX = 6;
|
||||
|
||||
private final Handler _handler;
|
||||
private final NBitIntegerParser _integerParser;
|
||||
private final NBitIntegerDecoder _integerDecoder;
|
||||
private State _state = State.IDLE;
|
||||
|
||||
private enum State
|
||||
|
@ -51,7 +51,7 @@ public class EncoderInstructionParser
|
|||
public EncoderInstructionParser(Handler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
_integerParser = new NBitIntegerParser();
|
||||
_integerDecoder = new NBitIntegerDecoder();
|
||||
}
|
||||
|
||||
public void parse(ByteBuffer buffer) throws QpackException
|
||||
|
@ -67,19 +67,19 @@ public class EncoderInstructionParser
|
|||
if ((firstByte & 0x80) != 0)
|
||||
{
|
||||
_state = State.SECTION_ACKNOWLEDGEMENT;
|
||||
_integerParser.setPrefix(SECTION_ACKNOWLEDGEMENT_PREFIX);
|
||||
_integerDecoder.setPrefix(SECTION_ACKNOWLEDGEMENT_PREFIX);
|
||||
parseSectionAcknowledgment(buffer);
|
||||
}
|
||||
else if ((firstByte & 0x40) != 0)
|
||||
{
|
||||
_state = State.STREAM_CANCELLATION;
|
||||
_integerParser.setPrefix(STREAM_CANCELLATION_PREFIX);
|
||||
_integerDecoder.setPrefix(STREAM_CANCELLATION_PREFIX);
|
||||
parseStreamCancellation(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_state = State.INSERT_COUNT_INCREMENT;
|
||||
_integerParser.setPrefix(INSERT_COUNT_INCREMENT_PREFIX);
|
||||
_integerDecoder.setPrefix(INSERT_COUNT_INCREMENT_PREFIX);
|
||||
parseInsertCountIncrement(buffer);
|
||||
}
|
||||
break;
|
||||
|
@ -103,7 +103,7 @@ public class EncoderInstructionParser
|
|||
|
||||
private void parseSectionAcknowledgment(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
long streamId = _integerParser.decodeInt(buffer);
|
||||
long streamId = _integerDecoder.decodeInt(buffer);
|
||||
if (streamId >= 0)
|
||||
{
|
||||
reset();
|
||||
|
@ -113,7 +113,7 @@ public class EncoderInstructionParser
|
|||
|
||||
private void parseStreamCancellation(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
long streamId = _integerParser.decodeLong(buffer);
|
||||
long streamId = _integerDecoder.decodeLong(buffer);
|
||||
if (streamId >= 0)
|
||||
{
|
||||
reset();
|
||||
|
@ -123,7 +123,7 @@ public class EncoderInstructionParser
|
|||
|
||||
private void parseInsertCountIncrement(ByteBuffer buffer) throws QpackException
|
||||
{
|
||||
int increment = _integerParser.decodeInt(buffer);
|
||||
int increment = _integerDecoder.decodeInt(buffer);
|
||||
if (increment >= 0)
|
||||
{
|
||||
reset();
|
||||
|
@ -134,6 +134,6 @@ public class EncoderInstructionParser
|
|||
public void reset()
|
||||
{
|
||||
_state = State.IDLE;
|
||||
_integerParser.reset();
|
||||
_integerDecoder.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ import org.eclipse.jetty.http3.qpack.internal.instruction.InsertCountIncrementIn
|
|||
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.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -56,9 +54,8 @@ public class BlockedStreamsTest
|
|||
{
|
||||
_encoderHandler = new TestEncoderHandler();
|
||||
_decoderHandler = new TestDecoderHandler();
|
||||
ByteBufferPool bufferPool = new ArrayByteBufferPool();
|
||||
_encoder = new QpackEncoder(bufferPool, _encoderHandler, MAX_BLOCKED_STREAMS);
|
||||
_decoder = new QpackDecoder(bufferPool, _decoderHandler, MAX_HEADER_SIZE);
|
||||
_encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS);
|
||||
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -14,18 +14,25 @@
|
|||
package org.eclipse.jetty.http3.qpack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.DuplicateInstruction;
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.IndexedNameEntryInstruction;
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.SetCapacityInstruction;
|
||||
import org.eclipse.jetty.http3.qpack.internal.parser.DecoderInstructionParser;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class DecoderInstructionParserTest
|
||||
{
|
||||
private final ByteBufferPool bufferPool = new ByteBufferPool.NonPooling();
|
||||
private DecoderInstructionParser _instructionParser;
|
||||
private DecoderParserDebugHandler _handler;
|
||||
|
||||
|
@ -41,6 +48,11 @@ public class DecoderInstructionParserTest
|
|||
{
|
||||
// Set Dynamic Table Capacity=220.
|
||||
ByteBuffer buffer = QpackTestUtil.hexToBuffer("3fbd 01");
|
||||
|
||||
// Assert that our generated value is equal to that of the spec example.
|
||||
ByteBuffer encodedValue = getEncodedValue(new SetCapacityInstruction(220));
|
||||
assertThat(buffer, equalTo(encodedValue));
|
||||
|
||||
_instructionParser.parse(buffer);
|
||||
assertThat(_handler.setCapacities.poll(), is(220));
|
||||
assertTrue(_handler.isEmpty());
|
||||
|
@ -51,6 +63,11 @@ public class DecoderInstructionParserTest
|
|||
{
|
||||
// Duplicate (Relative Index = 2).
|
||||
ByteBuffer buffer = QpackTestUtil.hexToBuffer("02");
|
||||
|
||||
// Assert that our generated value is equal to that of the spec example.
|
||||
ByteBuffer encodedValue = getEncodedValue(new DuplicateInstruction(2));
|
||||
assertThat(buffer, equalTo(encodedValue));
|
||||
|
||||
_instructionParser.parse(buffer);
|
||||
assertThat(_handler.duplicates.poll(), is(2));
|
||||
assertTrue(_handler.isEmpty());
|
||||
|
@ -61,6 +78,11 @@ public class DecoderInstructionParserTest
|
|||
{
|
||||
// Insert With Name Reference to Static Table, Index=0 (:authority=www.example.com).
|
||||
ByteBuffer buffer = QpackTestUtil.hexToBuffer("c00f 7777 772e 6578 616d 706c 652e 636f 6d");
|
||||
|
||||
// Assert that our generated value is equal to that of the spec example.
|
||||
ByteBuffer encodedValue = getEncodedValue(new IndexedNameEntryInstruction(false, 0, false, "www.example.com"));
|
||||
assertThat(buffer, equalTo(encodedValue));
|
||||
|
||||
_instructionParser.parse(buffer);
|
||||
DecoderParserDebugHandler.ReferencedEntry entry = _handler.referencedNameEntries.poll();
|
||||
assertNotNull(entry);
|
||||
|
@ -94,4 +116,13 @@ public class DecoderInstructionParserTest
|
|||
// There are no other instructions received.
|
||||
assertTrue(_handler.isEmpty());
|
||||
}
|
||||
|
||||
private ByteBuffer getEncodedValue(Instruction instruction)
|
||||
{
|
||||
ByteBufferPool.Accumulator lease = new ByteBufferPool.Accumulator();
|
||||
instruction.encode(bufferPool, lease);
|
||||
List<ByteBuffer> byteBuffers = lease.getByteBuffers();
|
||||
assertThat(byteBuffers.size(), equalTo(1));
|
||||
return byteBuffers.get(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentI
|
|||
import org.eclipse.jetty.http3.qpack.internal.instruction.SetCapacityInstruction;
|
||||
import org.eclipse.jetty.http3.qpack.internal.parser.DecoderInstructionParser;
|
||||
import org.eclipse.jetty.http3.qpack.internal.parser.EncoderInstructionParser;
|
||||
import org.eclipse.jetty.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
|
@ -58,8 +56,7 @@ public class EncodeDecodeTest
|
|||
{
|
||||
_encoderHandler = new TestEncoderHandler();
|
||||
_decoderHandler = new TestDecoderHandler();
|
||||
ByteBufferPool bufferPool = new ArrayByteBufferPool();
|
||||
_encoder = new QpackEncoder(bufferPool, _encoderHandler, MAX_BLOCKED_STREAMS)
|
||||
_encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS)
|
||||
{
|
||||
@Override
|
||||
protected boolean shouldHuffmanEncode(HttpField httpField)
|
||||
|
@ -67,7 +64,7 @@ public class EncodeDecodeTest
|
|||
return false;
|
||||
}
|
||||
};
|
||||
_decoder = new QpackDecoder(bufferPool, _decoderHandler, MAX_HEADER_SIZE);
|
||||
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
||||
|
||||
_encoderInstructionParser = new EncoderInstructionParser(new EncoderParserDebugHandler(_encoder));
|
||||
_decoderInstructionParser = new DecoderInstructionParser(new DecoderParserDebugHandler(_decoder));
|
||||
|
@ -94,7 +91,7 @@ public class EncodeDecodeTest
|
|||
assertThat(_decoderHandler.getInstruction(), instanceOf(SectionAcknowledgmentInstruction.class));
|
||||
assertTrue(_decoderHandler.isEmpty());
|
||||
|
||||
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(List.of(new SectionAcknowledgmentInstruction(_encoder.getByteBufferPool(), streamId))));
|
||||
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(List.of(new SectionAcknowledgmentInstruction(streamId))));
|
||||
|
||||
// B.2. Dynamic Table.
|
||||
|
||||
|
@ -146,8 +143,8 @@ public class EncodeDecodeTest
|
|||
assertTrue(_decoderHandler.isEmpty());
|
||||
|
||||
// Parse the decoder instructions on the encoder.
|
||||
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(List.of(new InsertCountIncrementInstruction(_encoder.getByteBufferPool(), 2))));
|
||||
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(List.of(new SectionAcknowledgmentInstruction(_encoder.getByteBufferPool(), streamId))));
|
||||
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(List.of(new InsertCountIncrementInstruction(2))));
|
||||
_encoderInstructionParser.parse(QpackTestUtil.toBuffer(List.of(new SectionAcknowledgmentInstruction(streamId))));
|
||||
|
||||
// B.3. Speculative Insert
|
||||
_encoder.insert(new HttpField("custom-key", "custom-value"));
|
||||
|
|
|
@ -20,8 +20,6 @@ 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.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -42,9 +40,8 @@ public class EvictionTest
|
|||
@BeforeEach
|
||||
public void before()
|
||||
{
|
||||
ByteBufferPool bufferPool = new ArrayByteBufferPool();
|
||||
_decoder = new QpackDecoder(bufferPool, _decoderHandler, MAX_HEADER_SIZE);
|
||||
_encoder = new QpackEncoder(bufferPool, _encoderHandler, MAX_BLOCKED_STREAMS)
|
||||
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
||||
_encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS)
|
||||
{
|
||||
@Override
|
||||
protected boolean shouldHuffmanEncode(HttpField httpField)
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.http3.qpack;
|
||||
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.compression.HuffmanDecoder;
|
||||
import org.eclipse.jetty.http.compression.HuffmanEncoder;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class HuffmanTest
|
||||
{
|
||||
public static Stream<Arguments> data()
|
||||
{
|
||||
return Stream.of(
|
||||
new String[][]{
|
||||
{"D.4.1", "f1e3c2e5f23a6ba0ab90f4ff", "www.example.com"},
|
||||
{"D.4.2", "a8eb10649cbf", "no-cache"},
|
||||
{"D.6.1k", "6402", "302"},
|
||||
{"D.6.1v", "aec3771a4b", "private"},
|
||||
{"D.6.1d", "d07abe941054d444a8200595040b8166e082a62d1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
|
||||
{"D.6.1l", "9d29ad171863c78f0b97c8e9ae82ae43d3", "https://www.example.com"},
|
||||
{"D.6.2te", "640cff", "303"},
|
||||
}).map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] spec={0}")
|
||||
@MethodSource("data")
|
||||
public void testDecode(String specSection, String hex, String expected) throws Exception
|
||||
{
|
||||
byte[] encoded = TypeUtil.fromHexString(hex);
|
||||
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
|
||||
huffmanDecoder.setLength(encoded.length);
|
||||
String decoded = huffmanDecoder.decode(ByteBuffer.wrap(encoded));
|
||||
assertEquals(expected, decoded, specSection);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}] spec={0}")
|
||||
@MethodSource("data")
|
||||
public void testEncode(String specSection, String hex, String expected)
|
||||
{
|
||||
ByteBuffer buf = BufferUtil.allocate(1024);
|
||||
int pos = BufferUtil.flipToFill(buf);
|
||||
HuffmanEncoder.encode(buf, expected);
|
||||
BufferUtil.flipToFlush(buf, pos);
|
||||
String encoded = TypeUtil.toHexString(BufferUtil.toArray(buf)).toLowerCase(Locale.ENGLISH);
|
||||
assertEquals(hex, encoded, specSection);
|
||||
assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "[{index}]") // don't include unprintable character in test display-name
|
||||
@ValueSource(chars = {(char)128, (char)0, (char)-1, ' ' - 1})
|
||||
public void testEncode8859Only(char bad)
|
||||
{
|
||||
String s = "bad '" + bad + "'";
|
||||
|
||||
assertThat(HuffmanEncoder.octetsNeeded(s), Matchers.is(-1));
|
||||
|
||||
assertThrows(BufferOverflowException.class,
|
||||
() -> HuffmanEncoder.encode(BufferUtil.allocate(32), s));
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ package org.eclipse.jetty.http3.qpack;
|
|||
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.IndexedNameEntryInstruction;
|
||||
import org.eclipse.jetty.http3.qpack.internal.instruction.SectionAcknowledgmentInstruction;
|
||||
import org.eclipse.jetty.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -26,14 +25,14 @@ import static org.hamcrest.Matchers.is;
|
|||
|
||||
public class InstructionGeneratorTest
|
||||
{
|
||||
private final ByteBufferPool _bufferPool = new ArrayByteBufferPool();
|
||||
private final ByteBufferPool _bufferPool = new ByteBufferPool.NonPooling();
|
||||
|
||||
private String toHexString(Instruction instruction)
|
||||
{
|
||||
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
|
||||
instruction.encode(accumulator);
|
||||
assertThat(accumulator.getSize(), is(1));
|
||||
return BufferUtil.toHexString(accumulator.getByteBuffers().get(0));
|
||||
ByteBufferPool.Accumulator lease = new ByteBufferPool.Accumulator();
|
||||
instruction.encode(_bufferPool, lease);
|
||||
assertThat(lease.getSize(), is(1));
|
||||
return BufferUtil.toHexString(lease.getByteBuffers().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -41,10 +40,10 @@ public class InstructionGeneratorTest
|
|||
{
|
||||
Instruction instruction;
|
||||
|
||||
instruction = new SectionAcknowledgmentInstruction(_bufferPool, 4);
|
||||
instruction = new SectionAcknowledgmentInstruction(4);
|
||||
assertThat(toHexString(instruction), equalToIgnoringCase("84"));
|
||||
|
||||
instruction = new SectionAcknowledgmentInstruction(_bufferPool, 1337);
|
||||
instruction = new SectionAcknowledgmentInstruction(1337);
|
||||
assertThat(toHexString(instruction), equalToIgnoringCase("FFBA09"));
|
||||
}
|
||||
|
||||
|
@ -53,10 +52,10 @@ public class InstructionGeneratorTest
|
|||
{
|
||||
Instruction instruction;
|
||||
|
||||
instruction = new IndexedNameEntryInstruction(_bufferPool, false, 0, false, "www.example.com");
|
||||
instruction = new IndexedNameEntryInstruction(false, 0, false, "www.example.com");
|
||||
assertThat(toHexString(instruction), equalToIgnoringCase("c00f7777772e6578616d706c652e636f6d"));
|
||||
|
||||
instruction = new IndexedNameEntryInstruction(_bufferPool, false, 1, false, "/sample/path");
|
||||
instruction = new IndexedNameEntryInstruction(false, 1, false, "/sample/path");
|
||||
assertThat(toHexString(instruction), equalToIgnoringCase("c10c2f73616d706c652f70617468"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.http3.qpack;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -22,7 +23,7 @@ import org.eclipse.jetty.http.HttpVersion;
|
|||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -32,10 +33,11 @@ public class QpackTestUtil
|
|||
{
|
||||
public static ByteBuffer toBuffer(Instruction... instructions)
|
||||
{
|
||||
ByteBufferPool.NonPooling bufferPool = new ByteBufferPool.NonPooling();
|
||||
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
|
||||
for (Instruction instruction : instructions)
|
||||
{
|
||||
instruction.encode(accumulator);
|
||||
instruction.encode(bufferPool, accumulator);
|
||||
}
|
||||
ByteBuffer combinedBuffer = BufferUtil.allocate(Math.toIntExact(accumulator.getTotalLength()));
|
||||
BufferUtil.clearToFill(combinedBuffer);
|
||||
|
@ -55,8 +57,9 @@ public class QpackTestUtil
|
|||
|
||||
public static ByteBuffer toBuffer(List<Instruction> instructions)
|
||||
{
|
||||
ByteBufferPool bufferPool = new ByteBufferPool.NonPooling();
|
||||
ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
|
||||
instructions.forEach(i -> i.encode(accumulator));
|
||||
instructions.forEach(i -> i.encode(bufferPool, accumulator));
|
||||
assertThat(accumulator.getSize(), is(instructions.size()));
|
||||
ByteBuffer combinedBuffer = BufferUtil.allocate(Math.toIntExact(accumulator.getTotalLength()), false);
|
||||
BufferUtil.clearToFill(combinedBuffer);
|
||||
|
@ -68,7 +71,7 @@ public class QpackTestUtil
|
|||
public static ByteBuffer hexToBuffer(String hexString)
|
||||
{
|
||||
hexString = hexString.replaceAll("\\s+", "");
|
||||
return ByteBuffer.wrap(StringUtil.fromHexString(hexString));
|
||||
return ByteBuffer.wrap(TypeUtil.fromHexString(hexString));
|
||||
}
|
||||
|
||||
public static String toHexString(Instruction instruction)
|
||||
|
@ -125,4 +128,15 @@ public class QpackTestUtil
|
|||
{
|
||||
return new MetaData(HttpVersion.HTTP_3, fields);
|
||||
}
|
||||
|
||||
public static boolean compareMetaData(MetaData m1, MetaData m2)
|
||||
{
|
||||
if (!Objects.equals(m1.getHttpVersion(), m2.getHttpVersion()))
|
||||
return false;
|
||||
if (!Objects.equals(m1.getContentLength(), m2.getContentLength()))
|
||||
return false;
|
||||
if (!Objects.equals(m1.getHttpFields(), m2.getHttpFields()))
|
||||
return false;
|
||||
return m1.getTrailersSupplier() == null && m2.getTrailersSupplier() == null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@ 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.io.ArrayByteBufferPool;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -48,9 +46,8 @@ public class SectionAcknowledgmentTest
|
|||
{
|
||||
_encoderHandler = new TestEncoderHandler();
|
||||
_decoderHandler = new TestDecoderHandler();
|
||||
ByteBufferPool bufferPool = new ArrayByteBufferPool();
|
||||
_encoder = new QpackEncoder(bufferPool, _encoderHandler, MAX_BLOCKED_STREAMS);
|
||||
_decoder = new QpackDecoder(bufferPool, _decoderHandler, MAX_HEADER_SIZE);
|
||||
_encoder = new QpackEncoder(_encoderHandler, MAX_BLOCKED_STREAMS);
|
||||
_decoder = new QpackDecoder(_decoderHandler, MAX_HEADER_SIZE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -77,7 +74,7 @@ public class SectionAcknowledgmentTest
|
|||
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(_encoder.getByteBufferPool(), 0);
|
||||
SectionAcknowledgmentInstruction instruction = new SectionAcknowledgmentInstruction(0);
|
||||
ByteBuffer instructionBuffer = toBuffer(instruction);
|
||||
SessionException error = assertThrows(SessionException.class, () -> _encoder.parseInstructions(instructionBuffer));
|
||||
assertThat(error.getErrorCode(), equalTo(QpackException.QPACK_ENCODER_STREAM_ERROR));
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.eclipse.jetty.http3.frames.Frame;
|
|||
import org.eclipse.jetty.http3.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http3.qpack.QpackDecoder;
|
||||
import org.eclipse.jetty.http3.qpack.QpackEncoder;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
|
||||
import org.eclipse.jetty.quic.common.StreamType;
|
||||
import org.eclipse.jetty.quic.server.ServerProtocolSession;
|
||||
|
@ -63,8 +62,7 @@ public class ServerHTTP3Session extends ServerProtocolSession
|
|||
long encoderStreamId = getQuicSession().newStreamId(StreamType.SERVER_UNIDIRECTIONAL);
|
||||
QuicStreamEndPoint encoderEndPoint = openInstructionEndPoint(encoderStreamId);
|
||||
InstructionFlusher encoderInstructionFlusher = new InstructionFlusher(quicSession, encoderEndPoint, EncoderStreamConnection.STREAM_TYPE);
|
||||
ByteBufferPool bufferPool = quicSession.getByteBufferPool();
|
||||
this.encoder = new QpackEncoder(bufferPool, new InstructionHandler(encoderInstructionFlusher), configuration.getMaxBlockedStreams());
|
||||
this.encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher), configuration.getMaxBlockedStreams());
|
||||
addBean(encoder);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("created encoder stream #{} on {}", encoderStreamId, encoderEndPoint);
|
||||
|
@ -72,7 +70,7 @@ public class ServerHTTP3Session extends ServerProtocolSession
|
|||
long decoderStreamId = getQuicSession().newStreamId(StreamType.SERVER_UNIDIRECTIONAL);
|
||||
QuicStreamEndPoint decoderEndPoint = openInstructionEndPoint(decoderStreamId);
|
||||
InstructionFlusher decoderInstructionFlusher = new InstructionFlusher(quicSession, decoderEndPoint, DecoderStreamConnection.STREAM_TYPE);
|
||||
this.decoder = new QpackDecoder(bufferPool, new InstructionHandler(decoderInstructionFlusher), configuration.getMaxRequestHeadersSize());
|
||||
this.decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher), configuration.getMaxRequestHeadersSize());
|
||||
addBean(decoder);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("created decoder stream #{} on {}", decoderStreamId, decoderEndPoint);
|
||||
|
|
|
@ -73,13 +73,15 @@ public interface CharsetStringBuilder
|
|||
*/
|
||||
String build() throws CharacterCodingException;
|
||||
|
||||
void reset();
|
||||
|
||||
static CharsetStringBuilder forCharset(Charset charset)
|
||||
{
|
||||
Objects.requireNonNull(charset);
|
||||
if (charset == StandardCharsets.UTF_8)
|
||||
return new Utf8StringBuilder();
|
||||
if (charset == StandardCharsets.ISO_8859_1)
|
||||
return new Iso8859StringBuilder();
|
||||
return new Iso88591StringBuilder();
|
||||
if (charset == StandardCharsets.US_ASCII)
|
||||
return new UsAsciiStringBuilder();
|
||||
|
||||
|
@ -110,7 +112,7 @@ public interface CharsetStringBuilder
|
|||
}
|
||||
}
|
||||
|
||||
class Iso8859StringBuilder implements CharsetStringBuilder
|
||||
class Iso88591StringBuilder implements CharsetStringBuilder
|
||||
{
|
||||
private final StringBuilder _builder = new StringBuilder();
|
||||
|
||||
|
@ -139,6 +141,12 @@ public interface CharsetStringBuilder
|
|||
_builder.setLength(0);
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_builder.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
class UsAsciiStringBuilder implements CharsetStringBuilder
|
||||
|
@ -172,6 +180,12 @@ public interface CharsetStringBuilder
|
|||
_builder.setLength(0);
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_builder.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
class DecoderStringBuilder implements CharsetStringBuilder
|
||||
|
@ -278,6 +292,12 @@ public interface CharsetStringBuilder
|
|||
_stringBuilder.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_stringBuilder.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,7 @@ public class Utf8StringBuilder implements CharsetStringBuilder
|
|||
/**
|
||||
* Reset the appendable, clearing the buffer, resetting decoding state and clearing any errors.
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
_state = UTF8_ACCEPT;
|
||||
|
|
Loading…
Reference in New Issue