Add basic implementation for the QpackDecoder.

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2021-02-18 18:51:10 +11:00 committed by Simone Bordet
parent 9d9e13cf3a
commit 08b612feca
18 changed files with 789 additions and 345 deletions

View File

@ -38,11 +38,22 @@ public class QpackContext
QpackContext(int maxDynamicTableSize)
{
_dynamicTable = new DynamicTable(maxDynamicTableSize);
_dynamicTable = new DynamicTable();
_dynamicTable.setCapacity(maxDynamicTableSize);
if (LOG.isDebugEnabled())
LOG.debug(String.format("HdrTbl[%x] created max=%d", hashCode(), maxDynamicTableSize));
}
public DynamicTable getDynamicTable()
{
return _dynamicTable;
}
public StaticTable getStaticTable()
{
return __staticTable;
}
public void resize(int newMaxDynamicTableSize)
{
_dynamicTable.setCapacity(newMaxDynamicTableSize);

View File

@ -14,14 +14,24 @@
package org.eclipse.jetty.http3.qpack;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.qpack.generator.DuplicateInstruction;
import org.eclipse.jetty.http3.qpack.generator.IndexedNameEntryInstruction;
import org.eclipse.jetty.http3.qpack.generator.InsertCountIncrementInstruction;
import org.eclipse.jetty.http3.qpack.generator.Instruction;
import org.eclipse.jetty.http3.qpack.generator.LiteralNameEntryInstruction;
import org.eclipse.jetty.http3.qpack.generator.SectionAcknowledgmentInstruction;
import org.eclipse.jetty.http3.qpack.generator.SetCapacityInstruction;
import org.eclipse.jetty.http3.qpack.parser.EncodedFieldSection;
import org.eclipse.jetty.http3.qpack.parser.NBitIntegerParser;
import org.eclipse.jetty.http3.qpack.table.DynamicTable;
import org.eclipse.jetty.http3.qpack.table.Entry;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.http3.qpack.table.StaticTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,19 +45,22 @@ public class QpackDecoder
public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 =
new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, 0L);
private final Handler _handler;
private final QpackContext _context;
private final MetaDataBuilder _builder;
private int _localMaxDynamicTableSize;
private final List<EncodedFieldSection> _encodedFieldSections = new ArrayList<>();
private final NBitIntegerParser _integerDecoder = new NBitIntegerParser();
/**
* @param localMaxDynamicTableSize The maximum allowed size of the local dynamic header field table.
* @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(int localMaxDynamicTableSize, int maxHeaderSize)
public QpackDecoder(Handler handler, int localMaxDynamicTableSize, int maxHeaderSize)
{
_context = new QpackContext(localMaxDynamicTableSize);
_localMaxDynamicTableSize = localMaxDynamicTableSize;
_builder = new MetaDataBuilder(maxHeaderSize);
_handler = handler;
}
public QpackContext getQpackContext()
@ -55,11 +68,6 @@ public class QpackDecoder
return _context;
}
public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize)
{
_localMaxDynamicTableSize = localMaxdynamciTableSize;
}
public interface Handler
{
void onMetadata(MetaData metaData);
@ -67,7 +75,7 @@ public class QpackDecoder
void onInstruction(Instruction instruction);
}
public MetaData decode(ByteBuffer buffer) throws QpackException.SessionException, QpackException.StreamException
public void decode(ByteBuffer buffer) throws QpackException
{
if (LOG.isDebugEnabled())
LOG.debug(String.format("CtxTbl[%x] decoding %d octets", _context.hashCode(), buffer.remaining()));
@ -76,213 +84,117 @@ public class QpackDecoder
if (buffer.remaining() > _builder.getMaxSize())
throw new QpackException.SessionException("431 Request Header Fields too large");
boolean emitted = false;
int encodedInsertCount = _integerDecoder.decode(buffer);
if (encodedInsertCount < 0)
throw new QpackException.CompressionException("Could not parse Required Insert Count");
while (buffer.hasRemaining())
boolean signBit = (buffer.get(buffer.position()) & 0x80) != 0;
int deltaBase = _integerDecoder.decode(buffer);
if (deltaBase < 0)
throw new QpackException.CompressionException("Could not parse Delta Base");
// Decode the Required Insert Count using the DynamicTable state.
DynamicTable dynamicTable = _context.getDynamicTable();
int insertCount = dynamicTable.getInsertCount();
int maxDynamicTableSize = dynamicTable.getMaxSize();
int requiredInsertCount = decodeInsertCount(encodedInsertCount, insertCount, maxDynamicTableSize);
int base = signBit ? requiredInsertCount - deltaBase - 1 : requiredInsertCount + deltaBase;
EncodedFieldSection encodedFieldSection = new EncodedFieldSection(requiredInsertCount, base);
encodedFieldSection.parse(buffer);
int streamId = -1;
if (encodedFieldSection.getRequiredInsertCount() >= insertCount)
{
if (LOG.isDebugEnabled())
LOG.debug("decode {}", BufferUtil.toHexString(buffer));
byte b = buffer.get();
if (b < 0)
{
// 7.1 indexed if the high bit is set
int index = NBitInteger.decode(buffer, 7);
Entry entry = _context.get(index);
if (entry == null)
throw new QpackException.SessionException("Unknown index %d", index);
if (entry.isStatic())
{
if (LOG.isDebugEnabled())
LOG.debug("decode IdxStatic {}", entry);
// emit field
emitted = true;
_builder.emit(entry.getHttpField());
// TODO copy and add to reference set if there is room
// _context.add(entry.getHttpField());
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("decode Idx {}", entry);
// emit
emitted = true;
_builder.emit(entry.getHttpField());
}
}
else
{
// look at the first nibble in detail
byte f = (byte)((b & 0xF0) >> 4);
String name;
HttpHeader header;
String value;
boolean indexed;
int nameIndex;
switch (f)
{
case 2: // 7.3
case 3: // 7.3
// change table size
int size = NBitInteger.decode(buffer, 5);
if (LOG.isDebugEnabled())
LOG.debug("decode resize={}", size);
if (size > _localMaxDynamicTableSize)
throw new IllegalArgumentException();
if (emitted)
throw new QpackException.CompressionException("Dynamic table resize after fields");
_context.resize(size);
continue;
case 0: // 7.2.2
case 1: // 7.2.3
indexed = false;
nameIndex = NBitInteger.decode(buffer, 4);
break;
case 4: // 7.2.1
case 5: // 7.2.1
case 6: // 7.2.1
case 7: // 7.2.1
indexed = true;
nameIndex = NBitInteger.decode(buffer, 6);
break;
default:
throw new IllegalStateException();
}
boolean huffmanName = false;
// decode the name
if (nameIndex > 0)
{
Entry nameEntry = _context.get(nameIndex);
name = nameEntry.getHttpField().getName();
header = nameEntry.getHttpField().getHeader();
}
else
{
huffmanName = (buffer.get() & 0x80) == 0x80;
int length = NBitInteger.decode(buffer, 7);
_builder.checkSize(length, huffmanName);
if (huffmanName)
name = Huffman.decode(buffer, length);
else
name = toASCIIString(buffer, length);
check:
for (int i = name.length(); i-- > 0; )
{
char c = name.charAt(i);
if (c > 0xff)
{
_builder.streamException("Illegal header name %s", name);
break;
}
HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c];
switch (token.getType())
{
case ALPHA:
if (c >= 'A' && c <= 'Z')
{
_builder.streamException("Uppercase header name %s", name);
break check;
}
break;
case COLON:
case TCHAR:
case DIGIT:
break;
default:
_builder.streamException("Illegal header name %s", name);
break check;
}
}
header = HttpHeader.CACHE.get(name);
}
// decode the value
boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
int length = NBitInteger.decode(buffer, 7);
_builder.checkSize(length, huffmanValue);
if (huffmanValue)
value = Huffman.decode(buffer, length);
else
value = toASCIIString(buffer, length);
// Make the new field
HttpField field;
if (header == null)
{
// just make a normal field and bypass header name lookup
field = new HttpField(null, name, value);
}
else
{
// might be worthwhile to create a value HttpField if it is indexed
// and/or of a type that may be looked up multiple times.
switch (header)
{
case C_STATUS:
if (indexed)
field = new HttpField.IntValueHttpField(header, name, value);
else
field = new HttpField(header, name, value);
break;
case C_AUTHORITY:
field = new AuthorityHttpField(value);
break;
case CONTENT_LENGTH:
if ("0".equals(value))
field = CONTENT_LENGTH_0;
else
field = new HttpField.LongValueHttpField(header, name, value);
break;
default:
field = new HttpField(header, name, value);
break;
}
}
if (LOG.isDebugEnabled())
{
LOG.debug("decoded '{}' by {}/{}/{}",
field,
nameIndex > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
huffmanValue ? "HuffVal" : "LitVal",
indexed ? "Idx" : "");
}
// emit the field
emitted = true;
_builder.emit(field);
// if indexed add to dynamic table
if (indexed)
_context.add(field);
}
MetaData metadata = encodedFieldSection.decode(_context, _builder);
_handler.onMetadata(metadata);
_handler.onInstruction(new SectionAcknowledgmentInstruction(streamId));
}
else
{
_encodedFieldSections.add(encodedFieldSection);
}
return _builder.build();
}
public static String toASCIIString(ByteBuffer buffer, int length)
public void onInstruction(Instruction instruction) throws QpackException
{
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; ++i)
StaticTable staticTable = _context.getStaticTable();
DynamicTable dynamicTable = _context.getDynamicTable();
if (instruction instanceof SetCapacityInstruction)
{
builder.append((char)(0x7F & buffer.get()));
int capacity = ((SetCapacityInstruction)instruction).getCapacity();
dynamicTable.setCapacity(capacity);
}
return builder.toString();
else if (instruction instanceof DuplicateInstruction)
{
DuplicateInstruction duplicate = (DuplicateInstruction)instruction;
Entry entry = dynamicTable.get(duplicate.getIndex());
// Add the new Entry to the DynamicTable.
if (dynamicTable.add(entry) == null)
throw new QpackException.StreamException("No space in DynamicTable");
_handler.onInstruction(new InsertCountIncrementInstruction(1));
}
else if (instruction instanceof IndexedNameEntryInstruction)
{
IndexedNameEntryInstruction nameEntryInstruction = (IndexedNameEntryInstruction)instruction;
int index = nameEntryInstruction.getIndex();
String value = nameEntryInstruction.getValue();
Entry referencedEntry = nameEntryInstruction.isDynamic() ? dynamicTable.get(index) : staticTable.get(index);
// Add the new Entry to the DynamicTable.
Entry entry = new Entry(new HttpField(referencedEntry.getHttpField().getHeader(), referencedEntry.getHttpField().getName(), value));
if (dynamicTable.add(entry) == null)
throw new QpackException.StreamException("No space in DynamicTable");
_handler.onInstruction(new InsertCountIncrementInstruction(1));
}
else if (instruction instanceof LiteralNameEntryInstruction)
{
LiteralNameEntryInstruction literalEntryInstruction = (LiteralNameEntryInstruction)instruction;
String name = literalEntryInstruction.getName();
String value = literalEntryInstruction.getValue();
Entry entry = new Entry(new HttpField(name, value));
// Add the new Entry to the DynamicTable.
if (dynamicTable.add(entry) == null)
throw new QpackException.StreamException("No space in DynamicTable");
_handler.onInstruction(new InsertCountIncrementInstruction(1));
}
else
{
throw new IllegalStateException("Invalid Encoder Instruction");
}
}
private static int decodeInsertCount(int encInsertCount, int totalNumInserts, int maxTableCapacity) throws QpackException
{
if (encInsertCount == 0)
return 0;
int maxEntries = maxTableCapacity / 32;
int fullRange = 2 * maxEntries;
if (encInsertCount > fullRange)
throw new QpackException.CompressionException("encInsertCount > fullRange");
// MaxWrapped is the largest possible value of ReqInsertCount that is 0 mod 2 * MaxEntries.
int maxValue = totalNumInserts + maxEntries;
int maxWrapped = (maxValue / fullRange) * fullRange;
int reqInsertCount = maxWrapped + encInsertCount - 1;
// If reqInsertCount exceeds maxValue, the Encoder's value must have wrapped one fewer time.
if (reqInsertCount > maxValue)
{
if (reqInsertCount <= fullRange)
throw new QpackException.CompressionException("reqInsertCount <= fullRange");
reqInsertCount -= fullRange;
}
// Value of 0 must be encoded as 0.
if (reqInsertCount == 0)
throw new QpackException.CompressionException("reqInsertCount == 0");
return reqInsertCount;
}
@Override

View File

@ -30,7 +30,7 @@ public abstract class QpackException extends Exception
*/
public static class StreamException extends QpackException
{
StreamException(String messageFormat, Object... args)
public StreamException(String messageFormat, Object... args)
{
super(messageFormat, args);
}
@ -43,7 +43,7 @@ public abstract class QpackException extends Exception
*/
public static class SessionException extends QpackException
{
SessionException(String messageFormat, Object... args)
public SessionException(String messageFormat, Object... args)
{
super(messageFormat, args);
}

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;
@ -15,6 +28,11 @@ public class DuplicateInstruction implements Instruction
_index = index;
}
public int getIndex()
{
return _index;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;
@ -22,6 +35,21 @@ public class IndexedNameEntryInstruction implements Instruction
_value = value;
}
public boolean isDynamic()
{
return _dynamic;
}
public int getIndex()
{
return _index;
}
public String getValue()
{
return _value;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import org.eclipse.jetty.io.ByteBufferPool;

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;
@ -22,6 +35,16 @@ public class LiteralNameEntryInstruction implements Instruction
_value = value;
}
public String getName()
{
return _name;
}
public String getValue()
{
return _value;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;
@ -15,6 +28,11 @@ public class SetCapacityInstruction implements Instruction
_capacity = capacity;
}
public int getCapacity()
{
return _capacity;
}
@Override
public void encode(ByteBufferPool.Lease lease)
{

View File

@ -1,3 +1,16 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.generator;
import java.nio.ByteBuffer;

View File

@ -0,0 +1,277 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.parser;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.qpack.MetaDataBuilder;
import org.eclipse.jetty.http3.qpack.QpackContext;
import org.eclipse.jetty.http3.qpack.QpackException;
public class EncodedFieldSection
{
private final NBitIntegerParser _integerParser = new NBitIntegerParser();
private final NBitStringParser _stringParser = new NBitStringParser();
private final List<EncodedField> _encodedFields = new ArrayList<>();
private final int _requiredInsertCount;
private final int _base;
public EncodedFieldSection(int requiredInsertCount, int base)
{
_requiredInsertCount = requiredInsertCount;
_base = base;
}
public int getRequiredInsertCount()
{
return _requiredInsertCount;
}
public void parse(ByteBuffer buffer) throws QpackException
{
while (buffer.hasRemaining())
{
EncodedField encodedField;
byte firstByte = buffer.get(buffer.position());
if ((firstByte & 0x80) != 0)
encodedField = parseIndexedFieldLine(buffer);
else if ((firstByte & 0x40) != 0)
encodedField = parseLiteralFieldLineWithNameReference(buffer);
else if ((firstByte & 0x20) != 0)
encodedField = parseLiteralFieldLineWithLiteralName(buffer);
else if ((firstByte & 0x10) != 0)
encodedField = parseIndexFieldLineWithPostBaseIndex(buffer);
else
encodedField = parseLiteralFieldLineWithPostBaseNameReference(buffer);
_encodedFields.add(encodedField);
}
}
public MetaData decode(QpackContext context, MetaDataBuilder builder) throws QpackException
{
if (context.getDynamicTable().getInsertCount() < _requiredInsertCount)
throw new IllegalStateException("Required Insert Count Not Reached");
for (EncodedField encodedField : _encodedFields)
{
builder.emit(encodedField.decode(context));
}
return builder.build();
}
private EncodedField parseIndexedFieldLine(ByteBuffer buffer) throws QpackException
{
byte firstByte = buffer.get(buffer.position());
boolean dynamicTable = (firstByte & 0x40) == 0;
_integerParser.setPrefix(6);
int index = _integerParser.decode(buffer);
if (index < 0)
throw new QpackException.CompressionException("Invalid Index");
return new IndexedField(dynamicTable, index);
}
private EncodedField parseLiteralFieldLineWithNameReference(ByteBuffer buffer) throws QpackException
{
byte firstByte = buffer.get(buffer.position());
boolean allowEncoding = (firstByte & 0x20) != 0;
boolean dynamicTable = (firstByte & 0x10) == 0;
_integerParser.setPrefix(4);
int nameIndex = _integerParser.decode(buffer);
if (nameIndex < 0)
throw new QpackException.CompressionException("Invalid Name Index");
_stringParser.setPrefix(8);
String value = _stringParser.decode(buffer);
if (value == null)
throw new QpackException.CompressionException("Value");
return new IndexedNameField(allowEncoding, dynamicTable, nameIndex, value);
}
private EncodedField parseLiteralFieldLineWithLiteralName(ByteBuffer buffer) throws QpackException
{
byte firstByte = buffer.get(buffer.position());
boolean allowEncoding = (firstByte & 0x10) != 0;
_stringParser.setPrefix(3);
String name = _stringParser.decode(buffer);
if (name == null)
throw new QpackException.CompressionException("Invalid Name");
_stringParser.setPrefix(8);
String value = _stringParser.decode(buffer);
if (value == null)
throw new QpackException.CompressionException("Invalid Value");
return new LiteralField(allowEncoding, name, value);
}
private EncodedField parseLiteralFieldLineWithPostBaseNameReference(ByteBuffer buffer) throws QpackException
{
byte firstByte = buffer.get(buffer.position());
boolean allowEncoding = (firstByte & 0x08) != 0;
_integerParser.setPrefix(3);
int nameIndex = _integerParser.decode(buffer);
if (nameIndex < 0)
throw new QpackException.CompressionException("Invalid Index");
_stringParser.setPrefix(8);
String value = _stringParser.decode(buffer);
if (value == null)
throw new QpackException.CompressionException("Invalid Value");
return new PostBaseIndexedNameField(allowEncoding, nameIndex, value);
}
private EncodedField parseIndexFieldLineWithPostBaseIndex(ByteBuffer buffer) throws QpackException
{
_integerParser.setPrefix(4);
int index = _integerParser.decode(buffer);
if (index < 0)
throw new QpackException.CompressionException("Invalid Index");
return new PostBaseIndexedField(index);
}
public interface EncodedField
{
HttpField decode(QpackContext context) throws QpackException;
}
private class LiteralField implements EncodedField
{
private final boolean _allowEncoding;
private final String _name;
private final String _value;
public LiteralField(boolean allowEncoding, String name, String value)
{
_allowEncoding = allowEncoding;
_name = name;
_value = value;
}
@Override
public HttpField decode(QpackContext context)
{
return new HttpField(_name, _value);
}
}
private class IndexedField implements EncodedField
{
private final boolean _dynamicTable;
private final int _index;
public IndexedField(boolean dynamicTable, int index)
{
_dynamicTable = dynamicTable;
_index = index;
}
@Override
public HttpField decode(QpackContext context) throws QpackException
{
if (_dynamicTable)
return context.getDynamicTable().getAbsolute(_base + _index + 1).getHttpField();
else
return context.getStaticTable().get(_index).getHttpField();
}
}
private class PostBaseIndexedField implements EncodedField
{
private final int _index;
public PostBaseIndexedField(int index)
{
_index = index;
}
@Override
public HttpField decode(QpackContext context) throws QpackException
{
return context.getDynamicTable().getAbsolute(_base - _index).getHttpField();
}
}
private class IndexedNameField implements EncodedField
{
private final boolean _allowEncoding;
private final boolean _dynamicTable;
private final int _nameIndex;
private final String _value;
public IndexedNameField(boolean allowEncoding, boolean dynamicTable, int nameIndex, String value)
{
_allowEncoding = allowEncoding;
_dynamicTable = dynamicTable;
_nameIndex = nameIndex;
_value = value;
}
@Override
public HttpField decode(QpackContext context) throws QpackException
{
HttpField field;
if (_dynamicTable)
field = context.getDynamicTable().getAbsolute(_base + _nameIndex + 1).getHttpField();
else
field = context.getStaticTable().get(_nameIndex).getHttpField();
return new HttpField(field.getHeader(), field.getName(), _value);
}
}
private class PostBaseIndexedNameField implements EncodedField
{
private final boolean _allowEncoding;
private final int _nameIndex;
private final String _value;
public PostBaseIndexedNameField(boolean allowEncoding, int nameIndex, String value)
{
_allowEncoding = allowEncoding;
_nameIndex = nameIndex;
_value = value;
}
@Override
public HttpField decode(QpackContext context) throws QpackException
{
HttpField field = context.getDynamicTable().getAbsolute(_base - _nameIndex).getHttpField();
return new HttpField(field.getHeader(), field.getName(), _value);
}
}
// TODO: move to QpackEncoder.
@SuppressWarnings("unused")
public static int encodeInsertCount(int reqInsertCount, int maxTableCapacity)
{
if (reqInsertCount == 0)
return 0;
int maxEntries = maxTableCapacity / 32;
return (reqInsertCount % (2 * maxEntries)) + 1;
}
}

View File

@ -13,84 +13,79 @@
package org.eclipse.jetty.http3.qpack.table;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http3.qpack.QpackContext;
import org.eclipse.jetty.http3.qpack.QpackException;
public class DynamicTable
{
public static final int FIRST_INDEX = StaticTable.STATIC_SIZE + 1;
private int _maxDynamicTableSizeInBytes;
private int _dynamicTableSizeInBytes;
private int _maxCapacity;
private int _capacity;
private int _absoluteIndex;
private final Map<HttpField, Entry> _fieldMap = new HashMap<>();
private final Map<String, Entry> _nameMap = new HashMap<>();
private final int _growby;
private final List<Entry> _entries = new ArrayList<>();
private Entry[] _entries;
private int _numEntries;
private int _offset;
private final Map<Integer, StreamInfo> streamInfoMap = new HashMap<>();
public DynamicTable(int maxSize)
public static class StreamInfo
{
private int streamId;
private final List<Integer> referencedEntries = new ArrayList<>();
private int potentiallyBlockedStreams = 0;
}
public DynamicTable()
{
_maxDynamicTableSizeInBytes = maxSize;
int initCapacity = 10 + maxSize / (32 + 10 + 10);
_entries = new Entry[initCapacity];
_growby = initCapacity;
}
public Entry add(Entry entry)
{
evict();
int size = entry.getSize();
if (size > _maxDynamicTableSizeInBytes)
if (size + _capacity > _maxCapacity)
{
if (QpackContext.LOG.isDebugEnabled())
QpackContext.LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxDynamicTableSizeInBytes));
evictAll();
QpackContext.LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxCapacity));
return null;
}
_dynamicTableSizeInBytes += size;
if (_numEntries == _entries.length)
{
Entry[] entries = new Entry[_entries.length + _growby];
for (int i = 0; i < _numEntries; i++)
{
int slot = (_offset + i) % _entries.length;
entries[i] = _entries[slot];
entries[i].setIndex(i);
}
_entries = entries;
_offset = 0;
}
int slot = (_numEntries++ + _offset) % _entries.length;
_entries[slot] = entry;
entry.setIndex(slot);
_capacity += size;
// Set the Entries absolute index which will never change.
entry.setIndex(_absoluteIndex++);
_entries.add(0, entry);
_fieldMap.put(entry.getHttpField(), entry);
_nameMap.put(entry.getHttpField().getLowerCaseName(), entry);
if (QpackContext.LOG.isDebugEnabled())
QpackContext.LOG.debug(String.format("HdrTbl[%x] added %s", hashCode(), entry));
evict();
return entry;
}
public int index(Entry entry)
{
return StaticTable.STATIC_SIZE + _numEntries - (entry.getIndex() - _offset + _entries.length) % _entries.length;
// TODO: should we improve efficiency of this by storing in the entry itself.
return _entries.indexOf(entry);
}
public Entry getAbsolute(int absoluteIndex) throws QpackException
{
if (absoluteIndex < 0)
throw new QpackException.CompressionException("Invalid Index");
return _entries.stream().filter(e -> e.getIndex() == absoluteIndex).findFirst().orElse(null);
}
public Entry get(int index)
{
int d = index - StaticTable.STATIC_SIZE - 1;
if (d < 0 || d >= _numEntries)
return null;
int slot = (_offset + _numEntries - d - 1) % _entries.length;
return _entries[slot];
return _entries.get(index);
}
public Entry get(String name)
@ -105,66 +100,91 @@ public class DynamicTable
public int getSize()
{
return _dynamicTableSizeInBytes;
return _capacity;
}
public int getMaxSize()
{
return _maxDynamicTableSizeInBytes;
return _maxCapacity;
}
public int getNumEntries()
{
return _numEntries;
return _entries.size();
}
public int getInsertCount()
{
return _absoluteIndex;
}
public void setCapacity(int capacity)
{
if (QpackContext.LOG.isDebugEnabled())
QpackContext.LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxDynamicTableSizeInBytes, capacity));
_maxDynamicTableSizeInBytes = capacity;
QpackContext.LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxCapacity, capacity));
_maxCapacity = capacity;
evict();
}
private boolean canReference(Entry entry)
{
int evictionThreshold = getEvictionThreshold();
int lowestReferencableIndex = -1;
int remainingCapacity = _capacity;
for (int i = 0; i < _entries.size(); i++)
{
if (remainingCapacity <= evictionThreshold)
{
lowestReferencableIndex = i;
break;
}
remainingCapacity -= _entries.get(i).getSize();
}
return index(entry) >= lowestReferencableIndex;
}
private void evict()
{
while (_dynamicTableSizeInBytes > _maxDynamicTableSizeInBytes)
int evictionThreshold = getEvictionThreshold();
for (Entry e : _entries)
{
Entry entry = _entries[_offset];
_entries[_offset] = null;
_offset = (_offset + 1) % _entries.length;
_numEntries--;
// We only evict when the table is getting full.
if (_capacity < evictionThreshold)
return;
// We can only evict if there are no references outstanding to this entry.
if (e.getReferenceCount() != 0)
return;
// Remove this entry.
if (QpackContext.LOG.isDebugEnabled())
QpackContext.LOG.debug(String.format("HdrTbl[%x] evict %s", hashCode(), entry));
_dynamicTableSizeInBytes -= entry.getSize();
entry.setIndex(-1);
_fieldMap.remove(entry.getHttpField());
String lc = entry.getHttpField().getLowerCaseName();
if (entry == _nameMap.get(lc))
_nameMap.remove(lc);
QpackContext.LOG.debug(String.format("HdrTbl[%x] evict %s", hashCode(), e));
Entry removedEntry = _entries.remove(0);
if (removedEntry != e)
throw new IllegalStateException("Corruption in DynamicTable");
_fieldMap.remove(e.getHttpField());
String name = e.getHttpField().getLowerCaseName();
if (e == _nameMap.get(name))
_nameMap.remove(name);
_capacity -= e.getSize();
}
if (QpackContext.LOG.isDebugEnabled())
QpackContext.LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", hashCode(), getNumEntries(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes));
QpackContext.LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", hashCode(), getNumEntries(), _capacity, _maxCapacity));
}
private void evictAll()
private int getEvictionThreshold()
{
if (QpackContext.LOG.isDebugEnabled())
QpackContext.LOG.debug(String.format("HdrTbl[%x] evictAll", hashCode()));
if (getNumEntries() > 0)
{
_fieldMap.clear();
_nameMap.clear();
_offset = 0;
_numEntries = 0;
_dynamicTableSizeInBytes = 0;
Arrays.fill(_entries, null);
}
return _maxCapacity * 3 / 4;
}
@Override
public String toString()
{
return String.format("%s@%x{entries=%d,size=%d,max=%d}", getClass().getSimpleName(), hashCode(), getNumEntries(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes);
return String.format("%s@%x{entries=%d,size=%d,max=%d}", getClass().getSimpleName(), hashCode(), getNumEntries(), _capacity, _maxCapacity);
}
}

