Add basic implementation for the QpackDecoder.
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
9d9e13cf3a
commit
08b612feca
|
@ -38,11 +38,22 @@ public class QpackContext
|
||||||
|
|
||||||
QpackContext(int maxDynamicTableSize)
|
QpackContext(int maxDynamicTableSize)
|
||||||
{
|
{
|
||||||
_dynamicTable = new DynamicTable(maxDynamicTableSize);
|
_dynamicTable = new DynamicTable();
|
||||||
|
_dynamicTable.setCapacity(maxDynamicTableSize);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug(String.format("HdrTbl[%x] created max=%d", hashCode(), maxDynamicTableSize));
|
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)
|
public void resize(int newMaxDynamicTableSize)
|
||||||
{
|
{
|
||||||
_dynamicTable.setCapacity(newMaxDynamicTableSize);
|
_dynamicTable.setCapacity(newMaxDynamicTableSize);
|
||||||
|
|
|
@ -14,14 +14,24 @@
|
||||||
package org.eclipse.jetty.http3.qpack;
|
package org.eclipse.jetty.http3.qpack;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpTokens;
|
|
||||||
import org.eclipse.jetty.http.MetaData;
|
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.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.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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -35,19 +45,22 @@ public class QpackDecoder
|
||||||
public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 =
|
public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 =
|
||||||
new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, 0L);
|
new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, 0L);
|
||||||
|
|
||||||
|
private final Handler _handler;
|
||||||
private final QpackContext _context;
|
private final QpackContext _context;
|
||||||
private final MetaDataBuilder _builder;
|
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 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
|
* @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);
|
_context = new QpackContext(localMaxDynamicTableSize);
|
||||||
_localMaxDynamicTableSize = localMaxDynamicTableSize;
|
|
||||||
_builder = new MetaDataBuilder(maxHeaderSize);
|
_builder = new MetaDataBuilder(maxHeaderSize);
|
||||||
|
_handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QpackContext getQpackContext()
|
public QpackContext getQpackContext()
|
||||||
|
@ -55,11 +68,6 @@ public class QpackDecoder
|
||||||
return _context;
|
return _context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize)
|
|
||||||
{
|
|
||||||
_localMaxDynamicTableSize = localMaxdynamciTableSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Handler
|
public interface Handler
|
||||||
{
|
{
|
||||||
void onMetadata(MetaData metaData);
|
void onMetadata(MetaData metaData);
|
||||||
|
@ -67,7 +75,7 @@ public class QpackDecoder
|
||||||
void onInstruction(Instruction instruction);
|
void onInstruction(Instruction instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetaData decode(ByteBuffer buffer) throws QpackException.SessionException, QpackException.StreamException
|
public void decode(ByteBuffer buffer) throws QpackException
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug(String.format("CtxTbl[%x] decoding %d octets", _context.hashCode(), buffer.remaining()));
|
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())
|
if (buffer.remaining() > _builder.getMaxSize())
|
||||||
throw new QpackException.SessionException("431 Request Header Fields too large");
|
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())
|
MetaData metadata = encodedFieldSection.decode(_context, _builder);
|
||||||
LOG.debug("decode {}", BufferUtil.toHexString(buffer));
|
_handler.onMetadata(metadata);
|
||||||
|
_handler.onInstruction(new SectionAcknowledgmentInstruction(streamId));
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
_encodedFieldSections.add(encodedFieldSection);
|
||||||
LOG.debug("decode Idx {}", entry);
|
|
||||||
// emit
|
|
||||||
emitted = true;
|
|
||||||
_builder.emit(entry.getHttpField());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onInstruction(Instruction instruction) throws QpackException
|
||||||
|
{
|
||||||
|
StaticTable staticTable = _context.getStaticTable();
|
||||||
|
DynamicTable dynamicTable = _context.getDynamicTable();
|
||||||
|
if (instruction instanceof SetCapacityInstruction)
|
||||||
|
{
|
||||||
|
int capacity = ((SetCapacityInstruction)instruction).getCapacity();
|
||||||
|
dynamicTable.setCapacity(capacity);
|
||||||
|
}
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
// look at the first nibble in detail
|
throw new IllegalStateException("Invalid Encoder Instruction");
|
||||||
byte f = (byte)((b & 0xF0) >> 4);
|
}
|
||||||
String name;
|
}
|
||||||
HttpHeader header;
|
|
||||||
String value;
|
|
||||||
|
|
||||||
boolean indexed;
|
private static int decodeInsertCount(int encInsertCount, int totalNumInserts, int maxTableCapacity) throws QpackException
|
||||||
int nameIndex;
|
|
||||||
|
|
||||||
switch (f)
|
|
||||||
{
|
{
|
||||||
case 2: // 7.3
|
if (encInsertCount == 0)
|
||||||
case 3: // 7.3
|
return 0;
|
||||||
// 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
|
int maxEntries = maxTableCapacity / 32;
|
||||||
case 1: // 7.2.3
|
int fullRange = 2 * maxEntries;
|
||||||
indexed = false;
|
if (encInsertCount > fullRange)
|
||||||
nameIndex = NBitInteger.decode(buffer, 4);
|
throw new QpackException.CompressionException("encInsertCount > fullRange");
|
||||||
break;
|
|
||||||
|
|
||||||
case 4: // 7.2.1
|
// MaxWrapped is the largest possible value of ReqInsertCount that is 0 mod 2 * MaxEntries.
|
||||||
case 5: // 7.2.1
|
int maxValue = totalNumInserts + maxEntries;
|
||||||
case 6: // 7.2.1
|
int maxWrapped = (maxValue / fullRange) * fullRange;
|
||||||
case 7: // 7.2.1
|
int reqInsertCount = maxWrapped + encInsertCount - 1;
|
||||||
indexed = true;
|
|
||||||
nameIndex = NBitInteger.decode(buffer, 6);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
// If reqInsertCount exceeds maxValue, the Encoder's value must have wrapped one fewer time.
|
||||||
throw new IllegalStateException();
|
if (reqInsertCount > maxValue)
|
||||||
}
|
|
||||||
|
|
||||||
boolean huffmanName = false;
|
|
||||||
|
|
||||||
// decode the name
|
|
||||||
if (nameIndex > 0)
|
|
||||||
{
|
{
|
||||||
Entry nameEntry = _context.get(nameIndex);
|
if (reqInsertCount <= fullRange)
|
||||||
name = nameEntry.getHttpField().getName();
|
throw new QpackException.CompressionException("reqInsertCount <= fullRange");
|
||||||
header = nameEntry.getHttpField().getHeader();
|
reqInsertCount -= fullRange;
|
||||||
}
|
|
||||||
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
|
// Value of 0 must be encoded as 0.
|
||||||
boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
|
if (reqInsertCount == 0)
|
||||||
int length = NBitInteger.decode(buffer, 7);
|
throw new QpackException.CompressionException("reqInsertCount == 0");
|
||||||
_builder.checkSize(length, huffmanValue);
|
|
||||||
if (huffmanValue)
|
|
||||||
value = Huffman.decode(buffer, length);
|
|
||||||
else
|
|
||||||
value = toASCIIString(buffer, length);
|
|
||||||
|
|
||||||
// Make the new field
|
return reqInsertCount;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toASCIIString(ByteBuffer buffer, int length)
|
|
||||||
{
|
|
||||||
StringBuilder builder = new StringBuilder(length);
|
|
||||||
for (int i = 0; i < length; ++i)
|
|
||||||
{
|
|
||||||
builder.append((char)(0x7F & buffer.get()));
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -30,7 +30,7 @@ public abstract class QpackException extends Exception
|
||||||
*/
|
*/
|
||||||
public static class StreamException extends QpackException
|
public static class StreamException extends QpackException
|
||||||
{
|
{
|
||||||
StreamException(String messageFormat, Object... args)
|
public StreamException(String messageFormat, Object... args)
|
||||||
{
|
{
|
||||||
super(messageFormat, args);
|
super(messageFormat, args);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public abstract class QpackException extends Exception
|
||||||
*/
|
*/
|
||||||
public static class SessionException extends QpackException
|
public static class SessionException extends QpackException
|
||||||
{
|
{
|
||||||
SessionException(String messageFormat, Object... args)
|
public SessionException(String messageFormat, Object... args)
|
||||||
{
|
{
|
||||||
super(messageFormat, args);
|
super(messageFormat, args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -15,6 +28,11 @@ public class DuplicateInstruction implements Instruction
|
||||||
_index = index;
|
_index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIndex()
|
||||||
|
{
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBufferPool.Lease lease)
|
public void encode(ByteBufferPool.Lease lease)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -22,6 +35,21 @@ public class IndexedNameEntryInstruction implements Instruction
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDynamic()
|
||||||
|
{
|
||||||
|
return _dynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex()
|
||||||
|
{
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBufferPool.Lease lease)
|
public void encode(ByteBufferPool.Lease lease)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -22,6 +35,16 @@ public class LiteralNameEntryInstruction implements Instruction
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBufferPool.Lease lease)
|
public void encode(ByteBufferPool.Lease lease)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -15,6 +28,11 @@ public class SetCapacityInstruction implements Instruction
|
||||||
_capacity = capacity;
|
_capacity = capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCapacity()
|
||||||
|
{
|
||||||
|
return _capacity;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBufferPool.Lease lease)
|
public void encode(ByteBufferPool.Lease lease)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
package org.eclipse.jetty.http3.qpack.generator;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,84 +13,79 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.qpack.table;
|
package org.eclipse.jetty.http3.qpack.table;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http3.qpack.QpackContext;
|
import org.eclipse.jetty.http3.qpack.QpackContext;
|
||||||
|
import org.eclipse.jetty.http3.qpack.QpackException;
|
||||||
|
|
||||||
public class DynamicTable
|
public class DynamicTable
|
||||||
{
|
{
|
||||||
public static final int FIRST_INDEX = StaticTable.STATIC_SIZE + 1;
|
public static final int FIRST_INDEX = StaticTable.STATIC_SIZE + 1;
|
||||||
private int _maxDynamicTableSizeInBytes;
|
private int _maxCapacity;
|
||||||
private int _dynamicTableSizeInBytes;
|
private int _capacity;
|
||||||
|
private int _absoluteIndex;
|
||||||
|
|
||||||
private final Map<HttpField, Entry> _fieldMap = new HashMap<>();
|
private final Map<HttpField, Entry> _fieldMap = new HashMap<>();
|
||||||
private final Map<String, Entry> _nameMap = 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 final Map<Integer, StreamInfo> streamInfoMap = new HashMap<>();
|
||||||
private int _numEntries;
|
|
||||||
private int _offset;
|
|
||||||
|
|
||||||
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)
|
public Entry add(Entry entry)
|
||||||
{
|
{
|
||||||
|
evict();
|
||||||
|
|
||||||
int size = entry.getSize();
|
int size = entry.getSize();
|
||||||
if (size > _maxDynamicTableSizeInBytes)
|
if (size + _capacity > _maxCapacity)
|
||||||
{
|
{
|
||||||
if (QpackContext.LOG.isDebugEnabled())
|
if (QpackContext.LOG.isDebugEnabled())
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxDynamicTableSizeInBytes));
|
QpackContext.LOG.debug(String.format("HdrTbl[%x] !added size %d>%d", hashCode(), size, _maxCapacity));
|
||||||
evictAll();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
_dynamicTableSizeInBytes += size;
|
_capacity += 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);
|
|
||||||
|
|
||||||
|
// Set the Entries absolute index which will never change.
|
||||||
|
entry.setIndex(_absoluteIndex++);
|
||||||
|
_entries.add(0, entry);
|
||||||
_fieldMap.put(entry.getHttpField(), entry);
|
_fieldMap.put(entry.getHttpField(), entry);
|
||||||
_nameMap.put(entry.getHttpField().getLowerCaseName(), entry);
|
_nameMap.put(entry.getHttpField().getLowerCaseName(), entry);
|
||||||
|
|
||||||
if (QpackContext.LOG.isDebugEnabled())
|
if (QpackContext.LOG.isDebugEnabled())
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] added %s", hashCode(), entry));
|
QpackContext.LOG.debug(String.format("HdrTbl[%x] added %s", hashCode(), entry));
|
||||||
evict();
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int index(Entry 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)
|
public Entry get(int index)
|
||||||
{
|
{
|
||||||
int d = index - StaticTable.STATIC_SIZE - 1;
|
return _entries.get(index);
|
||||||
if (d < 0 || d >= _numEntries)
|
|
||||||
return null;
|
|
||||||
int slot = (_offset + _numEntries - d - 1) % _entries.length;
|
|
||||||
return _entries[slot];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entry get(String name)
|
public Entry get(String name)
|
||||||
|
@ -105,66 +100,91 @@ public class DynamicTable
|
||||||
|
|
||||||
public int getSize()
|
public int getSize()
|
||||||
{
|
{
|
||||||
return _dynamicTableSizeInBytes;
|
return _capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxSize()
|
public int getMaxSize()
|
||||||
{
|
{
|
||||||
return _maxDynamicTableSizeInBytes;
|
return _maxCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNumEntries()
|
public int getNumEntries()
|
||||||
{
|
{
|
||||||
return _numEntries;
|
return _entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInsertCount()
|
||||||
|
{
|
||||||
|
return _absoluteIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCapacity(int capacity)
|
public void setCapacity(int capacity)
|
||||||
{
|
{
|
||||||
if (QpackContext.LOG.isDebugEnabled())
|
if (QpackContext.LOG.isDebugEnabled())
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxDynamicTableSizeInBytes, capacity));
|
QpackContext.LOG.debug(String.format("HdrTbl[%x] resized max=%d->%d", hashCode(), _maxCapacity, capacity));
|
||||||
_maxDynamicTableSizeInBytes = capacity;
|
_maxCapacity = capacity;
|
||||||
evict();
|
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()
|
private void evict()
|
||||||
{
|
{
|
||||||
while (_dynamicTableSizeInBytes > _maxDynamicTableSizeInBytes)
|
int evictionThreshold = getEvictionThreshold();
|
||||||
|
for (Entry e : _entries)
|
||||||
{
|
{
|
||||||
Entry entry = _entries[_offset];
|
// We only evict when the table is getting full.
|
||||||
_entries[_offset] = null;
|
if (_capacity < evictionThreshold)
|
||||||
_offset = (_offset + 1) % _entries.length;
|
return;
|
||||||
_numEntries--;
|
|
||||||
|
// 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())
|
if (QpackContext.LOG.isDebugEnabled())
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] evict %s", hashCode(), entry));
|
QpackContext.LOG.debug(String.format("HdrTbl[%x] evict %s", hashCode(), e));
|
||||||
_dynamicTableSizeInBytes -= entry.getSize();
|
|
||||||
entry.setIndex(-1);
|
Entry removedEntry = _entries.remove(0);
|
||||||
_fieldMap.remove(entry.getHttpField());
|
if (removedEntry != e)
|
||||||
String lc = entry.getHttpField().getLowerCaseName();
|
throw new IllegalStateException("Corruption in DynamicTable");
|
||||||
if (entry == _nameMap.get(lc))
|
_fieldMap.remove(e.getHttpField());
|
||||||
_nameMap.remove(lc);
|
String name = e.getHttpField().getLowerCaseName();
|
||||||
}
|
if (e == _nameMap.get(name))
|
||||||
if (QpackContext.LOG.isDebugEnabled())
|
_nameMap.remove(name);
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", hashCode(), getNumEntries(), _dynamicTableSizeInBytes, _maxDynamicTableSizeInBytes));
|
|
||||||
|
_capacity -= e.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void evictAll()
|
|
||||||
{
|
|
||||||
if (QpackContext.LOG.isDebugEnabled())
|
if (QpackContext.LOG.isDebugEnabled())
|
||||||
QpackContext.LOG.debug(String.format("HdrTbl[%x] evictAll", hashCode()));
|
QpackContext.LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d", hashCode(), getNumEntries(), _capacity, _maxCapacity));
|
||||||
if (getNumEntries() > 0)
|
|
||||||
{
|
|
||||||
_fieldMap.clear();
|
|
||||||
_nameMap.clear();
|
|
||||||
_offset = 0;
|
|
||||||
_numEntries = 0;
|
|
||||||
_dynamicTableSizeInBytes = 0;
|
|
||||||
Arrays.fill(_entries, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getEvictionThreshold()
|
||||||
|
{
|
||||||
|
return _maxCapacity * 3 / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,15 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http3.qpack.table;
|
package org.eclipse.jetty.http3.qpack.table;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
|
||||||
public class Entry
|
public class Entry
|
||||||
{
|
{
|
||||||
final HttpField _field;
|
private final HttpField _field;
|
||||||
private int _slot; // The index within it's array
|
private int _absoluteIndex;
|
||||||
|
private AtomicInteger _referenceCount = new AtomicInteger(0);
|
||||||
|
|
||||||
public Entry()
|
public Entry()
|
||||||
{
|
{
|
||||||
|
@ -33,7 +36,7 @@ public class Entry
|
||||||
public Entry(int index, HttpField field)
|
public Entry(int index, HttpField field)
|
||||||
{
|
{
|
||||||
_field = field;
|
_field = field;
|
||||||
_slot = index;
|
_absoluteIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSize()
|
public int getSize()
|
||||||
|
@ -44,12 +47,12 @@ public class Entry
|
||||||
|
|
||||||
public void setIndex(int index)
|
public void setIndex(int index)
|
||||||
{
|
{
|
||||||
_slot = index;
|
_absoluteIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIndex()
|
public int getIndex()
|
||||||
{
|
{
|
||||||
return _slot;
|
return _absoluteIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpField getHttpField()
|
public HttpField getHttpField()
|
||||||
|
@ -57,6 +60,11 @@ public class Entry
|
||||||
return _field;
|
return _field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getReferenceCount()
|
||||||
|
{
|
||||||
|
return _referenceCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isStatic()
|
public boolean isStatic()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -70,6 +78,6 @@ public class Entry
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,13 +150,13 @@ public class StaticTable
|
||||||
|
|
||||||
_staticTable[i] = entry;
|
_staticTable[i] = entry;
|
||||||
|
|
||||||
if (entry._field.getValue() != null)
|
if (entry.getHttpField().getValue() != null)
|
||||||
_staticFieldMap.put(entry._field, entry);
|
_staticFieldMap.put(entry.getHttpField(), entry);
|
||||||
|
|
||||||
if (!added.contains(entry._field.getName()))
|
if (!added.contains(entry.getHttpField().getName()))
|
||||||
{
|
{
|
||||||
added.add(entry._field.getName());
|
added.add(entry.getHttpField().getName());
|
||||||
staticNameMapBuilder.with(entry._field.getName(), entry);
|
staticNameMapBuilder.with(entry.getHttpField().getName(), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_staticNameMap = staticNameMapBuilder.build();
|
_staticNameMap = staticNameMapBuilder.build();
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,16 +54,19 @@ public class QpackDecoderTest
|
||||||
+-------------------------------+
|
+-------------------------------+
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
private final DecoderTestHandler handler = new DecoderTestHandler();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDecodeD3() throws Exception
|
public void testDecodeD3() throws Exception
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
// First request
|
// First request
|
||||||
String encoded = "828684410f7777772e6578616d706c652e636f6d";
|
String encoded = "828684410f7777772e6578616d706c652e636f6d";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -75,7 +78,8 @@ public class QpackDecoderTest
|
||||||
encoded = "828684be58086e6f2d6361636865";
|
encoded = "828684be58086e6f2d6361636865";
|
||||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -90,7 +94,8 @@ public class QpackDecoderTest
|
||||||
encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565";
|
encoded = "828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565";
|
||||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTPS.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTPS.asString(), request.getURI().getScheme());
|
||||||
|
@ -105,13 +110,14 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testDecodeD4() throws Exception
|
public void testDecodeD4() throws Exception
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
// First request
|
// First request
|
||||||
String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff";
|
String encoded = "828684418cf1e3c2e5f23a6ba0ab90f4ff";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -123,7 +129,8 @@ public class QpackDecoderTest
|
||||||
encoded = "828684be5886a8eb10649cbf";
|
encoded = "828684be5886a8eb10649cbf";
|
||||||
buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -140,14 +147,15 @@ public class QpackDecoderTest
|
||||||
{
|
{
|
||||||
String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
|
String value = "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
|
||||||
|
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d";
|
String encoded = "8682418cF1E3C2E5F23a6bA0Ab90F4Ff841f0822426173696320515778685a475270626a70766347567549484e6c633246745a513d3d";
|
||||||
byte[] bytes = TypeUtil.fromHexString(encoded);
|
byte[] bytes = TypeUtil.fromHexString(encoded);
|
||||||
byte[] array = new byte[bytes.length + 1];
|
byte[] array = new byte[bytes.length + 1];
|
||||||
System.arraycopy(bytes, 0, array, 1, bytes.length);
|
System.arraycopy(bytes, 0, array, 1, bytes.length);
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice();
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -162,7 +170,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testDecodeHuffmanWithArrayOffset() throws Exception
|
public void testDecodeHuffmanWithArrayOffset() throws Exception
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84";
|
String encoded = "8286418cf1e3c2e5f23a6ba0ab90f4ff84";
|
||||||
byte[] bytes = TypeUtil.fromHexString(encoded);
|
byte[] bytes = TypeUtil.fromHexString(encoded);
|
||||||
|
@ -170,7 +178,8 @@ public class QpackDecoderTest
|
||||||
System.arraycopy(bytes, 0, array, 1, bytes.length);
|
System.arraycopy(bytes, 0, array, 1, bytes.length);
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(array, 1, bytes.length).slice();
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -186,8 +195,9 @@ public class QpackDecoderTest
|
||||||
String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3";
|
String encoded = "886196C361Be940b6a65B6850400B8A00571972e080a62D1Bf5f87497cA589D34d1f9a0f0d0234327690Aa69D29aFcA954D3A5358980Ae112e0f7c880aE152A9A74a6bF3";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
MetaData.Response response = (MetaData.Response)decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData.Response response = (MetaData.Response)handler.getMetaData();
|
||||||
|
|
||||||
assertThat(response.getStatus(), is(200));
|
assertThat(response.getStatus(), is(200));
|
||||||
assertThat(response.getFields().size(), is(6));
|
assertThat(response.getFields().size(), is(6));
|
||||||
|
@ -204,8 +214,9 @@ public class QpackDecoderTest
|
||||||
{
|
{
|
||||||
String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f";
|
String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
MetaData metaData = decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData metaData = handler.getMetaData();
|
||||||
assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0"));
|
assertThat(metaData.getFields().get(HttpHeader.HOST), is("localhost0"));
|
||||||
assertThat(metaData.getFields().get(HttpHeader.COOKIE), is("abcdefghij"));
|
assertThat(metaData.getFields().get(HttpHeader.COOKIE), is("abcdefghij"));
|
||||||
assertThat(decoder.getQpackContext().getMaxDynamicTableSize(), is(50));
|
assertThat(decoder.getQpackContext().getMaxDynamicTableSize(), is(50));
|
||||||
|
@ -226,7 +237,7 @@ public class QpackDecoderTest
|
||||||
|
|
||||||
String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20";
|
String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
@ -244,8 +255,9 @@ public class QpackDecoderTest
|
||||||
String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f";
|
String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
|
||||||
QpackDecoder decoder = new QpackDecoder(128, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 128, 8192);
|
||||||
MetaData metaData = decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData metaData = handler.getMetaData();
|
||||||
|
|
||||||
assertThat(decoder.getQpackContext().getDynamicTableSize(), is(0));
|
assertThat(decoder.getQpackContext().getDynamicTableSize(), is(0));
|
||||||
assertThat(metaData.getFields().get("host"), Matchers.startsWith("This is a very large field"));
|
assertThat(metaData.getFields().get("host"), Matchers.startsWith("This is a very large field"));
|
||||||
|
@ -257,7 +269,7 @@ public class QpackDecoderTest
|
||||||
String encoded = "BE";
|
String encoded = "BE";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
|
||||||
QpackDecoder decoder = new QpackDecoder(128, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 128, 8192);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -442,12 +454,13 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testHuffmanEncodedStandard() throws Exception
|
public void testHuffmanEncodedStandard() throws Exception
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "82868441" + "83" + "49509F";
|
String encoded = "82868441" + "83" + "49509F";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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("GET", request.getMethod());
|
||||||
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme());
|
||||||
|
@ -460,7 +473,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testHuffmanEncodedExtraPadding()
|
public void testHuffmanEncodedExtraPadding()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "82868441" + "84" + "49509FFF";
|
String encoded = "82868441" + "84" + "49509FFF";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -472,7 +485,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testHuffmanEncodedZeroPadding()
|
public void testHuffmanEncodedZeroPadding()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "82868441" + "83" + "495090";
|
String encoded = "82868441" + "83" + "495090";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -485,7 +498,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testHuffmanEncodedWithEOS()
|
public void testHuffmanEncodedWithEOS()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "82868441" + "87" + "497FFFFFFF427F";
|
String encoded = "82868441" + "87" + "497FFFFFFF427F";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -497,7 +510,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testHuffmanEncodedOneIncompleteOctet()
|
public void testHuffmanEncodedOneIncompleteOctet()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "82868441" + "81" + "FE";
|
String encoded = "82868441" + "81" + "FE";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -509,7 +522,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testHuffmanEncodedTwoIncompleteOctet()
|
public void testHuffmanEncodedTwoIncompleteOctet()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "82868441" + "82" + "FFFE";
|
String encoded = "82868441" + "82" + "FFFE";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -521,7 +534,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testZeroLengthName()
|
public void testZeroLengthName()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "00000130";
|
String encoded = "00000130";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -532,11 +545,12 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testZeroLengthValue() throws Exception
|
public void testZeroLengthValue() throws Exception
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "00016800";
|
String encoded = "00016800";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
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().size(), is(1));
|
||||||
assertThat(metaData.getFields().get("h"), is(""));
|
assertThat(metaData.getFields().get("h"), is(""));
|
||||||
}
|
}
|
||||||
|
@ -544,7 +558,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testUpperCaseName()
|
public void testUpperCaseName()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "0001480130";
|
String encoded = "0001480130";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
@ -555,7 +569,7 @@ public class QpackDecoderTest
|
||||||
@Test
|
@Test
|
||||||
public void testWhiteSpaceName()
|
public void testWhiteSpaceName()
|
||||||
{
|
{
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
|
|
||||||
String encoded = "0001200130";
|
String encoded = "0001200130";
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded));
|
||||||
|
|
|
@ -41,11 +41,13 @@ public class QpackTest
|
||||||
static final HttpField XPowerJetty = new PreEncodedHttpField(HttpHeader.X_POWERED_BY, "jetty");
|
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())));
|
static final HttpField Date = new PreEncodedHttpField(HttpHeader.DATE, DateGenerator.formatDate(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())));
|
||||||
|
|
||||||
|
private final DecoderTestHandler handler = new DecoderTestHandler();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void encodeDecodeResponseTest() throws Exception
|
public void encodeDecodeResponseTest() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder();
|
QpackEncoder encoder = new QpackEncoder();
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
|
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
|
||||||
|
|
||||||
HttpFields.Mutable fields0 = HttpFields.build()
|
HttpFields.Mutable fields0 = HttpFields.build()
|
||||||
|
@ -62,7 +64,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original0);
|
encoder.encode(buffer, original0);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
Response decoded0 = (Response)decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
Response decoded0 = (Response)handler.getMetaData();
|
||||||
|
|
||||||
Response nullToEmpty = new MetaData.Response(HttpVersion.HTTP_2, 200,
|
Response nullToEmpty = new MetaData.Response(HttpVersion.HTTP_2, 200,
|
||||||
fields0.put(new HttpField(HttpHeader.CONTENT_ENCODING, "")));
|
fields0.put(new HttpField(HttpHeader.CONTENT_ENCODING, "")));
|
||||||
|
@ -72,7 +75,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original0);
|
encoder.encode(buffer, original0);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
Response decoded0b = (Response)decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
Response decoded0b = (Response)handler.getMetaData();
|
||||||
|
|
||||||
assertMetaDataResponseSame(nullToEmpty, decoded0b);
|
assertMetaDataResponseSame(nullToEmpty, decoded0b);
|
||||||
|
|
||||||
|
@ -90,7 +94,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original1);
|
encoder.encode(buffer, original1);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
Response decoded1 = (Response)decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
Response decoded1 = (Response)handler.getMetaData();
|
||||||
|
|
||||||
assertMetaDataResponseSame(original1, decoded1);
|
assertMetaDataResponseSame(original1, decoded1);
|
||||||
assertEquals("custom-key", decoded1.getFields().getField("Custom-Key").getName());
|
assertEquals("custom-key", decoded1.getFields().getField("Custom-Key").getName());
|
||||||
|
@ -100,7 +105,7 @@ public class QpackTest
|
||||||
public void encodeDecodeTooLargeTest() throws Exception
|
public void encodeDecodeTooLargeTest() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder();
|
QpackEncoder encoder = new QpackEncoder();
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 164);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 164);
|
||||||
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
|
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
|
||||||
|
|
||||||
HttpFields fields0 = HttpFields.build()
|
HttpFields fields0 = HttpFields.build()
|
||||||
|
@ -111,7 +116,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original0);
|
encoder.encode(buffer, original0);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
MetaData decoded0 = decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData decoded0 = handler.getMetaData();
|
||||||
|
|
||||||
assertMetaDataSame(original0, decoded0);
|
assertMetaDataSame(original0, decoded0);
|
||||||
|
|
||||||
|
@ -139,7 +145,7 @@ public class QpackTest
|
||||||
public void encodeDecodeNonAscii() throws Exception
|
public void encodeDecodeNonAscii() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder();
|
QpackEncoder encoder = new QpackEncoder();
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 8192);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 8192);
|
||||||
ByteBuffer buffer = BufferUtil.allocate(16 * 1024);
|
ByteBuffer buffer = BufferUtil.allocate(16 * 1024);
|
||||||
|
|
||||||
HttpFields fields0 = HttpFields.build()
|
HttpFields fields0 = HttpFields.build()
|
||||||
|
@ -151,7 +157,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original0);
|
encoder.encode(buffer, original0);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
Response decoded0 = (Response)decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
Response decoded0 = (Response)handler.getMetaData();
|
||||||
|
|
||||||
assertMetaDataSame(original0, decoded0);
|
assertMetaDataSame(original0, decoded0);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +167,7 @@ public class QpackTest
|
||||||
public void evictReferencedFieldTest() throws Exception
|
public void evictReferencedFieldTest() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder(200, 200);
|
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);
|
ByteBuffer buffer = BufferUtil.allocateDirect(16 * 1024);
|
||||||
|
|
||||||
String longEnoughToBeEvicted = "012345678901234567890123456789012345678901234567890";
|
String longEnoughToBeEvicted = "012345678901234567890123456789012345678901234567890";
|
||||||
|
@ -173,7 +180,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original0);
|
encoder.encode(buffer, original0);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
MetaData decoded0 = decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData decoded0 = handler.getMetaData();
|
||||||
|
|
||||||
assertEquals(2, encoder.getQpackContext().getNumEntries());
|
assertEquals(2, encoder.getQpackContext().getNumEntries());
|
||||||
assertEquals(2, decoder.getQpackContext().getNumEntries());
|
assertEquals(2, decoder.getQpackContext().getNumEntries());
|
||||||
|
@ -190,7 +198,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, original1);
|
encoder.encode(buffer, original1);
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
MetaData decoded1 = decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData decoded1 = handler.getMetaData();
|
||||||
assertMetaDataSame(original1, decoded1);
|
assertMetaDataSame(original1, decoded1);
|
||||||
|
|
||||||
assertEquals(2, encoder.getQpackContext().getNumEntries());
|
assertEquals(2, encoder.getQpackContext().getNumEntries());
|
||||||
|
@ -203,7 +212,7 @@ public class QpackTest
|
||||||
public void testHopHeadersAreRemoved() throws Exception
|
public void testHopHeadersAreRemoved() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder();
|
QpackEncoder encoder = new QpackEncoder();
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 16384);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 16384);
|
||||||
|
|
||||||
HttpFields input = HttpFields.build()
|
HttpFields input = HttpFields.build()
|
||||||
.add(HttpHeader.ACCEPT, "*")
|
.add(HttpHeader.ACCEPT, "*")
|
||||||
|
@ -219,7 +228,8 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input));
|
encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input));
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
MetaData metaData = decoder.decode(buffer);
|
decoder.decode(buffer);
|
||||||
|
MetaData metaData = handler.getMetaData();
|
||||||
HttpFields output = metaData.getFields();
|
HttpFields output = metaData.getFields();
|
||||||
|
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
|
@ -230,7 +240,7 @@ public class QpackTest
|
||||||
public void testTETrailers() throws Exception
|
public void testTETrailers() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder();
|
QpackEncoder encoder = new QpackEncoder();
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 16384);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 16384);
|
||||||
|
|
||||||
String teValue = "trailers";
|
String teValue = "trailers";
|
||||||
String trailerValue = "Custom";
|
String trailerValue = "Custom";
|
||||||
|
@ -243,7 +253,7 @@ public class QpackTest
|
||||||
BufferUtil.clearToFill(buffer);
|
BufferUtil.clearToFill(buffer);
|
||||||
encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input));
|
encoder.encode(buffer, new MetaData(HttpVersion.HTTP_2, input));
|
||||||
BufferUtil.flipToFlush(buffer, 0);
|
BufferUtil.flipToFlush(buffer, 0);
|
||||||
MetaData metaData = decoder.decode(buffer);
|
MetaData metaData = handler.getMetaData();
|
||||||
HttpFields output = metaData.getFields();
|
HttpFields output = metaData.getFields();
|
||||||
|
|
||||||
assertEquals(2, output.size());
|
assertEquals(2, output.size());
|
||||||
|
@ -255,7 +265,7 @@ public class QpackTest
|
||||||
public void testColonHeaders() throws Exception
|
public void testColonHeaders() throws Exception
|
||||||
{
|
{
|
||||||
QpackEncoder encoder = new QpackEncoder();
|
QpackEncoder encoder = new QpackEncoder();
|
||||||
QpackDecoder decoder = new QpackDecoder(4096, 16384);
|
QpackDecoder decoder = new QpackDecoder(handler, 4096, 16384);
|
||||||
|
|
||||||
HttpFields input = HttpFields.build()
|
HttpFields input = HttpFields.build()
|
||||||
.add(":status", "200")
|
.add(":status", "200")
|
||||||
|
|
Loading…
Reference in New Issue