View File

@ -13,12 +13,15 @@
package org.eclipse.jetty.http3.qpack.table;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http.HttpField;
public class Entry
{
final HttpField _field;
private int _slot; // The index within it's array
private final HttpField _field;
private int _absoluteIndex;
private AtomicInteger _referenceCount = new AtomicInteger(0);
public Entry()
{
@ -33,7 +36,7 @@ public class Entry
public Entry(int index, HttpField field)
{
_field = field;
_slot = index;
_absoluteIndex = index;
}
public int getSize()
@ -44,12 +47,12 @@ public class Entry
public void setIndex(int index)
{
_slot = index;
_absoluteIndex = index;
}
public int getIndex()
{
return _slot;
return _absoluteIndex;
}
public HttpField getHttpField()
@ -57,6 +60,11 @@ public class Entry
return _field;
}
public int getReferenceCount()
{
return _referenceCount.get();
}
public boolean isStatic()
{
return false;
@ -70,6 +78,6 @@ public class Entry
@Override
public String toString()
{
return String.format("{%s,%d,%s,%x}", isStatic() ? "S" : "D", _slot, _field, hashCode());
return String.format("{%s,%d,%s,%x}", isStatic() ? "S" : "D", _absoluteIndex, _field, hashCode());
}
}

View File

@ -150,13 +150,13 @@ public class StaticTable
_staticTable[i] = entry;
if (entry._field.getValue() != null)
_staticFieldMap.put(entry._field, entry);
if (entry.getHttpField().getValue() != null)
_staticFieldMap.put(entry.getHttpField(), entry);
if (!added.contains(entry._field.getName()))
if (!added.contains(entry.getHttpField().getName()))
{
added.add(entry._field.getName());
staticNameMapBuilder.with(entry._field.getName(), entry);
added.add(entry.getHttpField().getName());
staticNameMapBuilder.with(entry.getHttpField().getName(), entry);
}
}
_staticNameMap = staticNameMapBuilder.build();

View File

@ -0,0 +1,53 @@
//
// ========================================================================
// Copyright (c) 1995-2021 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.util.LinkedList;
import java.util.Queue;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.qpack.generator.Instruction;
public class DecoderTestHandler implements QpackDecoder.Handler
{
private final Queue<MetaData> _metadataList = new LinkedList<>();
private final Queue<Instruction> _instructionList = new LinkedList<>();
@Override
public void onMetadata(MetaData metaData)
{
_metadataList.add(metaData);
}
@Override
public void onInstruction(Instruction instruction)
{
_instructionList.add(instruction);
}
public MetaData getMetaData()
{
return _metadataList.poll();
}
public Instruction getInstruction()
{
return _instructionList.poll();
}
public boolean isEmpty()
{
return _metadataList.isEmpty() && _instructionList.isEmpty();
}
}

View File

@ -54,16 +54,19 @@ public class QpackDecoderTest
+-------------------------------+
*/
private final DecoderTestHandler handler = new DecoderTestHandler();
@Test
public void testDecodeD3() throws Exception
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
// First request
String encoded = "828684410f7777772e6578616d706c652e636f6d";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
MetaData.Request request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
MetaData.Request request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -75,7 +78,8 @@ public class QpackDecoderTest
encoded = "828684be58086e6f2d6361636865";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -90,7 +94,8 @@ public class QpackDecoderTest
encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTPS.asString(), request.getURI().getScheme());
@ -105,13 +110,14 @@ public class QpackDecoderTest
@Test
public void testDecodeD4() throws Exception
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
// First request
String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
MetaData.Request request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
MetaData.Request request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -123,7 +129,8 @@ public class QpackDecoderTest
encoded = "828684be5886a8eb10649cbf";
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -140,14 +147,15 @@ public class QpackDecoderTest
{
String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d";
byte[] bytes = TypeUtil.fromHexString(encoded);
byte[] array = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, array, 1, bytes.length);
ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice();
MetaData.Request request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
MetaData.Request request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -162,7 +170,7 @@ public class QpackDecoderTest
@Test
public void testDecodeHuffmanWithArrayOffset() throws Exception
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84";
byte[] bytes = TypeUtil.fromHexString(encoded);
@ -170,7 +178,8 @@ public class QpackDecoderTest
System.arraycopy(bytes, 0, array, 1, bytes.length);
ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice();
MetaData.Request request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
MetaData.Request request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -186,8 +195,9 @@ public class QpackDecoderTest
String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
QpackDecoder decoder = new QpackDecoder(4096, 8192);
MetaData.Response response = (MetaData.Response)decoder.decode(buffer);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
decoder.decode(buffer);
MetaData.Response response = (MetaData.Response)handler.getMetaData();
assertThat(response.getStatus(), is(200));
assertThat(response.getFields().size(), is(6));
@ -204,8 +214,9 @@ public class QpackDecoderTest
{
String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
QpackDecoder decoder = new QpackDecoder(4096, 8192);
MetaData metaData = decoder.decode(buffer);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
decoder.decode(buffer);
MetaData metaData = handler.getMetaData();
assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0"));
assertThat(metaData.getFields().get(HttpHeader.COOKIE), is("abcdefghij"));
assertThat(decoder.getQpackContext().getMaxDynamicTableSize(), is(50));
@ -226,7 +237,7 @@ public class QpackDecoderTest
String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
try
{
decoder.decode(buffer);
@ -244,8 +255,9 @@ public class QpackDecoderTest
String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
QpackDecoder decoder = new QpackDecoder(128, 8192);
MetaData metaData = decoder.decode(buffer);
QpackDecoder decoder = new QpackDecoder(handler, 128, 8192);
decoder.decode(buffer);
MetaData metaData = handler.getMetaData();
assertThat(decoder.getQpackContext().getDynamicTableSize(), is(0));
assertThat(metaData.getFields().get("host"), Matchers.startsWith("This is a very large field"));
@ -257,7 +269,7 @@ public class QpackDecoderTest
String encoded = "BE";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
QpackDecoder decoder = new QpackDecoder(128, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 128, 8192);
try
{
@ -442,12 +454,13 @@ public class QpackDecoderTest
@Test
public void testHuffmanEncodedStandard() throws Exception
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "82868441" + "83" + "49509F";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
MetaData.Request request = (MetaData.Request)decoder.decode(buffer);
decoder.decode(buffer);
MetaData.Request request = (MetaData.Request)handler.getMetaData();
assertEquals("GET", request.getMethod());
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
@ -460,7 +473,7 @@ public class QpackDecoderTest
@Test
public void testHuffmanEncodedExtraPadding()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "82868441" + "84" + "49509FFF";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -472,7 +485,7 @@ public class QpackDecoderTest
@Test
public void testHuffmanEncodedZeroPadding()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "82868441" + "83" + "495090";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -485,7 +498,7 @@ public class QpackDecoderTest
@Test
public void testHuffmanEncodedWithEOS()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "82868441" + "87" + "497FFFFFFF427F";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -497,7 +510,7 @@ public class QpackDecoderTest
@Test
public void testHuffmanEncodedOneIncompleteOctet()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "82868441" + "81" + "FE";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -509,7 +522,7 @@ public class QpackDecoderTest
@Test
public void testHuffmanEncodedTwoIncompleteOctet()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "82868441" + "82" + "FFFE";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -521,7 +534,7 @@ public class QpackDecoderTest
@Test
public void testZeroLengthName()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "00000130";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -532,11 +545,12 @@ public class QpackDecoderTest
@Test
public void testZeroLengthValue() throws Exception
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "00016800";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
MetaData metaData = decoder.decode(buffer);
decoder.decode(buffer);
MetaData metaData = handler.getMetaData();
assertThat(metaData.getFields().size(), is(1));
assertThat(metaData.getFields().get("h"), is(""));
}
@ -544,7 +558,7 @@ public class QpackDecoderTest
@Test
public void testUpperCaseName()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "0001480130";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
@ -555,7 +569,7 @@ public class QpackDecoderTest
@Test
public void testWhiteSpaceName()
{
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
String encoded = "0001200130";
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));

View File

@ -41,11 +41,13 @@ public class QpackTest
static final HttpField XPowerJetty = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, "jetty");
static final HttpField Date = new PreEncodedHttpField(HttpHeader.DATE, DateGenerator.formatDate(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())));
private final DecoderTestHandler handler = new DecoderTestHandler();
@Test
public void encodeDecodeResponseTest() throws Exception
{
QpackEncoder encoder = new QpackEncoder();
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
HttpFields.Mutable fields0 = HttpFields.build()
@ -62,9 +64,10 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original0);
BufferUtil.flipToFlush(buffer, 0);
Response decoded0 = (Response)decoder.decode(buffer);
Response nullToEmpty = new MetaData.Response(HttpVersion.HTTP_2, 200,
decoder.decode(buffer);
Response decoded0 = (Response)handler.getMetaData();
Response nullToEmpty = new MetaData.Response(HttpVersion.HTTP_2, 200,
fields0.put(new HttpField(HttpHeader.CONTENT_ENCODING, "")));
assertMetaDataResponseSame(nullToEmpty, decoded0);
@ -72,7 +75,8 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original0);
BufferUtil.flipToFlush(buffer, 0);
Response decoded0b = (Response)decoder.decode(buffer);
decoder.decode(buffer);
Response decoded0b = (Response)handler.getMetaData();
assertMetaDataResponseSame(nullToEmpty, decoded0b);
@ -90,7 +94,8 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original1);
BufferUtil.flipToFlush(buffer, 0);
Response decoded1 = (Response)decoder.decode(buffer);
decoder.decode(buffer);
Response decoded1 = (Response)handler.getMetaData();
assertMetaDataResponseSame(original1, decoded1);
assertEquals("custom-key", decoded1.getFields().getField("Custom-Key").getName());
@ -100,7 +105,7 @@ public class QpackTest
public void encodeDecodeTooLargeTest() throws Exception
{
QpackEncoder encoder = new QpackEncoder();
QpackDecoder decoder = new QpackDecoder(4096, 164);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 164);
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
HttpFields fields0 = HttpFields.build()
@ -111,7 +116,8 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original0);
BufferUtil.flipToFlush(buffer, 0);
MetaData decoded0 = decoder.decode(buffer);
decoder.decode(buffer);
MetaData decoded0 = handler.getMetaData();
assertMetaDataSame(original0, decoded0);
@ -139,11 +145,11 @@ public class QpackTest
public void encodeDecodeNonAscii() throws Exception
{
QpackEncoder encoder = new QpackEncoder();
QpackDecoder decoder = new QpackDecoder(4096, 8192);
QpackDecoder decoder = new QpackDecoder(handler, 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(HttpVersion.HTTP_2, 200, fields0);
@ -151,16 +157,17 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original0);
BufferUtil.flipToFlush(buffer, 0);
Response decoded0 = (Response)decoder.decode(buffer);
decoder.decode(buffer);
Response decoded0 = (Response)handler.getMetaData();
assertMetaDataSame(original0, decoded0);
}
@Test
public void evictReferencedFieldTest() throws Exception
{
QpackEncoder encoder = new QpackEncoder(200, 200);
QpackDecoder decoder = new QpackDecoder(200, 1024);
QpackDecoder decoder = new QpackDecoder(handler, 200, 1024);
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
String longEnoughToBeEvicted = "012345678901234567890123456789012345678901234567890";
@ -173,7 +180,8 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original0);
BufferUtil.flipToFlush(buffer, 0);
MetaData decoded0 = decoder.decode(buffer);
decoder.decode(buffer);
MetaData decoded0 = handler.getMetaData();
assertEquals(2, encoder.getQpackContext().getNumEntries());
assertEquals(2, decoder.getQpackContext().getNumEntries());
@ -190,7 +198,8 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, original1);
BufferUtil.flipToFlush(buffer, 0);
MetaData decoded1 = decoder.decode(buffer);
decoder.decode(buffer);
MetaData decoded1 = handler.getMetaData();
assertMetaDataSame(original1, decoded1);
assertEquals(2, encoder.getQpackContext().getNumEntries());
@ -203,7 +212,7 @@ public class QpackTest
public void testHopHeadersAreRemoved() throws Exception
{
QpackEncoder encoder = new QpackEncoder();
QpackDecoder decoder = new QpackDecoder(4096, 16384);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 16384);
HttpFields input = HttpFields.build()
.add(HttpHeader.ACCEPT, "*")
@ -219,7 +228,8 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input));
BufferUtil.flipToFlush(buffer, 0);
MetaData metaData = decoder.decode(buffer);
decoder.decode(buffer);
MetaData metaData = handler.getMetaData();
HttpFields output = metaData.getFields();
assertEquals(1, output.size());
@ -230,7 +240,7 @@ public class QpackTest
public void testTETrailers() throws Exception
{
QpackEncoder encoder = new QpackEncoder();
QpackDecoder decoder = new QpackDecoder(4096, 16384);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 16384);
String teValue = "trailers";
String trailerValue = "Custom";
@ -243,7 +253,7 @@ public class QpackTest
BufferUtil.clearToFill(buffer);
encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input));
BufferUtil.flipToFlush(buffer, 0);
MetaData metaData = decoder.decode(buffer);
MetaData metaData = handler.getMetaData();
HttpFields output = metaData.getFields();
assertEquals(2, output.size());
@ -255,7 +265,7 @@ public class QpackTest
public void testColonHeaders() throws Exception
{
QpackEncoder encoder = new QpackEncoder();
QpackDecoder decoder = new QpackDecoder(4096, 16384);
QpackDecoder decoder = new QpackDecoder(handler, 4096, 16384);
HttpFields input = HttpFields.build()
.add(":status", "200")